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. imwrite imshow机制

    今天在做数据增强的时候,遇到一个奇怪的问题.调用imwite生成的图片,在本地用图片查看器打开的时候是正常的.但是在代码里imshow的时候是一片亮白. 代码类似如下 gaussian_img = a ...

  2. 记录一则LOCAL_LISTENER的问题

    有网友反映,他在一套Oracle的测试环境中配置有两个监听,分别监听不同端口. 目前想把环境上的一套数据库同时注册到这两个监听,他将数据库参数local_listener和tnsname.ora文件配 ...

  3. Liunx软件安装之MySQL

    一.安装MySQL 1.1 配置 yum 源 centos 默认没有 MySQL 的 yum 源,所以需要先配置 yum 源. 1) 前往 官网,选择对应系统版本 2) 右键复制链接 3) 在 cen ...

  4. CSU1784

    题意略. 思路:为了更好地求出一段连续数字的异或和,我们可以用前缀异或和来维护,现在我们只需要考虑每一个在数组中的数字向前异或,且在指定范围内, 异或值为全1的个数有多少个.算出每一个位子能做出的贡献 ...

  5. HDU 6040

    题意略. 思路:题目就是在询问你m次,第k小是哪个数.首先我们可以想到直接排序后,即可O(1)来查找询问.但是题目中n的范围给的是1e7, 无法承受nlogn的复杂度.从而想到另外一种求静态第k小的方 ...

  6. 简单使用AspectJ

    AspectJ是一个AOP框架,由于SpringAOP的配置过于繁琐,因此使用了AspectJ依赖注解开发 1.Aspecj依赖坐标,此处省略了Spring相关依赖 <dependency> ...

  7. Netty源码分析 (一)----- NioEventLoopGroup

    提到Netty首当其冲被提起的肯定是支持它承受高并发的线程模型,说到线程模型就不得不提到NioEventLoopGroup这个线程池,接下来进入正题. 线程模型 首先来看一段Netty的使用示例 pa ...

  8. 理解Js的parseInt(转)

    parseInt() 方法首先查看位置 0 处的字符,判断它是否是个有效数字:如果不是,该方法将返回 NaN,不再继续执行其他操作.但如果该字符是有效数字,该方法将查看位置 1 处的字符,进行同样的测 ...

  9. codeforces 245 D. Restoring Table(位运算+思维)

    题目链接:http://codeforces.com/contest/245/problem/D 题意:给出一个矩阵b,b[i][j]=a[i]&a[j],b[i][i]=-1.然后求a[i] ...

  10. poj 3159 Candies(dijstra优化非vector写法)

    题目链接:http://poj.org/problem?id=3159 题意:给n个人派糖果,给出m组数据,每组数据包含A,B,c 三个数,意思是A的糖果数比B少的个数不多于c,即B的糖果数 - A的 ...