一文解决const、指针、引用、自动推断类型符之间的关系(C++)

        说到const我的第一印象就是一个和指针一样,遇到谁都能和它搭配一下,然后各种天花乱坠的操作弄得人晕头转向。接下来将总结一下const限定符的基础知识以及与指针、引用、auto、constexpr之间那些有趣的搭配(加深理解和记忆)。

        众所周知,const限定符的作用是对变量的类型加以限定。const对象一旦创建之后一般情况下其值是不可以改变的(有些情况虽然不能直接修改但是可以间接修改,详见下文const与引用),所以const对象必须初始化(简单理解这句话,我们将开启接下来的旅程)。

目录

1 const与引用

2 const与指针

3.顶层与底层const

3.1 顶层const

3.2 底层const

3.3 const与拷贝

4.auto类型说明符与const、复合类型、常量的组合

5decltype类型指示符

6.constexpr和常量表达式

6.1指针与constexpr


1 const与引用

 我们将通过通俗易懂的例子论证以下知识点(建议看完例子再回头看总结):

        1.1 对const的引用可能引用一个并非const的对象,也可以是非常量,甚至是字面值(纯数字)、表达式。
        1.2 当const引用指向的对象不是常量,我们就可以通过修改变量本身或者修改变量其他的非常量引用,这样const引用的对象的值也会随之改变。

        话不多说,我们通过第一个栗子证明1.1的观点。

//1.const引用指向一个非常量对象
    int a = 0;
	int& c = a;  //正确
	const int& c = a; // 正确

//2.非常量引用不能指向常量对象
	const int b = 10;
	const int& d = b; // 正确
	int& e = b; //报错!!

//3.const引用指向字面值和表达式
    const int &e = 100; //正确
    const int &e = a+a; //正确

        上述栗子告诉我们,const这把锁带上去容易摘下来难。非常量的引用指向的对象一定是非常量,而常量引用指向的对象可以是常量,也可以是非常量,甚至是字面值(纯数字)、表达式。(通俗的说,const引用位高权重,可以指向有const和没const的,但是不允许那些没有const的指向它)。

        我们知道引用的类型必须与其所引用的对象的类型一致,但是当引用遇上const时也会有例外。如下第二个栗子。

	double a = 3.14;
	const int& b = a; //正确,b = 3

        通过const我们让类型为int的引用指向了类型为double的对象。在这里我们将引入一个重要的概念叫临时量。并论证1.2,当const与引用组合时,是可以改变其指向对象的值。

        首先解释临时量,顾名思义,编译器在执行上述代码时创建了一个临时的对象,其过程如下

const int temp = a;
const int &b = temp;

        那么如何证明这个中间临时量的存在呢?当然是寻址

	double a = 3.14;
	const int& b = a; // b = 3
	cout << &b << endl; //003FFCCC
	cout << &a << endl; //003FFCE4
	a = 4;
	cout << b << endl; // b = 3
	cout << &b << endl; //003FFCCC
	cout << &a << endl; //003FFCE4

        如上代码可以直观的看到a和b的地址截然不同,b的地址其实就是中间变量temp的地址。

        下一个要解决的问题就是证明,当const与引用组合时,是可以改变其指向对象的值。我们只要把上面的代码稍作修改就能证明,如下所示。

	int a = 3;
	const int& b = a;  //b = 3
	cout << &b << endl; //010FFAD0
	cout << &a << endl; //010FFAD0
	a = 4;
	cout << b << endl; //b = 4
	cout << &b << endl; //010FFAD0
	cout << &a << endl; //010FFAD0

        当const引用指向的对象不是常量,我们就可以通过修改变量本身或者修改变量其他的非常量引用(一个对象可以被多个对象所指)进行修改。const引用所指向的对象的值也会随之改变(明摆着地址相同嘛)。

2 const与指针

2.1 const与指针的配合有部分是与const与引用极为相似的(相似之处,搭配例子理解)

        1.可以令指针指向常量或非常量,而指向常量的指针不能用于改变其所指对象的值。

        2.要想存放常量对象的地址,只能使用指向常量的指针。

实例如下:

	int a = 19;
	int* b = &a; //正确
	const int* p = &a; //正确

	const int c = 20;
	const int* e = &c; //正确
	int* f = &c; //!!错误

        上述例子和const与引用部分也极为相似。

2.2 接下来我们看看const指针这个概念,重点在于区分以下几个左值的区别。

        如下四个左值的根本区别在于int、const、*、p这四个对象的位置,区分他们的关键在于const 与*的相对位置。

//重点观察const与*的相对位置

1. const int *p;
//const 修饰的是*p, 说明*p是不可变的(*p是指针对应的值)。而p是可变的(p代表的是指针指向的地址)


2. int const *p;
//const 同样修饰的是*p, 说明*p是不可变的(*p是指针对应的值),与第一个没有区别


3. int *const p;
//const修饰的是p,说明p是不可变的(p代表的是指针指向的地址)。而*p是可变的(*p是指针对应的值)


4. const int *const p;
//有两个const,右边的const修饰的是p,表示p不可变。左边const修饰的是*p,表示*p也不可变。

3.顶层与底层const

        讲述顶层与底层const的概念是为了更容易理解和区分const与auto、constexpr、decltype之间的搭配与运用,同时也是能否执行对象拷贝氏的关键点。

        指针是一个对象(引用不是),指针又可以指向一个对象。所以指针自身是不是常量和指针指向的对象是不是常量是两个独立的问题。我们用顶层const表示指针本身是一个常量,用底层const表示指针所指的对象是常量。所以指针类型既可以是顶层const,也可以是底层const。那对于其他l类型的const呢?我们将做如下总结

3.1 顶层const

        1.指针本身是常量的情况(int *const p)

        2.一般的数据类型(const int a)

3.2 底层const

        1.被指针所指的对象是常量(int const *p)

        2.用于声明引用的const都是底层const(const int &r)

int a = 0;
int *const b = &a; //b的值不可变,是顶层const
const int c = 2; //c值不可变,是顶层const
const int *p = &a; //*p的值不可变,p可变。是一个底层const
const int *const d = &a;//都不可变,右边的const是顶层,左边的const是底层。
const int &r = a; //声明引用的cosnt都是底层const

3.3 const与拷贝

        首先,我们来看看顶层const和底层const如何帮助我们判断对象间的拷贝是否合法。先说结论:当进行拷贝时,顶层const将不受影响(不具有传递性),而底层拷贝的限制却不可忽视(底层const则与符合类型的基本类型有关),拷贝双方必须具有相同的底层const资格。论证如下(其中部分变量在上文讲解顶底const中已定义):

//顶层const
a = c; //正确,c中只有顶层const,不影响拷贝。
c = d //他们具有相同的底层const,虽然d具有顶层cosnt(不具有传递性),而c没有。但不影响拷贝。

//底层const
int *p2 = d //错误,d具有底层const,而p2不具备
p = &a;//正确,有const的对象可以指向没有const的对象(类型相同的情况下),而没有const则不能指向有const的对象。
int &r = c //错误,c拥有底层const。与上面同理

4.auto类型说明符与const、复合类型、常量的组合

        auto是可以让编译器代替我们去分析表达式所属的类型,显然auto定于的变量必须初始化。接下来将先总结auto的用法,再逐一论证。

4.1 如果设置一个类型为auto的引用(引用为左值),则初始值中的顶层const将得到保留。如果引用作为右值,那么编译器将引用引用的对象作为auto的类型。

4.2 auto一般会忽略掉顶层const(引用的情况要进行区分),但是将会保留底层const。

论证如下:

int i = 0, &a = i;
auto c = a; //此处进行了4.1的论证,c的类型是int型,而不是int&,


const int d = i, &e = d;
auto f = d //f的类型为int型,d具有的顶层const被忽略掉了。
auto g = e; //g也为int型,这里同时论证了4.1和4.2。auto的类型是引用指向的对象的类型,也就是d的类型const int,又因为const int是顶层const被忽略,所以g为int型

补充:4.1中提到,auto会忽略掉顶层const,如果想要auto的初始值具有顶层const,只需要在auto前面加上const即可(const auto f = d)。

关于auto还有一个关键点,如果要在一条语句中定义多个变量,类似于符号*和&只从属于某个声明符,而非基本数据类型的一部分,因此初始值必须是同一个类型。论证如下:

const int ci = 2;
int i = 1;

auto &m = ci,*p = &ci; //正确,m是对整形常量的引用,而p是指向整型常量的指针
auto &n = i, *p2 = &ci; //错误,i是int型,而&ci是const int型

5decltype类型指示符

        decltype的使用场景:当我们想使用自动推断类型,但又不不希望在该表达式中进行初始化的时候(如下例)。

int a = 10;
decltype(a) b;

decltype(fun()) c; // 函数fun返回值的类型

        在编译阶段,编译器并不会调用fun()函数,而是使用fun()函数返回值的类型作为c的类型。

5.1decltype与const和引用

        decltype处理const和引用与auto不同,decltype会保留顶层const和引用。需要注意的是,引用的类型需要初始化。

const int a = 10, &b = a;
decltype(a) c = 0; // c的类型为const int
decltype(b) d = c; // d的数据类型为const int&

        还需要注意的是,如果decltype处理的是解引用的操作,那么将返回引用数据类型。还需要注意的是,当decltype存在两个括号时【decltype(())】结果将永远是引用。

int a = 10, *p = &i;
int b = 20;
decltype(*p) q = 20; // 错误!!,*p是解引用,但是在此处q是引用类型int&
decltype((a)) q = b; //正确,因为存在(()),所以q的类型为int&

6.constexpr和常量表达式

        常量表达式是指值不会有所改变并且在编译过程就能得到计算结果的表达式(字面值、用常量表达式初始化的const对象都是常量表达式)。以下情况注意区分:

const int a = 10; //属于常量表达式
const int b = get_size(); //非常量表达式,因为过于复杂的函数要运行之后才能得到结果,而函数并不是在编译阶段运行。

        那么const和constexpr又有什么区别呢?还记得我们在1.2中讲过,当const引用指向的对象不是常量,我们就可以通过修改变量本身或者修改变量其他的非常量引用,这样const引用的对象的值也会随之改变(我们只是不能通过const引用去改变)。说明const修饰的对象是“只读”的,而并非不可改变,而constexpr是真正意义上不可变的常量(简要概括),还有不同的地方是和指针组合使用时,详见下文。

6.1指针与constexpr

        一个constexpr指针的初始值必须是nullptr或0,或者是存储于固定地址中的对象(所以一般情况下在函数体内constexpr指针不能指向局部变量的地址)。

在constexpr声明中如果定义了一个指针,那么constexpr仅对指针有效,与指针所指的对象无关(与const的不同之处之一)。

const int *p = 0; //p是指向整型常量的“指针”
constexpr int *p1; //p1是指向整数的“常量指针”

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

又是谁在卷

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

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

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

打赏作者

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

抵扣说明:

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

余额充值