左值和右值

  • c++的表达式不是右值就是左值。
  • 一个左值表达式的求值结果是一个对象或一个函数,然而以常量对象为代表的某些左值实际上不能作为赋值语句的左侧运算对象。此外,虽然某些表达式的求值结果是对象,但它们是右值而非左值。
  • 当一个对象被用作右值时,用的是对象的值(内容),当对象被用作左值的时候,用的是对象的身份(在内存中的位置)
  • 一个重要的原则是在需要右值的地方可以用左值来代替,但是不能把右值当作左值(也就是位置)使用。当一个左值被当作右值使用时,实际使用的是它的内容(值)。
    • 赋值运算符需要一个左值作为其左侧运算对象,得到的结果也仍然是一个左值。
    • 取地址符作用于一个左值运算对象,返回一个指向该运算对象的指针,这个指针是一个右值。
    • 内置解引用运算符、下标运算符、迭代器解引用运算符、string和vector的下标运算符的求值结果都是左值。
    • 内置类型和迭代器的递增递减运算符作用于左值运算对象,其前置版本所得的结果也是左值。
  • 使用关键字decltype时,左值和右值也各有不同。如果表达式的求值结果是左值,decltype作用于该表达式(不是变量)得到一个引用类型。
    • 假定p的类型是int*,因为解引用运算符生成左值,所以decltype(*p)的结果是int&。
    • 另一方面,因为取地址运算符生成右值,所以decltype(&p)的结果是int**,也就是说,结果是一个指向整型指针的指针。

类型转换

  • 在c++语言中,某些类型之间有关联。如果两种类型有关联,那么当程序需要其中一种类型的运算对象时,可以用另一种关联类型的对象或值来替代。如果两种类型可以相互转换,那么它们就是关联的。
int ival = 3.54 + 3;//编译器可能会报错损失了精度
  • 加法的两个运算对象不同,c++语言不会直接将两个不同类型的值相加,而是实现根据类型转换规则设法将运算对象的类型统一后再求值。上述的类型转换是自动执行的,因此它们被称作隐式类型转换。
  • 算术类型之间的隐式类型转换被设计的尽可能的避免损失精度。很多时候,如果表达式中既有整数类型的运算对象,又有浮点数类型的运算对象,整数会转换成浮点型。在上例中,3转换成double类型,然后执行浮点数加法,所得的结果类型是double
  • 接下来完成初始化,在初始化过程中,因为被初始化的对象的类型无法改变,所以初始值被转换成该对象的类型。在上例中,加法得到的double类型的结果转换成int类型,这个值被用来初始化ival。由double向int转换时忽略掉了小数部分,在上述表达式中,数值6被赋给了ival。

何时发生隐式类型转换

  • 在大多数情况下,比int类型小的整型值首先提升为较大的整数类型。
  • 在条件中,非布尔值转换成布尔类型。
  • 初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型
  • 如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型。
  • 函数调用时也会发生类型转换。

算术转换

  • 算术转换的含义是把一种算术类型转换成另一种算术类型。

    • 算术转换的规则定义了一套类型转换的层次,其中运算符的运算对象将转换成最宽的类型。例如,如果一个运算对象的类型是long double,那么不论另外一个运算对象的类型是什么都会转换成long double。
    • 当表达式中既有浮点类型又有整数类型时,整数类型将转换成相应的浮点类型。

整型提升

  • 整型提升负则把小整数类型换成较大的整数类型。对于bool、char、signed char、unsigned char、short和unsigned short等类型来说,只要它们所有可能的值都存在int里,它们就会提升成int类型;否则,提升成unsigned int类型。
  • 较大的char类型提升成int、unsigned int、long、unsigned long、long long和unsigned long long中最小的一种类型,前提是转换后的类型要能容纳原类型所有可能的值。

无符号类型的运算对象

  • 如果某个运算符的运算对象类型不一致,这些运算对象将转换成同一种类型。但是如果某个运算对象的类型是无符号类型,那么转换的结果就要依赖于机器中各个整数类型的相对大小了。
  • 首先执行整型提升。如果结果的类型匹配,无需进行进一步的转换。如果两个运算对象的类型要么都是带符号的、要么都是无符号的,则小类型运算对象转换为较大的类型。
  • 如果一个运算对象是无符号类型、另外一个运算对象是带符号类型,而且其中的无符号类型不小于带符号类型,那么带符号的运算对象转换成无符号的。例如,假设两个类型分别是unsigned int 和int,则int类型的运算对象转换成unsigned int类型。需要注意的是,如果int类型为负值,它的转换将出现问题。
  • 如果带符号类型大于无符号类型,此时的转换结果依赖于机器。如果无符号类型的所有值都能存在该带符号类型中,则无符号类型的运算对象转换成带符号类型。如果不能,那么带符号类型的运算对象转换成无符号类型。例如,如果两个运算对象的类型分别为long和unsigned int,并且int和long的大小相同,则long类型的运算对象转换成unsigned int类型;如果long类型占用空间比int更多,则unsigned int类型的运算对象转换成long。

其它隐式类型转换

  • 数组转换成指针:在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针
int ia[10];//含有10个整数的数组
int *ip = ia;//ia转换成指向数组首元素的指针
  • 当数组被用作decltype关键字的参数,或者作为取地址符&、sizeof及typeid等运算符的运算对象时,上述转换不会发生。

  • 如果用一个引用来初始化数组,上述转换也不会发生。

  • 指针的转换:c++还规定了几种其他的指针转换方式,包括常量整数值0或者字面值nullptr能转换成任意指针类型;指向任意非常量的指针能转换成void*;指向任意对象的指针能转换成const void*.

  • 在有继承关系的类型间还有另外一种指针转换的方式。

  • 转换成布尔类型:存在一种从算术类型或指针类型向布尔类型自动转换的机制。如果指针或算术类型的值为0,转换结果为false;否则转换结果为true

  • 转换成常量:允许将指向非常量类型的指针转换成指向相应的常量类型的指针,对于引用也是这样。也就是说,如果T是一种类型,就能将指向T的指针或引用分别转换成指向const T的指针或引用。

int i;
const int &j = i;//非常量转换成const int的引用
const int *p = &i;//非常量的地址转换成const的地址
int &r = j, *q = p;//错误:不允许const转换成非常量
  • 相反的转换并不存在,因为它试图删除掉底层const

  • 类类型定义的转换:类类型能定义由编译器自动执行的转换,不过编译器每次只能执行一种类类型的转换。如果同时提出多个转换请求,这些请求将被拒绝。

string s, t = "a value";//字符串字面值转换成string类型
while(cin >> s)//while的条件部分把cin转换成布尔值

显式转换

  • 有时希望显式地将对象强制转换成另外一种类型,这种方法称作强制类型转换。

命名的强制类型转换

  • 一个命名的强制类型转换具有如下形式
cast-name<type>(expression);
  • 其中,type是转换的目标类型,而expression是要转换的值。如果type是引用类型,则结果是左值。
  • cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一种。dynamic_cast支持运行时类型识别,cast_name指定了执行的是哪种转换。

ststic_cast

  • 任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast.
int i,j;
double slop = static_cast<double>(j) / i;//进行强制类型转换以便执行浮点数除法
  • 当需要把一个较大的算术类型赋值给较小的类型时,static_cast非常有用。此时,强制类型转换告诉编译器,我们知道且不在乎潜在的精度损失。一般来说,如果编译器发现了一个较大的算术类型试图赋值给较小的类型,就会给出警告信息;但是当执行了显式类型转换后,警告信息就会被关闭了。
  • static_cast对于编译器无法自动执行的类型转换非常有用。例如,可以使用static_cast找回存在于void*指针中的值。
void *p = &d;//正确:任何非常量对象的地址都能存入void*
double *dp = static_cast<double*>(p);//正确:将void*转换回初始的指针类型
  • 把指针存放在void*中,且使用static_cast将其强制转换回原来的类型时,应该确保指针的值保持不变。也就是说,强制类型转换的原地址相等,因此必需确保转换后所得的类型就是指针所指的类型。类型一旦不符,将产生未定义的后果。

const_cast

  • const_cast只能改变运算对象的底层const
const char *pc;
char *p = const_cast<char*>(pc);//正确:但是通过p写值是未定义的行为
  • 对于将常量对象转换成非常量对象的行为,一般称其为"去掉const性质"。一旦去掉了某个对象的const性质,编译器就不再阻止我们对该对象进行写操作了。如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果。
  • 只有const_cast能改变表达式的常量属性,使用其它形式的命名强制类型转换改变表达式的常量属性都将引发编译器错误。同样的,也不能用const_cast改变表达式的类型
const char *cp;
char *q = static_cast<char*>(cp);//错误:static_cast不能转换掉cast性质
static_cast<string>(cp);//正确:字符串字面值转换成string类型
const_cast<string>(cp);//错误:const_cast只改变常量属性
  • const_cast常常用于有函数重载的上下文中。

reinterpreter_cast

  • reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。假设有如下转换:
int *ip;
char *pc = reinterpret_cast<char*>(ip);
  • pc所指的真实对象是一个int而非字符,如果把pc当成普通的字符指针使用就可能再运行时发生错误,例如:
string str(pc);
  • 可能导致异常的运行时行为。
  • 使用reinterpret_cast是非常危险的,用pc初始化str的例子很好的证明了这一点。其中关键问题是类型改变了,但编译器没有给出任何警告或错误的提示信息。
  • 当用一个int的地址初始化pc时,由于显式的声称这种转换合法,所以编译器没法知道它实际存放的是指向int的指针。最终的结果是,在上例中虽然用pc初始化str没什么实际意义,甚至还可能引发更糟糕的后果,但仅从语法上而言,这种操作没错。
  • 查找这类问题的原因非常困难,如果将ip强制转换成pc的语句和用pc初始化string对象的语句分属不同文件就更是如此。
  • reinterpret_cast本质上依赖于机器。要想安全的使用reinterpret_cast必需对涉及的类型和编译器首先的转换过程非常了解。

建议:避免强制类型转换
强制类型转换干扰了正常的类型检查,强烈建议程序员避免使用强制类型转换。在有重载函数的上下文中使用const_cast无可厚非,但在其他情况下使用conat_cast也就意味着程序的某种设计缺陷。其它强制类型转换,都应该反复斟酌能否以其他方式实现相同的目标。就算实在无法避免,也应该尽量限制类型转换的作用域,并且记录对相关类型的所有假定,这样可以减少错误发生的机会。

旧式的强制类型转换

  • 在早期版本的c++语言中,显式的进行强制类型转换包含两种形式
type(expr);//函数形式的强制类型转换
(type) expr;//c语言风格的强制类型转换
  • 根据所涉及的类型不同,旧式的强制类型转换分别具有与const_cast、static_cast和reinterpret_cast相似的行为。在某处执行旧式的强制类型转换时,如果换成const_cast和static_cast也合法,则其行为与对应的命名转换一致。如果替换后不合法,则旧式强制类型转换执行与reinterpret_cast类似的功能的效果与使用reinterpret_cast一样
char *pc = (char*) ip;//ip是指向整数的指针

与命名的强制类型转换相比,旧式的强制类型转换从表现形式上来说不那么清晰明了,所以一旦转换过程出现问题,追踪起来也更加困难。

4.1 c++左值和右值、类型转换相关推荐

  1. C++/C++11中左值、左值引用、右值、右值引用的使用

    C++的表达式要不然是右值(rvalue),要不然就是左值(lvalue).这两个名词是从C语言继承过来的,原本是为了帮助记忆:左值可以位于赋值语句的左侧,右值则不能. 在C++语言中,二者的区别就没 ...

  2. C/C++左值性精髓(二)哪些表达式是左值,哪些是右值?----右值表达式

    2019独角兽企业重金招聘Python工程师标准>>> C对于右值的定义是表达式的值,C中所有完整表达式的结果都是右值.所谓完整表达式(full expression),指的是这样的 ...

  3. 【 C 】对左值与右值的一些个人思考

    今天重温C语言的指针,看的书是<C和指针>,关于左值和右值以及指针表达式的内容看得甚是迷惑与煎熬,怎么会这么难理解,指针表达式又是作为左值又一会作为右值,而且二者有着不一样的含义,为什么当 ...

  4. c/c++左值和右值

    C/C++中的变量有左值和右值之分,他们的区别主要如下: (1)左值可以放在赋值号 = 的左右两边,右值只能放在赋值号 = 的右边 (2)在C语言中,有名字的变量即为左值:而函数的运行结果或表达式中间 ...

  5. 《C++语言入门经典》一2.8 左值与右值

    2.8 左值与右值 C++中的每个语句.表达式的结果分为左值与右值两类.左值指的是内存中持续存储的数 据,而右值是指临时存储的结果. 在程序中,声明过的独立变量如: Int k; short p; c ...

  6. 39.左值、左值引用、右值、右值引用

    1.左值和右值的概念 左值是可以放在赋值号左边可以被赋值的值:左值必须要在内存中有实体:          右值当在赋值号右边取出值赋给其他变量的值:右值可以在内存也可以在CPU寄存器.       ...

  7. 2020-10-27(左值和右值)

    什么是表达式: 表达式由一个或多个操作数通过操作符组合而成.最简单的表达式仅包含一个字面值常量.较复杂的表达式则由操作符以及一个或者多个操作数构成. 一个变量是表达式但是一个表达式就不一定是变量了. ...

  8. c++中的左值与右值

    转载自 http://www.cnblogs.com/catch/p/3500678.html 左值 (lvalue)和右值 (rvalue) 是 c/c++ 中一个比较晦涩基础的概念,有的人可能甚至 ...

  9. c语言 变量的左值和右值,C++雾中风景10:聊聊左值,纯右值与将亡值

    C++11的版本在类型系统上下了很大的功夫,添加了诸如auto,decltype,move等新的关键词来简化代码的编写与降低阅读代码的难度.为了更好的理解这些新的语义,笔者确定通过几篇文章来简单窥探一 ...

  10. 左值、右值、左值引用、右值引用

    1. 左值 左值(lvalue,left value),顾名思义就是赋值符号左边的值,可以取地址.准确来说,左值是表达式(不一定是赋值表达式)后依然存在的持久对象. 可以将左值看作是一个关联了名称的内 ...

最新文章

  1. OpenAI数十亿代码训出Codex:能将英语翻译成代码,给四句话就能写个神经网络...
  2. Redis分布式锁(Redlock官方文档的理解)
  3. maven多profile环境打包下-P参数和-D参数
  4. python中MySQLdb的使用
  5. Web前端开发好学吗?谈谈一位学姐的前端工程师之路
  6. vue-13-swiper组件的使用
  7. 软件开发工具大题考点整理
  8. 计算机专业师范类毕业论文,师范生毕业论文范文
  9. 货币的一种互联网体系架构
  10. 测试图片,视频 地址
  11. UHL IOL NVMe测试工具安装及使用的常见问题
  12. 魔兽世界怀旧服最新服务器开发时间,怀旧服全部服务器开放时间
  13. 据说,证监会已承认证券交易所选址时没看好风水...
  14. 白盒测试:语句覆盖、条件覆盖、判定覆盖、条件-判定覆盖、组合覆盖、路径覆盖
  15. AI-多模态-2021:ALBEF
  16. CSS之排列系列--块级元素、内联元素、内联块元素--区别/详解
  17. 数据结构~07.栈和队列的基本概念
  18. 单位根检验、协整检验和格兰杰因果检验三者之间的关系
  19. pythoncookie自动登录_Python爬虫连载6-cookie深入使用实例化实现自动登录
  20. 当运行pychrm时遇到please select a valid interpreter怎么解决

热门文章

  1. ubuntu18.04 升级内核后,进入系统页面卡在“started gnome display manager“的解决方案
  2. 解决运行python脚本报错:ModuleNotFoundError: No module named ‘numpy‘
  3. 高仿QQ源码 界面(3)
  4. C/C++编程:std::move(将左值强制转换为右值)
  5. vue 引入json地图_使用vue引入maptalks地图及聚合效果的实现
  6. gitlab提交代码提示The remote end hung up unexpectedly
  7. 仿蜻蜓FM专辑页面滑动特效
  8. 多点生活的分布式服务框架DSF
  9. 关于地图开发的那些坑儿
  10. ACCESS模糊查询like的解决方法SQL查询语句通配符问题