表达式由一个或多个运算对象组成,对表达式求值将得到一个结果。字面值和变量是最简单的表达式,其结果就是字面值和变量的值。把一个运算符和一个或多个运算对象组合起来可以生成较复杂的表达式。

基础

1、基本概念

  • 一元运算符
  • 二元运算符
  • 三元运算符

左值和右值

  C++的表达式要么是左值,要么就是右值。

  当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。

  在需要右值的地方可以用左值来代替,但是不能把右值当成左值使用。

2、优先级与结合律

  复合表达式是指含有两个或多个运算符的表达式。求复合表达式的值需要首先将运算符和运算对象合理地组合在一起,优先级与结合律决定了运算对象组合的方式。

括号无视优先级与结合律

3、求值顺序

优先级规定了运算对象的组合方式,但是没有说明运算对象按照什么顺序求值。在大多数情况下,不会明确指定求值的顺序。

int i=f1()*f2();

上述就无法知道到底f1在f2之前调用,还是f2在f1之前调用。

NOTE:对于那些没有指定执行顺序的运算符来说,如果表达式指向并修改了同一个对象,将会引发错误并产生未定义的行为。

int i=;
cout<<i<<" "<<++i<<endl; //未定义

(但是VS2013中测试结果是先进行++i运算)

有四种运算符明确规定了运算对象的求值顺序。

  • 逻辑与(&&)运算符:先求左侧对象的值,只有当左侧运算对象的值为真时才继续求右侧对象的值
  • 逻辑或(||)运算符
  • 条件(?:)运算符
  • 逗号运算符

求值顺序、优先级、结合律

运算对象的求值顺序与优先级和结合律无关。当表达式中对象不改变同一对象时,其求值顺序不受限制,如果有对同一对象产生影响,则会是一条错误的表达式,产生未定义的行为。(VS13中已经不报错,而是按照从左到右的顺序求值,如果表达式中只有一个对象改变其值,则先求改变对象的值)

int n = 0;

int f1()
{
return ++n;
} int f2()
{
return --n;
} int main()
{
int a = f1() + f2();
cout << a << endl;
system("pause");
return 0;
}

输出结果是1.

因为f1和f2都对n有改变,所以先求f1的值再求f2的值。

int i = 0;
cout << i << " " << ++i << endl;

输出结果是1,1.

因为++i会改变对象,所以先对其求值,再从左到右的结合律执行。

算术运算符

算术运算符的运算对象和求值结果都是右值。在表达式求值之前,小整数类型的运算对象被提升成较大的整数类型(int),所有运算对象最终会转换成同一类型。

对大多数运算符来说,布尔类型的运算对象将被提升为int,对于一元运算符+或-,布尔值不应该参与运算。

整数相除的结果还是整数,小数部分直接弃掉。

%取余:计算两个整数相除所得的余数,运算对象必须都是整数。

逻辑和关系运算符

关系运算符作用域算术类型或指针类型,逻辑运算符作用于任意能转换成布尔值的类型。逻辑运算符和关系运算符的返回值都是布尔类型。二者的运算对象和求值结果都是右值。

逻辑与和逻辑或运算符

二者都是先求左侧运算对象的值再求右侧运算对象的值。

短路求值:当左侧运算对象的值无法确定表达式的结果时才会计算右侧运算对象的值。

赋值运算符

赋值运算符的左侧运算对象必须是一个可修改的左值。

赋值运算的结果是它的左侧运算对象,并且是一个左值。相应的,结果的类型就是左侧运算对象的类型。如果赋值运算符的左右两个运算对象类型不同,则右侧运算对象转换成左侧运算对象的类型。

赋值运算满足右结合律

其他二元运算符满足左结合律,赋值运算返回的是其左侧运算对象。

赋值运算优先级较低

通常需要给赋值部分加上括号使其符合我们的原意。

不要混用赋值运算符和相等运算符

复合赋值运算符

任意一种复合运算符都完全等价于:

a=a op b;

区别在于左侧运算对象的求值次数:使用复合运算符只求值一次,使用普通运算符则求值两次。

递增和递减运算符

递增和递减运算符有两种形式:前置版本和后置版本。

前置版本是先对对象进行递增或递减运算并返回运算之后的对象值,对象本身已经发生改变。

后置版本是先返回未改变之前的对象的值,然后再对对象进行递增或递减操作。

NOTE:除非必须,否则不用递增或递减运算符的后置版本。

在一条语句中混用解引用和递增运算符

如果想在一条复合表达式中既将变量加1或减1又能使用它原来的值,这时就可以使用递增或递减的后置版本。

NOTE:后置运算符的优先级高于解引用运算符,因此*p++等价于*(p++)

成员访问运算符

点运算符和箭头运算符

NOTE:解引用运算符的优先级低于点运算符,所以执行解引用运算的子表达式两端必须加上括号。

条件运算符

条件运算符(?:)允许把简单的if-else逻辑嵌入到单个表达式中

cond?expr1:expr2

cond是判断条件的表达式,expr1和expr2是两个类型相同或可能转换为某个公共类型的表达式。

表达式的结果是expr1或expr2的返回值。当两个表达式都是左值或能转换成同一种左值类型时,运算的结果哦是左值;否则运算的结果是右值。

嵌套条件运算符

条件运算的嵌套最好别超过两到三层。

在输出表达式中使用条件运算符

条件运算符的优先级非常低,因此当一条长表达式中嵌套了条件运算子表达式时,通常需要加上括号。

位运算符

位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能。

一般来说,如果运算复习时小整型,则会自动提升成较大的整数类型。运算对象可以是带符号,也可以是无符号。如果运算对象是带符号的且它的值为负,那么位运算符如何处理对象的“符号位”依赖于机器,左移操作可能改变符号位的值,因此是一种未定义的行为。

NOTE:强烈建议仅将位运算符用于处理无符号类型。

移位运算符

首先令左侧运算对象的内容按照右侧运算对象的要求移动指定位数,然后将经过移动的左侧运算对象的拷贝作为求值结果。

其中,右侧的运算对象一定不能为负,而且值必须严格小于结果的位数,否则产生未定义的行为。二进制位移出边界之外的位被舍弃掉。

位求反运算符

位与、位或、位异或运算符

sizeof运算符

sizeof运算符返回一条表达式或一个类型名字所占的字节数。其值是一个size_t类型的常量表达式。

sizeof并不实际计算其运算对象的值。

  • 对char或者类型为char的表达式执行sizeof运算,结果为1
  • 对引用和解引用指针执行sizeof,得到的是实际对象所占空间的大小
  • 对指针的运算结果是指针本身所占空间的大小
  • 对数组的运算结果是整个数组所占空间的大小,等价于对数组中所有的元素各执行一次的结果之和
  • 对string和vector的执行结果是该类型固定部分的大小,不会计算对象中的元素占用了多少空间

NOTE:sizeof的返回值是常量表达式,因此可以用来定义数组。

逗号运算符

逗号运算符含有两个运算对象,安装从左向右的顺序依次求值。

首先对左侧的表达式求值,然后将求值结果丢弃掉,逗号运算符真正的结果是右侧表达式的值。如果右侧对象是左值,最终结果也是左值。

逗号运算符经常被用在for循环中。

类型转换

算术类型之间的隐式转换被设计得尽可能避免损失精度。

何时发生隐式转换

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

算术转换

运算符的运算对象转换成最宽的类型。当表达式中既有浮点又有整型的时候,整型转换为浮点。

整型提升

  • 小整数类型(bool、char、signed char、unsigned char、short和unsigned short)转换成int类型
  • 较大的char类型(wchar_t、char16_t、char32_t)提升为int、unsigned、long、unsigned long、long long和unsigned long long中能容纳其值的最小的一种

无符号类型的运算对象

  • 无符号类型大于带符号类型,则带符号类型转换成无符号类型
  • 无符号类型小于带符号类型,转换结果依赖于机器。

其他隐式类型转换

数组转换成指针

在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针;

当数组被用作decltype关键字的参数,或者作为取地址符、sizeof、typeid等运算符的运算对象时,上述转换不会发生。

指针的转换

转换成布尔类型

如果指针或算术类型的值为0,转换结果是FALSE,否则是TRUE

转换成常量

允许将非常量类型的指针转换成指向相应的常量类型的指针,对于引用也是这样。相反的转换并不存在,因为它试图删除掉底层const。

类类型定义的转换

显式转换

也称为强制类型转换。

NOTE:强制类型转换很危险

命名的强制类型转换

cast-name<type>(expression);

其中,type是转换的目标类型而expression是要转换的值。

cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一种。

static_cast

任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast.

当需要把一个较大的算术类型赋值给较小的类型时,static_cast非常有用。它会让编译器知道我们不在乎精度损失,而不报警。

static_cast对于编译器无法自动执行的类型转换也非常有用。

const_cast

const_cast只能改变运算对象的底层const。常用于有函数重载的上下文中。

只有const_cast能改变表达式的常量属性,使用其他形式的命名类型转换改变表达式的常量属性都将引发编译器错误。

const char *p;
char *q = const_cast<char*>(p);

reinterpret_cast

通常为运算对象的位模式提供较低层次上的重新解释。尽量不使用。主要是指针之间的转换

int *ip;
char *q = reinterpret_cast<char*>(ip);

C++系统学习之五:表达式的更多相关文章

  1. 【mongodb系统学习之五】mongodb启动最常用参数

    五.mongodb启动时其他常用参数的使用(都是选用): 1).--logappend,指定日志的写入方式为追加,强烈建议使用: 2).--port,指定mongodb的端口号,当不使用这个参数的时候 ...

  2. day 83 Vue学习之五DIY脚手架、webpack使用、vue-cli的使用、element-ui

      Vue学习之五DIY脚手架.webpack使用.vue-cli的使用.element-ui   本节目录 一 vue获取原生DOM的方式 二 DIY脚手架 三 vue-cli脚手架的使用 四 we ...

  3. 系统学习 Java IO (六)----管道流 PipedInputStream/PipedOutputStream

    目录:系统学习 Java IO---- 目录,概览 PipedInputStream 类使得可以作为字节流读取管道的内容. 管道是同一 JVM 内的线程之间的通信通道. 使用两个已连接的管道流时,要为 ...

  4. 系统学习Javaweb6----JavaScript2

    感想:感觉自己还是只是学到皮毛,仍需继续努力,明天开始需要学习Android和阅读感想的书写. 学习笔记: 2.3.运算符 JavaScript运算符与java运算符基本一致. 这里我们来寻找不同点进 ...

  5. 【系统学习ES6】第二节:解构赋值

    [系统学习ES6] 本专题旨在对ES6的常用技术点进行系统性梳理,帮助大家对其有更好的掌握,希望大家有所收获. ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构.解构是一种打 ...

  6. 零基础如何系统学习Java Web

    零基础如何系统学习Java Web?   我来给你说一说 你要下决心,我要转行做开发,这样你才能学成. 你要会打字,我公司原来有一个程序员,打字都是两个手一指禅,身为程序员你一指禅怎么写出的代码,半个 ...

  7. Unity3D 装备系统学习Inventory Pro 2.1.2 基础篇

    前言 前一篇 Unity3D 装备系统学习Inventory Pro 2.1.2 总结 基本泛泛的对于Inventory Pro 这个插件进行了讲解,主要是想提炼下通用装备系统结构和类体系.前两天又读 ...

  8. MES系统学习

    MES系统是当今制造型企业信息化的热点,而统一建模语言UML是面向对象建模的标准语言,在软件工程发挥着重要作用.MES系统如何进行UML建模呢,今天和大家重点讨论一下MES系统的UML建模方法,请看本 ...

  9. 001 今天开始系统学习C#

    2016-01-16 之前只是大概了解过c#语言,感觉掌握不牢靠.现在开始系统学习C#.现以该博客作为学习笔记,方便后续查看.C# 目标:系统掌握c#知识 时间:30天 范围:C#基础,Winform ...

随机推荐

  1. [Xcode 实际操作]二、视图与手势-(10)UITapGestureRecognizer手势之单击

    目录:[Swift]Xcode实际操作 本文将演示使用视图的手势功能,完成视图的交互操作. import UIKit class ViewController: UIViewController { ...

  2. ReenTrantLock可重入锁和synchronized的区别

    ReenTrantLock可重入锁和synchronized的区别 可重入性: 从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入 ...

  3. ubuntu 14.04 源码编译postgresql

    环境 ubuntu 14.04 桌面版 postgresql 源码下载链接,本教程是使用postgresql 9.3.4 进行编译的 http://www.postgresql.org/ftp/sou ...

  4. web前端篇:html基础知识

    目录 1.web前端: 2.HTML概述 2.1HTML介绍 2.2HTML在计算机中如何表现 3.HTML基础语法 4.练习题: 1.web前端: 什么是web? web 就是网页,是一种基于B/S ...

  5. VRTK3.3.0-004传送

    直线传送: 一.无高度变换传送(VRTK_BasicTeleport) 1丶继续在VRScripts下创建空物体PlayArea,用来挂在传送相关脚本:创建Plane作为传送地面 2丶在PlayAre ...

  6. 推荐 VS2010入门教程—鸡啄米

    http://www.jizhuomi.com/catalog.asp?tags=VS2010 推荐 VS2010入门教程—鸡啄米,真的非常使用和经典!

  7. mysql ERROR 2003 (HY000): Can't connect to MySQL server on '' (10060

    关闭防火墙即可连接成功: systemctl stop firewalld

  8. 为什么static方法中不可以调用非static方法

    Java是面向对象的语言,所有的变量,方法都是针对对象而言的.一般来说,要调用一个方法,你需要new 这个方法的对象. 什么时候用static? 如果你想要: 对于一个类的所有对象共享一个变量或者是方 ...

  9. Docker与虚拟机

    Docker与虚拟机 简述 Docker 在容器的基础上,进行了进一步的封装,从文件系统.网络互联到进程隔离等等,极大的简化了容器的创建和维护.使得 Docker 技术比虚拟机技术更为轻便.快捷.下面 ...

  10. 记住,永远不要在MySQL中使用“utf8”-转

    http://www.infoq.com/cn/articles/in-mysql-never-use-utf8-use-utf8 最近我遇到了一个bug,我试着通过Rails在以“utf8”编码的M ...