在编程的时候,我们有时候希望定义这么一个对象:其内容在初始化后不能再修改。这个时候const关键字就排上用场了。
当一个对象被const修饰后,这个对象的内容在初始化后,再也无法被修改。值得注意的是,const修饰的对象必须要初始化。
可以看看以下面的例子:
int a = 10; //a是常量
//const int i; //编译器报错,因为const修饰的对象必须进行初始化(赋值)
const int j = 2; //正确
const int k = a; //正确
a = j; //正确
//j = 3; //编译器报错,const修饰的对象内容无法被修改
const和引用
我们看这个例子:
double a = 1.0;
//int &b = a; //报错,编译器不允许用const修饰的int引用double类型
const int &c = a; //正确
a = 10.0;
cout << c << endl; //输出:1
const int d = 100;
int e = &d; //报错,编译器不允许引用const常量
这里定义的对象c之所以能够通过编译,是因为编译器让c引用了一个临时的变量,形式如下面的代码:
const int temp = a;
const int& c = temp;
这么一来,修改a的值后,输出c会发现c还是之前的1,并没有随着变量a的改变而改变。
const和指针
(1)const放在指针声明的最前面,表示指针指向的地址内容不可修改,譬如下面这个例子:
const int a = 10;
//int *b = &a; //报错,因为编译器防止指针b指向a的地址后,通过b修改a的值
const int *b = &a; //正确
*b = 5; //报错,因为const修饰指针,表示指针指向的地址内容不可修改
const int *c; //正确,const修饰指针,指针可以不初始化,参考一行
c = &a; //正确,指针的值可以变,但指向的地址内容不能用这个指针修改
int *b = c; //报错,这是底层const的限制,拷入和拷出对象必须具备相同的底层const资格
注:在C++ Primer中,形如const int *c,这种表示指针所指对象内容无法修改的const修饰,称为底层const (low-level const),此外本文提到的其他const都是顶层const(top-level const)。
(2)const的位置放在 * 号的后面,表示指针的值不能修改,例子如下:
int a = 10;
int *const b = &a; //正确,声明一个b指针,受到const限制,指针b的值无法修改
*b = 5; //正确,修改指针b指向地址的内容
std::cout << a << std::endl; //输出:5,因为通过指针c修改了a的值
//int *const c; //报错,指针c被const修饰,必须进行初始化(赋值)
int *const c = b; //报错,指针c被const修饰,必须进行初始化(赋值)
//c = &a; //报错,指针由const修饰,值不可修改
const int *const d = &a; //正确,即是顶层const,又是底层const
const和类成员函数:
再深入一些,看看下面这个例子:
class Student
{
public:
Student() { age = 10; }
int getAge() { return age; }
private:
int age;
};
int main()
{
Student stu1;
const Student stu2;
int age1 = stu1.getAge(); //正确
int age2 = stu2.getAge(); //报错,错误为: [Error] passing 'const Student' as 'this' argument of 'int Student::getAge()' discards qualifiers [-fpermissive]
return 0;
}
我们在调用getAge函数的时候明明没有修改Student类的成员,但是编译器为什么会报错呢?这里有两个关键点:
1、在调用Student对象(譬如stu1、stu2)的getAge函数时,编译器会隐式地传入这个对象的指针。为什么呢?毕竟Student的每个对象都有可能使用getAget函数,而编译器不会为每个对象分配一段getAge函数的代码,因此,每个Student对象其实是调用同一段getAge函数代码的。函数内那怎么直到调用者是哪个对象?应该返回哪对象的age成员值呢?其实,编译器在调用该函数时,会隐式地传入这个对象的指针(就像python类成员函数传入self参数一样),函数通过这个指针来访问该对象的成员age。这个跟我们写代码时用的this指针访问类内部成员一样,而这个隐式传入的指针却没有用const来修饰。我们可以按照下面的代码来理解:
//编译器隐式传入指针类似于(伪代码)
int Student::getAge(Student *const this) { return this->age; }
//编译器调用stu1的getAge函数类似于(伪代码)
int age1 = Student::getAge(&stu1);
2、在上述代码中,我们使用了const修饰了对象stu2,因此,stu2是不可修改的。然而stu2的地址传入getAge函数后,函数内部却可以修改stu2的成员age的值(虽然这个例子中没有这么做),编译为了防止这种情况发生才会报错。
//针对const对象这种情况,隐式传入的指针类型应该为const Student *const,伪代码:
int Student::getAge(const Student *const this) { return this->age; }
以上两点即是报错的原因。然而这个指针的传入又是隐式的,没有参数列表,应该怎样修改才能使得编译通过呢?很简单,只要在getAget函数的参数列表后添加一个const关键字即可,表明隐式传入的指针是const的,不可用其修改成员变量的值,代码如下:
class Student
{
public:
Student() { age = 10; }
int getAge() const { return age; } //这里添加了const
private:
int age;
};
int main()
{
Student stu1;
const Student stu2;
int age1 = stu1.getAge(); //正确
int age2 = stu2.getAge(); //正确
return 0;
}