【C与0的不解之缘】

目录

引言

 bool 变量与"零值"进行比较

bool类型—C99定义

BOOL类型—Microsoft专利​

C中如何进行 bool 值与0比较呢?

float 变量与"零值"进行比较

范围精度比较

  两个精度定义

指针变量与“零值”进行比较

NULL, '\0', 0的整体理解​

强制类型转化

指针变量与“零值”进行比较的 if 语句

 写在最后:


引言

        0不管是在生活中还是在学习中都是十分常见的,这里我们先看一看关于0的历史沿革,然后再讨论C语言与0的”故事“

0是极为重要的数字,关于0这个数字概念在其它地区很早就有。公元前3000年,巴比伦人就已经懂得使用零来避免混淆。古埃及早在公元前2千年就有人在记帐时用特别符号来记载零。玛雅文明最早发明特别字体的0。玛雅数字中0以贝壳模样的象形符号代表。

标准的0这个数字由古印度人在约公元5世纪时发明。他们最早用黑点表示,后来逐渐变成了“0”。在东方国家由于数学是以运算为主(西方当时以几何并在开头写了“印度人的9个数字,加上阿拉伯人发明的0符号便可以写出所有数字)。由于一些原因,在初引入0这个符号到西方时,曾经引起西方人的困惑, 因当时西方认为所有数都是正数,而且0这个数字会使很多算式、逻辑不能成立(如除以0),甚至认为是魔鬼数字,而被禁用。直至约公元15,16世纪0和负数才逐渐给西方人所认同,才使西方数学有快速发展。

0的另一个历史:0的发现始于印度。公元前2000年左右,古印度婆罗门教最古老的文献《吠陀》已有“0”这个符号的应用,当时的0在印度婆罗门教表示无(空)的位置。约在6世纪初,印度开始使用命位记数法。7世纪初印度大数学家葛拉夫.玛格蒲达首先说明了0的0是0,任何数加上0或减去0得任何数。遗憾的是,他并没有提到以命位记数法来进行计算的实例。也有的学者认为,0的概念之所以在印度产生并得以发展,是因为印度佛教中存在着“绝对无”这一哲学思想。公元733年,印度一位天文学家在访问现伊拉克首都巴格达期间,将印度的这种记数法介绍给了阿拉伯人,因为这种方法简便易行,不久就取代了在此之前的阿拉伯数字。这套记数法后来又传入西欧

了解了0之后,我们进入正题:C语言与0的不解之缘


 bool 变量与"零值"进行比较

bool类型—C99定义

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

 bool类型源码如下:

#define bool         _Bool       //C99中是一个关键字,后续可以使用bool

#define false        0              //假

#define true         1              //真

由此可见bool类型实际就是来表示真假的

PS:理论上,表示真假,需要一个bit就够了,不过这个问题,还是要取决于编译器的理解。vs中认为是1个字节(其它编译器上就不一定是一个字节了)。那么问题来了,后面代码该怎么写?因为目前编译器对C99特性支持的并不全面,我们后面依旧默认使用C90的认识去编码即可,使用int表示真、假。具体要结合实际况情去定


BOOL类型—Microsoft专利

在vs中,光标选中BOOL,双击,可以看到转到定义,就能看到BOOL是什么

输出结果是4,因为在源代码中,是这么定义的:typedef int BOOL;  

这也就可以解释为什么结果是4了

这是什么情况?编译器竟然也能通过。

这都是Microsoft自己搞的一套BOOL值,我们这里不推荐使用,因为在其他编译器上可能会不通过,而bool是C99标准的,跨平台性比BOOL高,所以,后面万一要用bool,我们最好使用C99标准的。


C中如何进行 bool 值与0比较呢?

#include <stdio.h>
#include <stdbool.h>
#include <windows.h>

int main()
{
 int pass = 0;   //0表示假,C90,我们习惯用int表示bool
    //bool pass = false;   //C99

 if (pass == 0){  //理论上可行,但此时的pass是应该被当做bool看待的,==用来进行整数比较,不推荐                             
    //TODO
    }

 if(pass == false){ //不推荐,尽管在C99中也可行
    //TODO
    } 

 if(pass){//推荐
    //TODO
    } 
    //理论上可行,但此时的pass是应该被当做bool看待的,==用来进行整数比较,不推荐
    //另外,非0为真,但是非0有多个,这里也不一定是完全正确的

 if (pass != 1){
   //TODO
   } 

 if(pass != true){ //不推荐,尽管在C99中也可行
   //TODO
   }

 if(!pass){ //推荐
  //TODO
  }
 
 system("pause");
 return 0;
}

结论:bool类型,直接判定,不用操作符进行和特定值比较。


float 变量与"零值"进行比较

这里比较复杂,要理清楚更细节的内容,需要知道浮点数在内存中的存储原理,大家如果不清楚的话可以移步到我的另外一篇博客“浮点型存储”。

浮点数在内存中存储,并不想我们想的,是完整存储的,在十进制转化成为二进制,是有可能有精度损失的。注意:这里的损失,不是一味的减少了,还有可能增多。浮点数本身存储的时候,在计算不尽的时候,会“四舍五入”或者其他策略,请看下面两个代码:

  

结论:因为精度损失问题,两个浮点数,绝对不能使用==进行相等比较 

那么两个浮点数该如何比较呢?应该进行范围精度比较


范围精度比较


伪代码如下:

if((x-y) > -精度 && (x-y) < 精度)
{
    //TODO
}

//伪代码-简洁版

if(fabs(x-y) < 精度)
{   
  //fabs是浮点数求绝对值
  //TODO
} 

精度:
自己设置,后面如果有需要,可以试试,通常是宏定义。
暂时推荐使用系统精度
#include<float.h> //使用下面两个精度,需要包含该头文件
DBL_EPSILON         //double 最小精度
FLT_EPSILON         //float 最小精度

#include<float.h>      //使用下面两个精度,需要包含该头文件
DBL_EPSILON        //double 最小精度
FLT_EPSILON        //float 最小精度

//代码调整后

#include <stdio.h>
#include <math.h> //必须包含math.h,要不然无法使用fabs
#include <float.h> //必须包含,要不然无法使用系统精度
#include <windows.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_EPSILON
        printf("you can see me!\n");
    } 

    else{
        printf("oops\n");
    }

    return 0;
}

  两个精度定义

double类型的精度定义

#define DBL_EPSILON

2.2204460492503131e-016

 float类型的精度定义

#define FLT_EPSILON

1.192092896e-07F

XXX_EPSILON是最小误差,是: XXX_EPSILON+n不等于n的最小的正数。
EPSILON这个单词翻译过来是'ε'的意思,数学上,就是极小的正数

 


 拓展:

x > -DBL_EPSILON && x < DBL_EPSILON: 为何不是>= && <= 呢?
XXX_EPSILON是最小误差,是:XXX_EPSILON+n不等于n的最小的正数。
XXX_EPSILON+n不等于n的最小的正数: 有很多数字+n都可以不等于n,但是XXX_EPSILON是最小的,but,
XXX_EPSILON依旧是引起不等的一员。
换句话说:fabs(x) <= DBL_EPSILON(确认x是否是0的逻辑),如果=,就说明x本身,已经能够引起其他和他+-的数据本身的变化了,这个不符合0的概念,所以不应该加“=”


指针变量与“零值”进行比较

NULL, '\0', 0的整体理解

 上面三个打印结果是0,0,0,也就是说这NULL在数值上是等于0的;但是呢,他们本身的类型是不同的,在什么时候能体现出来呢?

比如说:char *p = 0;  

这行代码本身是有点奇怪的,它在这里把整数赋给了指针,编译器在编译的过程中可能会出现告警,当然了,在不同的编译器中可能会出现不一样的结果,比如vs就没有出现告警

所以我们在C语言当中就使用NULL来组织,这里又会有一些疑问,这个NULL是怎么变成0的呢?其实是强制类型转化,转到定义可以看到:

 我们可以看到这里的NULL其实就是一个用宏定义的,它就是一个0值,只不过把0的类型变成了void*


强制类型转化

把字符串“123456”转化为int型“123456”,由七个字节转化成四个字节,这个应该叫做真实的转化

真实的转化是需要我们编写算法,使用相关库函数进行的转化

而强制类型转化其实是用不同的类型去理解这个数据,这个数据本身没有改变

就好比如我们刚开始对一个人的印象是不好的,但是后来因为他做了一些好事,让你改变了对他的看法,但是他本身却没有发生改变,这其实就是强制类型转化

例:把1111 1111 1111 1111 1111 1111 1111 1111  这个二进制码由signed int 输出改为由unsigned int 形式输出,但是这个数据本身没有发生变化,这就是强制类型转化

所以,所谓的强制类型转化并不改变该数据在内存当中的任何二进制组合,只改变我们如何去解释该二进制;真实的类型转化改变内存中的数据。


指针变量与“零值”进行比较的 if 语句


int * p = NULL;        //定义指针一定要同时初始化


(A)写法: p 是整型变量?容易引起误会,不好。尽管 NULL 的值和 0 一样,但意义不同。
(B)写法: p 是 bool 型变量?容易引起误会,不好。
(C)写法:这个写法才是正确的,但样子比较古怪。为什么要这么写呢?是怕漏写一个“=”号,正常写法漏掉一个“=”:if(p = NULL),这个表达式编译器当然会认为是正确的,但却不是要表达的意思。如果用(C)的写法,漏掉“=”时:if(NULL=p),此时编译器会立刻报错,相当于有了一个自检查。

所以我们推荐第三种写法


 写在最后:

        以上就是C语言和0的”故事“了,如果有错误或者不严谨的地方,希望读者能够不吝指教,当然,如果你觉得写的还不错,不要忘了点赞、收藏、关注哦!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

幸福西西弗斯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值