C/C++ Const关键字知识点整理
参考:博客
const是constant的缩写,名词意思有 常数;常量,形容词意思有 不变的;一贯的。
在C++中,const用来修饰内置类型变量(int; double; char等);自定义对象(结构体或自定义类等);
成员函数;返回值;函数参数。
const指定一个语义约束,编译器会强制实施这个约束,允许告诉编译器某值是保持不变的。如果确实有某个值保持不变,就应该明确使用const,以便获得编译器的帮助。
一.const修饰普通类型的变量
const int a = 10;
cout << "a=" << a << endl; //可以对变量a进行访问
int b = a; //可以将变量a赋值给变量b
cout << "b=" << b << endl;
输出:
#a=10
#b=10
const int a = 10;
a = 20; //修改变量a的值
输出:
#"a": 不能给常量赋值
被const修饰的内置类型变量(int; double; char等),都会被编译器看作常量,而常量不能被赋值,也就是不能位于赋值符号的左侧。
通过指针修改const修饰的变量:
以下是在msvc,gcc, g++编译器下的测试
#msvc编译器
const int a = 10;
int* b = (int *)&a;//由const型转为非const型需要显式地进行转换,相反则不需要
*b = 20;
cout << "a=" << a << endl;
cout << "a的地址=" << &a << endl;
cout << "b=" << *b << endl;
cout << "b的地址=" << b << endl;
输出:
#a=10
#a的地址=00CFFC70
#b=20
#b的地址=00CFFC70
两个变量的地址一样,但是值不一样。
#gcc编译器
[root@iz2ze0k0bexac9myc5itdcz admin]# ./const1
#a=20,b=20
#g++编译器
[root@iz2ze0k0bexac9myc5itdcz admin]# ./const2
#a=10
#address of a=0x7fff61228494
#*b=20
#b=0x7fff61228494
最后:
gcc编译器的结果两个变量一致;
而msvc和g++在编译时因为a是常量,所以被编译为立即数,就算用指针修改也不会改变a的值了。
但是推荐不要使用指针来修改const修饰的变量,遇到其他编译器或者其他版本的,不知道会发生什么。
上述的变量a都是局部常变量;
如果变量a是全局常变量:
const int a = 10;
int main(int argc, char **argv)
{
int* b = (int *)&a;
*b = 20; //这里会引发异常
cout << "a=" << a << endl;
cout << "a的地址=" << &a << endl;
cout << "b=" << *b << endl;
cout << "b的地址=" << b << endl;
return 0;
}
#可以编译得过去,但是运行到*b=20时会出现段错误:
#引发了异常: 写入访问权限冲突。
#b 是 0x5A3C4C。
结论:
全部常变量存储在静态存储区的常量区,属于只读内存,不可修改;
局部常变量存储在栈区,可以由编译器做语法检查来保障其值不可修改,不过毕竟不是在只读内存里,只要可以获得局部常变量的地址,就可以进行间接修改,只不过结果随编译器版本类型可能会不太一样。
对const关键字的延伸(volatile关键字)
如果不想让编译器察觉到对const的操作,可以在const前面加上volatile关键字。
volatile是易变的意思,和const刚好相反,不会被编译器优化,编译器也就不会改变对a变量的操作。
具体的会另外写一篇来介绍。
二.const修饰指针变量
- const修饰指针指向的内容,则内容为不可变。
- const修饰指针,则指针为不可变。
- const修饰指针和指针指向的内容,则指针和指针指向的内容都不可变。
int b = 20;shisi
const int* a = &b; //由非const的转const的为不需要显式转换
cout << "a=" << *a << endl;
int c = 30;
a = &c;
cout << "a=" << *a << endl;
输出:
#a=20
#a=30
int b = 20;
const int* a = &b; //等价于int const * a = &b;
cout << "a=" << *a << endl;
int c = 30;
*a = c; //修改
cout << "a=" << *a << endl;
没有输出,编译出错:
# “a”: 不能给常量赋值
对于const修饰指针指向的内容,它的内容不可以改变,但是可以通过修改指针,间接修改它的内容。
int b = 20;
int * const a = &b; //指针常量a必须初始化
cout << "a=" << *a << endl;
int c = 30;
*a = c;
cout << "a=" << *a << endl;
输出:
#a=20
#a=30
int b = 20;
int * const a = &b;
cout << "a=" << *a << endl;
int c = 30;
a = &c;
cout << "a=" << *a << endl;
没有输出,编译出错:
# “a”: 不能给常量赋值
对于const修饰的指针,则指针不可变,指针指向的内容可变。
int b = 20;
const int * const a = &b;
cout << "a=" << *a << endl;
int c = 30;
*a = c;
a = &c;
cout << "a=" << *a << endl;
没有输出,编译出错:
# “a”: 不能给常量赋值
# “a”: 不能给常量赋值
const修饰指针和指针指向的内容,则指针和指针指向的内容都不可变。
结论:
两个易混淆的概念–指针常量和常量指针:
记忆方法:从左往右读,’’*’'读作“指针”,“const”读作“常量”。
这样子记忆的话和百度百科描述的一致:
指针常量:int *const p,指针地址不能变
常量指针:const int *p等价于int const *p,指针指向的内容不能变
另一种记忆方法:从右往左读,遇到p就替换成"p is a",遇到“*”就替换成“pointer to”。
这是《c++ primer》里的概念:
1.const int *p; //p is a pointer to int const,p是一个指向整型常量的指针
const *p; //p is a pointer to const in,同1
*const p; //p is a const pointer to int,p是一个指向整型的常量指针,即常量指针
4.const int *const p;
const * const p; //4,5都是指向整型常量的常量指针
发现了吗,网上流传的版本(百度)和《c++ primer》中的表述相反,但是意义都是一样的,在英文里,只有pointer to const和const pointer,分别是指向常量的指针和常量指针。”指向常量的指针“概念在《c++ primer》
中引入。
其实他们的含义都是一样的,就是表述这边竟然相反,个人选择《c++ primer》中的表述和记忆方法,有书为依据,还是比较可靠些的。
以下描述使用《c++ primer》中的概念:
指针变量 | 指针指向的内容 | |
指向常量的指针const int *p; | 可变 | 不可变 |
常量指针int * const p | 不可变 | 可变 |
就近原则:
p是指针变量(存放地址),从左往右,const靠近* p,即指针指向的内容不可变;
const靠近p,则指针变量不可变。
三、const传递参数和函数返回值
对于const修饰函数参数的传递
- 修饰值传递的参数
一般对于值传递的参数不用const修饰,因为按值传递,在调用函数的形参处会自动产生临时变量拷贝一份实参的值,const修饰没有意义,仅对指针和引用有意义。 - 当 const 参数为指针时,可以防止指针被意外篡改。
const指针可以接收非const和const指针,而非const指针只能接收非const指针。 - 自定义类型的参数传递,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,一般采取 const 外加引用传递的方法。
对于参数是类对象的情况下,传入参数,会调用构造函数来构造这个副本,调用完,还要使用析构函数释放这个副本,时间/空间的开销都有消耗。采用按引用传递可以解决这个问题,不过使用引用就可以直接修改实参的内部数据成员,所以再加个const.
//QT中经常能看到这种写法
QJsonObject::iterator insert(const QString &key, const QJsonValue &value);
QJsonObject fromVariantHash(const QVariantHash &hash);
对于const修饰函数返回值
- const 修饰内置类型的返回值,修饰与不修饰返回值作用一样。
- const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。
- const 修饰返回的指针或者引用,是否返回一个指向 const 的指针,取决于我们想让用户干什么。
四.const修饰类成员函数
//还是以QT为例子
QJsonObject::const_iterator find(const QString &key) const;
QJsonObject::const_iterator find(QLatin1String key) const;
将函数声明为const,那么该函数就不能修改对象的数据成员,也不能调用非const成员函数,因为非const成员函数可能会修改对象的数据成员,要调用只能调用const成员函数。
个人认为对这种查找函数或者要返回某个数据成员的函数可以声明为const.