自己看书时的一些理解,可能有错误的地方。随着指针的使用增多,会不断修改这篇文章的内容,过去错误的会用划线划去后保留。

1.对引用、指针、常量引用、指向常量的指针、常量指针的理解

//对引用、指针、常量引用、指向常量的指针、常量指针的理解
int main()
{
//引用
int a = 1;
int &r_a = a;//只要是引用都必须初始化
r_a = 5;//引用不是一个对象,但可以通过改变引用来间接改变引用对象的值
std::cout << "引用 " << a << std::endl; //指针
int b = 1;
/*
*同一符号在不同语句中有不同的意义。
*
*声明语句中,*是类型修饰符,表达式中,*是解引用符。
*
*声明语句=基本类型+声明符列表=int *p,其表示定义一个名为p、指向int类型的指针。
*
*int *p=&b;实际上等价于:
*int *p = nullptr;
*p=&b;
*如果想要给p赋int值,就要用上强制类型转换:
* *(int*)p=b;
*/
int *pb = &b;
int **pib = &pb;
int ***piib = &pib;//有3个*,表示pii是指向指针的指针的指针,最终指向的地址是&b
std::cout << "多重指针的指针 " << ***piib << std::endl; //常量引用
int c = 9;
/*
*const是一个限定符,其限定了不能对某个对象进行任何改变。不管是引用还是指针,只要对象是常量,声明中必须有const
*
*从右往左读,“&”表示r_c是一个引用,“int”说明了这是一个对int类型的引用,
*“const”则说明这个引用是一个常量,即c不能通过改变它的引用r_c而改变,但可以通过别的方式改变————r_c只可以通过改变c来改变。
*
*常量引用有什么用呢?当我们需要对一个函数传入某个变量作为参数时,如果直接传要进行拷贝等工作浪费空间和时间,
*此时如果传入那个变量的引用就可以省去那些操作(引用并没有申请存储空间,引用本身不是一个对象),而我们又怕
*使用引用的时候不小心改变了那个变量,所以我们用了常量引用。
*
*如果这里的c是一个常量,则c、r_c都不能改;
*/
const int &r_c = c;
c = 10;
std::cout << "常量引用 " << c << " " << r_c << std::endl; double d = 3.14;
/*
*引用的类型必须与其引用对象的类型一致,但有两个例外,其中之一就是在常量引用的初始化时,可以类型不一致,
*实际上它的执行是通过一个临时量来使其合法的:
*double d = 3.14;
*const int tem = d;
*const int &r_d = tem;
*在这里,无法通过改变d值来改变r_d(改变tem才行,但是tem是编译器为了类型转换而临时出现的,并不是一个真的对象)。要注意之所以会发生这种事主要是因为一个是int一个是double,而非const。
*需要说明的是,这种转换必须是可以互相转换的类型(double和int是可以互相转换的)
*
*不能用一个非常量引用来引用一个常量,即下面的语句是非法的:
*const int d = 3;
*int &r_d = d;
*/
const int &r_d = d;
d = 8.5;
std::cout << "常量引用类型不一致 " << d << " " << r_d << std::endl; //指向常量的指针
const int e = 9;
/*
*从右往左看,“*pe”表示一个名为pe的指针,“int”表示这个指针指向int类型,“const”表示这个指针指向的是一个常量。
*
*指向常量的指针,只是说明不能通过改变指针来改变指向的对象,并没有说指针本身的值不能改变。
*
*指针的类型必须与其所指对象的类型一样,但是有两个例外,其中一个就是指向常量的指针,指向的对象不一定非得是常量。
*
*e是常量,则pr声明处的const不可省略,因为必须强调指向的是一个常量才能存e这个常量的地址;如果e不是常量,pe声明处的const意在指出不能通过pe指针来改变指针指向的对象,不管这个对象是不是常量——如果不是常量,可以通过除指针外的方式改变对象。
*书上一句话可以很好的概括这一点:所谓指向常量的指针或引用,不过是指针或引用“自以为是”罢了,它们觉得自己指向了常量,所以自觉的不去改变所指对象的值。
*
*指针和引用不一样,有关指针的初始化并没有类似引用中的tem这个中间值的执行步骤——即如果基本类型不一样是不能初始化的,只是说常量和非常量都可以初始化。
*也就是说,不要过度的类比指针和引用的操作,两者相同点没有那么多,比如这里的pe即使是自己和指向的对象都加了const,pe还是可以改变。
*/
const int *pe = &e;
const int f = 10;
pe = &f;
std::cout << "指向常量的指针 " << *pe << std::endl; //常量指针
int g = 10;
/*
*从右往左看,“const”表示pg是一个常量,“*”表示pg是一个常量指针,“int”表示pg指向一个int对象
*
*常量指针必须初始化。
*
*常量指针不能改变(指向的地址不变),只能改变解引用后的值,即只能改变g值。
*/
int *const pg = &g;
g = 9;
std::cout << "常量指针指向非常量 " << *pg << std::endl; /*
*一样的同上从右往左看,我们发现在指针的声明中,限定符const在声明列表里则是限定列表里指针,在基本类型处则是限定指针指向的对象,
*这里的ph表示一个常量指针指向一个int的常量h。由于都是常量,所以都不能改变。
*/
const int h = 4;
const int *const ph = &h;
std::cout << "常量指针指向常量 " <<*ph << std::endl;
return 0;
}

2.类型不同,读取的方式也是不一样的,之所以要求类型一致就是这个原因,同一串二进制,用int去读和用double去读读出来的结果是不一样的。引用实际上是一个存储地址存了两个名字,一个是引用名,一个是对象名。

3.顶底const引入的意义漫谈

当执行对象的拷贝操作时,常量是顶层还是底层区别明显,其中顶层不受影响,因为执行拷贝操作不会改变被拷贝对象的值,因此拷入和拷出的对象是否是常量没什么影响。

底层限制却不能忽视,当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换为常量,反之不行。

这句话隐藏了一些细节,但只需要分析清楚,没有必要展开成顶底=底、底=底…..这样来一个个分析,那样反而更乱,只需要说清楚为什么要引入顶底层来判断赋值语句是否正确就好了。

首先,一个赋值语句是这样的:

变量=常量

这里的变量指可以被赋值的对象,常量指一个可以最终确定的值或是另一个对象,那么第一句话就能理解为,常量是否是顶层没什么区别(仅就这次赋值而言),因为赋值语句只改变变量,常量只是拿来拷贝的。

第二句话的意思是,常量有底层的时候就要小心对待了。为什么呢?

我们知道,但凡是声明引用的const都是底层——引用有没有顶层的说法?因为其本身绑定对象后就不能更改绑定的对象了,所以我认为引用都是顶层的;底层是针对指针、引用这种复合类型来说的,其它类型没有底层的说法(比如int、double没有底层的说法,因为他们并没有指向什么对象)

我们知道顶层为什么不需要注意,因为顶层意味着其本身不能改变,既然如此,就只能是在声明的时候初始化,后面就不能再改变了,所以不再考虑其处于变量位置的赋值语句;同样的道理,拷贝不影响原来的值,所以其处于常量位置也不需要考虑了。

底层为什么要注意?因为底层意味着拷贝时拷贝的是指向另一个对象的值,这时候要传递过去,就要考虑到指向对象的值是否可以成功的拷贝到变量身上,即类型是否一样或者类型是否是可以互相转换的。说到这,可能会有疑问:既然都是拷贝值,为什么顶层的时候不考虑这个而直接忽略了?

我们来看看究竟是怎么回事。

赋值语句是把常量拷贝出来,然后赋给变量。这一系列操作的基础是两者同类型或者是可以互相转换的类型,而通常来说,非常量可以转换成常量,反之则不行。而const的引入就是为了区分是否是常量,顶层底层只不过是用来区分是对象本身是常量还是对象指向的对象是常量。

这里要强调的是,引用绑定一个对象后就不能更改绑定的对象了,从这个角度看它自己就是一个常量,顶层常量。加上const后变成底层常量,因为此时限定了不能通过它改变绑定的对象。注意,引用的底层常量不再是“指向对象是个常量”,那是常量指针的定义!所以我必须强调第二次,底层const一词用在引用上和用在常量上是不同意义的,不能一概而论。顶层const也是这样,用在指针上指指针本身指向的地址不能改变,用在int型数上则是值不能改变,诸如此类!

讲到这,我觉得顶层底层的引入是没有必要的,为什么不直接判断类型是否符合赋值的条件就好了呢?

现在我们来看例子:

#include <iostream>
//顶底const引入的意义漫谈
int main()
{
const int ex1 = 9;
//int &r = ex1;不能把一个非常量绑在一个常量上,如果可以绑定,那么按照定义,改变r是可以改变ex1的,而ex1不能改,矛盾,所以不能绑定。
const int *pex1 = &ex1;
const int *const pex11 = pex1; //int *p = pex11;
/*
*p是一个普通指针,pex11是一个常量指针。如果可以这样赋值,就可以通过改变*p(这里的*是解引用的意思)来改变pex11这个对象所存的值,
*而这里的pex11是一个常量指针,其存的值是一个不能改变的地址,这样就矛盾了,所以不能这样赋值。
*还要注意的是,pex11存的是pex1的地址,p存的是pex11的地址。(这一段与下面的最后一段进行对比理解)
*
*这里想再强调一点:const int *p =&a(a是一个普通整形),这里的const是指针自己认为自己指向一个常量整形,
*即不可以通过p指针改变a的值,但可以通过别的途径改变(比如直接给a赋值)
*而p可以改变自己指向的对象,但不能改变指向对象的值。
*这里的意思是,可以改变p存的值(实际上是一个地址),但是不能改变存的这个值所指向的对象的值。如果没有const,是可以通过*p=4的语句使a=4的。
*还要注意的是,a存的是一个整形数,p存的是a的地址。
*/
int ex2 = 1;
const int ex3 = 2;
int *pex2 = &ex2;
const int *pex3 = &ex3;
//pex2 = pex3 ;
/*
*如果可以这样赋值意味着改变pex2可以改变pex3?不,要搞清楚,这里是两个指针的地址赋值,并不是说改变指针pex2就可以改变pex3,
*在这里只是把pex3的地址拷贝出来给了pex2.这意味着pex2存的是ex3的地址!也就相当于int *pex2 = &ex3;我们知道ex3是一个常量,
*而pex2不是指向常量的指针(注意,它也不是常量指针),这意味着可以通过改变pex2来改变ex3,矛盾,所以错误。
*/
int ex4 = 3;
const int *const pex4 = &ex4;
//pex2=pex4;
/*
*这里似乎和上一个例子很像,我们发现上一个例子中右值指向的是一个真正的常量,而这里指向的是一个普通的常量。那么就对了?
*其实这里我们要明确一点就够了(也是纠正上一个例子的分析),不能通过指针改变这个指针指向的对象,本质上说是不能对这个指针用解引用的方式改指向的对象,
*而我们先前所说的“a=4”来改变指向对象,实际上是直接给a这个对象赋值,并没有通过解引用的方式改。
*所以这里就错了,pex4已经有一个底层const规定它不能通过解引用来改变ex4,如果这里的赋值语句有效,
*则通过解引用pex2(pex2=pex4,两者存的是同一个地址值)就可以改变ex4的值
*
*上一个例子和这一个例子说明了“非常量可以转换成常量,反之不行”的原则(这里的常量在指针中指指向常量的指针中的那个底层const)
*/
}

  

看完这段代码,你就能知道为什么底层const的时候要注意能否赋值了,因为底层const实际上就是在考察赋值后会不会因为左值的类型而能直接改变底层const所指向的变量。

综上,我们只要明确:引用的赋值、指针的赋值、普通类型的赋值是不一样的,要牢牢记得它们各自的定义;赋值的基础是类型一致或者类型是可以互相转换的;顶层const和底层const广泛的含义是什么,以及具体应用到实际的类型上时的含义又是什么。

这里有一个例子需要注意:

int i=0;

const int ci=42;

const int *p2=&ci;

p2=&i;

此时的&i是一个地址,不再是一个整形。

  

4.关于引用必须说明的一点

引用声明中一旦出现const,这个const都是底层的意思。但是这个底层又有点不一样,其意思是“不能通过改变引用来改变绑定的对象”,并没有“引用不能改变”的意思,注意这里说的是“引用”而不是“引用的对象”(int &r=i,r是引用,i是引用的对象),也没有“引用绑定的对象可以改变”的意思。

(1)指针、引用、const限定符的更多相关文章

  1. Spline样条函数 //C++关键字:operator // 重载函数 // 隐含的this指针 // 指针和const限定符

    在数学学科数值分析中,样条是一种特殊的函数,由多项式分段定义.样条插值是使用一种名为样条的特殊分段多项式进行插值的形式.由于样条插值可以使用低阶多项式样条实现较小的差值误差,这样就避免了使用高阶多项式 ...

  2. 指针和Const限定符

    指针和Const限定符 1.指向const对象的指针 如果指针指向的是const对象,则不允许使用指针来改变其所指的const值.C++要求指向const对象的指针具有const特性. const d ...

  3. 【C】——指针与const限定符

    const限定符和指针结合起来常见的情况有以下几种. const int *a; int const *a; 这两种写法是一样的,a是一个指向const int型的指针,a所指向的内存单元不可改写,所 ...

  4. C++之const限定符

    作者:tongqingliu 转载请注明出处: C++之const限定符 const初始化 const的特点: 用const加以限定的变量,无法改变. 由于const对象定义之后就无法改变,所以必须对 ...

  5. C++之const限定符(顶层const,底层const)

    作者:tongqingliu 转载请注明出处:http://www.cnblogs.com/liutongqing/p/7050815.html C++之const限定符(顶层const,底层cons ...

  6. C++ Primer 第二章 引用 指针 const限定符

    1.引用: 为对象起了另外一个名字,引用类型引用另外一种类型,通过将声明符写成&d的形式来定义引用类型,其中d也就是声明的变量名(声明符就是变量名). PS:1.通过图片中编译所提示的报错信息 ...

  7. C++杂谈(一)const限定符与const指针

    const限定符 c++有了新的const关键字,用来定义常变量,可以替C语言中的#define.关于const限定符,有以下需要注意: 1.创建后值不再改变 2.作用范围在文件内有效 3.添加ext ...

  8. const 限定符

    1.定义const对象 const限定符把一个对象转换成一个常量 const int Bufsize = 512; 定义Bufsize 为常量并初始化为512.变量Bufsize仍然是一个左值,但是不 ...

  9. 变量和基本类型——复合类型,const限定符,处理类型

    一.复合类型 复合类型是指基于其他类型定义的类型.C++语言有几种复合类型,包括引用和指针. 1.引用 引用并非对象,它只是为一个已存在的对象所起的另外一个名字. 除了以下2种情况,其他所有引用的类型 ...

  10. 浅谈const限定符

    什么是const限定符? Const限定符是我们通常所说的常量限定符,被const修饰的对象具有常量性质,只能读,不能写. 为什么使用const限定符? 用const变量取代“魔数”,代码更容易理解和 ...

随机推荐

  1. rsyslog trouble shooting

    openstack,swift的log不输出了.trouble shooting过程 , 发现我们的程序 /var/log/swift/proxy.log等总是不输出log. 因为log rsyslo ...

  2. 怎样删除Tomcat下已经部署的项目

    lz说的是把web项目部署到tomcat之中,要把它删除..很简单,找到webapps文件(tomcat的根目录)下把它删除即可.. 2.Tomcat 6.0\webapps\项目名 只要在把这个目录 ...

  3. How to force immediate stop of threads in Jmeter servers如何在jmeter执行完,立即停止jmeter

    https://stackoverflow.com/questions/38900315/how-to-force-immediate-stop-of-threads-in-jmeter-server ...

  4. C#.NET如何判断是否有缺少的using

    调试的时候会报错,红色的波浪线表示出错的位置,右击即可找到对应的using      

  5. UML——用例图

    用例图是在需求分析阶段开发人员和用户对需求规格达成的某种共识.它描写叙述了待开发系统的功能需求. UML视频使我们对用例图的基本组成元素.属性.粒度等有了理论上的理解,我们还须要自己亲自己主动手画一画 ...

  6. C#之快速排序 C#之插入排序 C#之选择排序 C#之冒泡排序

    C#之快速排序   算法描述 1.假定数组首位元素为“枢轴”,设定数列首位(begin)与末位(end)索引: 2.由末位索引对应元素与“枢轴”进行比较,如果末位索引对应元素大于“枢轴”元素,对末位索 ...

  7. REST技术第二步 获取URL中的參数

    获取请求的參数.rest技术相对于servlet来说要方便很多. Servlet我们要获取请求的參数,非常麻烦啊.须要request.getParameter("").假设我们要的 ...

  8. Android学习笔记-tween动画之xml实现

    继上篇tween动画的java实现:http://www.cnblogs.com/fengtengfei/p/3957800.html, 这里我接着介绍一下tween动画的xml实现的方法,   首先 ...

  9. 一条SQL语句面试题:求选修所有课程的学生

    前几天求职面试,有一道SQL题:给出三个表:学生.课程.成绩,求选修了所有课程的学生. 一道看似很简单的问题,把我难住了,我改了又改,涂涂画画,抓耳挠腮,因为试卷没有多少空白位置了,最后只好放弃.心情 ...

  10. CANopen——总线基本知识

    1. 总线标准 2. 获取索引和子索引 2fh,2bh,23h,40h等,是不是对应cs的不同值: 主站1280h的对象字典?1280h-sub2,得到client的COB-ID值: 根据收到的m-& ...