博文

 C++/C编程规范(9.5-9.9)----林  锐(2007-04-10 21:22:00)

摘要:9.5 不要轻视拷贝构造函数与赋值函数        由于并非所有的对象都会使用拷贝构造函数和赋值函数,程序员可能对这两个函数有些轻视。请先记住以下的警告,在阅读正文时就会多心: u       本章开头讲过,如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。以类String的两个对象a,b为例,假设a.m_data的内容为“hello”,b.m_data的内容为“world”。 现将a赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。这将造成三个错误:一是b.m_data原有的内存没被释放,造成内存泄露;二是b.m_data和a.m_data指向同一块内存,a或b任何一方变动都会影响另一方;三是在对象被析构时,m_data被释放了两次。   u       拷贝构造函数和赋值函数非常容易混淆,常导致错写、错用。拷贝构造函数是在对象被创建时调用的,而赋值函数只能被已经存在了的对象调用。以下程序中,第三个语句和第四个语句很相似,你分得清楚哪个调用了拷贝构造函数,哪个调用了赋值函数吗? String  a(“hello”); String  b(“world”); String  c = a;  // 调用了拷贝构造函数,最好写成 c(a); c = b; // 调用了赋值函数 本例中第三个语句的风格较差,宜改写成String c(a) 以区别于第四个语句。 9.6 示例:类String的拷贝构造函数与赋值函数     // 拷贝构造函数     String::String(const String &other)     {   // 允许操作other的私有成员m_data     int length = strlen(other.m_d......

阅读全文(2000) | 评论:0

 C++/C编程规范(9.1-9.4)----林  锐(2007-04-10 21:21:00)

摘要:第9章 类的构造函数、析构函数与赋值函数 构造函数、析构函数与赋值函数是每个类最基本的函数。它们太普通以致让人容易麻痹大意,其实这些貌似简单的函数就象没有顶盖的下水道那样危险。        每个类只有一个析构函数和一个赋值函数,但可以有多个构造函数(包含一个拷贝构造函数,其它的称为普通构造函数)。对于任意一个类A,如果不想编写上述函数,C++编译器将自动为A产生四个缺省的函数,如     A(void);                    // 缺省的无参数构造函数     A(const A &a);              // 缺省的拷贝构造函数     ~A(void);                   // 缺省的析构函数     A & operate =(const A &a);  // 缺省的赋值函数   这不禁让人疑惑,既然能自动生成函数,为什么还要程序员编写? 原因如下: (1)如果使用“缺省的无参数构造函数”和“缺省的析构函数”,等于放弃了自主“初始化”和“清除”的机会,C++发明人Stroustrup的好心好意白费了。 (2)“缺省的拷贝构造函数”和“缺省的赋值函数”均采用“位拷贝”而非“值拷贝”的方式来实现,倘若类中含有指针变量,这两个函数注定将出错。        对于那些没有吃够苦头的C++程序员,如果他说编写构造函数、析构函数......

阅读全文(2265) | 评论:0

 C++/C编程规范(8.4-8.6)----林  锐(2007-04-10 21:19:00)

摘要:8.4 运算符重载 8.4.1 概念     在C++语言中,可以用关键字operator加上运算符来表示函数,叫做运算符重载。例如两个复数相加函数:     Complex Add(const Complex &a, const Complex &b); 可以用运算符重载来表示:     Complex operator +(const Complex &a, const Complex &b);     运算符与普通函数在调用时的不同之处是:对于普通函数,参数出现在圆括号内;而对于运算符,参数出现在其左、右侧。例如     Complex a, b, c;     …     c = Add(a, b);  // 用普通函数     c = a + b;      // 用运算符 +     如果运算符被重载为全局函数,那么只有一个参数的运算符叫做一元运算符,有两个参数的运算符叫做二元运算符。     如果运算符被重载为类的成员函数,那么一元运算符没有参数,二元运算符只有一个右侧参数,因为对象自己成了左侧参数。     从语法上讲,运算符既可以定义为全局函数,也可以定义为成员函数。文献[Murray , p44-p47]对此问题作了较多的阐述,并总结了表8-4-1的规则。   运算符 规则 所有的一元运算符 建议重载为成员函数 = () [] -> 只能重载为成员函数 += -= /= *= &= |= ~= %= >>= <<= 建议重载为成员函数 所有其它运算符 建议重载为全局函数 表8-4-1 运算符的重载规则   由于C++语言支持函数重载,才能将运算符当成函数来......

阅读全文(2406) | 评论:0

 C++/C编程规范(8.1-8.3)----林  锐(2007-04-10 21:17:00)

摘要:第8章 C++函数的高级特性 对比于C语言的函数,C++增加了重载(overloaded)、内联(inline)、const和virtual四种新机制。其中重载和内联机制既可用于全局函数也可用于类的成员函数,const与virtual机制仅用于类的成员函数。        重载和内联肯定有其好处才会被C++语言采纳,但是不可以当成免费的午餐而滥用。本章将探究重载和内联的优点与局限性,说明什么情况下应该采用、不该采用以及要警惕错用。 8.1 函数重载的概念 8.1.1 重载的起源     自然语言中,一个词可以有许多不同的含义,即该词被重载了。人们可以通过上下文来判断该词到底是哪种含义。“词的重载”可以使语言更加简练。例如“吃饭”的含义十分广泛,人们没有必要每次非得说清楚具体吃什么不可。别迂腐得象孔已己,说茴香豆的茴字有四种写法。     在C++程序中,可以将语义、功能相似的几个函数用同一个名字表示,即函数重载。这样便于记忆,提高了函数的易用性,这是C++语言采用重载机制的一个理由。例如示例8-1-1中的函数EatBeef,EatFish,EatChicken可以用同一个函数名Eat表示,用不同类型的参数加以区别。     void EatBeef(…);       // 可以改为     void Eat(Beef …); void EatFish(…);       // 可以改为     void Eat(Fish …); void EatChicken(…);    // 可以改为     void Eat(Chicken …);   示例8-1-1 重载函数Eat       C++语言采用重载机制的另一个理由是:类的构造函数需要重载机制。因为C++规定构造......

阅读全文(2581) | 评论:0

C++/C编程规范(7.6-7.12)----林  锐(2007-04-10 21:13:00)

摘要:7.6 动态内存会被自动释放吗?        函数体内的局部变量在函数结束时自动消亡。很多人误以为示例7-6是正确的。理由是p是局部的指针变量,它消亡的时候会让它所指的动态内存一起完蛋。这是错觉!       void Func(void) {     char *p = (char *) malloc(100); // 动态内存会自动释放吗? } 示例7-6 试图让动态内存自动释放       我们发现指针有一些“似是而非”的特征: (1)指针消亡了,并不表示它所指的内存会被自动释放。 (2)内存被释放了,并不表示指针会消亡或者成了NULL指针。 这表明释放内存并不是一件可以草率对待的事。也许有人不服气,一定要找出可以草率行事的理由:     如果程序终止了运行,一切指针都会消亡,动态内存会被操作系统回收。既然如此,在程序临终前,就可以不必释放内存、不必将指针设置为NULL了。终于可以偷懒而不会发生错误了吧?     想得美。如果别人把那段程序取出来用到其它地方怎么办? 7.7 杜绝“野指针” “野指针”不是NULL指针,是指向“垃圾”内存的指针。人们一般不会错用NULL指针,因为用if语句很容易判断。但是“野指针”是很危险的,if语句对它不起作用。 “野指针”的成因主要有两种: (1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如     char *p = NULL;     char *str = (char *) malloc(100);   (2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。参见7.5节。   (3)指针操作超越了变量的作用范围。这种情况让人防不胜防,示例程序如下:  ......

阅读全文(2180) | 评论:0

 C++/C编程规范(7.1-7.5)----林  锐(2007-04-10 21:10:00)

摘要:第7章 内存管理     欢迎进入内存这片雷区。伟大的Bill Gates 曾经失言: 640K ought to be enough for everybody — Bill Gates 1981 程序员们经常编写内存管理程序,往往提心吊胆。如果不想触雷,唯一的解决办法就是发现所有潜伏的地雷并且排除它们,躲是躲不了的。本章的内容比一般教科书的要深入得多,读者需细心阅读,做到真正地通晓内存管理。 7.1内存分配方式 内存分配方式有三种: (1)      从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。 (2)      在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。 (3)      从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。 7.2常见的内存错误及其对策        发生内存错误是件非常麻烦的事情。编译器不能自动发现这些错误,通常是在程序运行时才能捕捉到。而这些错误大多没有明显的症状,时隐时现,增加了改错的难度。有时用户怒气冲冲地把你找来,程序却没有发生任何问题,你一走,错误又发作了。 常见的内存错误及其对策如下: u       内存分配未成功,却使用了它。 编程新手常犯这种错误,因为他们没有意识到内存分配会不成功。常用解决办法是,在使用内存之前检查指针是否为NULL。如果指针p是函数的参数,那么在函数的入口处用assert(p!=NULL)进行检查。如果是用malloc或new来申请内存,应该用if(p==NULL) 或if(p!=NULL)进行防错处理。   u......

阅读全文(2121) | 评论:0

C++/C编程规范(4-6)----林  锐   (2007-04-08 15:37:00)

摘要:第4章 表达式和基本语句
读者可能怀疑:连if、for、while、goto、switch这样简单的东西也要探讨编程风格,是不是小题大做?
我真的发觉很多程序员用隐含错误的方式写表达式和基本语句,我自己也犯过类似的错误。
表达式和语句都属于C++/C的短语结构语法。它们看似简单,但使用时隐患比较多。本章归纳了正确使用表达式和语句的一些规则与建议。
4.1 运算符的优先级
 C++/C语言的运算符有数十个,运算符的优先级与结合律如表4-1所示。注意一元运算符 +  -  * 的优先级高于对应的二元运算符。 优先级 运算符 结合律
从 高 到 低 排 列 ( )  [ ]  ->  . 从左至右
 !  ~  ++  --  (类型) sizeof
+  -  *  & 从右至左  *  /  % 从左至右
 +  - 从左至右
 <<  >> 从左至右
 <   <=   >  >= 从左至右
 ==  != 从左至右
 & 从左至右
 ^ 从左至右
 | 从左至右
 && 从左至右
 || 从右至左
 ?: 从右至左
 =  +=  -=  *=  /=  %=  &=  ^=
|=  <<=  >>= 从左至右
表4-1 运算符的优先级与结合律  【规则4-1-1】如果代......

阅读全文(2200) | 评论:0

C++/C编程规范(1-3)----林  锐(2007-04-08 12:55:00)

摘要:第1章 文件结构
每个C++/C程序通常分为两个文件。一个文件用于保存程序的声明(declaration),称为头文件。另一个文件用于保存程序的实现(implementation),称为定义(definition)文件。
C++/C程序的头文件以“.h”为后缀,C程序的定义文件以“.c”为后缀,C++程序的定义文件通常以“.cpp”为后缀(也有一些系统以“.cc”或“.cxx”为后缀)。
1.1 版权和版本的声明
版权和版本的声明位于头文件和定义文件的开头(参见示例1-1),主要内容有:
(1)版权信息。
(2)文件名称,标识符,摘要。
(3)当前版本号,作者/修改者,完成日期。
(4)版本历史信息。 /*
* Copyright (c) 2001,上海贝尔有限公司网络应用事业部
* All rights reserved.
*
* 文件名称:filename.h
* 文件标识:见配置管理计划书
* 摘    要:简要描述本文件的内容
*
* 当前版本:1.1
* 作    者:输入作者(或修改者)名字
* 完成日期:2001年7月20日
*
* 取代版本:1.0
* 原作者  :输入原作者(或修改者)名字
* 完成日期:2001年5月10日
*/ 示例1-1 版权和版本的声明
1.2 头文件的结构
头文件由三部分内容组成:
(1)头文件开头处的版权和版本声明(参见示例1-1)。
(2)预处理块。
(3)函数和类结构声明等。
假设头文件名称为 graphics.h,头文件的结构参见示例1-2。  【规则1-2-1】为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块。
 【规则1-2-2】用 #include <filename.h> 格式来引用标准库的头文件(编译器将从标准库目录开始搜索)。
 【规则1-2-3】用 #include “filename.h” 格式来引用非标准库的......

阅读全文(2513) | 评论:0