C++ 关键字 const

1. 概念


C++提供了用户定义常量的概念,const就是为了直接表达“不变的值”这样一个概念。这种东西在一些环境中非常有用,例如,许多对象在初始化之后就不再改变自己的值了;与直接将字面值常量散布在代码中相比,采用符号常量写出的代码更容易维护;指针常常是边读边写,而不是边写边移动;许多函数参数是只读不写的。——摘自《The C++ Programming Language


2. 使用


① 常量

const int i = 0; 

const int a[] = {1, 2, 3, 4};

i = 1; //error C3892: 'i' : you cannot assign to a variable that is const

a[1] = 5; //error C3892: 'a' : you cannot assign to a variable that is const

因为不允许赋值,所以常量必须进行初始化。

const int i; //error C2734: 'i' : const object must be initialized if not extern


注意:在C++中尽量以const替代#define(Prefer const to #define), 以下是《Effective C++》中的解释:


② 指针和引用

在C++里有常量指针指针常量const char*, char const*, char*const。 一种是用来修饰指针所指向的变量(即指针所指向的变量不可变),另一种是修饰指针(即指针本身为常量),详细参见指针常量和常量指针

如果想要指针所指向的对象和指针本身都保持不可变:const char *const p;

对于引用,以 指针常量和常量指针 里相同的方式去理解和使用:把一个声明从右向左读( & 读成 reference to ) 

const int &a = i; //a is a reference to const int

因为引用只能在定义时被初始化一次,之后不能改变,所以const修饰引用本身(int &const)是不需要的


③ 函数

const 修饰函数参数:

当const修饰函数参数时,如果const修饰的是传递对象本身,不能保证传递对象的不变,因为修饰的本身是形参,如:

void Function(const int p);

void Function(int *const p);

虽然如此,但将传递对象本身修饰为const也还是有好处的,可以避免后面提到的一些特殊情况。更多地是用const修饰传递对象(如指针或引用)所指向或引用的对象,因为可以保证所指向或引用的对象不变,如:

void Function(const int *p);

void Function(const int &p);

const 修饰函数返回值:一般很少用到,和修饰函数参数一样,const修饰返回对象本身不能保证返回对象的不变,因为修饰的其实是其副本,如:

const int Function();

int *const Function();

虽然如此,但将返回对象本身修饰为const也还是有好处的,可以避免后面提到的一些特殊情况,当const修饰返回对象(如指针或引用)所指向或引用的对象时,保证了所指向或引用的对象的不变, 如:

const int *Function();

const int &Function();


特殊情况:当我们不小心写出了如下的代码时,《Effective c++》中称之为暴行

if (parameter = a) { ... }

if (Add(a,b) = c)

很显然我们的原意是做判断而不是赋值,但是这种错误编译器又捕捉不到,可是当我们将参数和返回值声明为const时,编译器就可以捕捉到,因为不能再赋值给常量。


④ 类

const修饰类成员变量:

被const修饰的成员变量在初始化后就不能被修改,而且只能在构造函数的初始化列表中初始化

class A
{
public:  
	A(int x): value(x) { } ; //只能在构造函数初始化列表中初始化  

private:  
	const int value; //成员常量不能被修改
};
const修饰成员函数:
const修饰的成员函数称之为常成员函数,一般const放在最后来修饰,常成员函数不能改变类的数据成员;常成员函数不能调用类中的非const成员函数,因为调用非const成员函数可能间接改变类的数据成员。

class A
{
public:  
	...
	int GetValue() const { return value; }
	...
};
const修饰类对象/对象指针/对象引用:

const 修饰对象称之为常对象,这里的修饰对象指针和对象引用指的是 指针所指向的对象和引用所引用的对象为常对象, 常对象的所有类数据成员都不能被改变,并且不能调用任何非const成员函数,因为那会间接改变类成员。
class A

{

    void Function1() const;

    void Function2();

}

const A object;

object.Function1(); //OK

object.Function2(); //Error

const A *pObject = new A();

pObject ->Function1(); //OK

pObject ->Function2(); //Error


⑤ const_cast

用法:const_cast<type_id> (expression)
该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。
常量指针被转化成非常量的指针,并且仍然指向原来的对象; 常量引用被转换成非常量的引用,并且仍然指向原来的对象;
注意:const_cast 用在class、struct等用户自定义数据类型上 和 用在int、char、double等基本数据类型上是不相同的
#include <iostream>
using namespace std;

class A
{
public:  
	A(int x):value(x){}

public:
	int value;
};

int main()
{
	const A a(0);
	cout<<a.value<<endl;

	A *pa = const_cast<A *>(&a);
	pa->value = 1;
	cout<<a.value<<endl;

	A &ra = const_cast<A &>(a);
	ra.value = 2;
	cout<<a.value<<endl;

	return 0;
}
WindowXP+VS2008下 运行结果 :
0
1
2
上述代码运行结果是我们可以预想到的,但下面的代码运行结果可能会出乎我们的意料:
#include <iostream>
using namespace std;

int main()
{
	const int a = 0;
	cout<<a<<endl;

	int *pa = const_cast<int *>(&a);
	*pa = 1;
	cout<<a<<", "<<*pa<<endl;

	int &ra = const_cast<int &>(a);
	ra = 2;
	cout<<a<<", "<<ra<<endl;

	return 0;
}
WindowXP+VS2008下 运行结果 :
0
0, 1
0, 2
仔细观察其实,上述代码中变量a其实是改变了的



但是为什么输出变量a的值却仍然是0呢?那是因为编译器在对用到变量a的地方统一以最初初始化的值(也就是0)来替代,有点C语言#define的意思,我们可以看反汇编代码:


const_cast在实际项目用到的不多,可能用到的情况是:我们可能调用了一个形参不是const修饰的函数,这个函数或许不是我们自己写的,而我们要传进去的实参又必须是const的,但是我们知道这个函数是不会对参数做修改的。于是我们就需要使用const_cast去除const限定,以便函数能够接受这个实参;还有就是常对象调用非const修饰成员函数。

最后,像《Effective C++里》里建议的一样:尽可能使用const (Use const whenever possible), 至于具体注意事项可参考Google C++编码规范const的使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值