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

【Effective C++读书笔记】篇二(条款02~条款04)

2016-05-06 22:05 573 查看
条款02:尽量以 const,enum,inline 替换 #define

#define  PI    3.14


1)对于这样的宏定义,PI 在编译之前被预处理器全部换成了 3.14,所以 PI 也许并不会进入符号表(symbol table),当运用此常量发生编译错误时,错误信息也许会提到 3.14 而不是 PI ,此时不知道此宏定义的程序员就可能会产生困惑。

解决方法是用 const :

const dobule Pi = 3.14;


2)其次,定义不好的宏会让人痛苦不堪。例如:

#define max(a, b) a > b ? a : b


看似正确的写法实则漏洞百出,比如我们这样使用:

int a = 1, b = 3, c;
c = ++max(a, b);


本意是要a 和 b 中最大值加 1 后的结果赋给 c ,实际经过预处理之后,代码变成了:

c = ++ a > b ? a : b;
c 的最后结果是 3 ,而非我们想要的 4。

所以记得要给宏中的所有实参加上小括号!!!如:

#define max(a, b) (((a) > (b)) ? (a) : (b))


即是这样,还是会发生一些意想不到的问题。

比如:

int a = 5, b = 0;
max(++a, b);		//a被累加二次
max(++a, b + 10);	//a被累加一次
a 的累加次数竟然取决于“它被拿来和谁比较”。

此时,你需要 inline 来解决此问题:

template <typename T>
inline T max(const &T a, const &T b)    //由于我们不知道 T 是什么,所以用 pass by reference-to-const
{
return (a > b ? a : b);
}


3)使用 #define 无法定义一个专属于一个 class 的常量或函数,而使用 const 和 inline 完全可以做到。

请记住:

1)对于单纯变量,最好以 const 对象或 enum 替换 #define;

2)对于形似函数的宏,最好改用 inline 函数替换 #define。

条款03:尽可能使用 const

关键字 const 可以指定一个“不该被改动的值” ,而编译器会强制实施这项约束。

如果关键字 const 出现在星号左边,表示被指物是常量;如果出现在星号右边,表示指针自身是常量;如果出现在星号两边,表示被指物和指针两者都是常量。

此外还要注意迭代器的 const 使用:

const std::vector<int>::iterator iter;     //类似 T *const, 迭代器本身不能改变
std::vector<int>::const_iterator iter;     //类似 const T *,迭代器所指内容不变


许多人漠视的一件事实:两个成员函数如果只是常量性(constness)不同,可以被重载。这也是C++一个重要的特性。

#include <iostream>
using namespace std;

class TextBlock
{
public:
TextBlock(string _str)
{
str = _str;
}
const char& operator[](size_t pos) const                  //operator[] for const 对象
{
return str[pos];
}
char& operator[](size_t pos)                              //operator[] for non-const 对象
{
return str[pos];
}

private:
string str;
};

int main()
{
const class TextBlock ctb("hello");
class TextBlock tb("world");
cout << ctb[0] << endl;
cout << tb[0] << endl;
cin >> tb[0];	                                                   //由于是 non-const,还可以cin
cout << tb[0] << endl;
return 0;
}


不过,non-const 对象是可以调用 const operator[] 的,反过来 const 对象不能调用 non-const operator[](可以注释掉上面的 non-const operator[] 试试 ^_^)。因为 const operator[]保证函数不修改对象的成员变量,而 non-const operator[] 不能保证。考虑到 const operator[] 完全做掉了 non-const operator[]
该做的一切,唯一不同的是其返回类型多了一个 const 修饰,这种情况下将 const 移除是安全的,所以我们可以用 non-const operator[] 调用const operator[] ,来避免代码的重复。

做法如下:

class TextBlock
{
public:
TextBlock(string _str)
{
str = _str;
}
const char& operator[](size_t pos) const                  //operator[] for const 对象
{
return str[pos];
}
char& operator[](size_t pos)                              //operator[] for non-const 对象
{
return
const_cast<char&>(
static_cast<const TextBlock&>(*this)              //使用 static_cast 将当前对象(*this)强制转换为
[pos]                                             //const类型然后调用 const operator[],最后使用
);                                                //const_cast将返回值的 const 属性移除
}

private:
string str;
};


请记住:

1)将某些东西声明为 const 可帮助编译器侦测出错误用法。const 可被施加与任何作用域内的对象、函数参数、函数返回类型、成员函数本体;

2)编译器强制实施 bitwise constness,但你编写程序是应该使用“概念上的常量性”(conceptual constness);

3)当 const 和 non-const 成员函数有着实质等价的实现时,令 non-const 版本调用 const 版本可避免代码重复。

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

始终记住这句话:读取未初始化的值会导致不明确的行为。永远在使用对象之前将其初始化。

1)对于内置类型,必须手工初始化;

2)对于内置类型以外的其他东西,初始化责任在构造函数身上,要确保每一个构造函数都将对象的每一个成员初始化。

使用构造函数初始化时,切记区别初始化(Initialization)与赋值(assignment)的区别。

构造函数初始化的地方在成员初始化列表那里,函数体里面其实并非初始化,而是赋值,所以对于未使用列表初始化的成员,编译器会自动调用其对应的 default 构造函数,然后紧接着在进入函数体后对它们赋予新值。

考虑到像 const 或 reference 这样的成员变量,一定要初始化,不能被赋值,所以我们最好总是使用成员初始化列表。

例如:

#include <iostream>
using namespace std;

class TextBlock
{
public:
TextBlock(const int &_a, int &_b):a(_a), b(_b){}    //注意此处引用的参数使用的是 int &,如果使用 const int&
private:                                                    //那么我们的成员变量也必须是常引用才行,否则报错。
const int a;
int &b;
};

int main()
{
int i = 1, j = 2;
class TextBlock tb(i, j);

return 0;
}


当我想把“初始化”的过程放在函数体内时,gcc 报错如下:

error: uninitialized member ‘TextBlock::a’ with ‘const’ type ‘const int’ [-fpermissive]
error: uninitialized reference member ‘TextBlock::b’ [-fpermissive]


再一次说明只有成员初始化列表才是成员正真初始化的地方,函数体里只是赋值。

请记住:

1)为内置型对象进行手工初始化,因为C++不保证初始化它们。;

2)构造函数最好使用成员初始化列表(member initialization list),而不要在构造函数体内使用赋值操作(assignment)。初始化列表的排序次序最好与它们在 class 中的声明顺序一致;

3)为免除“跨编译单元之初始化次序”问题,请以 local static 对象替换 non-local static 对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: