【Effective C++】让自己习惯C++
条款01:视C++为一个语言联邦
将C++视为一个由四个次语言组成的联邦:
- C
- Object-Oriented C++
- Template C++
- STL
条款02:尽量以const,enum,inline替换 #define
#define ASPECT_RATIO 1.653
使用#define的坏处
- #define不被视为语言的一部分,记号 ASPECT_RATIO可能没有进入记号表内,当运用此常量但获得一个编译错误信息时,错误信息会提到1.653而不是ASPECT_RATIO,你将因为追踪它而浪费时间。
- 预处理器盲目地将宏名称替换为1.653会导致目标码出现多份1.653,增加代码量。
- 无法利用#define创建一个class专属常量,因为#define并不重视作用域。
用const替换#defines的两种特殊情况
(1)当定义的常量指针时
由于常量定义式通常被放在头文件内(以便被不同的源码含入),因此有必要将指针声明为const(而不只是指针所指之物),例如:
const char* const authorName = "Scott Meyers";
(2)class专属常量
为了将常量的作用域限制于class内,你必须让它成为class的一个成员;为确保此常量只有一个实体,你必须让它成为一个static成员。
class GamePlayer { ... private: static const int Num;//声明,位于头文件中 }; const int GamePlayer::Num = 5;//定义,位于实现文件中
有时必须在声明时完成初值定义,例如:
class GamePlayer { ... private: static const int Num = 5;//声明 int score[Num]; };
由于上述的score数组声明式中必须要知道数组的大小,所以Num必须在声明中给出数值。
用enum代替#define
取一个const的地址时合法的,取enum的地址不合法,这和#define一样。
class GamePlayer { private: enum { Num = 5 };//声明 int score[Num]; };
用inline代替#define
对于形似函数的宏,最好改用inline函数替换#define
#define CALL_WITH_MAX(a,b) f((a)>(b)?(a):(b))//以a和b的较大值调用f
改为:
template<typename T> inline void callWithMax(const T& a, const T& b) { f(a>b?a:b); }
条款03:尽可能使用const
const的应用
在C++笔记(1)const关键字中大致了解了const的应用,本条款主要讲了const成员函数。
const成员函数的意义
(1)使class接口比较容易被理解
(2)它们使“操作const对象”成为可能
const成员函数的使用
两个成员函数如果只是常量性不同,可以被重载。比如下面的class可以被用来表现一大块文字:
class TextBlock { public: TextBlock(string s) { text = s;} const char& operator[](int index) const {//for const对象 return text[index]; } const char& operator[](int index) {//for 非const对象 return text[index]; } private: string text; };
在使用时,
TextBlock tb("Hello"); cout << tb[0] << endl; //调用 non-const TextBox::operator[] const TextBlock ctb("World"); cout << ctb[0] << endl; //调用const TextBox::operator[]
如果operator中还有很多其他的操作,重载会导致代码的重复,这时候可以利用non-const成员函数调用const成员函数。
const char& operator[](int index) const {//for const对象 ... return text[index]; } const char& operator[](int index) {//for 非const对象 ... return const_cast<char&>(static_cast<const TextBlock&>(*this)[index]); }
经历了两次转型:
(1)static_cast<const TextBlock&>(*this) 将*this从其原始类型TextBlock&转型为const TextBlock&。必须为它转型加上const,不然non-const的成员函数会递归调用它自己!
(2)const_cast<char&> 从const的成员函数的返回值中移除const
如果有一个成员函数,不会改变text的内容,但会改变其他的成员变量。改变其他的成员变量对const TextBlock来说是可以接受的,但编译器会报错。这时候可以使用mutable。
1. 内置型成员变量明确地加以初始化
对于五任何成员的内置类型,你必须手工完成此事。例如:
int x = 0; //对int进行手工初始化 const char* text = "A C-style string";//对指针进行手工初始化 double d; std::cin >> d; //以读取input stream的方式完成初始化
2. base classes和成员变量利用构造函数进行“成员初值列”初始化
对于内置类型以外的任何其他东西,初始化责任落在构造函数身上。规则很简单:确保每一个构造函数都将对象的每一个成员初始化。但重要的是别混淆了赋值和初始化。比如:
class Person { public: Person(string n, int a) { name = n;//这些都是赋值,而非初始化 age = a; } private: string name; int age; };
C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前,构造函数内的都不是初始化而是赋值。构造函数的一个较佳的写法是使用成员初值列,高效得多。
Person(string n, int a) :name(n), age(a){
//构造函数本体不必有任何动作 }
注意:
总是在初值列种列出所有成员变量,以免还得记住哪些成员变量无需初值。
如果这种classes存在许多成员变量和/或base classes,多份成员初值列的存在就会导致不受欢迎的重复和无聊的工作。这种情况下,可以合理地在初值列中遗漏那些“赋值表现像初始化一样好”的成员变量,改用赋值。
如果成员变量是const或reference。它们就一定需要初值,不能被赋值(见条款5)。
由于基类更早于子类被初始化,成员变量总是以声明的顺序初始化,因此在初值列时,最好以其声明的顺序为次序。
3. static对象的初始化
编译单元:指单一源码文件加上其所含入的头文件。
static对象:一般局部变量是存储在栈上,动态分配的空间都在堆上,static对象不会存储在堆或栈而是放在静态数据区。static对象生命周期会一直持续到整个程序结束,也就是它们的析构函数会在main()结束时被自动调用。static对象分为:
- local static对象 (函数内)
- non - local static对象(全局静态对象)
如果是static修饰的全局变量,且实现的函数写在头文件(h)中,在其他文件也可以访问。
接下来我们讨论不同编译单元内定义的non-local static对象的初始化次序。如果一个编译单元内的某个全局静态对象的初始化动作使用了另一个编译单元内的某个全局静态对象,它所用到的对象可能尚未被初始化,会发生什么?
例子1:
//头文件:A.h extern int a; //源文件1:B.cpp a = 3; //源文件2: C.cpp int main(){ cout<<a<<endl; return 0; }
可以看到源文件1应在源文件2之前执行,这样a才能获得初值,但事实上C++对于不同文件执行的相对次序并无明确定义。
解决这个问题是方法是不要使变量有全局的作用域,可以在文件1中定义:
1 int& GetA()
2 {undefined
3 static int a = 1;
4 return a;
5 }
而在文件2中调用
int b = GetA();
这样就一定保证a的初始化在先了。
例子2:
//----Person.h------------------------ class Person { public: void setNumber(int n); int getNumber() const; private: int number; }; //----Person.cpp----------------------- #include "Person.h" int Person::getNumber() const { return number; } void Person::setNumber(int n) { number = n; } //----Room.cpp-------------------------- #include <iostream> #include "Person.h" using namespace std; Person& p() { static Person p; return p; } class Room { public: Room() { size = p().getNumber(); } void show() { cout << "room size is " << size << endl; } private: int size; }; Room& room() { static Room room; return room; } int main() { p().setNumber(19); Room r; r.show(); getchar(); }