【C与0的不解之缘】

简介: 【C与0的不解之缘】

引言

       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 //使用下面两个精度,需要包含该头文件

DBL_EPSILON         //double 最小精度

FLT_EPSILON         //float 最小精度


#include      //使用下面两个精度,需要包含该头文件

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的”故事“了,如果有错误或者不严谨的地方,希望读者能够不吝指教,当然,如果你觉得写的还不错,不要忘了点赞、收藏、关注哦!

相关文章
|
7月前
|
JavaScript 前端开发 Java
二十年编程语言风云,哪款是你的爱豆?
二十年编程语言风云,哪款是你的爱豆?
|
缓存 监控 安全
偶遇DDoS攻击-江湖厮杀之一波三折
偶遇DDoS攻击-江湖厮杀之一波三折
152 0
|
Java Maven 数据库
他山之石,可以攻玉
本文章探讨了Springboot的启动过程,仅供个人学习
106 0
|
Java 中间件 应用服务中间件
他山之石,可以攻玉
本文将介绍Nginx的简单部署方法,仅供个人学习。
104 0
|
测试技术 Android开发
他山之石
他山之石
150 0
(转)阿里八卦:L氓出没,注意!
(转自 http://medic.iteye.com/blog/1056515) 2007年我面试了一个被阿里面试后刷掉的电话销售人员,她本来在广州有一份好好的工作,之所以来杭州就是想进阿里。
831 0
|
存储 安全 大数据
确认过眼神?上云之路需要遇上对的人!
在“上云就上阿里云”解决了上什么云的问题之后,如何上云成为企业技术人员头疼的问题。业务系统云上应用基础架构应该如何设计、系统存储与数据库如何才能平滑迁移等等成为企业上云之路的障碍。为了解决企业上云前的痛点,阿里云支持与服务团队重磅推出咨询与设计场景下五款专家服务产品。
|
新零售 供应链 双11
比肩雪梨张大奕,网红于momo身边有个秘密武器
10月25日,刚忙完淘宝店店庆的于MOMO抽空飞到深圳。她不是来拍照,也不是来工厂看订单,而是参加福布斯中国举办的活动。作为优秀创业者,她入选了2017福布斯中国30位30岁以下精英榜。 在各自不同的领域,这些上榜的年轻人们正在用自己的力量改变世界。
2718 0