谈谈C++中的几个关键字(主要是const)

参考《C++ Primer》

1、const

(1)被const修饰的量不可修改,必须初始化。例如:

	const int a;// 错误,没有初始化
	const double b=10.2;
	b=10.3;// 错误,试图修改被const修饰的b
	const double c=b; //正确,用b初始化c

(2)被const修饰的变量只能在一个文件中起作用,例如在文件filename.h中有const int a=233;在文件filename.cpp中也有const int a=233;但这两个a并不是同一个a(只不过都初始化了成233而已,事实上它们根本就是两个不同的量,只能在各自的文件中使用),如果需要在不同文件间共享同一个被const修饰的对象,可以在前面加上关键字extern,在上面里的例子中,如果在filename.h中写extern const int a=233;再在filename.cpp中申明extern int a;那么在filename.h中初始化好的a就可以在filename.cpp中使用了,是同一个a。

(3)const关键字和其他东西组合在一起时情况就变得复杂了,下面分情况介绍:

a、可以将引用绑定到const对象上,这叫做“对常量的引用”(reference to const,为了突出这个概念,出现时均使用斜体标注),表示不可以通过修改这个引用修改指向的值。对常量的引用常被简称为“常量引用”,这是一种有误导性的说法,因为有的人可能会将它臆想成“这个引用是一个常量”。看下面的代码:

	const int a=5;
	int aa=6;
	const int& b=a;// b是一个对整型的引用,这个整形是const
	int& const c=aa;// c本身是一个常量,是一个对整形的引用,但这毫无意义

编译以后会收到一个warning: “warning C4227: 使用了记时错误: 忽略引用上的限定符”,产生这个warning的原因就在于第四行代码,& const这样的写法毫无意义。要解释这个问题首先要对引用有一个相对深刻的认识:本质上,引用只是指针的语法糖,是一个在编译器层面做了限制的指针常量。而C++之所以引入引用,最关键的原因是运算符重载,可以看这个问题感受一下:为什么有的运算符重载要返回引用?至于其它用途,只是引入这个东西以后带来的副产品。为了证实引用是指针常量的说法,可以观察下面的汇编代码:


引用和指针的行为别无二致。可见,既然引用是个指针常量,那么再用const去修饰它就没有意义,这就是上述warning的解释。

作为引用的基本知识,我们知道:引用类型需要与引用对象类型一致。但这条在对常量的引用中例外,例如我们可以将一个对常量的引用绑定到非常量上:

	int a=10;
	const int& r1=a;// 绑定非常量
	const int& r2=42;// 绑定字面值
	const int& r3=r1*2;// 绑定表达式

甚至可以用不一样的类型来初始化这个对常量的引用:

	double pi=3.14;
	const int& r4=pi;

这等价于:

	const int tmp=3.14;
	const int& r4=tmp;

所以如果输出r4,将会得到3。

b、类似地有指向常量的指针(pointer to const)和常量指针(const pointer)的概念,前者表示指向的对象是常量,不可以通过这个指针修改它,而后者则表示指针本身是常量,自身的值(也就是某个地址)不可修改。这里有一个“顶层const”(top-level const)和“底层const”(low-level const)的概念,如果指针本身是const,那么这是一个顶层const(这个概念可以推广到一般对象,只要这个对象本身是const,这种const就叫顶层const),如果所指的对象是const,则属于底层const。在执行对象的拷贝操作(例如赋值)时这两者的区分是有用的:

①顶层const的拷贝不受任何影响;

②拷贝时如果一方是底层const,另一方也要是底层const(或者能够发生正确的值转换);

③强制类型转换函数cast_const只能改变底层const。

关于这两点可以看下面的例子:

	int a=10;
	const int aa=100; // 顶层const
	const int b=23; // 顶层const
	int c=b; // 顶层const的拷贝不受影响
	const int* p1=&a;// 允许改变p1,底层const
					 // int*能正确转换为const int*,正确 
	const int* const p2=&a;// 第一个是底层,第二个顶层
	int* const p3=p1; //错误,不允许顶层和底层之间的拷贝
	p1=p2;// 正确,两者都有底层const
	auto p4=const_cast<int*>(p1);// 正确,可以用const_cast除去底层const

c、const修饰函数参数。在C++中有三种向函数传值的方式:值传递、引用传递和指针传递。值传递会发生传入参数的拷贝、析构,假如现在有一个自定义的类A,有一个函数func(A a),那么直接向func传入A的实例将是很耗时的,同时也会占用空间,这时可以用后两种传递方式,并加入const保护。但是对于一些内建的数据类型,例如int、char,这样做是没有必要的,因为就算值会原封不动地拷贝,也不需要多大代价,使用const+&反而影响了可读性。

d、const修饰函数返回值

例如返回一个指针时,表示指针指向的内容不可修改。但是下面的做法没有必要:

const int func()
{
	return 1; //用const修饰返回值没有任何意义,因为本来就是值传递,不存在能不能修改这个值的问题
}

e、const修饰成员函数

看这个例子即可:

class ABC
{
	int a;
	mutable int b;
	int c;
public:
	ABC(){a=1;b=2;c=3;}
	int getA()const
	{
		// 函数申明为const后不可以修改数据成员
		// 同时这个函数只能访问const成员函数
		int b=getB();// 错误,getB()不具备const属性
		int c=getC(); //正确,getC()是const成员函数
		a++;// 错误,const函数不可以修改数据成员
		b++;// 正确,b被mutable修饰,即使const函数也可以修改
		return a;

	}
	int getB()
	{
		return b;
	}
	int getC()const
	{
		return c;
	}
};

这里要说明的是mutable关键字,它尽在类中使用,表示被修饰的类成员可以被修改。

2、constexpr

常量表达式指的是值不会改变并且在编译过程(而非运行)中就得到结果的表达式。constexpr可以申明一个变量的值为常量表达式,书中建议:“如果你认为变量是一个常量表达式,那就把它申明成constexpr类型”。

constexpr修饰指针时仅对指针有效,而与指向的对象无关。

有一类函数叫constexpr函数,如果传入的参数可以在编译期知道结果,那么这个函数就会产生编译时期的结果,从而提高程序效率,否则它和普通函数一样。关于这个关键字有一些讨论可以看:点击打开链接

3、static

内容其实挺多的,看这里

4、auto

用来自动推断类型的,用得比较多,略。

5、decltype

看代码就能理解了:

	const int ci=0;
	const int& cj=ci;
	decltype(ci) x=3;// x是const int类型
	decltype(cj) y=x;// y是const int&类型
	int i=42;
	int* p=&i;
	int& r=i;
	decltype(r+100) b; //表达式结果是int,b是int
	decltype(*p) c=b;//指针解引用,注意c是int&而不是int!

书上特别提醒:decltype((variable))的结果永远是引用,而decltype(variable)的结果是variable本身的类型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值