IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++ Primer 第四章学习 随笔 —— “表达式” -> 正文阅读

[C++知识库]C++ Primer 第四章学习 随笔 —— “表达式”

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

4.1 基础

本部分先把概念罗列出来,后面再逐一讲解。

4.1.1 基本概念

C++定义了一元运算符二元运算符。作用于一个运算符对象的是一元运算符;作用于两个运算对象的是二元运算符。除此之外还有一个三元运算符。函数的调用也是一种特殊的运算符,它对运算对象的数量没有限制。

一些符号既能作为一元运算符又能作为二元运算符(比如 * )。对于这类符号来说,它的两种用法互不相干,完全可以当成两个不同的符号。

组合运算符和运算对象

对于含有多个运算符的复杂表达式来说,要理解它,首先要理解运算符的优先级结合律以及运算对象的求值顺序。比如下面这条表达式,具体是什么含义呢?(后面再讲解)

? ? ? ? 5 + 10 * 20 / 2;

运算对象转换

在表达式的求值过程中,运算对象常被转化类型。运算对象的类型非表达式所需,但如能转化为目标类型,就可以正常运行。

类型转化的规则很杂,好在合乎情理、不难理解。注意,小整数类型(如bool、char、short等)常被提升成较大的整数类型,主要是int。后面4.11部分也会对此做出讲解。

重载运算符

当运算符作用于类类型的运算对象时,用户可以自行定义其含义。因为这种自定义的过程事实上是为已存在的运算符赋予了另外一层含义,所以称之为重载运算符。标准库当中很多类类型定义的对象,之所以能够使用大量的运算符,都是重载运算符的结果。具体如何实现重载运算符应该是书本靠后的内容,一般在学习重载运算符之前,我们会先学习重载函数。

左值和右值

C++表达式要不然是左值,要不然就是右值

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

不同的运算符的运算对象的要求各不同。一个重要的原则就是,在需要右的值的地方可以用左值代替,但是不能不右值当成左值来用。

有这么一个沿袭至C语言的说法,不能取址的表达式是右值,该说明有一定的参考性,但不论是以前还是现在,这个说法都是不完全准确的。

使用decltype关键字的时候,左值和右值也有所不同。比如:

? ? ? ? int *p;

? ? ? ? decltype(*p);? ? ? ? //其结果是 int&

? ? ? ? decltype(&p);? ? ? ?//其结果是 int**

容我来尝试解答一下这个问题。产生不同结果的主要原因不是左值和右值的差别,而是表达式的返回值本身就不同。

我们常说对一个指针解引用,得到的是指针所指对象本身,*p 表达式的返回值是一个左值,但如何让返回值是一个左值呢?返回引用类型是关键,所以 *p 的返回值类型是 int& ,decltype(*p)得到的类型也便是int&。(请结合参考2.5.3节,第62页)

对一个对象解引用,得到的是该对象的地址,是一个指向该对象的匿名指针,对象解引用的返回值因此也是一个右值。p本身是一个指针,那么&p表达式得到的就是一个匿名的指向指针的指针,即int**类型。因此?decltype(&p) 得到的类型为int**。

在C++里关于左值和右值的讨论还很多,左值和右值的定义确实比较模糊。最有争议的右值,会因所属类型是基础类型还是自定义类型,体现出不一样的性质。

这里推荐一篇文章:c++中的左值与右值 - twoon - 博客园

4.1.2 优先级和结合律

其实这块将的内容简单且基础,不过是用更加专业的说法去描述了些都懂的道理。

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

? ? ? ? ★根据运算符的优先级,表达式 3+4*5 的结果应该是23,不是35

????????★根据运算符的结合律,表达式20 - 15 - 3 的结果应该是2,不是8

再举个稍微复杂点的列子

? ? ? ? 6 + 3 * 4 / 2 + 2

如果完全从左到右依次计算,得到的结果将是 20,但综合考虑优先级和结合律的结果是 14。上述表达式等同于:

? ? ? ? (6 + ((3 * 4) / 2) + 2)

括号无视优先级与结合律

括号无视普通的组合规则,表达式中括号括起来的部分被当成一个单元来求值,然后再与其它部分一起按照优先级组合。

4.1.3 求值顺序

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

但有四种运算符明确规定了求值顺序。第一种是逻辑与(&&)运算符,它要求先求左侧运算对象的值,只有当左侧运算对象的值为真时才继续求右侧运算对象的值。另外三种分别是逻辑或(||)运算符、条件(? :)运算符(它也常被称为三目运算符),和逗号(,)运算符。

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

运算对象的求值顺序与优先级以及结合律无关,一条形如 f() + g() * h() + j() 的表达式中

? ? ? ?★ 优先级规定,g()的返回值优先和h()的返回值相乘

? ? ? ? ★结合律规定,f()的返回值优先和g()与h()的乘积相加,所得结果再与j()的返回值相加

? ? ? ?★ 对于这些函数的调用顺序没有明确规定。

4.2 算术运算符

表格请参照书本

没做特殊说明时,算术运算符都能作用于任意算术类型 以及 任意能转换为算术类型的类型。算术运算符的运算对象和求值结果都是右值。算术运算符也全部满足左结合律。在表达式运算之前,小整数类型的运算对象被提升为较大的整数类型,所以有的运算对象最终会转换成同一类型。

当一元正号运算符作用于一个指针或者算术值时,返回运算对象值的一个(提升后的)副本。(该副本是一个右值,是一个匿名对象)

一元符号运算符对运算对象取负值之后,返回其(提升后的)副本:

? ? ? ? int i = 1024;

? ? ? ? int k = -i;? ? ? ? ? ? ? ? //毫无疑问的,k的值为-1024

? ? ? ? bool b = true;

? ? ? ? bool b2 = -b;? ? ? ? ?//出乎意料的,b2还是true

对大多数运算符来说,布尔类型的运算对象将被提升为int类型。b为true,参与运算时将被提升为整数值1,对它求负后的结果是-1,将-1再转换为布尔值并将其作为b2的初始值。任何非0的数的布尔值都是真,故b2还是true。

整数相除结果还是整数,也就是说,如果商有小数部分,直接弃掉。

运算符%俗称 “取模” 或 “取余” 运算符,负责计算两个整数相除所得的余数,参与取余的运算对象必须是整数类型:

? ? ? ? int ival = 10;

? ? ? ? doubel dval = 3.14;

? ? ? ? ival % 12;? ? ? ? //没问题

? ? ? ? ival % dval? ? ? //错误,运算对象是浮点型

? ? ? ? dval % ival? ? ? //错误,运算对象是浮点型

在除法运算中,同号商为正,否则为负。C++早期版本允许结果为负值的商向上或向下取整,C++11新标准规定商一律向0取整,即前面所说的,小数部分直接去掉。

C++语言早期版本允许m%n的符号匹配n的符号,而商向负无穷一侧取整,这一方式在新标准中已经被禁止了。除了-m 导致溢出的特殊情况,其它时候(-m) / n 和 m/(-n)都等于-(m/n),m%(-n)等于m%n,(-m)%n等于(m%n) 。

害~都是些大段的原文复述,不值得花费这些时间,后面的内容我可能会省略大段大段的篇幅,还请以书本为主。

4.3 逻辑和关系运算符

这两类运算符,运算对象和求值结果都是右值。

短路求值

逻辑运算符与来说,当且仅当左侧运算符为真时才会再求右侧运算对象的值。

对逻辑运算符或来说,当且仅当左侧运算符为假时才会再求右侧运算对象的值。

短路求值,这一特点,有很强的实际意义,不仅提高了程序的运行效率,还可以有效避免诸如内存溢出的严重错误。(确保了当前索引位置安全的情况下,才会去访问该索引位置)

?举一个例子,一个名为text的vector对象中存有若干个string对象,想要遍历输出这些对象,如果当前输出的字符串为空串或者以‘.’结尾,那么进行换行操作,否则,用空格隔开这些string对象。

?
	for (const auto& s : text)	//范围for,遍历text对象中的每一个元素
	{
		cout << s;
		if (s.empty() || s[s.size() - 1] == '.')    //如果当前字符串为空输出空格,如果当前字符串不为空,但是字符串的最后一个元素是句号,都输出一个换行符
			cout << endl;
		else	//不然就用一个空格隔开
			cout << " ";
	}

?

这里补充说明两点:

一个是 const auto& s,这里选择引用的原因在于,text的元素是string占用的空间可能很大,但用引用的方式就可以避免对元素的拷贝。用const修饰的原因在于,我们没有修改vector对象的元素的意图,加上const更加保险。

再一个是?if (s.empty() || s[s.size() - 1] == '.') ,判空在前,查找句号在后,这里必须这样,只有在string对象非空时我们才用下标运算符去访问它,不然可能会引发缓冲区溢出错误。

关系运算符

关系运算符比较对象的大小关系,并返回布尔值。关系运算符都满足左结合律。

如果我们想要在 i比j小,j又比k小的多重条件下,执行if条件下的语句,我们可能会尝试这么写

? ? ? ? if(i < j < k) {......}? ? ? ? //无论i和j为多少,只要k大于1一定为真

但结果并非我们所设想的,我们来逐步分析一下。按照左结合律,我们先进行 i<j 的判断,如果i真的小于j,我们会得到一个布尔类型的返回值,它为true,否则,为false。这么一个布尔类型的返回值会再和k进行比较,此时,该布尔类型的返回值会提升为int类型,要么为0,要么为1,所以k只要大于1,就可以得到返回值为真的最终结果。真确的写法应该为:

? ? ? ? if(i < j && j< k) {......}?

4.4 赋值运算符

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

赋值运算符的结果是它的左侧运算对象,并且是一个左值。如果赋值运算符左右两个运算对象的类型不同,则右侧运算对象将尽可能转化为左侧运算对象的类型(如果,右侧无法转化过来,那么会报错)

? ? ? ? int k = 0;? ? ? ? ? ? ? ? //初始化而非赋值

? ? ? ? k = 10;? ? ? ? ? ? ? ? ? ?//结果:类型是int,值是10

? ? ? ? k = 3.14;? ? ? ? ? ? ? ? //结果:类型是int,值是3

? ? ? ? k = "hello";? ? ? ? ? ? ?//错误,不存在适当的转化

C++11新标准允许使用花括号括起来的初始值列表作为赋值语句的右侧运算对象:

? ? ? ? k = {10};? ? ? ? ? ? ? ? ?//结果:类型是int,值是10

? ? ? ? k = {3.14};? ? ? ? ? ? ? //错误:窄化转换(也可称为收缩转换)

? ? ? ? vector<int> vi;? ? ? ? //声明定义一个vector<int>类对象vi,初始化为空

? ? ? ? vi = {0,1,2,3,4}? ? ? ?//vi现在包含5个元素了,分别为0到4

无论左侧运算对象的类型是什么,初始值列表都可以为空。此时,编译器创建一个值初始化的临时量讲其赋给左侧对象。(书本这里说的有些过于绝对了,当左侧对象需要一个自定义类来赋值,但这个自定义恰好又没有默认构造函数时,会引发错误。引发错误代码如下:)

#include<iostream>
#include<vector>
using std::vector;

class person
{
public:
	person(int age) { _age = age; }
private:
	int _age;
};

int main()
{
	vector<person>vp(10);	
	vp = {};		//error:“person::person”: 没有合适的默认构造函数可用

	system("pause");

	return 0;
}

赋值运算符满足右结合律

赋值运算符满足右结合律,这一点和其它二元运算符不太一样。

? ? ? ? int ival, jval;

? ? ? ? ival = jval = 0;? ? ? ? //正确,都被赋值为0

因为遵循右结合律,所以靠右的子表达式jval = 0 先计算,其返回是jval本身,ival再与返回值jval进行赋值运算,最后值都变为了0

综合考虑了工作量以及记载的价值,本节后面的内容就不记载了。

4.5 递增和递减运算符

递增运算符(++)和递减运算符(--)为对象的加1和减1操作提供了一种简洁的书写形式。这两个运算符还可以应用于迭代器,因为很多迭代器本身不支持算术运算,所以此时递增递减运算符除了书写简单外,还是必须的。

前置版本和后置版本的区别这里就不论述了。

建议,除非必须,否则不用递增递减运算符的后置版本

前置版本的递增运算,把值加1后直接返回了改变后的运算对象本身。与之相比,后置版本需要将原始值储存下来以便于返回这个未修改的内容。如果我们不需要 修改前的 值,那么后置版本的操作就是一种浪费。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-11-10 12:12:56  更:2021-11-10 12:15:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 5:44:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码