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. Zabbix遇到的问题集锦

    一.Web界面上显示Zabbix server is not running 二.Zabbix显示中文字体 三.利用Python发送告警注意细节 四.zabbix上发告警信息不发恢复信息 五.Agen ...

  2. IDEA部署 java Web项目 常见配置

    前言 从eclipse上转到idea上,第一次使用idea部署web项目,真折磨人,写了一个 helloworld 5分钟,了解idea部署web项目5小时. 我使用的是idea 2019.1版本,其 ...

  3. Python爬虫(一)抓取指定的页面

    (以下是在windows环境下的操作,python版本为3) 1.urllib库介绍 官方文档上的解释是: urllib is a package that collects several modu ...

  4. 二阶段js 入门知识点 自我总结复习

    二阶段自我总复习   1.javascript基础 :  客户端   安全性   跨平台   脚本语言 三大结构:  顺序 .选择.循环                    顺序:运算符和表达式  ...

  5. vue-cli2.X中引入高德地图,将其设为全局对象

    平时一般未用脚手架构建或用webpack构建的项目只要用script标签引入即可,在vue-cli 2.X构建的项目中,需要用如下方式引入高德地图: 首先在高德地图开放平台中注册开发者账号并获取key ...

  6. ZooKeeper异步调用命令

    在ZooKeeper中,所有的同步调用命令,都会有一个相应的异步调用方法.异步调用能在一个单独线程中同时提交更多的命令,也能在一定程度上简化代码实现. 1 异步create方法 如创建zNode的命令 ...

  7. 设计模式(C#)——01单例模式

    推荐阅读:  我的CSDN  我的博客园  QQ群:704621321       为什么要学习设计模式呢?我以前也思考过很多次这个问题,现在也还困惑.为什么我最后还是选择了学设计模式呢?因为在游戏中 ...

  8. egret之弹幕

    要实现弹幕功能,首先需要将弹幕配置成配置表.然后代码随机生成. /**生成单个弹幕 */ private showCaptionAnim(captionText: string) { egret.lo ...

  9. 六大设计原则(C#)

    为什么要有设计原则,我觉得一张图片就可以解释这一切 一.单一职责原则(SRP) 对于一个类而言,应该只有一个发生变化的原因.(单一职责不仅仅是指类) 如果一个模块需要修改,它肯定是有原因的,除此原因之 ...

  10. MSIL实用指南-数学运算

    C#支持的数学运算是加.减.乘.除.取模,它们对应的指令是Add.Sub.Mul.Div.Rem. 这五个运算都需要两个参数,它们的通用步骤1.生成加载左边变量2.生成加载右边变量3.生成运算指令 实 ...