猿创征文丨学C/C++进来看看--你可能都不清楚的三种变量和零值比较

2023-11-06 18:59

本文主要是介绍猿创征文丨学C/C++进来看看--你可能都不清楚的三种变量和零值比较,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

本文主要讲解了:什么是bool类型,如何使用bool类型,bool类型如何和零值比较,浮点型变量如何和零值比较,三种常见的零值类型,指针变量和零值比较.

在这里插入图片描述

介绍三种变量以及如何和零值比较

  • 一.bool变量和零值比较
    • 1.bool类型介绍
    • 2.bool类型使用
    • 3.bool类型如何和零值比较
  • 二.浮点型变量和零值比较
    • 1.浮点数在内存中可能存在精度损失
    • 2.浮点数变量如何和零值比较
  • 三.指针变量与零值比较
    • 1.常见的零值
    • 2.指针变量如何和零值比较?
  • 四.总结

一.bool变量和零值比较

1.bool类型介绍

BOOL. bool表示布尔型变量,也就是逻辑型变量的定义符,以英国数学家、 布尔代数 的奠基人 乔治·布尔 (George Boole)命名。

C语言有没有bool类型?
c99之前,主要是c90是没有的,目前大部分书,都是认为没有的。因为书,一般都要落后于行业。
但是c99引入了_Bool类型(你没有看错,_Bool就是一个类型,不过在新增头文件stdbool.h中,被重新用宏写成了
bool,为了保证C/C++兼容性)。

2.bool类型使用

//测试代码1
#include <stdio.h>
#include <stdbool.h> //没有这个头文件会报错,使用新特性一定要加上
#include <windows.h>
int main()
{
bool ret = false;
ret = true;
printf("%d\n", sizeof(ret)); //vs2013 和 Linux中都是1
system("pause");
return 0;
}

上面代码(是c99标准下编译)可以得出bool类型占字节长度是1个字节
在c99里bool是一个新关键字,可以理解为和int 等一样,但是它类型长度是1个字节
它定义的变量只能赋值为true或false
true表示真,和常量1等价,false表示假和常量0等价,虽然等价但是用这两个表示更具有直观性,

但是!!!
因为目前编译器对C99特性支持的并不全面,我们后面依旧默认使用C90的认识去编码即可,使用int表示真假。
具体要结合实际情况去定。

如果想要类似的效果,可以使用#define true 1 #define false 0
#define定义标识符常量 方法 用这两个表示真假
但是不同的是类型整形大小是4个字节

3.bool类型如何和零值比较

int main()
{
int pass = 0; //0表示假,C90,我们习惯用int表示bool
//bool pass = false; //C99
if (pass == 0){ //理论上可行,但此时的pass是应该被当做bool看待的,==用来进行整数比较,不推荐
//TODO
} i
f (pass == false){ //不推荐,尽管在C99中也可行
//TODO
} i
f (pass){ //推荐
//TODO
} /
/理论上可行,但此时的pass是应该被当做bool看待的,==用来进行整数比较,不推荐
//另外,非0为真,但是非0有多个,这里也不一定是完全正确的
if (pass != 1){
//TODO
} i
f (pass != true){ //不推荐,尽管在C99中也可行
//TODO
} i
f (!pass){ //推荐
//TODO
} s
ystem("pause");
return 0;
}

上面代码讲解了bool多个和零值比较的方法
在c99中推荐:直接用bool变量作为真和假的判断
在c90中因为没有bool类型,但是也有非零为真,零为假的表示方法
推荐直接用变量作为真假的判断如(if(1) if(0)),√
不推荐再和特定值进行运算最后的结果作为真假判断(如:if(0==0))×
结论:bool类型,直接判定,不用操作符进行和特定值比较。(这样更直观的能看出表达式是真还是假.)

二.浮点型变量和零值比较

在这篇博客中详细介绍到了浮点型变量->浮点型变量
在上面博客介绍中:浮点数在内存中存储,并不想我们想的,是完整存储的,在十进制转化成为二进制,是有可能有精度损失的。
//注意这里的损失,不是一味的减少了,还有可能增多。浮点数本身存储的时候,在计算不尽的时候,会“四舍五入”或者其他策略

1.浮点数在内存中可能存在精度损失

#include <stdio.h>
int main()
{double x = 3.6;//浮点数3.6 存在内存里会发生精度损失printf("%.50f\n", x);return 0;
}

在这里插入图片描述

了解浮点数在内存存储后会明白浮点数最后存在内存中可能不准确.无法将所有准确数字存在内存中,导致最后取出来得到的结果会有精度位损失(精度位损失不一定是原来数据变小,也会发生增大)根据上面的例子以double类型为例:看出3.6存进去之后以保留后面50位小数形式打印它最后会得到原来的数字但是在后面十六位小数后会出现一些其他值,也就是发生精度损失,一般会显示6位小数,就感觉这个数最后还是3.6,
因为这是经过了一些相应的处理:最后使得十六小数后的那些数字为一些无法预测的数字
所以double类型数据一般我们主观认为前十六位数字是有效数字,因为后面数字可能会精度损失.
既然存在精度损失 ,浮点数和0.0比较时可以直接用==吗??
答案:不能!

2.浮点数变量如何和零值比较

浮点数在内存中的形式是二进制形式,而浮点数转换为二进制小数位数有些转换不了会经过相应转换,使得最后取出来的数字前十六数字是实际有效的,但并不排除有效数字后面的数字是0以外的不可预料的数字,这些数字仍然会参与运算,只是一般显示六位小数看不到这些数字的运算,但是浮点数之间用==比较则会出现问题!!

#include <stdio.h>
int main()
{double x = 1.0;double y = 0.1;if ((x - 0.9) == y){printf("相等\n");} else{printf("不相等\n");} return 0;
}

最后运行结果是什么?
正常思想1.0-0.9最后肯定会得到0.1输出结果会是相等
but!!
这是计算机在进行运算,计算机里的存放的这些数据可能会发生精度损失,所以结果不一定是相等

在这里插入图片描述

结果:不相等
那这他们具体是怎么样比较得出不相等的呢?

在这里插入图片描述

将它们各自的数据以保留50位小数形式打印出来
最后发现0.1和0.9数据存在计算机里最后会发生处理十六位有效数字后会有非0的数字
正常情况下这些数字我们看不到,只会看到0.100000,实际上在计算机里是存在的,
1.0在与0.9进行相减后以保留五十位小数位数显示出现的结果不会是0.1,
正常情况下我们看到的是0.100000是因为默认保留六位小数,后面的小数位会四舍五入最后得到的0.1,但是实际上更准确的是保留位数越多的,
所以最后和0.1实际上计算机做比较不会保留位数四舍五入进行比较而是所有位数都会进行比较.如果不相等,结果就是不相等,
而不相等的原因也正是因为有了那些存在计算机里进过处理后出现的精度损失的数字

因此,上面的比较方法是错误的,因为在计算机看来得到的数字并不是和我们想象的意义
那浮点数该如何比较呢?
应该进行范围精度比较!

两个浮点数作差得到的值 应该大于负精度同时小于正精度(这里精度用 EPS表示)
伪代码: (x-y)>-EPS&&((x-y)<EPS)
伪代码简洁形式:fabs(x-y)<EPS (fabs为数学库函数 表示得到常数的绝对值 使用需要包含头文件math.h)

精度是什么:就是和主观上保留十六位小数一样,产生的一个精度小数,表示任何数只要小于这个精度说明这个数在精度前面部分是0.000000后面部分可能是0可能是些非0 的数字
用精度区分了有效数字让计算机根据人的设计的精度来判断这个数除去小于精度的数字后得到的结果
精度可以是自己设定(通常是#define 宏定义),或者使用系统设定的精度
系统设定的精度有double类型数据的精度表示为DBL_EPSILON
float类型的精度表示为FLT_EPSILON
使用这两个精度需要包含头文件float.h
XXX_EPSILON是最小误差,是:XXX_EPSILON+n不等于n的最小的正数。
EPSILON这个单词翻译过来是’ε’的意思,数学上,就是极小的正数 ----来自百度 😃在这里插入图片描述
在这float.h的定义中找到这两个精度,可以看到是被宏定义的,它们表达意思也就是当0加上这个精度得到的结果不等于0,但是有很多的数加上0后的结果都不会等于0,但这个精度设定的意义表示的是最小的正数,即当比这个精度小的数字加上0后,最后的结果就会被当作0来看待!!!

#include<float.h> //使用下面两个精度,需要包含该头文件
//DBL_EPSILON //double 最小精度
//FLT_EPSILON //float 最小精度
//代码调整后
#include <stdio.h>
#include <math.h> //必须包含math.h,要不然无法使用fabs
#include <float.h> //必须包含,要不然无法使用系统精度
int main()
{double x = 1.0;double y = 0.1;printf("%.50f\n", x - 0.9);printf("%.50f\n", y);if (fabs((x - 0.9) - y) < DBL_EPSILON){ //原始数据是浮点数,我们就用DBL_EPSILONprintf("相等\n");} else{printf("不相等\n");} return 0;
}

上面代码引入了系统设定的精度,最后的结果是什么呢?

在这里插入图片描述

最后结果为相等,使计算机运算得到了我们想要的值
但是这不是随便设置的,有了这个精度值限制,最后x-0.9-0.1的绝对值在计算机经过运算后实际上是一个前面十六个有效数字都为0,而后面是一些无法确定的精度数字,而我们只需要,用得到的这个结果与设置的精度比较,如果是小于设置的精度,表示在这个精度前面的那些数字都是0,也就是告诉计算机,我们要两个浮点数比较是否相等的结果是通过两个浮点数相减,最后得到的数据是小于精度,满足这个条件则表示精度小数前面的数都是0后面的数忽略不计,
得到的结果就按零值看待,用这种方法就省略掉了最后损失的精度得到了想要的结果

除了用系统设置的精度外,我们可以自己用宏定义设置相应的精度

#include <stdio.h>
#include<math.h>
#define EPS 1E-16
int main()
{double x = 1.0;double y = 0.1;printf("%.50lf\n", x - 0.9);printf("%.50lf\n", y);printf("%.50lf\n", fabs((x - 0.9) - y));printf("%.50lf\n", EPS);if (fabs((x - 0.9) - y) < EPS) { printf("相等\n");} else{printf("不相等\n");} return 0;
}

上面代码为自己宏定义了精度EPS 为1E-16表示1的负十六次方

在这里插入图片描述

可以看到,当满足两个浮点数相减得到的数是小于我设置的这个精度EPS(1的负16次方)
表示前十六小数位都为0,后面的小数位数为精度位不用考虑,如果满足小于条件则得到的这个结果是比设置的精度还小的数字即这个数加上0实际上还是等于0,表示这个数与0等价,得到的结果就是这两个浮点数相等

知道了两个浮点数如何比较是否相等,那浮点数变量和零值如何比较呢?

下面这串代码结果是什么?

#include <stdio.h>
#include <math.h>
#include <float.h>
int main()
{
double x = 0.00000000000000000000001;
//if (fabs(x-0.0) < DBL_EPSILON){ //写法1
//if (fabs(x) < DBL_EPSILON){ //写法2
if(x > -DBL_EPSILON && x < DBL_EPSILON){ 
printf("x等于0\n");
} 
else
{
printf("x不等于0\n");
} return 0;
}

结果是x等于0, 尽管x小数位后面有一个1,但是这个1已经在设置的有效位数之后,被我们当做精度位来处理,而精度位就是可以直接省略的,
判断x是否等于0,我们只需要判断x是否大于负精度 同时小于正精度 也就是x的绝对值是否小于精度,满足小于 则x这个数加上0.0实际上是等同于0的,我们将x和零值等价
最后用这种方法判断出x是等于0的,后面的小数位1只是省略计算的精度位

三.指针变量与零值比较

指针变量用于存放指针,说的指针变量和零比较实际是用指针和零值的比较

1.常见的零值

0:数字常量0
‘\0’:字符常量’\0’它是以一个\加0组成的一个转义字符,通常用于作为字符串结束标志,实际上是一个字符,而字符是由ASCLL码转换而来’\0’转换为ASCLL码就是数字0,只是0的另一种形式
NULL:空指针 ,通常用于给指针变量初始化,防止野指针出现,而NULL实际上是(void*)0,
是将0强制类型转换为空类型的指针,也就是一个空类型的0地址,实质上也是一个数字0

2.指针变量如何和零值比较?

虽然零值有不同形式的存在,但是这样设计是为了适应不同类型的变量.
假设有一个 int *a =0;
看到这串代码是不是有一个奇怪的想法,a是一个指针变量存一个0干嘛,实际上存0表示的是0地址,也可以存在a里,但是由于我们人的惯性,0是一个常量,存在指针变量a里,看着也有点别扭
后面设置了一个类型NULL表示空指针 实际上也是0,以这种形式int *a=NULL;
表示这个指针变量里存着是一个空类型的0地址,就表达式空指针的意思
这种形式更让我们所接受.

指针应该如何和零值比较?
下面这三种类型的代码你更倾向哪种?

int *a=NULL;
> if(a)if(!a)  (1)
> if(a= =0)if(a!=0)  (2)
> if(a==NULL)if(a!=NULL) ( 3)

第一种:让我们一眼看起来和bool类型与零值比较一样,不推荐指针变量这样使用
第二种:指针变量本身存放的是0,但是是以NULL形式存在,此时又和0比较让人误解,不推荐
第三种:指针变量里为NULL和NULL比较 符合对应的设计,推荐!!!
结论:指针变量和零值比较,这个零值要用对应的NULL表示更让人一目了然.

在这里插入图片描述

四.总结

本篇博客介绍了三种类型变量和零值的比较
,bool变量和零比较: bool用来表示真假, 在c99中采用,在c90中直接使用当前变量的数作为bool, 不需要再和其他数比较再得出真假,直接用本身作为真假的判断即可

浮点型变量和零比较:因为浮点型变量可能存在精度损失,不能直接和零值进行比较,应采用作差法用范围精度比较,用自己设置的精度或者系统精度,当这个数的绝对值小于设置的这个精度时,表示这个数等于零值.
指针变量和零比较:指针变量和零值比较,零值尽可能用NULL形式能让人一目了然.

在这里插入图片描述

写文不易,给个一键三连,支持下吧~~~

这篇关于猿创征文丨学C/C++进来看看--你可能都不清楚的三种变量和零值比较的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/358451

相关文章

C#如何调用C++库

《C#如何调用C++库》:本文主要介绍C#如何调用C++库方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录方法一:使用P/Invoke1. 导出C++函数2. 定义P/Invoke签名3. 调用C++函数方法二:使用C++/CLI作为桥接1. 创建C++/CL

C++如何通过Qt反射机制实现数据类序列化

《C++如何通过Qt反射机制实现数据类序列化》在C++工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作,所以本文就来聊聊C++如何通过Qt反射机制实现数据类序列化吧... 目录设计预期设计思路代码实现使用方法在 C++ 工程中经常需要使用数据类,并对数据类进行存储、打印、调试等操作。由于数据类

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

C++中初始化二维数组的几种常见方法

《C++中初始化二维数组的几种常见方法》本文详细介绍了在C++中初始化二维数组的不同方式,包括静态初始化、循环、全部为零、部分初始化、std::array和std::vector,以及std::vec... 目录1. 静态初始化2. 使用循环初始化3. 全部初始化为零4. 部分初始化5. 使用 std::a

如何将Python彻底卸载的三种方法

《如何将Python彻底卸载的三种方法》通常我们在一些软件的使用上有碰壁,第一反应就是卸载重装,所以有小伙伴就问我Python怎么卸载才能彻底卸载干净,今天这篇文章,小编就来教大家如何彻底卸载Pyth... 目录软件卸载①方法:②方法:③方法:清理相关文件夹软件卸载①方法:首先,在安装python时,下

C++ vector的常见用法超详细讲解

《C++vector的常见用法超详细讲解》:本文主要介绍C++vector的常见用法,包括C++中vector容器的定义、初始化方法、访问元素、常用函数及其时间复杂度,通过代码介绍的非常详细,... 目录1、vector的定义2、vector常用初始化方法1、使编程用花括号直接赋值2、使用圆括号赋值3、ve

如何高效移除C++关联容器中的元素

《如何高效移除C++关联容器中的元素》关联容器和顺序容器有着很大不同,关联容器中的元素是按照关键字来保存和访问的,而顺序容器中的元素是按它们在容器中的位置来顺序保存和访问的,本文介绍了如何高效移除C+... 目录一、简介二、移除给定位置的元素三、移除与特定键值等价的元素四、移除满足特android定条件的元

Python获取C++中返回的char*字段的两种思路

《Python获取C++中返回的char*字段的两种思路》有时候需要获取C++函数中返回来的不定长的char*字符串,本文小编为大家找到了两种解决问题的思路,感兴趣的小伙伴可以跟随小编一起学习一下... 有时候需要获取C++函数中返回来的不定长的char*字符串,目前我找到两种解决问题的思路,具体实现如下:

C++ Sort函数使用场景分析

《C++Sort函数使用场景分析》sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变,如果某些场景需要保持相同元素间的相对顺序,可使... 目录C++ Sort函数详解一、sort函数调用的两种方式二、sort函数使用场景三、sort函数排序