5/14/2008

effective c++

[ 转 ] http://www.cppblog.com/apollo/articles/8127.html
面向过程的编程风格 Procedural Programming
1. C++ 不允许改变改变 reference 所代表的对象,对 reference 的所有操作与对“ reference 所代表的对象”所进行的操作相同。

2. 以 by reference 方式传递对象当作函数参数时,复制的将是对象的地址,函数中对该对象的所有操作都相当是对传入的对象进行间接操作。

3. pointer 和 reference 的最重要差异是, pointer 可以为空,使用前一定要确保其值非 0 ,而 reference 必定代表某个对象,不必作此检查。

4. 编译器无法根据函数返回值型别来区分两个具有相同名称的函数,因为返回值型别无法保证提供我们一个足以区分不同重载函数的情境。

5. 由函数指针寻址出来的函数,其调用方式和一般函数相同。

6. 可以给函数指针赋予初值,函数名称即代表了函数的地址。

7. 标准的或项目专属的头文件应用尖括号扩住;用户自行提供的头文件则使用引号。



泛型编程风格 Generic Programming
1. Standard Template Library (STL) 主要由两种组件构成:容器 container 和泛型算法 generic algorithm < 通过 function template 技术,实现与容器及数值类型无关之功能 > 。

2. 容器分类: < 切记: vector 可以是空的,数组则否 >

序列式容器 sequential container : vector, list, deque……

关联式容器 associative container : map, set, ……

3. iterator 及 const_iterator 实际上是各个容器定义式内的嵌套 nested 型别。

4. 使用泛型算法须 #include < algorithm > ,使用 function object 须 #include < functional > 。

5. function object 是某种 class 的实体对象,该 class 对 function call 运算符进行了重载操作从而可使 function object 被当作一般函数来使用。令 function call 运算符成为 inline ,从而消除“通过函数指针来调用函数“时需付出的额外代价。

6. function object adapter 会对 function object 进行修改操作。

7. 绑定配接器 binder adapter < bind1nd, bind2nd > 会使 bineary function object 变成 unary function object ; negator adaper < not1, not2 > 会逆转 function object 的真伪值;另外一种有用的 adapter 叫做 instertion adapter 。 #include < iterator >

8. map 被定义为一对数值,其中 key 通常是个字符串,扮演索引角色,另一个数值是 value 。

9. 任何一个 key 值在 map 或 set 内最多只有一份,若要多份相同 key 值,使用 multimap 或 multiset 。



基于对象的编程风格 Object-Based Programming
1 .在 class 内部定义的 member 函数被自动视为 inline 函数。对 inline 函数而言,声明在 class 内部或外部并无区别,同 non-member inline 函数一样,它应于头文件中定义。

2 . Triangular t(); 被编译器视为一个函数定义!并不是声明或定义一个 Triangular 对象!

3 .以某个 class object 作为另一个 object 的初值时,会发生 default memberwise initialization < 实际上是自动产生一个 copy constructor> ,可以为该 class 提供一个 copy constructor 来改变这一默认行为模式。

4 .若有必要为 class 撰写 copy constructor ,则同样有必要为它撰写 copy assignment operator ,除非撰写 copy constructor 的目的仅仅是为了激活编译器实施 NRV 优化。

5 .凡是在 class 主体以外定义的 const member function ,必须同时在声明与定义时都提供 const 关键字, const 紧接于函数参数表之后。

6 . member function 返回一个指向 member data 的 non-const reference ,实际上等于将该 member data 开放出去,允许程序在其它地方加以修改。由于函数可以根据参数 const 与否 而重载,故可以提供两份定义,一份为 const 版本,一份为 non-const 版本。

8. 设计 class 时,鉴定其 const member function 是一件很重要的事情!

9. 将 member data 声明为 mutable 表明:对该 member data 的修改不会破坏 class object 的常数性。

10. 欲以一个对象复制出另一个对象,先确定两个对象是否相同是个好习惯。

11. 运算符的重载规则:不可以引入新的运算符,除了 ., .*, ::, ?: 4个运算符,其它运算符皆可被重载;运算符的操作数 operand 不可改变;运算符的优先级不可改变;运算符函数的参数列中必须至少有一个参数为 class 型别。

12 . increment 和 decrement 运算符的前置及后置版本都可直接施行于 class object 其之上,编译器会自动为后置版产生一个 int 引数,其值必为 0 。

13 .所谓 friend ,具备了与 class member function 相同的存取权限,可以存取 class 的 private member 。

14 .只要 class 设计者显示提供了 copy assignment operator ,它就会被用来取代 default memberwise copy 行为。

15 .当编译器在编译过程中遇到函数调用,例如 lt(ival) , lt 可能是函数名称,可能是函数指针,也可能是一个提供了 function call 的 function object 。如果 lt 是个 function object ,编译器会在内部将此语句转化为: lt.operator(ival) ;

16 . function call 可以接受多个运算符,通常将 function object 当作参数传给泛型算法。

17 .为了取得某个 member function 的地址,只需对函数名称施以取址 address-of 运算符,同时,函数名称之前必须先以 class object 运算符加以修饰,而返回型别及参数表皆不需指明,如: void (classname::*mfptr) (int) = &classname::mfname;

18 .注意所谓的 maximal munch 编译规则,如: static vector< vector< int > > seq; 两个 ”>” 号之间必须加有空格,否则无法成功编译!

19 . pointer to member function 和 pointer to function 的一个不同点是:前者必须通过同类的对象加以调用。 .* 符号是针对 class object 的 pointer to member selection 运算符, ->* 符号是针对 pointer to class object 的 pointer to member selection 。使用它们时注意必须加上外围小括号!如: (classobject.*mfptr)(par);





面向对象编程风格 Object-Oriented Programming
1. 面向对象编程的两项最主要的特性是继承 inheritance 和多态 polymorphism 。

2. 动态绑定 Dynamic binding 是面向对象编程风格的第三个独特概念,即找出实际被调用的究竟是哪一个派生类的函数。而静态绑定 Static binding 则在程序运行之前就决议出应该调用哪一个函数。

3. 多态和动态绑定的特性只有在使用 pointer 或 reference 时才能发挥。

4. staitic member function 无法被声明为虚拟函数。

5. 任何一个类只要有纯虚拟函数,程序就会因其接口的不完整而无法为它产生任何对象,这种类只能作为派生类的子对象 subobject 之用,而且派生类必须为所有纯虚拟函数提供确切的定义。

6. 根据一般规则,凡基类定义有虚拟函数,其 destructor 应声明为 virtual 。但 Stanley B.Lippman 并不建议在这个基类中将其 destructor 声明为 pure virtual ,而是提供空白定义: inline baseclass::~baseclass(){};

7. 对于 public inheritance ,继承而来的 public 成员和 protected 成员,无论在继承体系中的深度如何,都可视为派生类自身拥有的成员。

8. 每当派生类有某个 member 与其基类的 member 同名时,便会遮蔽住基类的那份 member ,若要在派生类中使用继承而来的那份 member ,必须使用 class scope 运算符加以修饰。

9. 不可为抽象基类定义任何对象,它们扮演的角色是每个派生类的 subobject ,基于此点,一般将抽象基类的 constructor 声明为 protected 而非 public 。

10. 派生类之 constructor ,不仅必须为派生类之 data members 进行初始化操作,还需为其基类之 data members 提供适当的值。 copy constructor 和 copy assignment operator 的情形也一样,唯一棘手的是,必须明白调用基类的 copy assignment operator : base::operator = (rhs);

11. 改写基类提供的虚拟函数,派生类提供的定义其函数型别必须完全符合基类所声明的函数原型,包括参数列、返回型别、常量型 const-ness 。但是,对于“返回型别”有个例外:当基类的虚拟函数返回某个基类形式(通常是 pointer 或 reference )时,派生类中的同名函数可以返回该基类所派生出来的型别。

12. 在两种情况下,虚拟函数机制不会出现预期行为: 1 )在基类的 constructor 和 destructor 内; 2 )使用基类的对象而非对象的 pointer 或 reference 。

13. typeid 运算符是 RTTI 的一部分,可以用它来查询多态化的 class pointer 或 class reference ,获得其所指对象的实际型别。 typeid 运算符会返回一个 type_info 对象,其中存储着与型别相关的种种信息。 #include



异常处理 Exception Handling
1. 初学者常犯的错误:将 C++ 异常和 segmentation fault 或是 bus error 这类硬件异常混淆在一起。

2. 在异常处理机制终结某个函数之前, C++ 保证函数中的所有局部对象的 destructor 都会被调用。

3. auto_ptr 是标准程序库提供的 class template ,它会自动 delete 通过 new 表达式配置的对象。 auto_ptr 将 dereference 运算符和 arrow 运算符予以重载,使得我们可以像使用一般指针一样使用 auto_ptr 对象。 #include

4. 如果 new 表达式无法从程序的自由空间 free store 配置到足够的内存,它会抛出 bad_alloc 异常对象。如果要压抑不让 bad_alloc 异常被抛出,可以这么写: somepointer = new (nothrow) someclass; 这样,如果 new 动作失败,返回值为 0 。

5. 标准程序库定义了一套异常类体系 exception class hierarchy ,其最根部是名为 exception 的抽象基类。 exception 声明有一个 what() 虚拟函数,会返回一个 const char* ,用以表示被抛出异常的文字描述。 #include

6. ostringstream class 提供“内存内的输出操作”,输出到一个 string 对象上。当需要将多笔不同型别的数据格式转化为字符串表现式时,它尤其有用。 ostringstream 提供的 str() 可以返回对应的那个 string 对象。 #include

7. iostream 库也对应提供了 istringstream class ,如果需要将非字符串数据的字符串表现式转化为其实际型别, istringstream 可派上用场。

8. string class 的转换函数 c_str() 会返回 const char* !

没有评论: