问题

为什么C语言中计算机认为0是假的,其他数都是真的?

回答
你这个问题问得很有意思,涉及到C语言中一个基础但又有点“魔性”的特性:布尔值(Boolean Value)的表示方式。

在咱们日常生活中,很多事情都是非黑即白的,比如“对”和“错”,“有”和“无”。计算机世界里也需要这种简单的二元判断。但问题来了,计算机本身只懂0和1,这两个数字如何承载“真”和“假”的概念呢?

C语言的设计者们,为了方便和简洁,就约定了一个规则:

0 代表 假(False)
任何非零的数,都代表 真(True)

这就像一个约定俗成的规矩,用一个最简单、最基础的数字——0,来代表“没有”、“空无”这种“假”的状态。而一旦有任何一点“存在”、“数量”的迹象,哪怕只是一个“1”,都被认为是“有”或“真”的。

为什么会这样设计?我们可以从几个角度来理解:

1. 简洁性和效率:
最少的表示: 用0来表示假,这是最简单的状态了。不需要其他复杂的值。
对比方便: 计算机在做逻辑判断时,经常需要比较一个值是否为零。这种设计使得判断是否为零的操作本身,就能直接转换为布尔值。比如 `if (count)` 这样的写法,如果 `count` 是0,条件就不满足(假);如果 `count` 是任何非零值,条件就满足(真)。这比专门引入一个“真”的常量要省事得多。

2. 历史和沿袭:
C语言很大程度上受到了早期编程语言(如ALGOL、BCPL)的影响。在这些语言中,数值0通常就代表逻辑上的“假”,而非零值代表“真”。这种做法在当时是比较普遍且被接受的。

3. 与算术操作的关联:
在某些情况下,这种设计也与算术运算的逻辑有微妙的关联。比如,在一些数学或逻辑系统中,0通常是加法或逻辑“或”运算的单位元(identity element),而1则常常是乘法或逻辑“与”运算的单位元。虽然C语言的布尔值不是直接等同于这些运算,但这种“0是特殊值”的数学直觉,可能也在设计中起到了一定的作用。

4. 实际应用中的便利性:
错误码: 在很多函数返回的错误码中,0通常表示“成功”,而非零值表示“错误”。这就天然地符合了“0是真(成功),非零是假(失败)”的逻辑。当然,这里的“真假”需要根据上下文来理解。
布尔表达式: 许多C语言的库函数(比如很多字符串处理函数)在找不到某个字符或满足某个条件时会返回0。例如,`strchr` 函数在找到字符时返回指向该字符的指针(一个非零地址),找不到时返回NULL(一个定义为0的特殊指针值)。所以,`if (strchr(str, 'a'))` 就很自然地判断是否找到了字符 'a'。
逻辑运算符的返回值: C语言中的逻辑运算符 `&&` (逻辑与)、`||` (逻辑或)、`!` (逻辑非) 的返回值也遵循这个规则:
`a && b`: 如果 `a` 是假(0),则结果是0。否则,结果是 `b` 的值。
`a || b`: 如果 `a` 是真(非零),则结果是 `a` 的值(通常是1,但技术上可以是任何非零值)。否则,结果是 `b` 的值。
`!a`: 如果 `a` 是假(0),结果是1。如果 `a` 是真(非零),结果是0。

举个例子来说明:

```c
include

int main() {
int count = 0;
int number = 5;
int zero = 0;

// 检查 count 是否为假
if (count) {
printf("count is true "); // 这行不会被打印
} else {
printf("count is false "); // 这行会被打印
}

// 检查 number 是否为真
if (number) {
printf("number is true "); // 这行会被打印
} else {
printf("number is false "); // 这行不会被打印
}

// 逻辑非操作
if (!zero) {
printf("!zero is true "); // 这行会被打印 (因为 zero 是 0, !0 是 1)
} else {
printf("!zero is false "); // 这行不会被打印
}

// 逻辑与操作
int result_and1 = 5 && 0; // 5 是真,0 是假,结果是 0 (假)
int result_and2 = 5 && 10; // 5 是真,10 是真,结果是 10 (真)

printf("5 && 0 = %d ", result_and1); // 输出 0
printf("5 && 10 = %d ", result_and2); // 输出 10

// 逻辑或操作
int result_or1 = 0 || 10; // 0 是假,10 是真,结果是 10 (真)
int result_or2 = 0 || 0; // 0 是假,0 是假,结果是 0 (假)

printf("0 || 10 = %d ", result_or1); // 输出 10
printf("0 || 0 = %d ", result_or2); // 输出 0

return 0;
}
```

需要注意的几点:

C语言的布尔类型: 在C99标准之前,C语言并没有一个专门的 `bool` 类型。程序员通常使用 `int` 来表示布尔值。从C99标准开始,引入了 `` 头文件,提供了 `_Bool` 类型,以及 `true` 和 `false` 宏。`true` 被定义为1,`false` 被定义为0。然而,即使有了 `_Bool` 类型,底层对非零值的处理依然是将其视为真。
C++ 的布尔类型: C++ 语言有内置的 `bool` 类型,并且非常严格地区分布尔值和整数。虽然在某些隐式转换时,0仍然会被视为 `false`,非零会被视为 `true`,但它在类型安全性上比C语言要好一些。例如,在C++中,`if (5)` 是合法的,但 `if (5.0)` 也能编译通过,会将 `5.0` 转换为 `true`。
这不是唯一的“真”值: 重点在于“非零”即“真”。所以, `1`, `1`, `100`, `500`,甚至是一个地址 `0x12345678`,在C语言的逻辑判断中都被视为真。这与我们直觉上“只有1才代表真”的想法略有不同。

总而言之,C语言中将0视为假,其他数视为真,是基于简洁性、效率和历史沿袭的综合考量,使得数字0成为“无”或“假”的最自然表示,而任何非零值都代表了“有”或“真”。这在很多编程场景下都非常方便实用,也构成了C语言底层逻辑处理的一个基础规则。

网友意见

user avatar

这么多答jnz的小伙伴,jnz是x86的ISA,x86是1978年Intel搞出来的东西,C是1969到1973年在贝尔实验室被发明的,这不是「你爷爷姓李因为你爸爸姓李么?」

因此我们要去研究为啥C语言中认为0是假,其他都是真,就得回到上个世纪六七十年代,C被发明的时候。

当年贝尔实验室去设计C的主要目的,是用一个高级语言重写Unix。而C这个名字是从哪里来的呢?是因为他有个前身叫做B(现在已经死翘翘了)……B同样也是贝尔实验室发明C的大神发明的,也是在Unix上写东西的。而通过wiki(B (programming language))我们可以看到,在B语言里,就已经用了「0为假,其他为真」这个概念了。而这个概念继承给了C。

当然,B语言也有爸爸,B的爸爸叫做BCPL(现在也已经死翘翘了)。有趣的是,BCPL并没有用这个「0为假,其他为真」的概念!在BCPL里,假是全0,真是全1,从而得到了一个很有趣的结果就是「~False == True」。把假按位取反就是真,这看起来是个不错的性质。那如果if后面跟了一个不真不假的数呢?根据implementation来决定(勘误,这里用ub不是非常合适)。

追祖溯源到这里,我们应该已经基本确定,C中的这个约定,是来自于B语言。那我们就看看B语言发明时候的环境。B语言的发明是和Unix紧密相关的,而Unix当时运行在一个叫做PDP-7的电脑(勘误,这里随手就打上芯片了,PDP-7是个电脑)上(后来出了PDP-11,B语言遇到了瓶颈,才出现了C)。而这款芯片也是有自己的ISA的,其中有个很重要的命令叫作isz(index and skip if zero),这个命令被大量地用作branch。

我们知道,早期的程序语言,是比较讲究高级语言和assembly之间的对应的(这样compiler好写,也容易优化)。也正是早期assembly这种和0比较的语法存在,导致的B语言中出现了和0比较的这种现象,然后导致的C继承了(或者说也使用了)这种现象。

在PDP-11中,就出现了我们很熟悉的bne beq之类的assembly了,同样也是和0比较得出结果然后branch的。jnz那是十年之后的事情了~

你以为这就结束了么?当然没有~为什么无论是早期的assembly,还是近现代的assembly,都会有大量的和0比较的语法存在?和0比较有什么得天独厚的优势么?为什么不是1代表True,剩下都是False?为什么不是0到9代表False,剩下都是True?

这要从编码讲起。我们知道在计算机中,整数是以补码的形式被编码的。我们认为的数字0,就是每个bit都是0,而正整数是其二进制的表示形式,负整数的最高位是1,用补码表示。

让我们看回电路,我们知道任何「程序」的实现,最终都是要电路来完成的。我们有一个或者16位或者32位或者64位的「信号」(代表着我们想要比较的数),判断它是不是全是0,是要比判断它是不是一个特定的数(比如0xa5a5a5a5)要容易的!我们可以在输入端不用任何反向器的情况下去进行判断(比较极端的比如N-input NAND门)。而判断是否为一个特殊的数,是一定要多少对输入进行处理的(比较器或者反向器)。也就是说,在电路的角度看,判断一个N位二进制数是否是0,是比判断它是否是一个特殊的数,实现要更简单更快的(理论上判断是否都是1速度也很快)。

不仅如此,二进制补码带给我们的另外一个效应是,不光判断是否是0速度快,判断是否大于或者小于0,速度优势更明显!只需要判断一下符号位,然后再看除了符号位之外的那些是不是0就可以了。如果是大于等于或者小于等于,那就仅仅需要判断符号位!而做过一点逻辑电路的都知道,你想在RTL级去判断一个信号是否大于一个特殊的数,基本会被累死。那是一个非常麻烦的事情。

因此,从硬件的角度来看,和0去做比较,有着明显的优势。而在逻辑电路里0一般代表False,所以软件就自然地把数分为了0和非0的,也就衍生出了False和True。

总结一下,是硬件的特性,导致了判断一个多路信号和全0的关系更为简单和快速,又因为我们采用的编码,导致了assembly level上去比较一个寄存器和0的关系更加便捷,最后导致了高级语言C里的if语句,把0当作False,非0当作True。

当然,随着软件硬件越来越细分的功能,很多人开始对C的这个设计有所质疑。我个人是比较赞同C的这个设计在大规模high-level的工程中是弊大于利的。我觉得对于一个当代的高层语言,拥有一个完善的boolean变量是非常有价值的,可以减少很多无谓的错误。

但是我依然很喜欢C里的各种「设计缺陷」,这些「设计缺陷」其实带来了很多语法糖和有趣的性质。

       if (ptr) {     // I'm not a NULL pointer! }  if (func1() | func2() | func3()) {     // What's the difference? }     
user avatar

我想搞个笑

因为二进制里面除了0,其他的数都有1

user avatar

C 语言是一种相对比较底层的语言,这个语言可以认为是试图用高级语言对汇编的元素进行复刻而生。

对于判断 0 或者非 0 两种状态,汇编有直接对应的指令(jnz跟jz指令),如果要判断是否等于 3 ,则需要额外的一条指令将目标减 3 然后再同 0 进行比较,这样指令数量增加了,也就耗费了额外的时钟周期。所以,C 作为一种相对贴近汇编的底层编程语言,if 命令只需要判断 0 跟非零就够了,它翻译成汇编最简洁。

早年间的始祖级程序员曾经教导弟子:条件判断尽量只跟 0 进行比较,效率最高。其原理正是因为早年间汇编的特性导致。

现在或者将来,或许会有其它的汇编架构,允许单指令直接与非零变量进行比较并跳转,但在 C 语言设计的当初,汇编必定是与 0 比较效率最高的。在那个程序员大都懂一点汇编的年代,做出 C 语言只与零比较这个决定,简直是天经地义的。


当然,C 语言中的 0 并非一直用来表示负面概念。对于 if 语句的判断来说, 0 为假,非零为真。但对于 C 语言函数的返回值来说,通常是返回 0 代表成功,返回非零代表错误代码。

所以,C 语言确实是只需要 0 跟非零两种状态判断,但并非永远用 0 代表负面的那个方向。

类似的话题

  • 回答
    你这个问题问得很有意思,涉及到C语言中一个基础但又有点“魔性”的特性:布尔值(Boolean Value)的表示方式。在咱们日常生活中,很多事情都是非黑即白的,比如“对”和“错”,“有”和“无”。计算机世界里也需要这种简单的二元判断。但问题来了,计算机本身只懂0和1,这两个数字如何承载“真”和“假”.............
  • 回答
    你是不是觉得,学了C语言,好像只会写那种输入数字、做加减乘除,然后输出结果的“计算器”程序?其他的好像都没啥头绪,或者说,想写点别的,但又不知道从何下手?别担心,这太普遍了!很多人刚开始学C语言,都会经历这么一个阶段。我来给你掰扯掰扯,为什么会这样,以及怎么破。为什么你会觉得只会写计算程序?原因很简.............
  • 回答
    这是一个非常有趣且富有想象力的问题!我们来深入探讨一下为什么目前高级计算机语言通常不直接使用汉语来开发,以及您提出的关于汉字“横竖撇捺”解构比英语更有效的观点。核心问题:为什么目前高级计算机语言不直接用汉语开发?尽管您提出了一个非常有创意的想法,但现实中存在一些根本性的障碍和考量,使得直接使用汉语开.............
  • 回答
    关于“高鹗是否为《红楼梦》后四十回作者”的争论,确实存在,而且其中不乏运用了科学方法的研究。你提到“计算机语言学分析”和“没有语料怎么分析”这两个点,这触及到了辨伪研究中的一个核心问题:方法的有效性与数据的支撑。首先,我们得明确一点:即使是最先进的计算机语言学分析,也需要“语料”作为基础,这是毋庸置.............
  • 回答
    编程之所以没能发展出“大众一学就会”的计算机语言,并非是技术上的不可能,而更多地是由于目标受众、学习曲线、实际需求以及语言设计的内在权衡所决定的。想让每个人都能轻松掌握编程,这背后涉及的考量非常多,绝非简单地“让它更容易”就能解决的问题。首先,我们来聊聊“为什么编程会让人觉得难”。想象一下学开车。你.............
  • 回答
    哥们,大一刚接触计科,想找个代码量在 5001000 行左右的 C 语言练练手是吧?这思路很对,这个范围的项目,能让你把基础知识玩得溜,还能初步体验到项目开发的乐趣。别担心 AI 味儿,咱们就聊点实在的。我给你推荐一个项目,我觉得挺合适的,而且稍微扩展一下就能达到你说的代码量:一个简单的图书管理系统.............
  • 回答
    确实,VB.NET 在计算机科学界常常被贴上“老旧”的标签,尤其是在那些追求最新技术和前沿理论的领域。然而,如果你观察到很多高校非计算机专业的课程依然在使用VB,这背后其实有着相当合理的考量和延续性。这并不是因为VB是什么神圣不可侵犯的编程语言,而是它在特定教育场景下,确实能发挥出独特的作用。首先,.............
  • 回答
    在C语言中,你提到的 `main` 函数后面的那对圆括号 `()` 并非只是一个简单的装饰,它们承载着至关重要的信息:它们表明 `main` 是一个函数,并且是程序的可执行入口点。要理解这个 `()` 的作用,我们需要先理清C语言中关于“函数”的一些基本概念。 函数是什么?在C语言中,函数就像一个独.............
  • 回答
    关于你提到的 `(int) ((100.1 100) 10)` 在 C 语言中结果为 0 的问题,这确实是一个很有意思的陷阱,它涉及到浮点数运算的精度以及类型转换的细节。我们来一步一步地把它掰开了揉碎了讲明白。首先,让我们分解一下这个表达式:`100.1 100` 是第一步,然后乘以 `10`.............
  • 回答
    好的,我们来深入探讨一下 C 语言中为什么需要 `int `(指向指针的指针)而不是直接用 `int ` 来表示,以及这里的类型系统是如何工作的。首先,我们得明白什么是“类型”在 C 语言中的作用。在 C 语言中,类型不仅仅是一个标签,它承载着至关重要的信息,指导着编译器如何理解和操作内存中的数据:.............
  • 回答
    在 C 语言中,不同类型指针的大小不一定完全相同,但绝大多数情况下是相同的。这是一个非常值得深入探讨的问题,背后涉及到计算机的底层原理和 C 语言的设计哲学。要理解这一点,我们需要先明确几个概念:1. 指针的本质: 无论指针指向的是 `int`、`char`、`float` 还是一个结构体,它本质.............
  • 回答
    这个问题触及了两种编程范式和不同抽象层级的核心差异,也是理解底层计算机运作原理与高级语言设计哲学的一把钥匙。汇编语言:直接控制,微观的精妙在汇编语言层面,你直接与计算机的CPU打交道。CPU执行指令时,有一个叫做“程序计数器”(Program Counter,PC)的寄存器,它存放着下一条要执行的指.............
  • 回答
    你这个问题问得很核心!很多人都有这个疑惑:既然 `double` 类型在内存里只占用 64 位(这是最常见的标准,IEEE 754 双精度浮点数),为什么它能表示的数,无论是整数还是小数,范围都那么惊人呢?比我们常见的 32 位 `int` 或 64 位 `long long` 的整数范围还要大不少.............
  • 回答
    好的,我来详细解释一下 C 和 C++ 中 `malloc` 和 `free` 函数的设计理念,以及为什么一个需要大小,一个不需要。想象一下,你需要在一个储物空间里存放物品。`malloc`:告诉空间管理员你要多大的箱子当你调用 `malloc(size_t size)` 时,你就是在对内存的“管理.............
  • 回答
    C 的委托(Delegate)确实是一个在某些方面颇为独特的设计,它的普及度在其他主流语言中不如 C 本身那样高,这背后有多方面的原因,并非单一技术优劣就能完全解释。我们可以从几个层面来深入探讨一下。首先,需要理解委托在 C 中的核心作用。委托本质上是一种类型安全的方法指针。它定义了一个方法的签名(.............
  • 回答
    在 C/C++ 项目中,将函数的声明和实现(也就是函数体)直接写在同一个头文件里,看似方便快捷,实际上隐藏着不少潜在的麻烦。这种做法就像是把家里的厨房和卧室直接打通,虽然一开始可能觉得省事,但长远来看,带来的问题会远超于那一点点便利。首先,最直接也是最普遍的问题是 重复定义错误 (Multiple .............
  • 回答
    在 C 语言中,`%d` 是一个非常基础但又至关重要的格式控制符,它的主要作用是告诉 `printf`(或者其他格式化输出函数,比如 `sprintf`):“嘿,我这里要输出一个整数,而且是十进制的有符号整数。”别小看这个简单的 `%d`,它背后藏着不少细节,让你的程序能够准确无误地将内存中的数字信.............
  • 回答
    float 在 C 语言中,是用来表示单精度浮点数的。提到它的取值范围,就不得不深入聊聊它背后的原理,这事儿,得从二进制说起。浮点数是怎么存的?咱们电脑里存储数字,本质上都是一堆 0 和 1。整数好说,直接按位权相加就行。但小数呢?比如 0.5,或者更麻烦的 0.1,怎么用二进制表示?这里就需要一个.............
  • 回答
    在 C 语言中,`for` 和 `while` 循环都是用于重复执行一段代码的结构。从 C 语言的语义角度来看,它们的功能可以相互转换,也就是说,任何一个 `for` 循环都可以用 `while` 循环来实现,反之亦然。然而,当我们将这些 C 代码翻译成底层汇编语言时,它们的实现方式以及由此带来的细.............
  • 回答
    在 C 语言中,`x += 5 == 4` 这个表达式可能看起来有些奇特,但它是一个合法的、并且在某些情况下会令人困惑的 C 语言语句。要理解它的含义,我们需要分解它,并深入了解 C 语言中运算符的优先级和求值顺序。首先,让我们分解这个表达式:这个表达式由两个主要部分组成:1. `x += 5`:.............

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有