关于const限定符的一些总结
写在前面
有时候我们希望定义这样一种变量,它的值不能被改变。例如,用一个变量表示缓冲区的大小。使用变量的好处是我们觉得缓冲区不再合适的时候,很容易对其进行调整。另一方面,也应随时警惕,防止程序一不小心修改了这个值。为了满足这一要求,可以用关键词const对变量的类型加以限定。
在C++中,一个const不必创建内存空间,而在C中,const总是需要一块内存空间。在C++中,是否为const常量分配内存空间依赖于如何使用。一般来说,如果一个const仅仅用来把一个名字用一个值来代替,那么该存储空间就不必创建。
在存储空间没有分配内存的话,在进行完数据类型检查后,为了代码更加有效,值也许会折叠到代码中。
不过取一个const地址,或者把它定义为extern,则会为该const创建内存空间。
在C++中,出现在所有函数之外的const作用于整个文件(也就是说它在该文件外部不可见,默认为内部连接,C++中其他标识符一般默认为外部连接。
1.初始化和const
const类型的对象能完成非const类型所能完成的大部分操作,但是对于const类型对象的主要限制就是只能在const类型对的对象上执行不改变其内容的操作。
在不改变const对象的操作中还有一种是初始化,如果利用一个对象去初始化另外一个对象,则它们是不是const都无关紧要。
#include <iostream>
using namespace std;
int main()
{
int i = 12;
const int ci = i;//i的值拷贝给了ci
int j = ci;
cout << "i=" << i << endl;
cout << "ci=" << ci << endl;
cout << "j=" << j << endl;
}
输出结果如下:
2.const引用
可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为——常量引用。
与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象。
错误示范如下:
3.初始化和对const的引用
引用的类型必须与其所引用对象的类型一致,但是有两个情况例外。第一个例外情况就是在初始化常量引用时,允许用任意表达式作为初始值,只要该表达式的结果能转化成引用的类型即可。
允许一个常量引用绑定非常量的对象、字面值、甚至是一般表达式。
#include <iostream>
using namespace std;
int main()
{
int i = 42;
const int &r1 = i;//允许将const int &绑定到一个普通int对象上
const int &r2 = 42;//r1是一个常量引用
const int &r3 = r1 * 2;//r3是一个常量引用
cout << "i=" << i << endl;
cout << "r1=" << r1 << endl;
cout << "r2=" << r2 << endl;
cout << "r3=" << r3 << endl;
}
输出结果如下:
4.对const的引用可能引用一个并非const的对象
#include <iostream>
using namespace std;
int main()
{
int i = 42;
int &r1 = i;//引用r1绑定对象i
const int &r2 = i;//常量引用r2绑定i,但是r2不能修改i的值
cout << "i=" << i << endl;
cout << "r1=" << r1 << endl;
cout << "r2=" << r2 << endl;
r1 = 10;
cout << "i=" << i << endl;
cout << "r1=" << r1 << endl;
cout << "r2=" << r2 << endl;
//r2 = 5;//此处报错,因为r2是一个常量引用
}
输出结果如下:
对于常量引用来说,一般情况下是没有办法来对它进行修改的,但是可以使用下面的方法来尝试对常量引用进行修改。代码图如下:
#include <iostream>
using namespace std;
int main()
{
const int &ref = 10;
cout << "ref=" << ref << endl;
int *p = (int *)&ref;
*p = 100;
cout << "ref=" << ref << endl;
}
输出结果如下:
这里我们使用了一个指针来对常量引用进行修改。首先,我们要知道的是,常量引用不是一个对象,编译器并没有给它分配内存空间。当我们使用指针指向常量引用时,编译器会给常量引用临时开辟一块内存空间。代码示意如下:
int tmp=ref;//tmp有内存
int *p=(int *)&ref;//*p指向的是那块临时空间,临时空间看不到
5.指针和const
与引用一样,也可以令指针指向常量或非常量。类似于常量引用,指向常量的指针不能改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针。
实例代码如下:
#include <iostream>
using namespace std;
int main()
{
const int i = 10;
//int *r1 = &i;//报错,r1是一个普通指针,并不能指向一个常量
const int *r2 = &i;//常量指针指向常量
//常量指针可以指向常量,但是并不能修改常量的值
cout << "常量i的地址为:" << &i << endl;
cout << "r2指向的地址为:" << r2 << endl;
cout << "i=" << i << endl;
cout << "r2=" << *r2 << endl;
}
输出结果如下:
6.const指针
指针是对象而引用不是,就像其他对象类型一样,允许把指针本身定义为常量。常量指针必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再修改。把*放在const关键字之前用以说明指针是一个常量。这样书写的意味着,不变的是指针本身的值,而不是指向的那个值。
#include <iostream>
using namespace std;
int main()
{
int i = 10;
int *const r1 = &i;//r1将一直指向i
const int i1 = 11;
const int *const r2 = &i1;//r2是指向常量的常量指针
cout << "i=10时地址为:" << &i << endl;
cout << "常量指针r1指向的地址为:" << r1 << endl;
i = 100;
cout << "i=100时地址为:" << &i << endl;
cout << "常量指针r1指向的地址为:" << r1 << endl;
}
输出结果如下:
7.顶层const
指针本身就是一个对象,它又可以指向另外一个对象。因此指针本身是不是常量,以及指针指向的对象是不是一个常量,是两个独立的问题。
顶层const表示指针本身是一个常量。
底层const表示指针所指的对象时一个常量。
更一般地说,顶层const可以表示任意的对象时常量,这一点对任何数据类型都适用,如算术类型、类、指针等。底层const则与指针和引用等符合类型的基本类型有关。比较特殊的是,指针类型既可以是顶层const,也可以是底层const。
8.尽量使用const代替#define
在旧版本的C中,如果想建立一个常量,必须使用预处理器。
#define MAX 1024;
这里我们定义的宏MAX从未被编译器看到过,因为在预处理阶段,所有的MAX已经全部被替换成了1024,于是MAX并没有将其加入符号表中。但是我们使用这个常量获得一个编译错误信息时,可能会带来一些困扰,因为这个信息可能会提到1024,但是并没有提到MAX。如果MAX被定义在一个不是我们自己写的头文件中,我们可能并不知道1024代表着什么,也许解决这个问题需要很长的时间。
解决这个问题的办法就是用一个常量来替换掉上面的宏:
const int MAX=1024;
#undef A //卸载宏常量A
const和#define的区别
- const有类型,可进行编译器类型安全检查,#define无类型,不可进行类型检查
- const有作用域,而#define不重视作用域,默认定义处到文件末尾,如果定义在指定作用域下有效的常量,那么#define就不能用。