C++ const 引用 指针

先简单回忆一下常量的性质:

int main()
{
const int buffSize = 512;
buffsize = 512; //× buffSize是常量
}

初始化时:

const int i = get_val();	//√	运行时初始化
const int j = 42; //√ 编译时初始化
const int k; //× k未经初始化

当用一个对象去初始化另外一个对象,他们是不是const就无关紧要

int i = 42;
const int ci = i;
int j = ci;

ci是整形常量,但ci的常量特征仅仅在执行 改变ci 的操作时才会发挥作用

const和引用

对常量的引用

把引用绑定到const对象上,称之为对常量的引用

对常量的引用不能用作修改它所绑定的对象,也就是说:引用 及其 引用的对象 都是常量

const int ci = 1024;
const int &r1 = ci;

需要注意的是,非常量引用不能引用常量对象

const int ci = 1024;
const int &r1 = ci;
r1 = 42; //× r1是对常量的引用
int &r2 = ci; //× r2是一个非常量引用,ci是一个常量对象

因为不允许把ci用作修改它所绑定的对象,所以也不能通过引用去改变ci(假设第四句合法,那我们就可以通过r2去改变ci了,显然是不对的)

以下两句同理

int &r3 = r1;		//×
const int &r4 = r1; //√

我们口头所说的常量引用其实是对const的引用,严格来说是不存在常量引用的,因为和指针不一样,引用不是对象,我们没有办法让引用本身很定不变

(P.S:由于C++不允许随意改变引用所绑定的对象,所以也可以理解为,所有的引用都是常量,当然了,引用的对象是否是常量,会决定其所能参与的操作,但无论如何也不会影响到引用和对象的绑定关系)

初始化对常量的引用

我们知道引用的类型必须要和所引用的对象类型一致,但涉及初始化常量的引用会出现第二种例外(第一种:初始化常量引用是允许用任意表达式作为初始值,只要该表达式能转换成引用的类型)

int i = 42;
const int &r1 = i; //√ 允许const int绑定到一个普通int对象上
const int &r2 = 42; //√ r2是一个常量引用
const int &r3 = r1 * 2; //√ r3是一个常量引用
int &r4 = r1 * 2; //× r4是一个普通的非常量引用,非常量引用初始值必须为左值
int &r5 = 2; //× 非常量引用初始值必须为左值

我们知道,非常量引用的初始值必须为左值,常量引用的初始值可以为左值、右值,再看看下面的情况

int i =2;
double &r =i; //编译报错
const double &r =i; //编译通过
//难道这个i不是左值? double i =2;
double &r =i; //编译通过
//难道这里的i又是左值了?

为什么会出现这种情况?先来看一个简单的例子

double dval = 0.114514;
const int &ri = dval;
cout << "ri = " << ri <<endl;

运行输出

ri=0

在这个过程中,由于类型不匹配,编译器把代码改成了:

double dval = 0.114514;
const int temp = dval;
const int &ri = temp;
cout << "ri = " << ri <<endl;

这种情况下,ri绑定了一个临时量对象,临时变量都是const,所以没有const的引用会失败

这下你可看懂上面的代码发生了什么了吧

你可以想象以下,如果ri不是常量时,执行了上述初始化过程会带来怎样的后果:如果ri不是常量。就允许对ri赋值,这样就会改变ri所引用对象的值(此时绑定的是临时量而非dval),所以C++也把以下这种行为归为非法

double dval = 0.114514;
int &ri = dval;//编译报错
cout << "ri = " << ri <<endl;

同时注意,对const的引用可能引用一个并非const的对象

对const的引用仅对引用可参与的操作做出了限定,对于引用对象本身是否是一个常量没有做出限定,因此对象也可能是个非常量,允许通过其他途径改变它的值

int i = 42;
int &r1 = i;
const int &r2 = i;
//r2 = 0; //× r2是一个常量引用
cout << "r2 = " << r2 <<endl;
r2 = 0; //不可通过编译
i = 0; //可通过编译
cout << "r2 = " << r2 <<endl;

该程序输出如下:

r2 = 42

r2 = 0

const和指针

指向常量的指针

类似于对常量的引用指向常量的指针不能用于改变其所指对象的值

同时,想要存放常量对象的地址,只能使用指向常量的指针:

const double homo = 1.14;
double *ptr = &homo; //× ptr是一个普通指针
const double *cptr = &homo; //√
cptr = 5.14; //× 不能给*cptr赋值
//不允许 指向常量的指针 用于改变其所指对象的值

不同于引用,我们能改变指向常量的指针所指向的对象

const double homo = 1.14;
const double *cptr = &homo;
cout << "cptr = " << *cptr <<endl;
cout << "*cptr = " << cptr <<endl;
const double homo2 = 5.14;
cptr = &homo2;
cout << "cptr = " << *cptr <<endl;
cout << "*cptr = " << cptr <<endl;
cptr++; //合法
//允许一个 指向常量的指针 指向 一个非常量对象

注意,与引用类似,虽然我们说指针的类型必须与所指对象一致,但是这里有第一种例外:允许一个指向常量的指针指向一个非常量对象,但是不允许通过 指向常量的指针 修改非常量对象的值

const double homo = 1.14;
const double *cptr = &homo;
double dval = 3.14;
cptr = &dval; //允许一个 指向常量的指针 指向 一个非常量对象
*cptr = 0.0 //但是不允许通过 指向常量的指针 修改非常量对象的值

所以,指向常量的指针也没有规定其所指的对象必须是一个常量,所谓指向常量仅仅要求不能通过该指针修改所指向对象的值,而没有规定所指对象的值不能通过其他途径改变

const double homo = 1.14;
const double *cptr = &homo;
cout << "cptr = " << *cptr <<endl double dval = 5.14;
cptr = &dval; //允许一个 指向常量的指针 指向 一个非常量对象
//*cptr = 0.0 //但是不允许通过 指向常量的指针 修改非常量对象的值
cout << "cptr = " << *cptr <<endl; dval = 0.0 //所指对象的值可以通过其他途径改变
cout << "cptr = " << *cptr <<endl;

现在我们输出就变成了:

cptr = 1.14

cptr = 5.14

cptr = 0

const指针

和引用不同,指针本身是对象,所以允许把指针本身定为常量,也就是常量指针,常量指针必须被初始化,并且初始化完成后值(存放在指针对象里的地址)不能改变

*放const关键字之后,用以说明指针是指向常量的指针(不能通过该指针修改所指向的对象)

*放const关键字之前,用以说明指针是一个常量(即指针本身的值——存储的地址不变)

int errorNumb = 0;
int* const curErr = &errorNumb; //curErr是一个常量指针,一直指向errNumb
const double pi = 3.1415;
const double* const pip = &pi; //pip是一个 指向常量对象 的 常量指针

以下两种写法区别很大:

int* const curErr = &errorNumb; //curErr一直指向errNumb
*curErr = 1; //可以修改所指变量的值
const int* curErr = &errorNumb; //curErr是一个 指向常量的指针
*curErr = 1; //× 不能通过curErr修改所指对象的值

顶层const和底层const

由于指针本身是一个对象,它又可以指向另外一个对象,因此指针本身是不是常量指针所指的对象是不是常量就是两个互相独立的问题,顶层const表示指针本身是个常量,底层const表示指针所指的对象是个常量

顶层const其实可以表示任意的对象(自身)是常量,指针式比较特殊的,因为它既可以是顶层也可以是底层const

int i = 0;
int* const p1 = &i; //p1本身是常量,顶层const
const int ci = 42; //ci本身是常量,顶层const
const int* p2 = &ci; //*在const之后,p2是指向常量的指针,底层const
const int* const p3 = p2; //先看左边是顶层,再看右边是底层,p3是指向常量的常量指针
const int& r = ci; //声明引用的const都是底层const,r是一个对常量的引用

拷贝操作不会影响被拷贝对象的值,顶层const不受影响

i = ci;
p2 = p3;

但是底层const就会产生限制:

拷贝操作时,拷入和拷出的对象必须有相同的底层const资格,活着两个对象数据类型能转换(非常量能转成常量,反之不行)

int* p = p3;		//× p3包含底层const,p没有
const int* p = p3; //√ p和p3都是底层const
p2 = p3; //√ p2和p3都是底层const
p2 = &i; //√ int能转换成const int*
int& r = ci; //× 普通int&不能绑到const int&上
const int& r2 = i; //√ const int&可以绑到一个普通int上

常量对象成员

class Corrdinate	//坐标
{
public:
Corrdinate(int x,int y);
private:
const int m_iX;
const int m_iY;
};

可以看到两个整形成员变量都是常量,我们也类似指针和数组,称之为常量成员

由于是常量,初始化肯定就会受到限制:

//m_iXh,m_iY是常量成员,以下写法是错误的
Corrdinate::Corrdinate(int x, int y) {
m_iX = x;
m_iY = y;
}

正确的方法应该是使用初始化列表

Corrdinate::Corrdinate(int x, int y):m_iX(x), m_iY(y) {

}

那如果类的成员也是对象呢?

我们再看一个线段类

class Line
{
public:
Line(int x1, int y1, int x2, int y2);
private:
const Corrdinate m_corrA;
const Corrdinate m_corrB;
};

我们需要让一个线段定义后不能修改,于是我们把线段的两个端点定义为const,类似地称之为常量对象

定义完后,想通过构造函数初始化,也是使用初始化列表

Line::Line(int x1, int y1, int x2, int y2) :m_corrA(x1, y1), m_corrB(x2, y2) {

}

常量成员函数

我们还可以用cosnt修饰成员函数,这样的成员函数叫常量成员函数

class Corrdinate	//坐标
{
public:
Corrdinate(int x,int y);
void changeX() const; //常量成员函数
void changeX();
private:
int m_iX;
int m_iY;
};

然后你就会发现

void Corrdinate::changeX() const {	//×
m_iX = 10;
} void Corrdinate::changeX() { //√
m_iX = 20;
}

常量成员函数中不能改变数据成员的值,为什么呢?

这个函数只是看似没有参数

void Corrdinate::changeX() {
m_iX = 20;
}

实际在编译时会变成

void changeX(Corrdinate *this) {
this->m_iX = 20;
}

当我们定义了常量成员函数时:

void Corrdinate::changeX() const {
m_iX = 20;
}

实际在编译时会变成

void changeX(const Corrdinate *this) {
this->m_iX = 20;
}

此时的this指针是个常量指针,不能用于改变其所指对象的值

还要注意的是,由于我们定义了两个版本的changeX函数,你还要弄明白什么时候会调用哪个changeX

int main()
{
Corrdinate cor1(3, 5);
cor1.changeX(); //此时调用的是void changeX()
const Corrdinate cor2(3, 5); //常量对象
cor1.changeX(); //此时调用的是void changeX() const
}

当成员函数的 const 和 non-const 版本同时存在时:

const object 只能调用 const 版本,non-const object 只能调用 non-const 版本

const object(data members不得变动) non-const objectdata members可变动)
const member function(保证不更改data members)
non-const member function(不保证) 不可

C++ const 引用 指针的更多相关文章

  1. 弄清const与指针、引用之间的关系

    const和 define在常量定义上的差别 在C++中,我们可以使用const 或者 宏define来定义常量.但是C++鼓励使用const定义常量,而不是宏define.原因有很多. 1.defi ...

  2. 【c++基础】const、const指针、const引用

    一.const常量 声明时必须同时初始化(和“引用”一样) 二.const指针 三.const引用 引用本身和引用的对象都是const对象,可以用字面值来赋给const引用(普通引用则不行) ; co ...

  3. 各类形参(引用,const,指针)

    #include <stdlib.h> #include <iostream> //这是一个关于引用形参,const形参,指针形参的程序,用于理解不同形式的区别 using n ...

  4. C++引用和const引用、常量指针、指针常量

    1.引用.常量引用 引用主要被用做函数的形式参数--通常将类对象传递给一个函数. 引用在内部存放的是一个对象的地址,它是该对象的别名.引用不占用内存,因为取地址引用的值和被引用变量的地址相同.但是ob ...

  5. 【C++编程基础】(1)—— 函数原型声明、函数模板、引用、const 常引用、const 常量指针

    一.函数原型声明: 1.函数声明告诉编译器函数的名称,和如何调用函数(返回类型和参数):函数定义提供了函数的实际主体. 2.强制性的:在C++中,如果函数调用的位置在函数定义之前,则要求在函数调用之前 ...

  6. 第12课.经典问题解析(const;指针和引用)

    问题1:const什么时候为只读变量?什么时候是常量? const常量的判别准则: a.只有用字面量初始化的const常量才会进入符号表(直接初始化过的const为常量) b.被使用其他变量初始化的c ...

  7. Const指针 、 指向const的指针 、引用、指针

    1. const指针和 指向const的指针 指向const的指针: 不允许通过指针来改变其指向的const值 const double *cptr *cptr = 42;  // error! 指针 ...

  8. 函数返回值为 const 指针、const 引用

    函数返回值为 const 指针,可以使得外部在得到这个指针后,不能修改其指向的内容.返回值为 const 引用同理. class CString { private: char* str; publi ...

  9. const与指针、引用

    const与指针类型 定义一个指针*p: const int* p = NULL; int const* p = NULL; int* const p = NULL; 上面两行定义完全等价,第三行则不 ...

随机推荐

  1. HBase 系列(八)——HBase 协处理器

    一.简述 在使用 HBase 时,如果你的数据量达到了数十亿行或数百万列,此时能否在查询中返回大量数据将受制于网络的带宽,即便网络状况允许,但是客户端的计算处理也未必能够满足要求.在这种情况下,协处理 ...

  2. 阿里分布式事务seata入门(采坑)

    1. 阿里分布式事务seata入门(采坑) 1.1. 前言 seata是feascar改名而来,这是阿里在19年年初开源出来的分布式事务框架,当初刚出来的时候就想研究下了,一直拖到了现在,目前是0.8 ...

  3. 常用Http status code 如何记

    一直记不住http常用的status code,最近思考可以这样想.http无非就是客户端和服务端之间请求嘛.结果么要么成功,要么失败. 成功了,可以提示信息 -- Informational 1xx ...

  4. Android P不能使用http

    三种方法解决Android P(安卓9.0)联网问题: 1.最简单的方法就是改用https,但很多的http接口都要一一改(非全局接口可以忽略方法1). 2.target降低至27,target27之 ...

  5. Go语言学习——如何实现一个过滤器

    1.过滤器使用场景 做业务的时候我们经常要使用过滤器或者拦截器(听这口音就是从Java过来的).常见的场景如一个HTTP请求,需要经过鉴权过滤器.白名单校验过滤.参数验证过滤器等重重关卡最终拿到数据. ...

  6. 使用wait/notify/notifyAll实现线程间通信的几点重要说明

    在Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信.在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调 ...

  7. Attention机制的精要总结,附:中英文机器翻译的实现!

    1. 什么是Attention机制 在"编码器-解码器(seq2seq)"⼀节⾥,解码器在各个时间步依赖相同的背景变量来获取输⼊序列信息.当编码器为循环神经⽹络时,背景变量来⾃它最 ...

  8. 面试必备:常考Java基础知识总结(持续更新)

    面试必备:常考Java基础知识总结(持续更新) 本文的Java方面基础知识是我在面试过程中的积累和总结. Java基本数据类型.所占空间大小及对应包装类 基本类型 大小 包装类 boolean - B ...

  9. d3.js 教程 模仿echarts legend功能

    上一节记录没有加上echarts的legend功能,这一小节补一下. 1. 数据 我们可以从echarts中看出,折线数据并不是我们传进入的原始数据(多数情况下我们也不会修改原始数据),而是原始数组的 ...

  10. MySQL5.7.27报错[Err] 1055 - Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated

    mysql5.7.27在运行更新语句时出现如下情况,mysql5.6之前没有这种情况出现. of ORDER BY clause is not in GROUP BY clause and conta ...