Aoki Job Seeker

关于const限定符的一些总结

2019-03-16
C++

关于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就不能用。







The End



下一篇 C++复合类型

Comments

Content