C++中const修饰符深度小结

C++中的const是在C语言的const基础上进行了拓展,C语言中的const所修饰的变量并非真正意义上的常量,而只是赋予了变量以只读属性,而C++中的const所修饰的变量才是真正的常量,C++编译器在编译const所修饰的变量时,会将该变量及所对应的初始值存入一个符号表中,后面再次出现该变量时,则直接从符号表中获取对应的值进行替换。同时,为了兼容C语言中const的特性,当C++代码中对const所修饰的变量取地址时,编译器也会对该const变量分配内存,并在内存中保存初始值。所以C++中const修饰的变量若直接通过变量名取值,得到的将是符号表中的值,这是一个常量值,不可变的,当利用该变量的地址来修改或获取值时,该值是可变的。

那么,C++中const修饰的变量作为常量,它与宏定义的变量有何区别呢?其实有两个很明显的区别,一是宏变量只是一个字面量,没有任何类型的,它是由预编译器直接替换掉所对应的值,跟编译器没有关系,而const常量是由编译器存储到符号表中,而且取地址时会按照变量类型分配内存空间的;二是作用域的区别,宏在预编译阶段就会将代码中定义的宏全部替换成所对应的值,编译器压根不知道宏的存在,所以不会对他有作用域的约束,而const常量是由编译器控制的,会进行作用域检查。

下面结合C++ const的本质分别对const修饰变量,函数以及类对象的常规用法进行分析。

1. const修饰变量

const可以修饰普通变量和指针变量,其实从本质上来看,不管是普通变量还是指针变量,若要判断该变量是否为常量,只需要看定义的时候,该变量是否被编译器存储到符号表中,若变量本身包括其地址都被存入了符号表中,那么不管是通过变量本身还是取变量地址都无法对该变量进行修改。

int main()
{
	const int c = 5;
	int const c = 5;
	int* p = (int*)&c;
	*p = 10;
	printf("取c地址对应的值 is %d\n", *p);
	printf("c = %d\n", c);

	int d = 5;
	const int *p = &d;
	int e = 10;
	p = &e;
	printf("p = %d\n", *p);

	int* const p1 = &d;
	//p1 = &e;
    *p1 = e;
	printf("p1 = %d\n", *p1);

	const int* const p2 = &d;
	//p2 = &e;
	//*p2 = e;
	int const * const p3 = &d;

	system("pause");
	return 0;
}

对于上面这段代码,int const c和const int c中的const都是修饰的普通变量c,那么c及对应的值5就被编译器存储到符号表中,当后面的代码printf("c = %d\n", c);用到了变量c时,编译器就会先去符号表中查到c对应的值5,并替换掉,所以如果出现c=10这种对c进行修改的代码,编译后其实是5 = 10,这种复制肯定是不合法的,所以C++中不能对const修饰的常量进行赋值。

对于const int*p,这里面const修饰的是*p,p是一个地址,但*p就是指的这片内存,所以这段定义编译器会将*p及所对应的值保存到符号表中,后面若出现直接使用这片内存进行赋值的如*p = 10, 编译器从符号表中查询到*p对应的值并替换掉,那么赋值语句就变成5 = 10,这明显也不合法,但是这时用过内存地址赋值是可以的,如p = &e,因为内存地址在符号表中查询不到,所以不会被替换。

对于int* const p, 在这里const修饰的是内存的地址p,所以编译器会将p -- oxxxxxx存储到符号表中,当出现 p = &e时,编译器查询到p对应的值,则代码变成oxxxx = oxddddd,这明显不合法,但是*p = 10,这样赋值是成立的,因为*p是在符号表中查询不到的。所以,同样的,const int * const p 或者int const * const p会将p指向的这片内存及内存地址都作为常量保存到符号表中,所以通过内存变量或内存地址都无法修改这一片内存。

2. const修饰函数

const可以修饰函数的入参,修饰函数的返回值,也可以直接放在类成员函数的末尾进行修饰

(1)const修饰函数入参

void test(const int a)
{
	//a = 10;  //不合法
	int b = 10;
	int *p = (int*)&a;
	p = &b;//可取地址修改
}

void test1(const int* p)
{
	int a = 5;
	p = &a;//可取地址修改
	//*p = 10;//不合法
}

void test2(int* const p)
{
	int a = 5;
	//p = &a;  //不合法
	*p = 10;
}

void test3(const int* const p)
{
	int a = 5;
	//p = &a;  // 不合法
	//*p = 10;  //不合法
}

如上述代码,cosnt修饰函数入参时,其目的是使形参变量在函数内部不可任意修改,至于是控制变量本身不可修改还是通过取地址不可修改则跟上面const修饰变量原理一样。

(2)const修饰函数返回值

const int FuncReturnVal()
{
	//返回值加const和不加const效果一样
	int a = 5;
	//const int a = 5;
	//return a;
    return 5;
}

const int* FuncReturnVal1()
{
	//返回指针内容不可变
	int a = 5;
	return &a;
}

int const* FuncReturnVal2()
{
	//返回指针不可变
	int a = 5;
	return &a;
}

const int const* FuncReturnVal3()
{
	//返回指针内容和指针均不可变
	int a = 5;
	return &a;
}

如上代码,const 修饰返回普通类型变量值类型时,因为return 具有拷贝属性,加不加const效果是一样的,一般最好不要加。若函数返回的是指针类型的值,那么其修饰可为const int*,表示返回指针指向的内存不可改变;也可为int* const,表示返回的指针本身不可变,或者为const int* const, 表示返回的指针本身和指针指向的内存均不可变.

另外还有一种不常用的修饰方法,就是函数的返回值为某自定义类型的对象或对象的引用,这时返回值具有const属性,返回的实例只能访问类A中的公有(保护)数据成员和const成员函数,并且不允许对其进行赋值操作。

(3)类成员函数末尾使用const

class A
{
public:
	void SayHello() const
	{
		//a = 10; //不合法
		c = 10;
		Print();
		//Num();  //不合法
		printf("hello,world!\n");
	}

	int Print() const
	{
		return 1;
	}

	int Num()
	{
		return 5;
	}
private:
	int a;
	mutable int c;

};

const修饰的成员函数不可以修改任何成员变量(mutable修饰的变量除外)

const修饰的成员函数不能调用其他非const成员函数

const修饰的成员函数不可以为静态函数,因为静态函数不包含this指针,即不能实例化,const成员函数必须具体到某一实例

3. const修饰类的对象/对象指针/对象的引用/对象指针的引用

    const A obj;
	obj.SayHello();
	//obj.Num();  //不合法
	//obj.b = 10;  //不合法

	const A* pObj = new A;
	//pObj->Num();  //不合法
	pObj->SayHello();

	A obj1;
	const A& obj2 = obj1;
	//obj2.Num(); //不合法
	obj2.SayHello();

	A* pObj1 = new A;
	const A*& pObj2 = pObj1;
	//pObj2->Num();
	pObj2->SayHello();

const修饰的类对象或对象指针及其引用,其实例所包含的成员变量均不可改变(mutable修饰的变量除外),且该对象不能调用非const的成员函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值