《Thinking in C++, 2nd Edition》笔记-第八章(Constants)

值替换

const的最初动机是消除使用预处理器#define对常量的替换。#define只是对文本进行替换并且没有类型检查,因此会产生一个隐藏的问题,而这些用const可以避免。
例如:const int bufsize = 100;    可以在任何需要知道这个值(100)的地方使用bufsize,编译器会进行常量折叠(常量折叠是在编译时间简单化常量表达的一个过程。简单来说就是将常量表达式计算求值,并用求得的值来替换表达式,放入常量表。)
const在C++中默认是内部连接的,所以它只在当前文件里可见,别的编译单元里不可见。
把const定义在头文件中,这样包含这个头文件的编译单元都会拥有这个const变量。除非使用extern,否则都应该在定义const变量时赋值。
extern const int bufsize;
通常情况上,C++避免给const分配内存,而是把它的定义保存在一个符号表里,在它被使用时进行常量折叠。而extern表明“用外部连接”(意味着别的编译单元能够引用到它),因此此时const被强制分配了内存。
但是对于复杂的结构(无法把它放在符号表里),编译器会分配存储,阻止常量折叠,这就是const为什么默认是内部连接的原因,否则多个文件定义会产生编译错误。此时,它意味着“不能改变的一块存储”,由于编译器在编译时不需要知道存储的内容,它的值在编译时不能被使用。
const int i[] = { 1, 2, 3, 4 };
//! float f[i[3]]]; //非法
struct s{ int i, j; };
const s S[] = { {  1, 2 }, { 3, 4 } };
//! double d[S[1].j]; //非法

常量指针与指针常量


const int* u;          从变量开始读, 我们读作“ u是一个指针,它指向一个const int.”
int d = 1; 

int* const w = &d;       “w是一个常量,指向一个int"


不能直接将const对象的地址赋给一个非const指针,如下:
int d = 1; 
const int e = 2; 
int* u = &d; // OK -- d not const 
//! int* v = &e; // Illegal -- e const 
int* w = (int*)&e; // Legal but bad practice

串字面值

const限制很严格,除了串字面值。比如:
char* cp="howdy";

编译器将接受它而不报告错误。从技术上讲,这是一个错误,因为串字面值(这里是“howdy”)是被编译器作为一个常量串建立的,所引用串的结果是它在内存里的首地址。


函数参数和返回值

函数参数如果传的是值,那么加const,那么表示函数内部不能对这个参数修改,因此限制的其实是函数创建者,而不是调用者,而此时在参数时加const可能会混淆调用者,可以使用下面这种方法来限制:

void f2(int ic) { 
  const int& i = ic; 
  i++;  // Illegal -- compile-time error 
} 

而如果返回一个const值,表示返回的值是一个const,不可修改,表明它返回的值不能作为一个左值(因此对于内置类型,没有什么意义,因为内置类型本来就不能是左传;但是对于一个自定义的数据类型,这是有意义的)

// Constant return by value 
// Result cannot be used as an lvalue 
 
class X { 
  int i; 
public: 
  X(int ii = 0); 
  void modify(); 
}; 
 
X::X(int ii) { i = ii; } 
 
void X::modify() { i++; } 
 
X f5() { 
  return X(); 
} 
 
const X f6() { 
  return X(); 
} 
 
void f7(X& x) { // Pass by non-const reference 
  x.modify(); 
} 
 
int main() { 
  f5() = X(1); // OK -- non-const return value 
  f5().modify(); // OK 
// Causes compile-time errors: 
//!  f7(f5()); f5产生一个临时变量,临时变量都自动是const的(因为你无法修改它),它不能引用传入到f7中(除非参数是const引用)。
//!  f6() = X(1); 
//!  f6().modify(); 
//!  f7(f6()); 
}
传入一个const引用 或指针表明函数内部不能对参数进行修改,事实上,只要有可能,就应该将传入的指针参数设为const。

而返回值为引用 或指针是否应该用const修饰取决于你是否允许客户使用(修改)这个返回值。

临时变量是const的。

// Temporaries are const 
 
class X {}; 
 
X f() { return X(); } // Return by value 
 
void g1(X&) {} // Pass by non-const reference 
void g2(const X&) {} // Pass by const reference 
 
int main() { 
  // Error: const temporary created by f(): 
//!  g1(f()); 
  // OK: g2 takes a const reference: 
  g2(f()); 
}

一个类中的const成员变量的初始化只能在参数列表中进行,因为参数列表中初始化是在构造函数之前发生。

但是,如果希望有一个运行时的常量应该怎么创建?事实上,static const修饰的内置类型可以作为一个运行时的常量。它的初始化必须在定义时就完成。

class StringStack { 
  static const int size = 100; 
  const string* stack[size]; 
  int index; 
public: 
  StringStack(); 
  void push(const string* s); 
  const string* pop(); 
};

其实static const int size = 100; 也可以被替换成 enum { size = 100 }; 此时的enum在对象中没有占据内存,枚举成员会在编译期求值。

class Bunch { 
  enum { size = 1000 }; 
  int i[size]; 
};

一个const成员函数能够被const对象调用,而非const成员函数不能被const对象调用。声明一个函数为const不能保证它的行为也是const,所以编译器要求它的定义也必须有const标识。但是事实上,一个const成员函数仍然不能保证不修改成员变量(强行将this指针转型为非const指针,然后修改成员变量),如下:(虽然通常不会被用到)

class Y { 
  int i; 
public: 
  Y(); 
  void f() const; 
}; 
 
Y::Y() { i = 0; } 
 
void Y::f() const { 
//!  i++; // Error -- const member function 
  ((Y*)this)->i++; // OK: cast away const-ness 
  // Better: use C++ explicit cast syntax: 
  (const_cast<Y*>(this))->i++; 
} 
 
int main() { 
  const Y yy; 
  yy.f(); // Actually changes it! 
}
这种方法几乎不会被用到,如果要在const 成员函数中改变成员变量,可以将这个变量声明为mutable
// The "mutable" keyword 
 
class Z { 
  int i; 
  mutable int j; 
public: 
  Z(); 
  void f() const; 
}; 
 
Z::Z() : i(0), j(0) {} 
 
void Z::f() const { 
//! i++; // Error -- const member function 
    j++; // OK: mutable 
} 
 
int main() { 
  const Z zz; 
  zz.f(); // Actually changes it! 
} 





  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值