C笔记-左值与右值
前言:工欲善其事,必先利其器
两种资料
学习编程语言, 有两类资料可以让人"高潮".
一类是针对初学者而设计的入门类书籍, 这种书总是适时地结合生动的生活实例, 来让啥都不懂的萌新理解一些基本的和关键的东西, 达到拨云见日的效果. 为将来的进一步学习培养出良好的兴趣和打下坚实的基础. 最具代表性的就是 headfirst 系列丛书.
而另一类资料, 便是标准文献了. 它就像博学的导师或者修仙小说里的随身老爷爷, 能够完美地解答你的任何疑惑(就算有解答不了的问题, 那也是暂时的, 因为标准文献本身也是不断改进和迭代的).
这边作者假设读者都有一定的C基础,不是啥都不懂的萌新, 但是对于左值和右值的概念仍存有疑惑的朋友, 另外作者水平有限, 如有错误和瑕疵, 欢迎各位朋友指正.
参考资料及其使用说明
参考资料
本文的参考资料是C11标准文献草案(N1570), 是免费且几乎等同于C11标准文献的版本.
本文的链接及资料使用说明
本文链接说明
本文的链接部分,均是国内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笔记-左值与右值的更多相关文章
- c++左值和右值
c++编程中如果出现把一个函数的返回值.强行转化后的对象 作为函数的参数传进去时,编译器会报错的情况.这时候就该注意了,你需要把该函数的参数类型前加上const修饰. 原因在于c++的左值和右值有所区 ...
- c++ 左值 和 右值
什么是lvalue, 什么是rvalue? lvalue: 具有存储性质的对象,即lvalue对象,是指要实际占用内存空间.有内存地址的那些实体对象,例如:变量(variables).函数.函数指针等 ...
- C++中的左值与右值(二)
以前以为自己把左值和右值已经弄清楚了,果然发现自己还是太年轻了,下面的这些东西是自己通过在网上拾人牙慧,加上自己的理解写的. 1. 2. 怎么区分左值和右值:知乎大神@顾露的回答. 3. 我们不能直接 ...
- C++中的左值和右值
左值和右值的定义 在C++中,能够放到赋值操作符=左边的是左值,能够放到赋值操作符右边的是右值.有些变量既能够当左值又能够当右值.进一步来讲,左值为Lvalue,事实上L代表Location,表示在内 ...
- C++11 左值与右值
概念 左值:表达式结束后依然存在的对象 右值:表达式结束后就不存在的临时对象 2.如何判断左值和右值 能不能对表达式取地址,如果能,就是左值,否则就是右值 3.对下面的语句进行区分 int a = 3 ...
- c++ 11 移动语义、std::move 左值、右值、将亡值、纯右值、右值引用
为什么要用移动语义 先看看下面的代码 // rvalue_reference.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #includ ...
- C++ 11 左值,右值,左值引用,右值引用,std::move, std::foward
这篇文章要介绍的内容和标题一致,关于C++ 11中的这几个特性网上介绍的文章很多,看了一些之后想把几个比较关键的点总结记录一下,文章比较长.给出了很多代码示例,都是编译运行测试过的,希望能用这些帮助理 ...
- C++11 左值、右值、右值引用详解
C++11 左值.右值.右值引用详解 左值.右值 在C++11中所有的值必属于左值.右值两者之一,右值又可以细分为纯右值.将亡值. 在C++11中可以取地址的.有名字的就是左值,反之,不能取地址的.没 ...
- C++11 左值、右值、右值引用
左值.右值 在C++11中所有的值必属于左值.右值两者之一,右值又可以细分为纯右值.将亡值.在C++11中可以取地址的.有名字的就是左值,反之,不能取地址的.没有名字的就是右值(将亡值或纯右值).举个 ...
随机推荐
- 用Kubernetes部署Springboot或Nginx,也就一个文件的事
1 前言 经过<Maven一键部署Springboot到Docker仓库,为自动化做准备>,Springboot的Docker镜像已经准备好,也能在Docker上成功运行了,是时候放上Ku ...
- C#远程连接代码
/// <summary> /// 服务连接配置类(验证服务账号是否正确) /// </summary> /// <param name="remoteHost ...
- SQLserver-MySQL的区别和用法
对于程序开发人员而言,目前使用最流行的两种后台数据库即为MySQL and SQL Server.这两者最基本的相似之处在于数据存储和属于查询系统.你可以使用SQL来访问这两种数据库的数据,因为它们都 ...
- 接口测试基础——session认证和token认证
总算是把这个过程理清楚了,现在我们的思路是:what?why?How?,实际上这些个机制产生的内部逻辑是从下至上的的:遇到问题了,想办法解决,总结归纳并取名.从解决一些小问题开始生长,不断打补丁直至完 ...
- flutter学习01-flutter起步安装配置(window, vscode开发)
从零开始配置flutter环境,如果直接去看官方文档配置的话,太过复杂,其实正式没有那么多步骤,记录一下: 1.首先,前往下面这个网站,下载flutter sdk https://flutter.d ...
- Java加密与安全
数据安全 什么是数据安全?假如Bob要给Alice发送一封邮件,在发送邮件的过程中,黑客可能会窃取到邮件的内容,所以我们需要防窃听:黑客也有可能会篡改邮件的内容,所以Alice必须要有能有去识别邮 ...
- 安装visual stdio 2017后依然报错:Unable to find vcvarsall.bat
安装visual stdio 2017后依然报错:Unable to find vcvarsall.bat 解决办法:更新setuptools 原文章:https://blog.csdn.net/wl ...
- 概率图模型(CPD)(二)
CPD是conditional probability distribution的缩写,翻译成中文叫做 条件概率分布.在概率图中,条件概率分布是一个非常重要的概念.因为概率图研究的是随机变量之间的练习 ...
- P1433 吃奶酪(洛谷)状压dp解法
嗯?这题竟然是个绿题. 这个题真的不(很)难,我们只是不会计算2点之间的距离,他还给出了公式,这个就有点…… 我们直接套公式去求出需要的值,然后普通的状压dp就可以了. 是的状压dp. 这个题的数据加 ...
- 【软件安装】CentOS7安装MariaDb(mysql_替代品安装)
1.背景 Maria Db是流行的跨平台MySQL数据库管理系统的分支,被认为是MySQL 的完全替代品.Maria Db是由Sun在Sun Micro systems合并期间被Oracle收购后,于 ...