漫谈 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 成员函数
posted @ 2021-02-01 23:41  Sumelt  阅读(81)  评论(0编辑  收藏  举报