zoukankan      html  css  js  c++  java
  • (1)指针、引用、const限定符

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

    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是引用的对象),也没有“引用绑定的对象可以改变”的意思。

    wuduojia
  • 相关阅读:
    android判断程序是否前台显示---及判断屏幕是否是亮的----附赠BAIDU定位的工具类实现代码
    Lock and synchronized 摘自官网, 摘录下来方便笔记
    SQLite 基础
    如何stop,suspend一个线程 在deprecated的情况下
    HashMap 的遍历 for:each 和EntrySet两种方法
    ListView图片异步加载与缓存策略
    android debug学习笔记 monkeyrunner
    回溯法和DFS leetcode Combination Sum
    leetcode Count and Say
    leetcode Jump Game I II 待续 贪心看不懂啊!!!!
  • 原文地址:https://www.cnblogs.com/wuduojia/p/7511808.html
Copyright © 2011-2022 走看看