C++ const 引用 指针
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 = π //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 引用 指针的更多相关文章
- 弄清const与指针、引用之间的关系
const和 define在常量定义上的差别 在C++中,我们可以使用const 或者 宏define来定义常量.但是C++鼓励使用const定义常量,而不是宏define.原因有很多. 1.defi ...
- 【c++基础】const、const指针、const引用
一.const常量 声明时必须同时初始化(和“引用”一样) 二.const指针 三.const引用 引用本身和引用的对象都是const对象,可以用字面值来赋给const引用(普通引用则不行) ; co ...
- 各类形参(引用,const,指针)
#include <stdlib.h> #include <iostream> //这是一个关于引用形参,const形参,指针形参的程序,用于理解不同形式的区别 using n ...
- C++引用和const引用、常量指针、指针常量
1.引用.常量引用 引用主要被用做函数的形式参数--通常将类对象传递给一个函数. 引用在内部存放的是一个对象的地址,它是该对象的别名.引用不占用内存,因为取地址引用的值和被引用变量的地址相同.但是ob ...
- 【C++编程基础】(1)—— 函数原型声明、函数模板、引用、const 常引用、const 常量指针
一.函数原型声明: 1.函数声明告诉编译器函数的名称,和如何调用函数(返回类型和参数):函数定义提供了函数的实际主体. 2.强制性的:在C++中,如果函数调用的位置在函数定义之前,则要求在函数调用之前 ...
- 第12课.经典问题解析(const;指针和引用)
问题1:const什么时候为只读变量?什么时候是常量? const常量的判别准则: a.只有用字面量初始化的const常量才会进入符号表(直接初始化过的const为常量) b.被使用其他变量初始化的c ...
- Const指针 、 指向const的指针 、引用、指针
1. const指针和 指向const的指针 指向const的指针: 不允许通过指针来改变其指向的const值 const double *cptr *cptr = 42; // error! 指针 ...
- 函数返回值为 const 指针、const 引用
函数返回值为 const 指针,可以使得外部在得到这个指针后,不能修改其指向的内容.返回值为 const 引用同理. class CString { private: char* str; publi ...
- const与指针、引用
const与指针类型 定义一个指针*p: const int* p = NULL; int const* p = NULL; int* const p = NULL; 上面两行定义完全等价,第三行则不 ...
随机推荐
- laravel为模型中所有查询统一添加WHERE条件
在使用laravel开发web系统的过程,需要在model处为该模型统一添加一个条件或者多个条件,研究了一个laravel的模型类,发现model中有个方法是构建查询的,方法如下: /** * Reg ...
- 整合-flowable-modeler,第一篇
BPMN流程想必大家都不陌生,经过这十几年的不断发展完善,在处理业务流程操作已经相当完善,我这里先不进行流程引擎的具体描述,单对集成流程设计器这块进行笔记,如有不对,跪求指出.
- [SCOI2009]粉刷匠(动态规划,序列dp,背包)
分别对每块木板做区间dp,设\(g[i][j]\)表示前i个格子,刷恰好j次,并且第i格是合法的最多合法的格子数.从前往后枚举断点来转移就好了. 这样处理再出来\(g[i][j]\)每一块木板i刷j次 ...
- Code signing is required for product type 'Unit Test Bundle' in SDK 'iOS 11.0.1'
Code signing is required for product type 'Unit Test Bundle' in SDK 'iOS 11.0.1' 进入 projects and lis ...
- count(*) count(1) count(column)的区别
count(1)中的1并不是指第一个column: count(*)和count(1)一样,包括对值为NULL的统计: count(column)不包括对值为NULL的统计,这里的column指的不是 ...
- 「每日五分钟,玩转JVM」:线程独占区
前言 如果我们对计算机组成有所了解,那么我们一定会知道在计算机中有一块儿特殊的区域,称之为寄存器,寄存器包括了指令寄存器和程序计数器,这两样位于CPU中,作为程序运行的大脑来控制程序的运行和流转. 而 ...
- Mybatis延迟加载的实现以及使用场景
首先我们先思考一个问题,假设:在一对多中,我们有一个用户,他有100个账户. 问题1:在查询用户的时候,要不要把关联的账户查出来? 问题2:在查询账户的时候,要不要把关联的用户查出来? 解答:在查询用 ...
- 微信小程序 es6-promise.js封装请求 处理异步进程
下载es6-promise.js置于根目录下的libs文件夹下: 在根目录utils文件夹下新建httpsPromisify.js,即定义封装请求的方法 var Promise = require(' ...
- 马蜂窝视频编辑框架设计及在 iOS 端的业务实践
(马蜂窝技术公众号原创内容,ID: mfwtech) 熟悉马蜂窝的朋友一定知道,点击马蜂窝 App 首页的发布按钮,会发现发布的内容已经被简化成「图文」或者「视频」. 长期以来,游记.问答.攻略等图文 ...
- CodeForces 909C
题意略. 思路: 开始的时候,定义dp[i]:当前行在第i行,i~n有多少种排列方式,如果i为f,那么dp[i] = dp[i + 1],因为第i + 1条语句只能放在f后且向右缩进一位: 如果i为s ...