Bootstrap

【学习总结】const关键字

Const关键字作用

在c语言中,const修饰的是只读变量,并不是一个常量,本质还是一个变量。
const修饰的变量会占用内存空间,本质上const只对编译器有用,它告诉编译器,这个变量是只读的。

Effective C++ 条款02/条款03:
const允许你指定一个语义约束,而编译器会强制实施这项约束。

1. const修饰全局变量/局部变量
2. const修饰指针
3. const修饰函数参数
4. const修饰函数返回值
5. const修饰成员函数
编译器如何处理Const

参考:编译器在处理const变量跟一般变量时的区别

读一般的变量的时候,首先获取该变量的地址,然后到该地址去取数据。const变量是如何被读取?

#include <iostream>
using namespace std;

const int b = 2;

int main() {
	const int a = 1;
	const int * pa = &a;						 //指针pa指向常量a所在的内存
	int * p_var = const_cast<int *>(pa);		 //强制转换const指针为非const
	*p_var = 11;
	int *pc = (int *)(&a);
	*pc = 13;
	int *pb = (int *)(&b);
	//*pb = 12; error
	cout << "a= " << a << endl;
	cout << "Address a = " << (&a) << endl;
	cout << "pa = " << *pa << endl;
	cout << "Address pa = " << (pa) << endl;
	cout << "pb = " << *pb << endl;
	cout << "Address pb = " << (pb) << endl;
	cout << "pc = " << *pc << endl;
	cout << "Address pc = " << (pc) << endl;
	cout << "p_var = " << *p_var << endl;
	cout << "Address p_var = " << (p_var) << endl;
	return 0;
}

运行结果:

a= 1
Address a = 00F9FBF0
pa = 13
Address pa = 00F9FBF0
pb = 2
Address pb = 001DEB34
pc = 13
Address pc = 00F9FBF0
p_var = 13
Address p_var = 00F9FBF0

输出结果显示:
一个内存地址得到三个不同的值。
原因是对于const变量,编译器会在编译之前做处理,在所有要读取该const变量的地方,把该const变量替换成制定的常量。

测试程序反映的几个问题:

  1. *pb赋值失败
    const int b = 2;//全局常量 const int a = 1;//局部常量
    全局常量位于只读存储器中,无法修改,常量局部变量在堆栈上创建,可以修改。
  2. 常量局部变量的值修改
    虽然是只读变量,无法通过赋值操作改变值,可以通过指针修改其值;
    常量局部变量的值如何变化,最终还是取决于编译器。
Const 与 Volatile 分析

const volatile int i= 1;

const和volatile放在一起的意义在于

  1. 本程序段中不能对a作修改,任何修改都是非法的,或者至少是粗心,编译器应该报错,防止这种粗心;
  2. 另一个程序段则完全有可能修改,因此编译器最好不要做太激进的优化。

需要注意的是:频繁地使用volatile很可能会增加代码尺寸和降低性能,因此要合理的使用volatile

关于volatile关键字的作用及编译器优化可参考

Const修饰 全局变量/局部变量
//a.h
#pragma once

int e = 4;
const int f = 5;
#include <iostream>
#include "a.h"

using namespace std;

int a = 1;
int  const b = 2;
const int c = 3;
//int d const = 4; error

extern int e;
extern const int f;

int main()
{
	const int  g = 6;
	const volatile int h = 7;

	int *pa = (int *)(&a);
	*pa = 11;
	int *pb = (int *)(&b);
	//*pb = 12;
	int *pc = (int *)(&c);
	*pc = 13;
	int *pe = (int *)(&e);
	*pe = 14;
	int *pf = (int *)(&f);
	//*pf = 15;
	int *pg = (int *)(&g);
	*pg = 16;
	int *ph = (int *)(&h);
	*ph = 17;

	cout << "a=" << a << endl;
	cout << "b=" << b << endl;
	cout << "c=" << c << endl;
	cout << "e=" << e << endl;
	cout << "f=" << f << endl;
	cout << "g=" << g << endl;
	cout << "h=" << h << endl;

	cout << "pa=" << *pa<<endl;
	cout << "pb=" << *pb << endl;
	cout << "pc=" << *pc << endl;
	cout << "pe=" << *pe << endl;
	cout << "pf=" << *pf << endl;
	cout << "pg=" << *pg << endl;
	cout << "ph=" << *ph << endl;
	system("pause");
	return 0;
}
a=11
b=2
c=3
e=14
f=5
g=6
h=17
pa=11
pb=2
pc=3
pe=14
pf=5
pg=16
ph=17
Const修饰指针

Effective C++ 条款03:
如果关键字const出现在星号左边,表示被指事物是常量;
如果出现在星号右边,表示指针自身是常量;
如果出现在星号两边,表示被指物和指针两者都是常量。

const int *a; 等价 int const *a; //常量指针
int *const a; //指针常量
const int *const a; //指向常量的指针常量

常量指针 [ 底层const(low-level const) ]:
指针指向的变量的值不可通过该指针修改,但是指针指向的地址可以改变。
指针常量 [ 顶层const(top-level const) ]:
指针指向的地址不能被修改,但是该地址里的内容可以被修改。

#include<iostream>
using namespace std;

int main() {
	int a1 = 1, b1 = 2, c1 = 3, d1 = 4, e = 5;
	const int *a = &a1; //常量指针
	int const *b = &b1; //常量指针
	int *const c = &c1; //指针常量
	const int *const d=&d1; //指向常量的指针常量
	cout << "The address of A:  " << a << endl;
	cout << "The address of B:  " << b << endl;
	cout << "The address of C:  " << c << endl;
	cout << "The address of D:  " << d << endl;
	//*a = 11;//error
	a = &e;
	//*b = 12;//error
	b = &e;
	*c = 13;
	//c = &e;//error
	//*d = 14;//error
	//d = &e;//error
	cout << "The address of A:" << a << endl;
	cout << "The address of B:" << b << endl;
	cout << "The address of C:" << c << endl;
	cout << "The address of D:" << d << endl;

	cout << "The data of A: " << *a << endl;
	cout << "The data of B: " << *b << endl;
	cout << "The data of C: " << *c << endl;
	cout << "The data of D: " << *d << endl;
}
The address of A:  0057FC34
The address of B:  0057FC28
The address of C:  0057FC1C
The address of D:  0057FC10
The address of A:0057FC04
The address of B:0057FC04
The address of C:0057FC1C
The address of D:0057FC10
The data of A: 5
The data of B: 5
The data of C: 13
The data of D: 4
Const修饰函数参数

Const修饰函数参数是为了防止函数体内可能会发生修改参数原始对象的情况。

  1. 函数参数为值(pass-by-value):
    void func(int x)不用写成void func(const int x)
    传递一份参数的拷贝给函数;
    故只会修改拷贝,无法修改原始对象,不需要将参数声明为const。
  2. 函数参数为指针(pass-by-pointer):
    void func(char *dest_str, const char *src_str)
    拷贝一份指针给函数,不会拷贝一份原始对象;
    因此,给指针参数加上顶层const可以防止指针指向的地址被篡改,给指针参数加上底层const可以防止指针指向的对象被篡改。
  3. 函数参数为引用(pass-by-reference):
    void func(const A &a)
    引用是对象的一个别名,因此不需要拷贝对象,减小了开销;
    导致可以通过修改引用直接修改原始对象,故函数参数为引用时推荐设置pass-by-reference-to-const。给引用加上底层const,既能减小拷贝开销,又可以防止修改引用对象。

备注: 关于为何使用引用传递的说明

Effective C++ 条款20:
pass-by-reference-to-const,由于没有任何新对象被创建,所以没有任何构造函数或析构函数被调用,效率更高。

对于非内部的数据类型的参数而言,采用void func( A a)这样声明的函数注定效率低;
因为函数体内将自动产生临时对象,而临时对象的构造、析构都很耗时。

指针与引用的区别
深拷贝/浅拷贝参考

Const修饰函数返回值
  1. 函数返回值为(pass-by-value):
    void func(int x)不用写成 const void func(int x)
    由于函数返回值会复制到外部的临时存储单元中,加上const进行修饰没有实际意义。

  2. 函数返回值为指针(pass-by-pointer):
    const char *func(void)
    加上const,保证函数的返回值的内容不能被修改。

  3. 函数返回值为引用(pass-by-reference):
    const A &func(void)
    加上const,保证函数的返回值的内容不能被修改,避免产生临时对象来存放返回的数据。

Const修饰成员函数

注意区分:
在成员函数的后边加上const, void show() const 代表修饰成员函数;
在成员函数的前边加上const, const void show() 代表修饰函数的返回值。

#include <iostream>
#include <string>

using namespace std;

class A
{
public:
	void show1()
	{
		cout << "show1()" << endl;
	}
	void show2() const
	{
		cout << "show2()" << endl;
	}
};
void show3(A &_a)
{
	cout << "show3()" << endl;
}
int main()
{
	A a;
	const A b;
	a.show1();
	a.show2();
	//b.show1();
	b.show2();
	show3(a);
	//show3(b);
	system("pause");
	return 0;
}

说明:

  1. const对象可以调用const修饰的成员函数,不能调用非const修饰的成员函数。
  2. 非const对象可以调用const修饰的成员函数,能调用非const修饰的成员函数。
  3. const成员函数不能调用非const成员函数。
  4. 非const成员函数能调用const成员函数。

编译器对成员函数的处理方式,show1( )/show2( ) 等价如下:

void show1(A* const this)
{
	cout << "show1()" << endl;
}

void show2(const A* this) 
{
	cout << "show2()" << endl;
}

Const修饰成员函数的原理:
修饰成员函数的this指针所指向的对象,保证在调用这个const成员函数的对象时不会被改变。

this指针机制

总结

对于const的用法总感觉似懂非懂,到使用的时候总是会出现各式各样的问题,暴露了对const用法的理解不到位。因此参考了一些书籍和微博文章,做了一下总结,如有不准确或者错误的地方,也希望大家能够批评指正。

;