以下内容参考自《程序员面试宝典》
(一)指针常量 & 常量指针
关于const修饰指针的情况,可以分为以下4种情况:
int b = 1;
const int* a = &b; // case 1
int const *a = &b; // case 2
int* const a = &b; // case 3
const int* const a = &b; // case 4
如果const位于星号的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量(指针常量),e.g. case 1和case 2;如果const位于星号的右侧,则const就是修饰指针本身,即指针本身是常量(常量指针),e.g. case 3。
对于指针常量,我们不允许对指针指向地址的内容进行更改。e.g.
int b = 1;
const int* a = &b;
*a = 0; // error
但是可以通过其他方式来修改*a的值:
(1)通过改变b的值:
int b = 1;
const int* a = &b;
b = 0;
cout << *a << endl; // 0
(2)使a指向另一处地址:
int b = 1;
const int* a = &b;
int c = 0;
a = &c;
cout << *a << endl; // 0
对于指针常量,可以先不进行初始化。因为虽然指针内容是常量,但是指针本身不是常量。e.g.
const int* a; // 编译通过
对于常量指针,我们不能使指针指向另一处地址,因为指针本身是一个常量,也正因如此,在定义时就需要初始化。e.g.
int b = 1;
int* const a; // error,需要初始化
int* const a = &b;
*a = 0; // 编译通过,允许对指向地址的值就行修改
cout << a++ << endl; // error,不允许对指向的地址进行修改
对于case 4,则是严格要求指针和指针指向地址的值都是常量,那么就是只能作为右值了。
(二)const成员函数
为了使成员函数的意义更加清楚,我们可以在不改变对象的成员函数的函数原型中加上const。e.g.
class A{
private:
int a;
public:
int getA() const;
};
// 关键字const必须在函数声明与函数实现中保持一致,否则编译器会把它们看成不同的函数
int A::getA() const{
return a;
}
如果const成员函数中试图修改成员变量或者调用另一个非const成员函数,编译器将会报错(cannot assign to non-static data member within const member function 'getA')。但是对于静态变量(不过要注意静态变量一定要初始化)或者是带有mutable关键字修饰的成员变量,则是可以编译通过的。e.g.
class A{
private:
int a;
static int b;
mutable int c;
public:
int getA() const;
};
int A::b = 0;
// 关键字const必须在函数声明与函数实现中保持一致,否则编译器会把它们看成不同的函数
int A::getA() const{
b++;
c--;
return a;
}
如果把const放在函数声明的前面,那么就意味着函数的返回值是常量,意义就完全不同了。
(三)const & #define
C++可以用const定义常量,也可以用#define定义常量,但是前者比后者有更多的优点:
- const常量有数据类型,而宏定义常量没有数据类型。编译器可以对前者进行类型安全检查,而对后者只进行字符替换,没有类型安全检查,并且在字符替换中可能会产生意料不到的错误(边际效应——由于边缘运算符优先级较高而先对边缘进行运算)。
- 有些集成化的调试工具可以对const常量进行调试,但是不能对宏定义常量进行调试。