前言:工欲善其事,必先利其器

两种资料

学习编程语言, 有两类资料可以让人"高潮".

​ 一类是针对初学者而设计的入门类书籍, 这种书总是适时地结合生动的生活实例, 来让啥都不懂的萌新理解一些基本的和关键的东西, 达到拨云见日的效果. 为将来的进一步学习培养出良好的兴趣和打下坚实的基础. 最具代表性的就是 headfirst 系列丛书.

​ 而另一类资料, 便是标准文献了. 它就像博学的导师或者修仙小说里的随身老爷爷, 能够完美地解答你的任何疑惑(就算有解答不了的问题, 那也是暂时的, 因为标准文献本身也是不断改进和迭代的).

​ 这边作者假设读者都有一定的C基础,不是啥都不懂的萌新, 但是对于左值和右值的概念仍存有疑惑的朋友, 另外作者水平有限, 如有错误和瑕疵, 欢迎各位朋友指正.


参考资料及其使用说明

参考资料

​ 本文的参考资料是C11标准文献草案(N1570), 是免费且几乎等同于C11标准文献的版本.

  • 外网版C11标准文献资料(需翻墙)

    html版

    pdf版

  • 笔者提供的国内版(笔者自建站)

    html版

  • 笔者所提供的本地下载(7z压缩包, 内含pdf与html版)

    本地下载

本文的链接及资料使用说明

  • 本文链接说明

    本文的链接部分,均是国内html版的链接

  • 本地下载的资料说明

    • c11标准文献不仅每一个章节都有编号, 且每一个自然段都有编号,方便定位

    • c11标准的html版: 可以用锚点直接定位到对应章节, 自然段 以及 注解

      • 锚点: 形如 #6.3.1.2p3 的东西, 出现在网址栏的最后, 用于定位到网页中的位置(滚轮会自动滚到对应内容处)

      • c11标准html版的锚点构成说明:

        示例1: #6.3.1.2p3

        • 6.3.1.2是具体的章节编号: 第6章第3部分1小节第2节
        • p3是对应的自然段编号: p3代表第3自然段

        示例2: #note99

        • note99是对应的注解编号: note99代表第99个注解
      • 应用说明:

        • 查看国内版c11标准的第3章第2部分7小节第4自然段,可以直接输入以下网址: peterzhang.cool:3000/pdfs/c11.html#3.2.7p4,然后回车
        • 查看本地下载的c11的html版本也可以打开c11.html之后,在网址后面加上#3.2.7p4,然后按回车即可

官方对于左值和右值的定义

​ 可见, 左值右值的概念来自赋值表达式, =号左边的为左值(可修改的左值), 它代表(定位)了一个可用于存放数据的存储空间; 而右值通常被理解为 "表达式的值"(value of an expression).

实际使用时的疑问

​ 那么到底哪些是左值, 哪些又属于右值? 什么情况下属于左值, 什么情况下属于右值呢?


左值的涵盖范围

  • 变量名

  • 指针变量

  • 一些运算符的运算结果:

    • * -- 取内容运算符
    • [] -- 数组下标运算符
    • (type-name){initialize-list} -- 复合字面量
    • . (只有左操作数为左值时,结果才为左值)
    • ->(无论左操作数为左值还是右值,结果均为左值)

    举例说明:

    • a是数组名,绝大部分情况下属于指针值(见后续部分),是右值
    • a[1]属于运算符[]的结果, 属于左值, 可以放在等号左边进行赋值操作.

重要概念: 左值转化(lvalue conversion)

#6.3.2.1p2: 满足以下条件的左值会被转化成对应的存储空间(数据对象)中所存储的值,并且不再是一个左值, 这一过程被称为 左值转化

  • 不是 sizeof, _Alignof, &, ++, -- 运算符的操作数

  • 不是 . 或 赋值运算符的左操作数

  • 该左值不是数组类型(数组类型的左值按其他规定进行转化)

    • 一维数组: 不是数组名,但可以是数组元素

    • 多维数组: 不是任意N维(N>1)的数组名或数组元素,但可以是一维的数组元素

      (也就是说: 二维数组arr[][]中, arr[1]仍旧代表一个数组, 等同于一个数组名,不满足左值不是数组类型的条件)


左值与指针

概念上的区别

  • 左值: 可以放在赋值号的左边, 与一个存储单元(数据对象)对应, 代表了可直接获取和设置该单元内容的途径. (左值就像是一个已经拨通且未挂断的电话)
  • 指针值: 某一数据的存储位置的信息. (指针值就像是一个电话号码)

通过左值, 你可以通过它直接获取和设置存储单元(数据对象)中的内容, 就像你可以直接问已拨通电话的另一头问题或告诉另一头一些信息; 而指针值, 就像一个电话号码, 想要像左值那样获取或设置内容, 必须先要 "按照号码拨打电话", 这一步骤通常由取内容运算符 * 完成. 如果我们用另一个变量保存这个 "电话号码", 这个变量就成了 "指针变量".

注意: 指针变量是一个变量, 它是左值, 而指针值并不是左值.

举例: (我们把其他人当作是一个存储空间,而你扮演主程序)

你正在跟小张通电话 -- 左值 <==> int a;

你手里有小张的电话号码 -- 指针值 <==> &a;

你通过给小刘打电话,获取了小张的电话号码,然后再给小张打电话告诉他一些事 -- 利用指针变量 <==> int *p = &a; *(p) = 314;

左值与指针值的互相转化

我们声明的变量名是一类天然的左值, 它就像是我们和朋友直接面对面说话(或者一通已打通的电话); 而有时候,我们需要交谈的对象并不在我们身边, 这时候就需要我们自己去拨打电话.

  • 将指针值转化为对应的左值: 取内容运算符*
  • 获取某一左值的指针值: 取地址运算符&

指针值的构成

补充知识:存储单元的地址编排

  • 地址编号是基于字节的: 一个字节对应一个地址编号, 地址值(指针值)只能指向单个字节

  • 除了char外,C中的数据类型是多字节

  • 读取多字节数据的策略:

    • 地址值(指针值)指向存储单元的第一个字节

    • 定义一个取值范围, 说明取得数据的长度

指针值的构成

  • 指针值/地址值: 指向存储空间的起始字节

    • 指针值的类型是无符号的多字节数值
    • 指针的类型并不影响指针值的sizeof大小
  • 指针类型: 规定利用指针一次读取/设置字节的范围
    • 一次读取或设置: 同时操作包含起始字节在内的N个字节(N由指针类型确定)
    • 指针变量增加或减少1: 地址值/指针指增加或减少N

图示:

测试代码: test.c

#include <stdio.h>
#include <stdlib.h> int main(void)
{
short int test = 314;
int *pInt = &test;
float *pFloat;
double *pDouble;
long double *pLongDouble; printf("The sizeof short int is %d\n",sizeof(short int)); //2 //指针类型(地址类型)是一个独立的存储类型,占用的内存大小相同
printf("The sizeof pInt is %d\n",sizeof(pInt)); //4
printf("The sizeof pFloat is %d\n",sizeof(pFloat)); //4
printf("The sizeof pDouble is %d\n",sizeof(pDouble)); //4
printf("The sizeof pLongDouble is %d\n",sizeof(pLongDouble)); //4 //指针类型确定读取的字节范围
printf("The address of test is %p\n",pInt);
printf("Input the address above and use it without a type bounded:\n");
unsigned long long p;
scanf("%x",&p);
printf("The value of p is %lx\n",p);
printf("The value of *(short int *)p is %d\n",*(short int *)p); //314(10)
printf("The value of *(char *)p is %d\n",*(char *)p); //只读取后8位,所以是58(10) //指针变量+1,指针指/地址值的变化?
short int *pTest = &test;
printf("The address of test is %p\n",pTest);
pTest++;
printf("The address of test now is %p\n",pTest); getchar();
return 0;
}

控制台输出:

数组名与数组下标运算

#6.3.2.1p3: 满足下列条件的数组类型值(通常是数组名)会被转换为一个指向该数组首个元素的首个字节的指针值(注意,不是指针变量而是指针值):

  • 数组名不是sizeof或&的操作数
  • 不是用来初始化一个数组的数组字面量

因此:

  • 数组名本身是属于左值的, 但是这并没有什么卵用

  • 因为绝大多数情况下(包括位于赋值号左边的时候),数组名会被转换为指针值(不再是左值)

  • 数组名经过下标[]运算或*运算符却会变成左值,代表数组内某一元素,可以用于赋值


运算符归纳表格及实例说明

各种运算符运算结果左右值类型总结表

实例分析

  • 复合字面量(compound literial)

    #include <stdio.h>
    #include <stdlib.h> int main(void)
    {
    int p = ((int){314})++; //works just fine
    printf("p is %d\n",p); //314 //int *p = ((int [2]){314,110})++; //error: lvalue required as increment operand getchar();
    return 0;
    }

    分析:

    • int p = ((int){314})++;

      复合字面量(int){314}生成一个未命名的左值(其值为314)

      对该左值应用后缀形式的++运算符,生成一个右值(314)

      将该右值赋值给变量p

    • int *p = ((int [2]){314,110})++; //报错语句

      复合字面量(int [2]){314,110}生成一个未命名的数组左值

      数组左值经过转化,变成指向该数组第一个元素的指针值(右值)

      对该指针值应用后缀++运算符报错(++运算符的操作数必须是左值)

  • 结构体相关运算符(*与->)

    #include <stdio.h>
    #include <stdlib.h> //声明结构体s
    struct s { double i; }; //声明联合体g
    union {
    struct {
    int f1;
    struct s f2;
    } u1;
    struct {
    struct s f3;
    int f4;
    } u2;
    } g; struct s f(void){ //返回结构体s的函数
    return g.u1.f2; //返回g.u1.f2
    } int main(void)
    {
    //测试: 结构体变量
    struct s varible = {3.1415};
    varible.i++;
    printf("varible.i.i is %f\n",varible.i); //4.1415 //测试: 结构体返回值函数
    struct s f(void);
    //f().i = 20.0; //error: lvalue required as left operand of assignment getchar();
    return 0;
    }

    分析:

    • varible.i++;语句工作正常: 说明其执行结果为左值
    • f().i = 20.0;语句报错: 说明f().i不是左值
      • 函数调用的返回值是右值(尽管它返回的是文件域的联合体变量的成员的内容)
      • 右值.i,根据C11标准的规定,其执行结果也是右值,因此报错

C笔记-左值与右值的更多相关文章

  1. c++左值和右值

    c++编程中如果出现把一个函数的返回值.强行转化后的对象 作为函数的参数传进去时,编译器会报错的情况.这时候就该注意了,你需要把该函数的参数类型前加上const修饰. 原因在于c++的左值和右值有所区 ...

  2. c++ 左值 和 右值

    什么是lvalue, 什么是rvalue? lvalue: 具有存储性质的对象,即lvalue对象,是指要实际占用内存空间.有内存地址的那些实体对象,例如:变量(variables).函数.函数指针等 ...

  3. C++中的左值与右值(二)

    以前以为自己把左值和右值已经弄清楚了,果然发现自己还是太年轻了,下面的这些东西是自己通过在网上拾人牙慧,加上自己的理解写的. 1. 2. 怎么区分左值和右值:知乎大神@顾露的回答. 3. 我们不能直接 ...

  4. C++中的左值和右值

    左值和右值的定义 在C++中,能够放到赋值操作符=左边的是左值,能够放到赋值操作符右边的是右值.有些变量既能够当左值又能够当右值.进一步来讲,左值为Lvalue,事实上L代表Location,表示在内 ...

  5. C++11 左值与右值

    概念 左值:表达式结束后依然存在的对象 右值:表达式结束后就不存在的临时对象 2.如何判断左值和右值 能不能对表达式取地址,如果能,就是左值,否则就是右值 3.对下面的语句进行区分 int a = 3 ...

  6. c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用

    为什么要用移动语义 先看看下面的代码 // rvalue_reference.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #includ ...

  7. C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward

    这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运行测试过的,希望能用这些帮助理 ...

  8. C++11 左值、右值、右值引用详解

    C++11 左值.右值.右值引用详解 左值.右值 在C++11中所有的值必属于左值.右值两者之一,右值又可以细分为纯右值.将亡值. 在C++11中可以取地址的.有名字的就是左值,反之,不能取地址的.没 ...

  9. C++11 左值、右值、右值引用

    左值.右值 在C++11中所有的值必属于左值.右值两者之一,右值又可以细分为纯右值.将亡值.在C++11中可以取地址的.有名字的就是左值,反之,不能取地址的.没有名字的就是右值(将亡值或纯右值).举个 ...

  10. C++ 左值与右值 右值引用 引用折叠 =&gt; 完美转发

    左值与右值 什么是左值?什么是右值? 在C++里没有明确定义.看了几个版本,有名字的是左值,没名字的是右值.能被&取地址的是左值,不能被&取地址的是右值.而且左值与右值可以发生转换. ...

随机推荐

  1. Cocos2d-x 3.x游戏开发之旅

    Cocos2d-x 3.x游戏开发之旅 钟迪龙 著   ISBN 978-7-121-24276-2 2014年10月出版 定价:79.00元 516页 16开 内容提要 <Cocos2d-x ...

  2. Apache设置页面认证(原创贴-转载请注明出处)

    ================写在前面的话============== 1.本试验使用的apache版本是2.4.24 场景描述:网站后台管理页面比较重要,不应该任何人都让访问,所以对后台页面做认证 ...

  3. 第六章 - 图像变换 - 图像拉伸、收缩、扭曲、旋转[1] - 仿射变换(cvWarpAffine)

    拉伸.收缩.扭曲.旋转是图像的几何变换,在三维视觉技术中大量应用到这些变换,又分为仿射变换和透视变换.仿射变换通常用单应性建模,利用cvWarpAffine解决密集映射,用cvTransform解决稀 ...

  4. 学完STM32开发板,就选4412开发板让你有目标的学习嵌入式开发

    600余页用户使用手册 linux实验手册(资料不断更新)100期配套零基础高清视频教程 轻松入门 (资料不断更新)2000人售后认证群 在线支持 售后无忧 源码全开源  原厂技术资料经典学习书籍推荐 ...

  5. Spring事务管理使用

    发现问题 最近,碰到一个问题,再用spring实现事务管理的时候,发现不起作用,在出异常时,并不会回滚数据库操作. 我想实现的功能如下: @Transactional(isolation=Isolat ...

  6. 如何利用keytool查看一个apk的签名

  7. 【NOIP2012】 疫情控制

    [NOIP2012] 疫情控制 标签: 倍增 贪心 二分答案 NOIP Description H 国有 n 个城市,这 n 个城市用 n-1 条双向道路相互连通构成一棵树, 1 号城市是首都, 也是 ...

  8. jeecg入门操作—表单界面

    一.搭建jeecg开发环境 参考环境搭建步骤 https://www.cnblogs.com/dyh004/p/10687633.html 二.创建用户数据库表: 登录上jeecg平台,点击在线开发- ...

  9. ansible 变量详解

    定义变量的方法 1. 主机变量,在hosts文件中设置变量, [atlanta] host1 http_port= maxRequestsPerChild= host2 http_port= maxR ...

  10. Unix下5种I/O模型

    Unix下I/O模型主要分为5种: (1)阻塞式I/O (2)非阻塞式I/O (3)I/O复用(select和poll) (4)信号驱动式I/O (5)异步I/O 1.阻塞式I/O模型 unix基本的 ...