您的位置:首页 > 编程语言 > C语言/C++

《Effective C++ 改善程序与设计的55个具体做法》——第一章笔记

2015-03-15 20:51 316 查看
导读中有两点注意:

构造函数声明为explicit,可以阻止隐式类型转换,禁止编译器执行非预期的类型转换。推荐这样使用,除非有理由进行隐式类型转换。

拷贝构造函数:以同型对象初始化自我对象;拷贝赋值操作符:从同型对象拷贝值到自我对象。

Widget w2(w1); // 拷贝构造函数

w2 = w1; // 拷贝赋值操作符

Widget w3 = w2; // 拷贝构造函数

区分:如果有一个新对象被定义,则调用拷贝构造函数;没有新对象定义,调用赋值操作。

拷贝构造函数很重要,他定义了值传递的方式。如果函数参数是值传递,会发生对象的拷贝,拷贝构造函数被调用。一般来说,值传递用户自定义类型不是一个好主意。

第一章

条款1:视C++为一个语言联邦

四种次语言:C、object-oriented C++、Template C++、STL

各种次语言之间,编程策略有所不同。

比如:内置类型(C-like)值传递通常比引用传递高效,但面向对象中,自定义类型有构造函数和析构函数,引用传递更好。Template C++尤其如此,因为此时你甚至不知道所处理的对象类型。然而STL中迭代器和函数对象都是在C指针上塑造出来的,值传递规则再次适用。

C++并不是一个带有一组守则的一体语言,四个次语言都有自己的规约,关键看你使用C++的哪个部分。

条款2:尽量用const enum inline替换#define

宁可以编译器替换预处理器,#define不被看做是语言的一部分,预处理器后,编译器看不到了。

const常量替换宏

常量字符串用string对象比char*合适:const std::string name("ABC");

类的静态属性如果是整数类型(比如int char bool),只要不取它们的地址,可以只声明而不定义,其它情况都需要提供定义。在头文件声明,在实现文件中定义。声明时提供初值,定义时不可再设初值,旧编译器不支持这种语法,不允许声明时获得初值,可以将初值放在定义中。

无法利用#define创建一个类的专属变量,#define不重视作用域,宏一旦定义就一直有效,不能提供封装性。

如果类的编译期间需要类的常量值(比如类的成员是个数组,其大小是个常量),但编译器又不支持静态变量的声明时初始化,可以利用enum hack,其理论基础是枚举类型的数值可以权充int使用。enum hack更像#define,而不是const。有时候这正是我们想要的,比如enum不允许取地址,如果不想让一个指针指向某个整数常量,可以用enum

inline函数替换形似函数的宏定义,既可获得宏的效率,又有可预料行为和类型安全性。

宏只用于控制编译

条款3:尽可能使用const

const告诉编译器和程序员某值应该保持不变。只有满足就应该使用const

const可以修饰各作用域的变量、指针。最具威力的就是函数声明时,返回值、参数、函数本身。

令函数返回一个常值,可以降低客户错误而造成的意外;

良好的用户自定义类型的特征是它们避免无端的与内置类型不兼容。

const参数没什么特别,不需要修改时将参数声明为const

const成员函数:

C++中修改const变量的值被视为编译错误。面向对象中为体现封装性,通常不允许直接修改类的数据成员,应当使用公用接口完成。为保证const对象的常量性,编译器需要区分不安全和安全函数(即区分修改对象和不修改对象的函数)。在C++中只有被声明为const的成员函数才能被一个const对象调用。声明时在参数列表后加const,定义时也要加上。const成员函数不允许修改类的数据成员。需要注意,const成员函数不修改数据成员,但如果数据成员是指针,const成员函数不保证不修改指针指向的对象,编译器不会检测到错误。const成员函数可以被具有同参数列表的非const成员函数重载。类对象的常量性决定调用哪个函数。const成员函数可以访问非const对象和const对象的所有数据成员,非const成员函数只能访问非const对象的数据成员,不可访问const对象的数据成员。声明成员函数时如果该函数不对数据做修改,就应该声明为const成员函数。

const成员函数是为了可作用于const对象,表明可不可以改动对象内容。

两个成员函数如果只是常量性不同,可以重载。

如果函数返回类型是个内置类型,改动函数返回值是不合法的。比如[ ]返回一个值,则a[0] = 1;错误。

bitwise constness(physical constness):不更改对象的任何一个bit。指针成员时,指针指向不变,指向的对象可能发生变化,此时却满足了bitwise

logical constness:const成员函数可以修改过部分bits,但只有客户侦测不到时可以。

mutable关键字修饰的成员,在const成员函数中也可以被修改。

const和非const成员函数避免重复,非const调用const成员,将常量性去掉。需要类型转换,先将*this转为const,然后调用const成员函数,然后将返回值转为非const。不能用const成员函数调用非const成员函数。

总结:1、const帮助编译器侦测错误;2、编译器实施bitwise constness,编写程序时应使用概念常量性;3、当const和非const成员实质等价实现时,令非const调用const版本,避免重复。

条款4:确定对象被使用前已先被初始化

内置类型手工完成,用户自定义类型由构造函数完成初始化——每个成员都被初始化!

不要混淆赋值和初始化!!在构造函数体内都是赋值,初始化发生在进入构造函数本体之前,构造函数被调用之时。

用初始化列表进行初始化,效率更高。

初始化列表要列出所有成员。如果成员变量是const或reference,必须使用初始化列表。

最简单做法:总是使用初始化列表进行初始化。

C++成员变量初始化顺序是固定的:基类早于派生类成员,成员变量以声明顺序初始化。

非本地静态对象包括全局对象、namespace作用域内的对象、class内static对象、file作用域内static对象;本地静态对象是函数内定义的static对象。

对不同编译单元内的非本地静态对象,无法明确其初始化顺序。解决方案:将每个非本地静态对象搬到自己的专属函数中,函数返回一个引用,然后通过这些函数获取这些对象,而不直接指涉这些对象。换句话说,把非本地静态对象替换为本地静态对象。理论基础在于:C++保证函数内的本地静态对象会在该函数被调用期间首次遇到该对象之定义式时被初始化。替换后,保证了引用指向的对象初始化了。更棒的是,如果你从未调用这些函数,就不会引发构造和析构成本。这些函数一般适合为inlining的。

总结:1、内置类型进行手工初始化,C++不保证初始化他们;2、构造函数最好使用初始化列表,而不是在构造函数中使用赋值操作。初始化列表列出的成员变量其排列次序与声明次序相同;3、为免除跨编译单元的初始化次序问题,请以本地静态对象替换非本地静态对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: