C++学习 十一、const, mutable关键字
前言
本篇继续C++学习,const关键字。
const常量
C++中,const被用于向编译器指定不变值,也就是常量。
const修饰普通类型的变量
当const用于修饰普通类型的变量时,该变量值将不能被修改,也就是成为一个常量,否则将报错error: assignment of read-only variable
:
const int a = 1;
a = 2; // error!
即使获取常量的地址,使用指针也无法修改常量的值。示例如下:
int *pa = (int*) &a;
*pa = 2;
cout << "a=" << a << endl;
// print: a=1
可见,使用指针仍然无法修改常量的值。
这是由于编译器对于程序会进行优化,对于const常量将直接采用类似于#define
的方式,将a
替换为1
,而使用指针将修改时,内存空间中a
的地址处的确被修改成了2
。
volatile关键字
volatile
关键字可以关闭编译器的优化,使得指针能够修改const常量的值:
volatile const int b = 1;
int* pb = (int*) &b;
*pb = 2;
cout << "b=" << b << endl;
// print: b=2
尽量不要使用volatile使得const常量能够被指针修改,因为这并不符合使用const常量的初衷。
常量指针与指针常量
常量指针与指针常量是const关键字的重要应用,也是容易混淆的概念。
常量指针
我们通常说int指针,double指针等,都是说某个类型的指针。常量指针也是这样,即常量的指针:
int a = 1;
const int* pa = &a;
上面的声明指出,指针pa
指向一个const int
类型。
注意,常量的指针,不是说指针指向的值是常量,而是说对于该指针而言,这个指向值是个常量。换句话说,常量指针指向的值是只读的。下面的内容将报assignment of read-only location '*pa'
:
*pa = 2; // error!
但直接修改a
就没问题:
a = 2;
此外,const int*
与int const*
没有区别,都是常量指针,对应的汇编指令如下图所示:
指针常量
所谓指针常量,就是说指针本身是个常量。换句话说,指针保存的地址是个常量,不能改变指向的位置,示例如下:
int b = 1;
int* const p = &b;
常量指针保存的地址可以修改,但指针常量的地址不能被修改,否则会报error: assignment of read-only variable 'pb'
:
pa = pb;
pb = pa; // error!
一句话辨析
简而言之,常量指针int const* p
,*p
只读;int* const p
,p
只读。
const变量与指针
带const关键字的变量和指针有下面这三种可能的搭配关系:
int a = 1;
const int b = 1;
const int c = 1;
int const* pa = &a;
int const* pb = &b;
int* pc = &c; //error
其中,pa
就是上面提到的常量指针,*pa
只读,但可以修改a
;pb
也是常量指针,但是其指向的b
也是const常量,因此*pb, b
都是只读的;
像int* pc = &c
这样,将const变量赋给常规指针的操作将报error: invalid conversion from 'const int*' to 'int*'
。这在上面提到过,使用const变量即认为该变量不应当被改变,而通过常规指针取地址则是希望能够改变变量,这在编译器看来是矛盾的。如果非要这么做,需要通过强制类型转换把常规指针转为常量指针。
常量引用
类似常量指针,常量引用也不能修改被引用的变量:
int d = 1;
const int& rd = d;
d = 2;
rd = 2; // error!
可以直接修改变量d = 2;
,但是通过引用修改变量rd = 2;
报error: assignment of read-only reference 'rd'
。即,常量引用变量自身是只读的。
此外,常规引用中,引用类型必须与被引用的变量类型相同,否则将报error: cannot bind non-const lvalue reference of type 'double&' to an rvalue of type 'double'
。但常量引用不受类型限制:
int e = 1;
double& re = e; // error!
const double& cre = e;
上面实例中,第二行常规引用报引用类型错误,而第三行正确。
注意:C++ Primer Plus中提到,只有在const引用,并且引用类型正确但不是左值,或引用类型不正确但可以类型转换时,将生成临时变量。不过在我的程序中,即使是非const引用变量&re
来引用int变量e
,而报错信息是rvalue of type 'double'
,也就是说e
仍然被创建了一个double类型的临时变量,只不过不能被非const引用变量绑定。实操与书本出现了矛盾。
const与函数参数
前几篇中提到,函数传参有传值,传地址,传引用的方式。传值不会影响实参的值,而传地址,传引用都可能改变实参。
如果希望函数传参不会影响实参的内容,可以通过const使实参只读。
const指针参数
函数参数类型前添加const关键字表明,该参数是只读的,即使参数是指针或是引用。指针经常被用于数组操作:
void printArray(int const arr[], int n){
for(int i=0; i<n; i++){
std::cout << arr[i] << std::endl;
// arr[i] = i; // error!
}
}
上面的示例中,int const arr[]
表明数组arr
是只读的,因此arr[i]=i;
将报assignment of read-only location '*(arr + ((sizetype)(((long unsigned int)i) * 4)))'
。根本原因实际上就是常量指针指向的值是只读的。
const引用参数
const引用与const指针行为类似。引用经常被用于大型结构,对象的操作:
struct myStruct{
int a;
float b;
double c;
};
void printStruct(const myStruct& st){
std::cout << st.a << std::endl;
std::cout << st.b << std::endl;
std::cout << st.c << std::endl;
// st.c = st.b; // error!
}
上面的示例中,const myStruct& st
表示结构st
是只读的,因此st.c = st.b;
将报error: assignment of member 'myStruct::c' in read-only object
。根本原因实际上就是常量引用变量自身是只读的。
另外,还是要重复以下常量引用参数与临时变量:
const引用参数在以下情况下,生成临时变量:
- 实参类型正确,但是右值;
- 实参类型不正确,但可以转换为正确的类型。
并绑定到形参上。顺序是:传递实参,生成临时变量,与常量引用形参绑定。
const返回类型
函数的返回类型也可以声明const。
返回值
对于int,double这些普通返回值类型,加不加const都一样。
int const func(int a){
return a;
}
返回引用
对于返回引用的函数,添加const关键字使得返回的引用成为不能修改的左值:
int const& func(int& a){
return a;
}
mutable关键字
对于const结构变量或者const对象,我们不能修改其成员的值。
如果希望能够修改const结构变量、对象中某个成员的值,可以使用关键字mutable
,示例如下:
struct myStruct{
int a;
float b;
mutable double c;
};
void main(){
const myStruct t {1, 2, 3};
t.c = 5;
// t.b = 4; // error
}
被mutable
修饰的成员,即使结构变量被声明为const常量,该成员也能够被修改,而修改非mutable
成员则会报error: assignment of member 'myStruct::b' in read-only object
。
后记
本篇比较详细的记录了C++中const关键字的使用方法。下篇将探讨C++的作用域问题。