本节书摘来自异步社区出版社《C++覆辙录》一书中的第2章,第2.9节,作者: 【美】Stephen C. Dewhurst(史蒂芬 C. 杜赫斯特),更多章节内容可以访问云栖社区“异步社区”公众号查看。
2.9:自反初始化
在以下的代码里,var的值变成了多少?
int var = 12;
{
double var = var;
// ...
}```
未有定义。C++语言中,某个名字在它的初始化对象被解析到之前就进入了其辖域的话,在初始化对象引用到这个名字时,它引用到的不是别的,正是这个刚刚被声明的对象。没有几个软件工程师会写出像上面这么莫名其妙的声明代码,但也许复制、粘贴的手法会让你陷入困境:
int copy = 12; // 某深藏不露的变量
// ...
int y = (3x+2copy+5)/z; // 将y的赋值运算符的右手边操作数剪切……
// ...
void f(){
// 这里需要y的初始化值
int copy = (3x+2copy+5)/z; // 把上面的剪切内容粘贴到此
}`
用预处理符号的话,你会犯和恣意复制、粘贴的行为完全一样的错误(参见常见错误26):
int copy = 12; // 某深藏不露的变量
// ...
#define Expr ((3*x+2*copy+5)/z);
// ...
void g(){
// 这里需要y的初始化值
int copy = Expr; // 噩梦重现
}```
此问题的另一种表现形式就是命名时把型别的名字和非型别的名字弄混了:
struct buf{
char a, b, c, d;
};
// ...
void aFunc(){
char *buf = new char[sizeof(buf)];
// ... `
那个局域里的buf很可能会获取4字节的内存,足够放置一个char *。这个错误可能会很久都校验不出来,尤其在型别struct buf
和指针型别变量buf
`具有相同大小的时候23。遵守一个把型别和非型名的名字区分清楚的命名约定就可以在这个问题上防患于未然(参见常见错误12):
struct Buf{
char a, b, c, d;
};
// ...
void aFunc(){
char *buf = new char[sizeof(Buf)]; // 没问题
// ...
}```
现在我们知道怎么解决下面这样的问题了:
int var = 12;
{
double var = var;
// ...
}
但它的变形呢?
const int val = 12;
{
enum {val = val};
// ...
}`
枚举量val的值是多少?未有定义吗?再猜一次。正确答案是其值为12,理由是枚举量的声明位置,与变量不同,是在它的初始化对象(严格地说,是枚举量的定义)之后的。“=”之后的那个val
,是在外层辖域中的常量。这段讨论把我们带入了一个更错综复杂的局面:
const int val = val;
{
enum {val = val};
// ...```