你好,C++(15)0.1*10不等于1.0——4.1.4 关系操作符4.1.5 逻辑操作符

4.1.4 关系操作符

在C++中,除了需要用算术操作符对数据进行加减乘除的算术操作之外,我们有时候还需要对数据之间的关系进行操作,也就是对两个数据进行大小比较,得出它们之间的大小关系。在现实世界中,这种大小关系的比较是非常常见的。例如,这家摊位上的西红柿5元一斤,而另外一家相同的西红柿却只卖3元一斤,5和3一比较,就知道第二家的西红柿更便宜了。我们说,程序是用来抽象和描述现实世界的,为了在程序中表达这种大小关系的比较,C++专门提供了关系操作符,包括“>”(大于)、“>=”(大于或等于)、“<”(小于)、“<=”(小于或等于)、“==”(等于)、“!=”(不等于)。

最佳实践:注意“==”和“=”的区别

这两个符号在形式上非常相似,但是所表达的意义却是千差万别:

“==”是判断两个值是否相等的关系操作符;

“=”则是用其右边的值对左边的变量进行赋值的赋值操作符。

因为两者在形式上的相似性,如果不注意,很容易将“==”误写作“=”,或是将“=”误写作“==”,从而导致代码无法正确地表达设计者的意图,最终导致程序错误。更让人头疼得是,编译器无法发现这种隐秘的语义上的错误,即使在代码中出现了误用,编译器也不会给出错误信息,这使得我们防范这种错误的发生变得更加困难。例如:


 

int a;
// 程序员本来的意图是将a赋值为1
// 结果却将“=”误写成了“==”,
// 代码意义成了将a和1进行相等比较
a == 1;// 程序员本来的意图是将a和1进行相等比较
// 结果却将“==”误写成了“=”,代码意义成了将a赋值为1
if(a = 1)
{cout<<"a等于1"<<endl;
}

从这段代码的实际意义上来看,整个都是在胡言乱语,但是从代码的执行结果上来看,却与我们的设计预期相符,成功地输出了“a等于1”。同时,编译器也不会报告任何错误信息,这使得这一错误更具隐秘性。

要杜绝第一种误用,只有靠我们在书写代码时多加注意。另外,如果是给变量赋初始值,最好是在定义变量的时候同时进行。例如:

// 定义变量的同时赋初始值
// 在这种形式下,编译器会帮助发现“int a == 1;”这样的错误
int a = 1;

而对于第二种误用,同样需要我们在书写代码时小心谨慎。此外,有一种特殊情况是,当我们在将变量与某个常量进行比较时,我们最好是将常量放在等号的左边。这样,因为不能对常量进行赋值,即使我们将“==”误写成了“=”,编译器也会帮助我们发现这种误用。例如:


 

// 将常量1同变量a进行相等比较
// 如果误写作“if( 1 = a )”,就成了对常量1进行赋值,
// 而常量是不能被赋值的,编译器会报告一个错误信息,帮助我们发现这种误用
if( 1 == a)
{cout<<"a等于1"<<endl;
}

此外,虽然编译器对这种误用没有错误信息,但是却有相应的警告信息。在编译代码的时候,打开相应的警告信息开关(使用gcc编译器的-Wall编译选项打开全部警告开关),并注意编译器输出的警告信息,也可以很好地防止这种误用的发生。

关系操作符是二元操作符,其作用是将两个操作数进行关系运算,比较两个操作数的大小或者是否相等,其运算结果是bool类型的true或者false。如果两个操作数的大小关系符合操作符所表达的大小关系,则表达式的结果为true,反之则为false。例如:


 

// 两个摊位上的西红柿价格
int a = 5;
int b = 3;// 对a和b的值进行小于比较,但a的值大于b的值,不符合操作符“<”的意义,
// 所以表达式“a < b”的计算结果值为false,然后赋值给bCheap,
// bCheap的值也为false,表示a并不比b便宜
bool bCheap = a < b;

在C++中,我们通常是用关系操作符来判断某个条件是否成立,然后配合稍后我们将要学到的条件结构,决定代码的执行路径,对数据进行不同的处理以获得不同的执行结果。比如,我们要表示网吧禁止未满十八岁的未成年人进入:


 

int nAge = 0;
cout<<"请输入你的年龄:";
// 用户输入年龄   
cin>>nAge;
// 用关系操作符“>=”判断输入的年龄是否大于等于18
// 判断是否成年人
if(nAge >= 18)
{// nAge的值大于等于18cout<<"欢迎来到红树林网吧"<<endl;
}
else   // nAge的值小于18
{cout<<"很抱歉,未成年人不能进入"<<endl;
}

在这段代码中,我们首先让用户输入年龄并将其保存到nAge变量中,然后在if条件结构中,用“>=”关系操作符将其与18进行大小比较。如果nAge的值大于等于18,则“nAge >= 18”表达式的值为true,表示是成年人,代码会进入if后面的语句执行,输出欢迎信息。反之,则表示是未成年人,代码会进入else后面的代码执行,将其拒之门外。

最佳实践:不要使用“==”判断两个浮点数是否相等

考虑下面这段代码的输出是什么:


 

float a = 0.1;
if(a*10 == 1.0)
{cout<<"0.1*10等于1.0"<<endl;
}
else
{cout<<"0.1*10不等于1.0"<<endl;
}

如果我要是告诉你这段代码的输出是“0.1*10不等于1.0”会不会让你大跌眼镜呢?虽然有点意外,可这就是事实。这是因为在C++中,除了某些特殊的浮点数(比如,0.5、0.25等)之外,我们无法精确地表达大多数浮点数,所以比较两个浮点数是否相等是不保险的。虽然在表面上看来,a的值为0.1,a*10等于1.0是确定无疑的,但是因为浮点数a无法精确地表达0.1这个值,存在一个十分微小的误差,当经过一定的运算后,这个误差会累积到一个可被察觉的程度,这时再用“==”将其与它的理论结果进行比较时,其结果有可能是相等,也有可能是不相等。而至于到底是哪一个,则取决于计算机硬件和编译器优化设置。这段代码在某台计算机上输出的结果可能是“0.1*10不等于1.0”,但是在另外一台计算机上输出的结果却又可能是“0.1*10等于1.0”。所以,为了保证代码行为的一致性,不要在代码中使用“==”比较两个浮点数是否相等。

如果确实需要在代码中比较两个浮点数是否相等又该怎么办呢?最简单的方法就是设定一个允许的误差值(根据我们的精度要求而定),当两个浮点数之差的绝对值在这个误差范围内时,就认为两个浮点数相等,反之则认为两个浮点数不相等。例如,上面的代码可以修改为下面的样子,以保证代码行为的一致。


 

float a = 0.1;
// 相等的误差范围 
const float fEpsilon = 0.0001;
// 判断两个浮点数之差的绝对值(用fabs()函数取得)是否在误差范围内
// 如果在,则认为两个浮点数相等
if(fabs(a*10 - 1.0) < fEpsilon)
{cout<<"0.1*10等于1.0"<<endl;
}
else // 反之,则认为两个浮点数不相等
{cout<<"0.1*10不等于1.0"<<endl;
}

经过这样的改写,这段浮点数相等比较的代码在任何计算机上都可以得出一致的正确的结果。

4.1.5 逻辑操作符

在处理复杂事务时,我们往往需要根据多个条件来决定是否执行某项操作。例如,网吧门口贴着这样一张条子:

“只有年满十八岁而且兜里有钱的人才可以进入网吧。”

这里的“而且”,实际上就是对“年满十八岁”和“兜里有钱”这两个条件进行“与”的逻辑运算,只有这两个条件同时满足时,才算是符合条件,才能够执行“进入网吧”的操作。像“而且”这种对两个条件进行逻辑运算的动词,在C++中,我们用逻辑操作符来表达。

C++提供的逻辑操作符包括以下三种。

l !(非):计算操作数的逻辑非。

l &&(与):计算两个操作数的逻辑与。

l ||(或):计算两个操作数的逻辑或。

其中,“!”是一元操作符,只能放在单个bool类型的操作数之前,对其进行取非操作,获得与之相反的逻辑值。例如:

bool bFlag = true;     // 定义一个bool类型的变量bFlag并赋值为true
// 对操作数bFlag进行取非操作,整个表达式的结果为false,
// 与操作数bFlag的值相反
!bFlag;               

“&&”和“||”都是二元操作符,它们连接两个bool类型的操作数,并对其进行逻辑运算,所获得的bool类型的结果值作为整个表达式的值。“&&”的作用是计算两个操作数的逻辑与,也就是只有当两个操作数的值都为true时,整个表达式的值才为true,否则为false。“||”的作用是计算两个操作数的逻辑或,只要两个操作数中有一个为true,整个表达式的值就为true,否则为false。

在具体的编程实践中,逻辑操作符常常和关系操作符配合使用,在条件结构中被用来表达比较复杂的条件逻辑判断。例如,我们要根据学生的语文和数学成绩综合评定学生的考核等级,规则是:如果语文和数学成绩都是60分以上,则为“合格”;在“合格”的基础上,只要其中有一门成绩是85分以上,就是“优秀”。在C++中,我们可以这样来表达这个复杂的逻辑判断:


 

cout<<"请输入学生的语文、数学成绩(例如,82 96):";
// 定义变量保存输入的数据
int nChs = 0;
int nMath = 0;
// 输入数据,并保存到变量
cin>>nChs>>nMath;// 对变量进行逻辑判断评定等级
// 首先判断两个分数是否都在60分以上,达到“合格”的标准
if((nChs >= 60)&&(nMath >= 60))
{// 在“合格”的基础上,判断是否有一门的成绩在85以上,达到“优秀”的标准if((nChs >= 85)||(nMath >= 85)){cout<<"优秀"<<endl;}else // 如果没有达到“优秀”的标准,那就是“合格”{cout<<"合格"<<endl;}
}
else // 如果没有达到“合格”标准,那就是“不合格”
{cout<<"不合格"<<endl;
}

在这里,我们用“&&”操作符对“nChs >= 60”和“nMath >= 60”进行了“与”运算,也就是这两个表达式的值同时为true时,最终结果才为true。换句话说,要想让最终结果为true,也就是达到“及格”标准,那就必然要求“nChs >= 60”和“nMath >= 60”这两个表达式的值同时为true,而要想让这两个表达式的值同时为true,自然就要求nChs和nMath的值要同时大于60,这样就表达了“语文和数学成绩都是60分以上”的“合格”标准的逻辑判断。只有满足这个逻辑判断,达到合格标准,才能进入下一级判断是否“优秀”;否则,只要nChs和nMath中的任意一个小于60,就无法满足这个逻辑判断,程序就会进入else分支输出“不合格”的提示。在用“||”操作符对“优秀”条件进行判断时,只要“nChs >= 85”和“nMath >= 85”这两个表达中的任意一个为true,最终结果就为true。换句话说,也就是只要nChs和nMath中任意一个大于85,最终结果就为true,这也就表达了“只要其中有一门成绩是85分以上”的“优秀”标准。

最佳实践:注意“&&”和“||”操作符的“逻辑短路”机制

这两个操作符是用来对多个表达式进行逻辑运算,取得多个表达式经过运算后的最终结果。在进行运算时,如果凭借前面部分表达式的值就已经可以确定整个表达式的最终结果,C++将不再继续对后面剩下的表达式进行运算,而是直接抄近路返回已经得到的整个表达式的值。这种机制被称为“逻辑短路”,也就是走了捷径。

这样的解释还是有点抽象,我们就以上面的例子为例,看看“短路机制”到底是怎么“抄近路”的。

if((nChs >= 60)&&(nMath >= 60))

在这个条件判断中,我们用“&&”操作符对“nChs >= 60”和“nMath >= 60”进行“与”运算,如果nChs的值小于60,也就是第一个条件表达式“nChs >= 60”的值为false时,无论后面的“nMath >= 60”表达式的值为true或者false,我们都已经能够确定整个表达式的值为false,C++将用false作为整个表达式的值,跳过对后面“nMath >= 60” 的判断而直接结束对整个表达式的计算。所以,为了减少计算提高效率,C++就直接跳过对第二个关系表达式的计算,抄了近路。

在使用“||”进行“或”运算时,也同样存在这种“逻辑短路”。例如:

if((nChs >= 85)||(nMath >= 85))

在这个用“||”操作符表达的“或”逻辑运算中,如果nChs的值大于等于85,则第一个关系表达式“nChs >= 85”的值为true,进而可以确定整个表达式的值为true,C++同样也会跳过对第二个关系表达式“nMath >= 85”的计算,而用true作为整个表达式的值,直接结束对整个表达式的计算。

除了减少不必要的计算,在一定程度上提高效率之外,“逻辑短路”更大的意义在于,在某些情况(后一个条件判断以前一个条件成立为前提)下,它可以减少逻辑判断的层次。比如,如果我们要以某个结构体指针的成员变量作为条件,得先判断这个结构体指针是否有效,然后才能对指针的成员变量进行判断。如果没有“逻辑短路”机制,我们的代码要写成:


 

Human* p = nullptr;// …if(nullptr != p)// 先判断指针是否有效
{// 然后再判断指针的成员变量是否符合条件// 第二个条件判断以第一个条件成立为前提if(p->m_nAge >= 18){// ...}
}

而如果利用“逻辑短路”机制,这种条件判断就可以简化为:

Human* p = nullptr;
// …
if((nullptr != p) && (p->m_nAge >= 18))
{// ...
}

这样在进行条件判断时,同样会先判断指针p是否为空指针,如果第一个条件满足,才会继续向下判断它的成员变量是否满足条件。实现相同的条件判断,但是却减少了逻辑判断的层次,简化了代码,而这才正是“逻辑短路”机制的主要运用场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/684225.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue-组件组成和组件通信(四)

组件的三大组成部分 (结构/样式/逻辑) scoped样式冲突 默认情况&#xff1a;写在组件中的样式会 全局生效 → 因此很容易造成多个组件之间的样式冲突问题。 1. 全局样式: 默认组件中的样式会作用到全局 2. 局部样式: 可以给组件加上 scoped 属性, 可以让样式只作用于当前组…

探索未来科技:人工智能与量子计算的新纪元

引言 在这个科技飞速发展的时代&#xff0c;我们正站在一个全新的起点。人工智能&#xff08;AI&#xff09;和量子计算作为两项颠覆性的技术&#xff0c;正引领着科技的未来。在这篇博客中&#xff0c;我们将深入探讨人工智能与量子计算的最新进展&#xff0c;以及它们如何改…

pytorch创建模型方式

1.继承自nn.Module的方式 from torch import nn import torch.nn.functional as F 继承自nn.Moduleclass LModel(nn.Module):def __init__(self):super().__init__()self.L1 nn.Linear(10,10)self.L2 nn.Linear(10,64)self.L3 nn.Linear(64,10)self.L4 nn.Linear(10,5)se…

C++初阶:适合新手的手撕list(模拟实现list)

上次讲了常用的接口&#xff1a;今天就来进行模拟实现啦 文章目录 1.基本结构与文件规划2.空参构造函数&#xff08;constructor)3.完善迭代器&#xff08;iterator&#xff09;(begin(),end())4.List Capacity&#xff08;size(),empty())4.增删改查(push_back,pop_back,pop_f…

C++ bfs反向搜索(五十七)【第四篇】

今天我们来学习bfs的反向搜索。 1.反向搜索 反向搜索&#xff1a;是从目标状态出发进行的搜索&#xff0c;一般用于终点状态唯一&#xff0c;起点状态有多种&#xff0c;且状态转移是可逆的&#xff08;无向边&#xff09;情况。 例题&#xff1a;在一个长度为 n 的坐标轴上&a…

医药零售企业运营BI解决方案 连锁药房大数据解决方案 药店大数据解决方案

一、项目背景 连锁药店大数据项目建设的背景可以从以下几个方面进行分析: 行业趋势:随着医药行业的快速发展,连锁药店已成为药品零售的主要渠道。然而,随着市场竞争的加剧,连锁药店需要寻找新的竞争优势。通过大数据技术,连锁药店可以更好地了解消费者需求,提供个性化的…

2024年【T电梯修理】报名考试及T电梯修理复审考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 T电梯修理报名考试参考答案及T电梯修理考试试题解析是安全生产模拟考试一点通题库老师及T电梯修理操作证已考过的学员汇总&#xff0c;相对有效帮助T电梯修理复审考试学员顺利通过考试。 1、【多选题】增加旧电梯曳引…

p图考试,搜题软件哪个好?如何利用大学搜题工具查找特定学科的题目? #微信#知识分享

大学生必备&#xff0c;这条笔记大数据一定定要推给刚上大学的学弟学妹&#xff01;&#xff01; 1.三羊搜题 这是个微信公众号 是个公众号&#xff0c;支持文字、语音、截图搜题&#xff0c;截图搜题&#xff0c;同时也支持相似题查看及解析&#xff0c;因为可以在电脑上使…

【医学图像分割 2024】VM-UNet

文章目录 【医学图像分割 2024】VM-UNet摘要1. 介绍2. 预备知识3. 方法3.1 VM-UNet3.2 VSS Block3.3 损失函数 4. 实验4.1 数据集4.2 实现细节4.3 主要结果4.4 消融实验 5. 结果 【医学图像分割 2024】VM-UNet 论文题目&#xff1a;VM-UNet Vision Mamba UNet for Medical Imag…

Junit测试套件(Test Suite)

0. 什么是测试套件 对多个测试类的统一执行 只有一个测试类 点击一下执行就好有 5个测试类 分别打开 挨个点执行有100个测试类 &#xff1f;&#xff1f;分别点开执行 为100个测试类创建一个测试套件&#xff0c;然后再执行一次测试套件 √ 一个测试套件“囊括“三个测试类…

[嵌入式系统-16]:RT-Thread -2- 主要功能功能组件详解与API函数说明

目录 一、RT-Thread主要功能组件 二、内核组件 2.1 概述 2.2 API 三、设备驱动 3.1 概述 3.2 API 四、通信组件 4.1 概述 4.4 API 五、网络组件 5.1 概述 5.2 API 5.3 补充&#xff1a;MQTT协议 六、文件系统 6.1 概述 6.2 API 七、GUI 组件 7.1 概述 7.2 …

[office] Excel CHITEST 函数 使用实例教程 #媒体#知识分享#其他

Excel CHITEST 函数 使用实例教程 提示 此函数已由 CHISQ.TEST 函数替换&#xff0c;新函数可以提供更好的精确度&#xff0c;其名称更好地反映其用法。旧函数仍可用于与早期版本Excel 的兼容。但是&#xff0c;如果不需要向后兼容&#xff0c;那么应考虑直接使用新函数&…

【AIGC】Stable Diffusion的模型微调

为什么要做模型微调 模型微调可以在现有模型的基础上&#xff0c;让AI懂得如何更精确生成/生成特定的风格、概念、角色、姿势、对象。Stable Diffusion 模型的微调方法通常依赖于您要微调的具体任务和数据。 下面是一个通用的微调过程的概述&#xff1a; 准备数据集&#xf…

平时积累的FPGA知识点(6)

平时在FPGA群聊等积累的FPGA知识点&#xff0c;第六期&#xff1a; 1 万兆网接口&#xff0c;发三十万包&#xff0c;会出现掉几包的情况&#xff0c;为什么&#xff1f; 原因&#xff1a;没做时钟约束&#xff0c;万兆网接口的实现&#xff0c;本质上都是高速serdes&#xf…

MinGW下载安装教程 傻瓜式操作【超详细】

一、下载 下载地址&#xff1a;MinGW - Minimalist GNU for Windows - Browse Files at SourceForge.net 点击“Download Latest Version”即可 注意&#xff1a;小伙伴儿们也可以私信我要安装包。 二、安装 下载完成后&#xff0c;会得到一个名为 mingw-get-setup.exe 的安…

机器学习网格搜索超参数优化实战(随机森林) ##4

文章目录 基于Kaggle电信用户流失案例数据&#xff08;可在官网进行下载&#xff09;数据预处理模块时序特征衍生第一轮网格搜索第二轮搜索第三轮搜索第四轮搜索第五轮搜索 基于Kaggle电信用户流失案例数据&#xff08;可在官网进行下载&#xff09; 导入库 # 基础数据科学运…

GNU工具

GNU GNUs Not Unix OSS open source software 最初的Linux缺少实用工具来实现标准功能 GNU组织开发了一套完整的Unix实用工具&#xff0c;但是缺少用于支撑其运行的内核系统 由此&#xff0c;Linux内核 GNU操作系统实用工具 构成了一个完整的Linux操作系统 或者说&…

Docker之MySQL8安装、容器数卷挂载、数据库导入导出操作

Docker之MySQL8安装、容器数卷挂载、数据库导入导出操作 文章目录 Docker之MySQL8安装、容器数卷挂载、数据库导入导出操作1. 拉取镜像与运行容器2. 进入容器登录数据库3. 导出数据库1. 导出数据库2. 导出常见问题4. 导入数据库到myql容器中 Docker中安装Mysql8 1. 拉取镜像与运…

MySQL性能调优篇(9)-数据库的水平与垂直分割

在大规模互联网应用中&#xff0c;数据库的性能和可扩展性是至关重要的。为了满足高并发和大规模数据需求&#xff0c;水平和垂直分割成为了大型数据库架构的关键概念。在本篇博客中&#xff0c;我将介绍MySQL数据库的水平和垂直分割的概念、原则、示例以及其优缺点。 水平分割…

系统架构27 - 软件架构设计(6)

基于架构的软件开发方法 基于架构的软件开发方法&#xff08;ABSD&#xff09;概述概念与术语开发模型体系结构需求体系结构设计体系结构文档化体系结构复审体系结构实现体系结构的演化 基于架构的软件开发方法&#xff08;ABSD&#xff09; 基于体系结构的软件设计 (Architec…