漫谈 const 关键字
const 关键字
const 偶尔会在面试的过程中被提及,const 能讲述的东西很多,大致可以概括为以下几点:
- 指针
- 引用
- 类
指针
我们都知道,指针又分为指针常量和常量指针,简单理解为就是:指针常量是指不能利用指针修改所指对象的值;常量指针表示指针本身的值不可以被修改,即是不能使得指针重新指向新的对象。
只有指针才有顶层和底层 const 之分,其余的都统一为顶层 const,const int val = 3
顶层 const
常量指针,const 的位置在 * 的后面,靠近变量名,简单记忆是“变量名的头顶,顶层 const”。顶层 const 必须要在定义时就完成初始化,因为后续不能再被修改。
int val = 1;
int element = 2;
int * const ptr = &val; //正确,ptr 被赋予顶层 const 属性
ptr = &element; //非法,禁止修改 ptr 的值
底层 const
指针常量,注意的是,指针所指的对象不一定是常量,这里所说的常量是指抹去了通过指针修改所指对象的权限。const 在 * 的前面,远离变量名,才显得底层。
int val = 1;
const int c_val = 2;
const int *ptr = &val;
*ptr = 3; //非法的,静止利用指针修改所指对象的值
ptr = &c_val; //合法的,修改指针本身的值
val = 4; //合法的,val 可以修改自身的值
c_val = 3; //非法的,c_val 为 const 属性
除了上述两种情况外,还有一种是即是顶层 const 也是底层 const,在定义的时候就要完成初始化。
const int *const ptr = &val;
引用
引用和指针的根本区别是:引用不是对象,而指针是对象,引用更像一种别名。定义引用时就必须绑定对象,之后也不能修改引用绑定的对象。当然也不能定义引用的引用。
引用和指针区别
- 分配空间,引用不是对象,定义引用时不需要分配空间,但必须在定义时完成引用对象的绑定;指针是对象,需要分配存储空间
- 程序执行时,一旦引用绑定了对象,后续就不能修改绑定的对象;指针可以随时指向新的对象,但指针常量除外
- 高效率,因为引用不能绑定空对象,所以不存在空引用,在判断时省去了判空条件;指针可以为空,使用指针时都要判断指针是否为空
- 级别,引用只有一级;指针可以存在多级
没有指针引用
,因为引用根本不是对象,也就不存在指向引用的指针。但有引用指针
,定义引用时可以绑定指针。
int val = 3;
int &f_obj = val; //绑定普通对象
int *ptr;
int *&f_obj = ptr; //引用指针,引用了一个指针对象
int &*p_obj = f_obj; //非法,不存在指向引用的指针
如何判断指针和引用的复合类型呢?我们可以从右往左看,靠近变量名的符号是 * 表示指针,剩余部分为类型,表示指针所指类型。
引用使用场景
引用因为不是对象,在某些场合下能避免值拷贝,提高代码的效率。
- 函数参数列表
- 函数返回值
const 引用
const 引用也称为常量引用,相比普通引用,常量引用不能通过引用修改所绑对象的值。同理,常量引用一旦定义就必须初始化。
定义
引用的定义一般都要求,引用类型和绑定对象的类型一致,严格匹配。
int val = 3;
double d_val = 3.0;
int &f_obj = val; //合法的
int &f_obj2 = d_val; //非法的
但存在两点例外情况:
- 在初始化 const 引用时,允许用任意表达式作为初始值,只要结果类型能转换成引用的类型即可
const int &val = 3 + 2;
const int &doub = 5;
int i = 3;
const int &f_int = i * 3;
- const 引用可以引用一个非 const 对象,const 引用只是对自身的操作做了限定,对于引用对象本身是否常量不做限定
int val = 3;
const int &f_val = val;
类
修饰成员变量
类中的成员变量被 const 修饰时,在类被构造时,const 变量必须在成员初始化列表中被初始化,不能在构造函数中被赋值,同时也不能定义时赋值。因为类对象可能存在多个。
必须使用成员初始化列表的情况
- 初始化 const 变量
- 初始化引用对象
- 类中含有类类型成员,该成员的构造函数含有参数
- 从类初始化角度来看,对于类中含有类类型成员,该成员的构造函数是可以被编译器自动调用的,不过编译器调用的是该类的默认构造(不带参数),而如果你需要的是携带参数的构造函数,必须显式写出来
class base {
public:
base(int val) : _val(val) {
}
private:
int _val;
};
class driver {
public:
driver(base &arg) : _base(arg) { //调用 _base 携带参数的构造函数
}
private:
base _base;
};
- 派生类调用基类的构造函数,基类的构造函数含有参数
class driverBase : public base {
public:
driverBase(int arg) : _val(arg), base(arg) { //同上
}
private:
int _val;
};
修饰成员函数
在函数中,const 被修饰在函数返回值,函数参数列表,函数体中
//函数返回值、函数参数列表
const int function(const int arg)
{}
//函数体
class base {
public:
void fuc() const {
//函数中禁止修改类成员变量的值,mutable 关键字修饰除外
//const 函数只能在类中声明定义,在非类中是无法声明定义 const 函数,所以说 const 函数专属类
}
}
- 返回值
- 对于返回值是 const 类型,如果我们需要修改返回的对象时怎么办?我们可以使用 const_static 进行类型强转,去除 const 属性,但不建议这样做
- 函数参数列表
- 对于不需要改动实参,函数的形参应该为 const,这是良好的编程习惯
- 函数体
- 不能调用类中任何非 const 成员函数