为什么说c++提倡尽可能使用const?

欢迎来到博主的专栏——c++杂谈
博主ID:代码小豪


关于const的权限问题

如果还将c++里的const和C语言里的const画上一个等号,那么你是一定会吃上大亏的。

在C语言中,const修饰的变量是不可改变的。但是我们可以通过指针,绕开这层束缚。

const int a = 0;
a = 10;//error ,const变量不能被修改
int* pa = &a;
(*pa) = 10;//ok,因为这个指针并没有const修饰,因此修改它的数据是合法的
//但是a也因此被修改了

也就是说,C语言的const更像是在声明一个const变量,而不是给这个变量加上一个权限。(也不是说没有权限,而是这个权限太薄弱了)

于是c++改变了这个问题,在c++中,const变量的修改变得严格了。在c++中,如果一个变量被声明成了const类型,那么我们无法使用非const的指针或者引用指向这个变量。

const int a = 0;
a = 10;//error ,const变量不能被修改
int* pa = &a;//error,非const指针不能指向const变量
int& ra = a;//error,非const引用不能引用const变量

这在c++当中称为权限的放大。一个const权限的对象,如果被非const的指针/引用指向/引用了。那么其const的权限就被放大成非const,这在c++中是不允许的

与权限放大相反的概念是权限的缩小。即一个非const对象,可以被const修饰的指针/引用指向。权限的缩小时编译器允许的行为。因为这不会对对象的权限造成影响,你只是想要在使用这个指针/引用时,得到编译器的权限保护

在函数当中const的使用

const被应用最广泛的场景就是函数的声明了。在一个函数声明式内,const可以修饰函数返回值、各参数、成员函数。当const作用在不同的位置(即返回值、参数、成员函数)时,造成不同的函数性质。

这里我们抛出这么一个概念

  • 尽可能使用const(use const whenever possible)

想要知道这是为何。我们就得先了解不这么做会导致什么?

我们先从const修饰的返回值开始说吧。

令函数返回一个常量值,目的在于降低使用者错误使用而造成意外。提高程序的安全性。

比如我们设定了一个const对象。那么根据const的定义,这个对象的内容是无论如何都不能被改变的吧。如果我们忘记了对返回值添加const,很可能就会发生这种乌龙。

class array
{
public:
	array() : arr(new int[10])
	{

	}
	int& operator[](size_t pos) const {
		return arr[pos];
	}
private:
	int* arr;
};

int main()
{
	const array arr1;
	arr1[5] = 10;//对const对象修改程序竟然被通过了!!!这当然不行
}

我们仔细分析一下原因,原来是operator []的返回值是int&,非const引用是可以被修改的,所以造成了这种乌龙。如果程序当中不能被修改的对象都被修改了,会造成什么结果是不可预计的。而且还难以发现(因为你看到const修饰了这个对象,就对其放心了,将注意力转到非const对象身上)。

于是解决方案就是在返回值前面加上const,以保证这个函数的返回值不被修改,也就保护了const对象的常性(constness)。

const修饰函数参数

const修饰函数参数的作用是什么,首先我们先来了解一个概念。c++的函数的返回值和参数都有两种传递方式,一是值传递,二是引用传递(指针传递)

  1. 值传递(pass by value),值传递的方式是函数形参拷贝调用实参。也就是说明形参在函数内部发生的任何变化都不会影响实参,因此函数形参无论具不具备常性(constness),都可以接收实参。所以值传递的函数不在我们的考虑范围。(因为形参、返回值是不是const都不会对对象造成任何影响,也就无所谓的权限放大问题了)
  1. 引用传递(pass by reference),一个具有常性的参数或返回值会导致调用实参的权限发生变化,因此、use const whenever possible这个概念在此处是应用最广泛的,也是本博客的重点。
  1. 指针传递(pass by pointer),指针传递和引用传递的性质是一样的,因为c++当中的引用实际上是指针的语法糖,因此无论是引用传递还是指针传递,其底层原理都是一致。

如果你了解了拷贝构造函数,就会明白在类类型的成员函数中,引用传递比值传递的效率要高了不少(由于这不是本篇的重点,所以博主就不加以概述了),我们只需要了解一件事,在有关对象的函数中,引用传递的使用率比值传递的使用率高了好几十倍。

那么当函数形参是引用类型时,const对象和非const对象作为调用实参会有不同的效果。我们回顾一下开篇提到的关于c++权限放大的问题。如果函数形参是非const类型的引用,那么根据权限不能放大这个概念,const对象是不能作为调用参数进行传递的。而当函数形参是const的引用类型时,根据权限可以缩小这个概念,无论是const对象还是非const对象,都能调用这个函数。但是请你先确定这个函数不会修改参数的数据。

class array
{
public:
	array() : arr(new int[10]), _size(10)
	{

	}
	array& operator=(array& copyarr) const {
		memcpy(arr, copyarr.arr, _size * sizeof(int));
	}
private:
	int* arr;
	int _size;
};

int main()
{
	const array arr1;
	array arr2;
	arr2 = arr1;
	//error,原因是operator =函数声明的形参是非const类型的引用,
	//因此不能将const对象作为参数上传。
	//但是该函数的原理是拷贝赋值,const对象也能被拷贝(这不对const对象的数据进行修改),不符合我们的设计逻辑,
	//因此更好的方案是将copyarr加上const修饰。
}

因此我们在设计函数时,首先要明白这个函数能不能将const对象作为调用参数,如果可以,那么请将函数形参加上const修饰。前提是你确定这个函数设计的初心就是不对const对象造成修改。

关于const修饰成员对象,可以在本专栏的上一篇博客中看到,因此博主不再赘述。

  • 22
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码小豪

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值