C++ const 全网最全总结(推荐)

368 阅读7分钟

文章目录


在这里插入图片描述


前言

  • const 的作用很多,而且在被用作 常量指针 和 指针常量 的时候经常容易搞混,今天就来总结一下 所有 const 的用法;

一、const 在普通变量中的应用

1、修饰内置类型

i)左右皆可

  • const 修饰内置类型时,位置出现在 变量类型 的左边或者右边,其含义一样,代表被修饰的对象是一个常量,生命周期内不能被改变;
	const int maxn = 1024;
	int const maxm = 1024;

ii)常量的非法修改

  • 直接通过赋值修改常量,编译器会报错;
  • 但是我们可以通过取地址的方式取得常量的地址,然后再强转成 int 指针,再在对应地址上去取值修改,然后在 watch 窗口观察,发现变量的值的确被修改了!!!但是用 printf 打印出来还是原来的值,所以说明这是一种未定义行为,编译器没想到你会干出这种事,写代码的时候应该坚决避免;
	const int maxn = 1024;
	*((int *)&maxn) = 1;
	printf("%d\n", maxn);      // 1024

2、修饰指针类型

i)常量指针

  • 定义:是一个指针,指针变量指向常量;
  • 记忆方法:常量(const)在指针(*)左边,所以从左往右跟我读:常量指针!
  • 特性:指向的对象不可变;类型 和 const 的相对位置可以交换;
	const int cInt = 1024;
	const int* p = &cInt;
	*p = 43;                    // 错误行为,企图修改指针指向对象

ii)指针常量

  • 定义:是一个常量,指针常量指向变量;
  • 记忆方法:指针(*)在常量(const)左边,所以从左往右跟我读:指针常量!
  • 特性:指针本身不可变;
	int iInt;
	int * const q = &iInt;
	q = &iInt;                  // 错误行为,企图修改指针本身

iii)常量指针常量

  • 定义:是一个指针常量,指针常量指向常量;
  • 记忆方法:常量(const)、指针(*)、常量(const)从左往右读!(这个名字是我编的)
  • 特性:指针本身不可变,指向对象亦不可变;
	const int cw = 1024;
	const int * const w = &cw;

3、修饰引用类型

i)常量引用

  • 定义:是一个引用,并且值不能被修改;
  • 引用就是变量的 “别名” ,必须被初始化,并且需要初始化为已有的变量,但是当它被限定为 const 常量以后,可以被初始化为常量;但是一旦初始化以后,引用的值就不能被修改了;
	int cw = 1024;
	const int& cw1 = cw;
	const int& cw2 = 4;

	cw = 5;          // 正确行为
	cw1 = 6;         // 错误行为,引用在初始化以后值不能被修改
	cw2 = 7;         // 错误行为,引用在初始化以后值不能被修改

ii)引用常量

  • 定义:不存在;
	int cw = 1024;
	int& const cc = cw;
  • 编译后报警告:warning C4227: anachronism used : qualifiers on reference are ignored
  • 原因是引用实质是一个指针常量,所以已经不需要用 const 修饰了;

二、const 在函数中的应用

1、修饰函数传参

  • 函数传参 作为 常量 传入的目的,主要是为了函数的调用不要修改实参的值;

i)内置类型参数

  • 函数传参会拷贝一份数据,所以对于内置类型来说,加上 const 作用不大;
void XFunc(const int x) {
	printf("%p\n", &x);
}

int x;
printf("%p\n", &x);
XFunc(x);

008FFD80
008FFCA0

  • 可以看到,输出的实参和传参的地址是不同的,所以不用担心函数的调用会修改实参的值;

ii)指针类型参数

  • 指针传入的时候,实际是传入对应类型对象的一个地址;
  • 传指针的好处是,函数传递过程中不需要进行类的构造和析构;
  • 希望传递的对象本身不被修改,就传常量指针;希望传递的指针本身不被修改,就传指针常量;
class A {
public:
	A() {}
	A(int a) {}
};
void YFunc(const A *x, A *const y) {
	*x = 1;      // 错误行为
	*y = 1;      // 正确行为
	x = NULL;    // 正确行为
	y = NULL;    // 错误行为
}

iii)引用类型参数

  • Effective C++ 一书中曾反复提到,宁以 pass-by-reference-to-const 替换 pass-by-value,说的就是函数作为常引用传参;
  • 当以引用的形式传入参数的时候,并且不希望函数内部修改对应参数的值,那就加上 const 限定符;
  • 最经典的例子就是类的拷贝构造函数;
class B {
public:
	B() {}
	B(const B& other) {
		i = other.i;
	}
private:
	int  i;
};

B b1;
B b2(b1);

2、修饰函数返回值

  • const 修饰返回值传参的相似之处不再累述;
  • 这里举个例子来说明,如果期望返回值不被修改,但是又没有加 const 造成的一些困扰;
class AddObject {
public:
	AddObject() {
		v = 0;
	}
	AddObject(int iv) {
		v = iv;
	}
	const AddObject operator+(const AddObject& other)
	{
		v += other.v;
		return *this;
	}
	...
private:
	int v;
};
	AddObject a, b, c;
	if(a + b = c) {
		...
	}
  • 这里如果类 AddObject 的 返回值不定义成 const ,那么上面那个表达式将会成为未定义行为,仅仅是因为把 “==” 写成了 “=” ;

三、const 在类中的应用

1、修饰成员变量

i)声明即定义

  • 可以在常量被声明的时候直接初始化他的值;
  • 一般类变量如果定义为 const,代表不会变,那么可以加上 static 关键字,所有对象公用一个数据;
class InitTest {
public:
	InitTest() {}
private:
	const int a = 1;
};

ii)初始化列表

  • 修饰成员变量的时候,可以在构造函数的初始化列表(initialization list)中将 const 常量初始化;
class InitTest {
public:
	InitTest() : a(2) {}
private:
	const int a;
};

2、修饰成员函数

i)修饰方式

  • 当修饰类的成员函数时,const 放置在函数声明(圆括号后)和函数体(花括号前)之间;
  • 含义是:这个函数中所有的非静态(non-static)成员变量的值都不允许被修改;
class ConstTest {
public:
	ConstTest() {}
	void setval(int iv) {
		v = iv;
	}
	int getval() const {
		return v;
	}
private:
	int v;
};
  • 思考题:如果有两个成员函数实现同样的功能,一个是const成员函数,一个是非const成员函数,那么为了避免代码冗余,比较常用的做法是:一个函数调用另一个?那么请问,应该是谁调用谁?

四、const 在 STL 中的应用

  • STLStandard Template Library 的简称,中文名:标准模板库;
  • 其中 迭代器 是 STL 里面一种遍历容器的指针;

1、常量迭代器

  • std::vector< T >::const_iterator 等同于 const T*,是一个常量迭代器,迭代器指向的数据不可被更改;
	std::vector<int>::const_iterator iter1 = vec.begin();
	*iter1 = 13;                // 错误行为
	iter1 = vec.end();          // 正确行为

2、迭代器常量

  • const std::vector< T >::iterator 等同于 T* const,是一个迭代器常量,迭代器本身不可被更改;
	const std::vector<int>::iterator iter2 = vec.begin();
	*iter2 = 13;               // 正确行为
	iter2 = vec.end();         // 错误行为

五、const 和 #define 的区别

-const#define
实现原理走C++语法不走C++语法,单纯进行字符替换
类型检查
处理阶段编译阶段预处理阶段
存储方式存储在符号表不存储

六、突破 const 限定

1、mutable 关键字

  • 还是以成员函数为例,如果 const 成员函数中,期望某些非静态成员变量能够改变,则加上 mutable 关键字即可;
class ConstTest {
public:
	ConstTest() {}
	void setval(int iv) {
		v = iv;
	}
	int getval() const {
		++c;
		return v;
	}
private:
	int v;
	mutable int c;
};