const的最初动机是取代预处理器#define进行值替代,被用于指针、函数变量、返回类型、类对象及其成员函数。可以在编译器需要知道这个值的地方只用常量,同时执行常量折叠。
与使用#define 一样,使用const必须把const放进头文件里。C++中的const默认为内部连接,也就是说,const仅在const被定义过的文件里才是可见的,在链接时不能被其他编译单元看到。当定义一个常量时,必须初始化,除非用extern进行说明:
extern const bufsize;
虽然extern强制进行了存储空间分配(另外还有一些情况,如取一个const的地址,也要进行存储空间分配)【注释:extern申明后,】,但C++编译器通常并不为const分配存储空间,而是保存在符号表里(进程内存常量区?)。绝对部位任何const分配存储是不可能的,对于复杂结构,编译器建立存储(分配内存)会阻止常量折叠。
const可以用于集合,但编译器不能把一个集合存放在它的符号表(常量区)里,所以必须分配内存,这时在编译期间,编译器将不能获得集合元素的值,不能进行常量折叠。
cosnt int i[] = {1, 2, 3, 4} ;
float f[i[3]]; //非法
struct s {int i, j;};
cosnt s S[] = {{1, 2}, {3, 4}};
double d[s[1].j] //非法
const 在C和C++中的区别
cosnt bufsize = 100;
char buf[bufsize];
因为bufsize占用存储的摸个地方,所以编译器不知道它在编译时的值,在C中可以这样书写
cosnt bufsize;
但在C++中是不对的,C编译器吧它当做一个声明,指明在别的地方有内存分配。因为C默认const是外部链接的,而C++默认为内部链接,所以同样的事在C++总,需要用rxtern把链接改成外部链接
extern const bufsize;
在C中使用限定符cosnt不是很有用,即使在常数表达式里(必须在编译期间被求出),想使用一个已命名的值,使用cosnt也不是很有用的。C迫使程序员在预处理器使用#define
指针
cosnt int* x ; //从表示符开始这样读:x是一个指针,它指向一个const int int cosnt *x; //x是一个指向恰好是const的int的普通指针
int d = 1;
int* const x = &d; //x是一个指针,这个指针是指向int的const指针
C++关于类型检查有器特别之处,可以把一个非const对象的地址赋个给一个const指针(因为也许又是不想改变某些可以改变的东西),然而不能将一个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
char*cp = “steven”;
编译器将接收它而不报错误,但从技术上讲,这是一个错误,因为串字面值(“steven”)是被编译器作为i个常量串建立的,所以引用串的结果是它在内存的首地址。所以串字面值实际上是常量串。然而编译器把他们作为非常量看待,这是因为有许多现在的C代码是这样做的。
函数参数和返回值
int f(const int i)
{
int a = i * i;
retutn a;
}
int f(int i) //内部限定参数要优于参数限定,可以避免不清楚传值过程中隐藏的生成临时副本变量而引起的混淆
{
const int& b = i;
int a = b * b;
return a;
}
对于返回内部基本类型的函数来说,返回值const限定是没有意义的,但是如果返回的是自定义类型,则cosnt返回对象将不能做左值(不能被复制和修改)
临时变量
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
void main()
{
//Error: const temporary created by f():
//g1(f());
//OK: g2 takes a const reference:
g2(f());
}
类
程序员可能想在一个类里建立一个局部常量,将他用在常数表达式里,这个常数表达式在编译期间被求值。然而,const的意思在类里是不同,必须使用另一技术--枚举
要保持类对象为常量通常比较复杂。编译器能保证一个内部数据类型为常量,但不能控制一个类中错综复杂的事物。为了保证一个类对象为常量,引进了const成员函数:对于一个常量对象,只能调用const成员函数。
在一个类里,const恢复它在C中的一部分意思。它在每个类对象里分配存储并代表一个值,这个值一旦被初始化以后就不能改变。在一个类里使用const的意思是“在这个对象寿命期内,这是一个常量”。然而, 对这个常量来讲,每个不同的对象可以包含一个不同的值。类里的const成员变量必须放在构造函数的初始化列表中初始化
因为在类对象里进行了内存分配,编译器不能知道const的内容是什么,所以不能把它用作编译期间的常量。不能这样写:
class bob {
const size = 100; //illegal
int array[size]; //illegal
//...
普通的八法是使用不带实例的无标记的enum,枚举的所有值必须在编译是建立,它对类来说是局部的,但常数表达式能得到它的值
class bunch
{
enum { size = 1000 };
int i[size];
}
使用enum不会占用对象中的存储空间,枚举常量在编译时被全部求值
cosnt对象必须保证对象的数据成员在对象寿命周期内不被改变。可以很容易保证共有数据不被改变,但不能知道哪些成员函数会改变数据。如果一个成员函数声明为const函数,在一个const对象就可以调用改成员函数。没有被声明为const的函数,被认为会改变对象成员变量,因此不能被const对象调用。
构造函数和析构函数不能是const函数
按位与按成员cosnt
按位const的意思是对象中的每个位是固定的,所以对象的每个位映像从不改变。
按成员const的意思是虽然整个对象从概念上讲是不变的,但是某成员可能有变化。但编译器被告知一个对象是cosnt对象时,它将保护这个对象。有两种方法可以在const成员函数里改变数据成员:
1.强制转换const (过时,不推荐)
class Y
{
int i, j;
public:
Y() { i = j = 0; }
void f() const;
};
void Y::f() const
{
//i++; Error--const member function
((Y*)this)->j++; //OK cast away const-ness
}
void main()
{
const Y yy;
yy.f(); //actually changes it!
}
2.mutable
class Y
{
int i;
mutable int j;
public:
Y() { i = j = 0; }
void f() const;
};
void Y::f() const
{
//i++; Error--const member function
j++; //OK mutable
}
void main()
{
const Y yy;
yy.f(); //actually changes it!
}
volatile
防止编译器优化,每次读数据操作都务必执行可以建立const volatile对象,这个对象不能被程序员改变,但看通过外面的工具改变
将像cosnt一样,可以对数据成员、成员函数和对象本身使用volatile,并且也只能为volatile对象调用volatile成员函数