本文内容参考《c 和 指针》。

声明:本博文只为那些能沉得住气,认真研究,探索真知的人参考,浮躁的人请离开,因为看不懂。

感觉以前学c的时候,学的指针真是白学了,今天看到这个内容,困惑后,又让我茅塞顿开。

贴出上篇博文的地址,可供参考:【C】对左值与右值的一些个人思考

说些题外话,由于在查看资料的时候,经常看到博客园中的一些博主的博文,写的也是十分的精彩,于是,尝试注册了一下,准备发几篇博客,可能是由于不熟悉吧,怎么使用怎么别扭。自从CSDN博客改版了以后,写博客也变得很方便了,页面做的越来越精简,重点突出,不得不夸赞下这个平台,真是一个分享知识的殿堂。


进入正题:

有关左值和右值这个话题,我们在上篇博文中也说到了,而有关变量的一点内容,我也写了一篇不太完善的博客:【C】关于变量、地址、指针变量等关系的一点思考,这些都是我最近遇到的问题,知识这个东西,越是琢磨越是有味道,这些都是我以前没有发现的问题,我以前怎么会想到,不起眼的变量,突然让我大脑短路,糊涂了起来。

今天的正题是表达式,其实不管是表达式也好,单独的一个变量也好,它们作为左值和右值使用时,所指示的含义是不一样的,知道这一点,对于变量也会有一个更深刻地理解。


先看一个声明:

char ch = 'a';
char *cp = &ch;

简简单单的两句声明语句,拉开我们今天的讨论。

1、ch

我们都知道ch是一个字符变量,正如声明所言,那里的ch是一个字符变量,且被赋值了一个字符 'a',这里的ch是作为左值使用的,也就是占据着赋值操作符左边的位置。作为左值,它的含义是一个内存的地址,该内存中可以存放字符‘a’;而如果我们将ch作为一个右值使用时,它的含义是什么呢?

很简单,它代表的是该内存中的数值,也即是变量的值,在本例中就是‘a’。

我们在下面可以写这么一行代码:

ch1 = ch;

那么ch1这个字符变量的值就是ch的值,ch作为右值使用,代表的是一个变量值。(当然,ch1要提前声明为一个char型变量)。

综上所述,我们总结:

ch作为右值使用时,代表一个变量值,也即是地址指定的内存中存放的数值。

ch作为左值使用时,代表的是一个地址,ch是为某个地址的一个名字而已,其他的数值可以赋值给ch,也即是存放于该地址指定的内存中去。


2、&ch

这个表达式作为右值代表的是变量ch的地址。

但是它不能作为左值使用,也即是作为左值使用是非法的。为什么呢?

可以这样解释:

当表达式&ch进行求值时,它的结果存放于计算机的什么地方呢?它肯定会位于某个地方,但是你无法知道它存放于何处。这个表达式并未标识任何机器内存的特定位置,所以它不是一个合法的左值。


3、cp

从声明中可以看出cp是一个指向字符变量的指针变量,那么既然它作为一个变量,如果作为左值使用的话,就应该是一个地址了。给它赋值的值应该是一个地址。

如果作为右值使用呢?

作为右值使用,就是cp的值。在这个例子中就是ch的地址值。


4、&cp

这个表达式和第二个情况有点类似,首先,我们知道&操作符不能作为左值使用,因此,这个表达式作为左值就非法的(还可以像&ch那个情况一样解释,这个值的位置并未清晰定义,所以不是为一个合法的左值)。但作为右值的话,这个表达式就是去指针变量cp的地址了。(指针变量也是变量,是变量就有地址。)


5、*cp

往往作为右值更好理解一点,*cp作为右值,也就是cp指向的地址内存中存放的内容,这里易知就是字符‘a’;作为左值就是cp指向的地址,也即是变量ch代表的地址。

对于这个表达式的理解,也可以参考我的博文:【C】对左值与右值的一些个人思考


6、*cp + 1

越来越刺激了,也意味着理解起来越来越有点困难了,加把劲吧。

还是先看作为右值的情况,由于*cp作为右值表示cp指向的地址内存中存放的内容,那么很容易推理出表达式(*cp + 1)表示存放的内容加1,本例中就是字符‘a’+1,不就是字符‘b’吗?

作为左值的情况,我们好好讨论下:

由于*cp作为左值的意思是cp指向的那个地址本身,由于*cp + 1 这个表达式的最终结果的存储位置并未清晰定义,所以它不是一个合法的左值。

也许你可能会问:*cp都可以作为左值,为什么*cp + 1就不能作为左值?

*cp作为左值,指的是cp指向的那个地址本身,本例中指的就是变量ch代表的地址,而*cp + 1,作为左值的话,它的位置并未清晰定义,(这里的清晰定义,例如字符‘a’的位置就定义成了ch,ch代表‘a’存放的内存的位置),没有清晰定义的位置就不能作为左值了。

而操作符+的优先级也证实了,+ 的结果不能作为左值。

这里给出我写的一篇关于优先级的博文:C/C++操作符的优先级和结合性问题浅析


7、 *(cp + 1)

先看作为右值的情况,作为右值cp指的是变量ch的地址,该地址加1,就成了ch之后的那个地址,然后间接访问该地址的内容。

总结上面的一句话就是上述表达式作为右值,访问的是cp指向的位置的下一个位置的值。

作为左值的话,就是cp + 1 这个指针对应的位置本身。

感觉说到这里,有些没看懂的人就会说我胡说八道,前后矛盾了。

这里确实需要说明一下:

指针加法(cp + 1)的结果是个右值,因为它的存储位置并未清晰定义。如果没有间接访问操作符*,这个表达式将不是一个合法的的左值。然而,间接访问跟随指针访问一个特定的位置。这样,*(cp + 1)就可以作为左值使用,尽管cp + 1本身并不是左值。间接访问操作符是少数几个其结果为左值的操作符之一。

上面这段话是书上的原话,我也很无奈呀,可是没办法,这个间接访问操作符就是看的,人家特殊还不行吗?接受吧,骚年!


8、++cp

++和--操作符在指针变量中使用得相当频繁,所以在这种上下文中理解它们是非常重要的。

这里使用的操作符是前缀++,意味着先增加操作数的值,再返回操作后的这个结果。

在这个表达式中,我们增加了指针变量cp的值。表达式的结果是增值后的指针的一份拷贝,因为前缀++先增加它的操作数的值再返回这个结果。所以作为右值而言, 它是ch的地址值加1的地址值。同样由于该位置未清晰定义,故而不能作为左值。

这里声明,前缀--类似。


9、cp++

这个与8作为比较出现,这里使用的是后缀++,后缀++操作符同样增加cp的值,但是它先返回cp的值的一份拷贝再增加cp的值。

因此,作为右值使用的话,它表示cp本身的值,也就是ch的地址值。

那么能不能作为左值呢?

是不能的,因为++的优先级高于=(赋值)操作符,所以,如果cp++作为左值,会对cp进行加1操作后在被赋值,由于cp++代表的地址未有清晰的定义,所以不能作为左值。(cp++作为左值相当于cp+1了,所以是非法的。)


10、*++cp

感觉越来越变态了,我相信贴心的程序员会把上面的表达式写成这个样子*(++cp),(因为++优先级高于*)尽管语法上没有任何差别,但是这样显然更加的清晰。

其实分析了这么多,我想大家如果看懂了上面的东西,这个也不难了,这个肯定可以作为左值使用的。

首先分析作为右值使用的情况,cp作为指针变量的值加1后,也就是ch的地址值加1,得到一个新的地址,加上间接访问符之后,便是取该地址对应的内存中的内容。

作为左值呢?

显然是cp +1指向的那个地址本身,也即是ch的地址加1后的那个新地址本身。

这里再次显现了间接访问操作符将其结果作为左值的强大作用。


11、*cp++

这个和10想比较,由于++的优先级高于*,所以上面的表达式可以这样写,*(cp++),如此以来,便清晰多了。

后缀++的作用不必多言,作为右值的话,固然是先取cp值的拷贝作为结果,之后在进行加1的操作,所以表达式作为右值的结果是取cp指向的地址(即ch代表的地址&ch)的内容(值)。这里也就是字符‘a’。

那作为左值,这个表达式就表示cp指向的地址本身,也即是ch的地址。

综上:使用后缀++操作符所产生的结果与使用前缀++的结果不同,它的右值和左值分别是变量ch的值和ch的内存位置,也即是cp原先所指。

同样,后缀++操作符在周围的表达式中使用其原先操作数的值。

间接访问操作符和后缀++的组合常常令人误解。优先级表格显示后缀++操作符的优先级高于*操作符,但表达式的结果看上去像是先执行间接访问操作。

事实上,这里涉及3个步骤:

(1)++操作符产生cp的一份拷贝;

(2)然后++操作符增加cp的值;

(3)最后,在cp的拷贝上执行间接访问操作。


12、++*cp

说实话,看到这样的表达式未免有些愤怒,这里还好,如果用到程序中去,看起来未免会造成心理负担。所以还是要适当的使用括号来表达自己的意思。

由于++ 和* 的结合性都是从右到左(R-L),所以,先运算*cp,之后在++。

所以上面的表达式可以写成这样:++(*cp);

然后我们分析,作为右值的情况,*cp的意思是去cp指向的地址的值,这里为‘a’,然后执行++操作,也就是加1,那么表达式的值为字符‘b’。

那能不能作为左值呢?

恐怕都说腻了,*cp作为左值的意思是cp指向的那个地址本身,也即是ch代表的地址,在进行++操作,也就是地址加1,得到的新地址未清晰定义,所以不能作为左值。

C/C++操作符的优先级和结合性问题浅析

这篇博文的优先级表格,++ 操作符的结果不能作为左值,也算是作为一种验证了。


问题的所有情况基本上都说的差不多了。事实上可以到此为止了。但是呢?下面还有最后三种终极大变态情况,它们在实际应用中基本上不出现,但是正如《c和指针》这本书上说的一样,对它有一个透彻地理解有助于提高你的技能。

我就提高下自己的技能吧。

13、(*cp)++

简单地说,作为右值,就是先取cp指向的地址的值(本文中为‘a’),然后++,这样得到的就是‘b’。

作为左值,显然是非法的,因为最后执行的是++操作。或者说,*cp作为左值,代表cp指向的地址本身,在进行++,得到的是一个未清晰定义的地址,不能作为左值。


14、++*++cp

看到这个表达式,我第一件事情是喝了口水让自己冷静下来,别冲动,别冲动,所以也请大家不要冲动。

冷静下来之后,可以用括号来划分下层次,得知可以表示成等价形式++(*(++cp))。

由于最后进行的是++操作,所以不能作为左值。

下面看作为右值的情况,先进行++cp操作,是cp的地址加1得到的一个新地址(&ch + 1),之后进行间接访问操作,取新地址对应的内存存储的的值,再次进行++,是对这个新地址对应的内存中的值加1.


15、++*cp++

这个更需要冷静!真的是最后一个了。

*的优先级低于++,所以加上第一个括号: ++*(cp++)

结合性都是自右向左,第二个括号:++(*(cp++))

同样,不能作为左值使用,因为最后操作的是++。

那么作为右值的话,cp++中使用的是后缀++,故参与表达式运算的是cp的一份拷贝,之后再进行cp加1操作,*(cp++)取cp指向的地址的内容,即‘a’,之后对‘a’加1得到‘b’。

【C】对指针表达式的个人总结与思考的更多相关文章

  1. C指针决心 ------ 指针表达式

    本文是自己学习所做笔记.欢迎转载.但请注明出处:http://blog.csdn.net/jesson20121020 所谓的指针表达式是指一个表达式.其结果是一个指针. 例1. int  a,b; ...

  2. C#使用指针表达式

    如何:获取指针变量的值 使用指针间接运算符可获取位于指针所指向的位置的变量. 表达式采用下面的形式,其中, p 是指针类型: *p; 不能对除指针类型以外的任何类型的表达式使用一元间接寻址运算符. 此 ...

  3. <c和指针>学习笔记3之操作符,表达式与指针

    1 操作符 (1)移位操作符 左移<<:值最左边的几位丢弃,右边多出来的几个空位用0补齐 01101101 011(丢弃)01101000(后面三位补0) 右移>>: 算术左移 ...

  4. c/c++ 函数、常量、指针和数组的关系梳理

    压力才有动力,15年中旬就要准备实习,学习复习学习复习学习复习学习复习……无限循环中,好记性不如烂笔头……从数组开始,为主干. c 的array由一系列的类型相同的元素构成,数组声明包括数组元素个数和 ...

  5. 《C与指针》第八章练习

    本章问题 1.根据下面给出的声明和数据,对每个表达式进行求值并写出它的值.在对每个表达式进行求值时使用原先给出的值(也就是说,某个表达式的结果不影响后面的表达式).假定ints数组在内存中的起始位置是 ...

  6. C/C++学习笔记----指针的理解

    指针是C/C++编程中的重要概念之一,也是最容易产生困惑并导致程序出错的问题之一.利用指针编程可以表示各种数据结构,通过指针可使用主调函数和被调函数之间共享变量或数据结构,便于实现双向数据通讯:指针能 ...

  7. 《C与指针》第六章练习

    本章问题 1.如果一个值的类型无法简单的通过观察它的位模式来判断,那么机器是如何知道应该怎样对这个值进行操纵的? answer:The machine doesn't make this determ ...

  8. 《C与指针》第一章练习

    本章例程 程序1.1 重排字符 #include <stdio.h> #include <stdlib.h> #include <string.h> #define ...

  9. c/c++: c++继承 内存分布 虚表 虚指针 (转)

    http://www.cnblogs.com/DylanWind/archive/2009/01/12/1373919.html 前部分原创,转载请注明出处,谢谢! class Base  {  pu ...

随机推荐

  1. 内置的HTTP服务器【Modern PHP】

    目录 启动服务器 配置服务器 路由器脚本 判断是否为内置的服务器 PHP5.4.0起,PHP内置了Web服务器.对本地开发是个极好的工具,便捷,无需安装WAMP.XAMP或大新那个web服务器,就能在 ...

  2. 沉淀,再出发:python爬虫的再次思考

    沉淀,再出发:python爬虫的再次思考 一.前言    之前笔者就写过python爬虫的相关文档,不过当时因为知识所限,理解和掌握的东西都非常的少,并且使用更多的是python2.x的版本的功能,现 ...

  3. 解决Android sdk manager无法访问google服务器的问题

    开发Android应用,使用最广泛的开发工具应该就是ADT了,但是ADT默认只带了Android 4.3(API 18),如果需要安装其他版本的SDK,就需要启动Android SDK Manager ...

  4. 深入理解 iOS Rendering Process

    本文将从 OpenGL 的角度结合 Apple 官方给出的部分资料,介绍 iOS Rendering Process 的概念及其整个底层渲染管道的各个流程. 相信在理解了 iOS Rendering ...

  5. ESP和EBP 栈顶指针和栈底指针

    http://blog.csdn.net/hutao1101175783/article/details/40128587 (1)ESP:栈指针寄存器(extended stack pointer), ...

  6. 23、springboot与缓存(1)

    一.JSR107 Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry. 1.CachingPro ...

  7. python3之安装mysql问题

    python3是不能通过pip install mysql或pipinstall mysqldb这样的形式来安装mysql. 只能 pip install PyMySQL 至于如何在文件中引用? 答曰 ...

  8. 将本地已经存在的非git项目提交到github上的空仓库

    一.本地项目执行操作 1.在本地项目目录下初始化git仓库 git init 2.将本地项目下工作区的所有文件添加到git版本库的暂存区中 git add . (可以创建.gitignore文件忽略不 ...

  9. CC2540 低功耗串口, POWER_SAVING 模式 下 串口 0 的使用

    低功耗 模式 下 使用 串口 ,  因为 PM2 或者 PM3 状态下  32M晶振 是不工作 的,根据手册得知没有32M晶振, 串口是不能工作的,但是可以使用 外部中断,因此,我把  串口的接收引脚 ...

  10. [原创]HBase学习笔记(2)- 基本操作

    1.使用hbase shell连接hbase 2.输入help可以查看帮助 3.输入list查看当前hbase中的所有表 4.使用create创建表test 其中test是表名,cf是列族.该表只创建 ...