值替换
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"
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!
}