嗨翻C语言--这里没有蠢问题(一)

时间:2020-12-07 02:04:31


问:card_name[0]是什么意思?
答:它是用户输入的第一个字符。如果用户输入了10,那么card_name[0]就将是1。
问:总是得用/*和*/写注释吗?
答:如果你的编译器支持C99标准,就可以用//开始注释。编译器会将这一行的其余部分当做注射处理。
问:怎么才能知道我的编译器支持哪种标准?
答:你可以查看编译器的文档。对gcc来讲,ANSI,C,C99和C11这三种标准它全部支持。
问:为什么我在linux和Mac中运行程序时必须在程序前加上./?
答:因为在类Unix操作系统中,运行程序必须指定程序所在的目录,除非程序的目录已经列在了PATH环境变量中。
问:为什么字符要从0开始编号?为什么不是1?
答:字符的索引值是一个偏移值:它表示当前要引用的这个字符到数组中第一个字符之间有多少个字符。
问:为什么要这样做?
答:计算机在存储器中以连续字节的形式保存字符,并利用索引计算出字符在存储器中的位置。如果计算机知道c[0]位于存储器1 000 000号单元,那么就可以很快地计算出c[96]在1 000 000+96号单元。
问:为什么要设立哨兵字符?难道计算机就不知道字符串的长度吗?
答:通常不知道。记录数组的长度不是C语言的强项,字符串其实就是个数组。
问:C语言居然不知道数组有多长?
答:是的,虽然编译器有可以通过分析代码计算出数组的长度,但一般情况下,C语音希望你来记录数组的长度。
问:单、双引号有区别吗?
答:有区别,单引号通常用来表示单个字符,而双引号通常用来表示字符串。
问:我应该用双引号(“)定义字符串。还是以显式字符数组的形式定义字符串?
答:通常应该用双引号来定义字符串。用双引号定义的字符串叫字符串字面值(string literal),比起字符数组,它输入起来也更方便。
问:字符串字面值和字符数组有没有区别?
答:只有一个区别:字符串字面值是常量。
问:那是什么意思?
答:也就是说这些字符一旦创建完毕,就不能在修改它们。
问:如果我改了会怎么样?
答:这取决于编译器,gcc通常会显示总线错误(bus error)。
问:总线错误?那是什么东西?
答:C语言采取不同的方式在存储器中保存字符串字面值。总线错误意味着程序无法更新那一块存储器空间。
问:为什么不能写一个|和&?
答:也不是不行。&和|操作符总是计算两个条件,而&&和||可以跳过第二个条件。
问:那还要|和&干什么呢?
答:对逻辑表达式求值只是它们的一个用处,它们还能对数字的某一位进行布尔运算。
问:那是什么意思?
答:6 & 4等于4,是因为当对(二进制110)和4(二进制数100)的每一个二进制位布尔与时,就会得到4(二进制数100)。
问:为什么我要用switch语句取代if?
答:当需要多次检查同一变量时,使用switch语句会更方便。
问:使用switch语句有什么好处?
答:有这几个好处。第一,让代码更清晰,一段代码处理一个变量的结构,结构一目了然,相反,一连串的if语句就没那么清晰了;第二,可以用下落逻辑在不同的分支之间复用代码。
问:switch语句只能检查变量吗?它能检查值吗?
答:能,switch语句仅仅检查两个值是否相等。
问:我能在switch语句中检查字符串吗?
答:不能用switch语句检查字符串或任何形式的数组,switch语句只能检查值。
问:如果我创建了一个void函数,是否就意味它一定不能有return语句?
答:你还是可以包含return语句,但编译器很可能会产生一条警告消息。而且在void函数中包含return语句没有任何意义。
问:真的吗?为什么没有意义?
答:因为如果你试图读取void函数的值,编译器会报错。
问:C语音为什么需要编译?其他一些语音就不需要编译,比如JavaScript,是吗?
答:为了让代码执行起来更快,C语音需要编译。尽管有写语音不是编译型语言,但它们中的一些,向JavaScript和Python,为了提高速度通常会在幕后使用一些编译技术。
问:C++是另一个版本的C语音吗?
答:不是,虽然C++的设计初衷是为了扩展C,但现在看来远不止如此,人们最初创造的C++和Objective-C都是为了C语音面向对象的程序。
问:什么是面向对象?我们在本书中会学吗?
答:面向对象是一种对抗软件复杂性的技术,我们在本书中不会做专门的研究。
问:C语音为什么看起来很像JavaScript,Java和C#语言?
答:C语言的语法非常简洁,因此影响到了很多其他语言。
问:gcc这三个字母分别代表什么含义?
答:GNU编译器套装(GNU Compiler Collection)
问:为什么是“套装”?难道不止C语言一种吗?
答:GNU编译器套装可以用来编译很多语言,而C语言可能是人们在应该gcc时使用最多的语言。
问:我能创建一个永无止境的循环吗?
答:可以,如果循环条件的值是1,循环就会永无止境地运行下去。
问:创建一个永无止境的循环是个好主意吗?
答:有时候是,通常在一些如网络服务器的程序中会使用无限循环(一个永无止境的循环),程序会反复地做一件事知道人们停止它。但大部分的程序员使用循环是为了让它们在某个时刻停止。
问:我在自己的机器上打印了变量的单元号,但它不是4 100 000。是不是哪里做错了?
答:你没有做错,在不同机器中,程序用来保存变量的存储器单元号不同。
问:为什么局部变量保存栈里,而全局变量保存在其他地方?
答:局部变量和全局变量的用法不同。你永远只能得到一份全局变量,但如果写了一个调用自己的函数,就会得到同一个局部变量的很多个实例。
问:存储器中的其他区域是用来做什么的?
答:你会在本书的后续章节中看到它们的作用。
问:指针是真实的地址单元,还是某种形式的引用?
答:它们是进程存储器中真实编号的地址。
问:为什么存储器是进程的?
答:计算机会为每个进程分配一个简版存储器,看起来就像是一长串字节。
问:但存储器并非如此?
答:实际的存储器复杂多了,但细节对进程隐藏了起来,这样操作系统就可以在存储器中移动进程,或释放并重新加载到其他位置。
问:存储器不仅仅是一长列字节?
答:物理存储器的结构十分复杂,计算机通常会将存储器地址分组映射到存储芯片的不同的存储体(memory bank)。
问:我需要理解它是怎么映射的吗?
答:对大部分程序来说,你不需要关心机器组织存储器的细节。
问:为什么我一定要用%p格式串来打印指针?
答:不一定要用%p,在大多数的现代计算机上可以用%li,但编译器可能会给出一条警告。
问:为什么%p以十六进制显示存储器地址?
答:工程师通常以十六进制表示存储器地址。
问:如果我们把读取存储器单元的内容称为“解引用”,那么指针是不是应该叫“引用”?
答:人们有时候会把指针叫做“引用”,因为它引用了存储器中的某个地址单元。但C++程序员通常用“引用”表示C++中一个稍有不同的概念。
问:太好了,C++,我们会学到吗?
答:别想了,这本书只教C语言
问:sizeof是一个函数吗?
答:不是,它是一个运算符。
问:有什么区别?
答:编译器会把运算符编译为一串指令,而当程序调用函数时,会跳到一段独立的代码中执行。
问:所以程序是在编译期间计算sizeof的?
答:没错,编译器可以在编译时确定存储空间的大小。
问:为什么在不同的计算机上指针变量的大小不同?
答:在32位操作系统中,存储器地址以32位数字的形式保存,所以它叫32位操作系统。32位==4字节,所以64位操作系统要用8个字节来保存地址。
问:如果我创建了一个指针变量,它位于存储器中吗?
答:是的,指针变量只不过是一个保存数字的变量罢了。
问:我可以找到指针变量的地址吗?
答:可以用&运算符找到它的地址。
问:我可以把指针转化为一般的数字吗?
答:在大多数操作系统中,可以这样做。C编译器通常会把long数据类型设为和存储器地址一样长。如果想要把指针p保存在long变量a中,可以输入a=long(p)。
问:是在大多数操作系统中吗?所以并不是全部?
答:并不是全部。
问:我真的需要理解指针算术运算吗?
答:有的程序员不使用指针算术运算,因为它很容易出错,但你可以用它有效地处理数组数据。
问:指针能做减法吗?
答:能,但小心别让指针越过数组的起点。
问:C语言什么时候对指针算术运算进行调整?
答:在编译器生成可执行文件时,编译器会根据变量的类型,用变量的大小乘以指针的增量或减量。
问:然后呢?
答:假如编译器看到你对一个指向int数组的指针加2,就会用2乘以4(int长度),然后对地址加8。
问:C语言在调整指针算术运算时用了sizeof运算符吗?
答:本质上如此,sizeof运算符的结果也是在编译时决定的,对各种数据类型,sizeof和指针算术运算都将使用相同的长度。
问:指针可以相乘吗?
答:不可以。
问:为什么要把数组定义成tracks[][80]而不是tracks[5][80]?
答:也可以这样定义,但编译器知道列表有5项,所以你可以省略5写成[]。
问:既然如此,为什么不直接写tracks[][]?
答:每首歌的名字不一样长,为了放下最长的歌名,需要让编译器分配足够大的空间。
问:也就是说tracks数组中每个字符串都有80个字符?
答:程序会为每个字符串分配80个字符,即使歌名很短。
问:所以tracks数组一共占了80 * 5 = 400字符?
答:没错。
问:如果我忘了string.h这样的头文件会怎样?
答:对于某些头文件来说,编译器会给出一个警告,但最后还是会包含它们;但对另一些来讲,编译器会直接提示编译错误。
问:为什么我们要把tracks数组定义在函数外面?
答:我们把tracks放在全局域,全局变量可以在所有函数中使用。
问:既然我们创建了两个函数,计算机会先运行哪一个?
答:程序总是首先运行main()函数。
问:为什么我一定要把find_track()放在main()之前?
答:在调用函数前,编译器需要知道两件事,函数接收什么参数以及函数的返回值类型是什么。
问:如果我把main()放在前面会怎么样?
答:你会得到几个警告。
问:既然有stdout和stderr,自然就有stdin吧?
答:有,如你所料,它代表标准输入。
问:我可以打印stdin吗?
答:不可以
问:我可以从stdin中读取数据吗?
答:恩,你可以使用fscanf()来读取,它的用法和scanf()很像,区别是可以自动fscanf()从哪条数据流中读取数据。
问:也就是说fscanf(stdin,...)和scanf()等价?
答:没错,它完全相同。说到底,scanf()就是用fscanf(stdin,...)实现的。
问:我可以重定向标准错误吗?
答:可以,你可以用>重定向标准输出,2>重定向标准错误。
问:所以我要写geo2json > errors,txt?
答:没错
问:为什么小工具要使用标准输入和标准输出?
答:有了它们,就可以轻易用管道将小工具蒙串连起来。
问:为什么要把它们串连在一起?
答:小工具只能解决一个小技术问题,例如转换数据的格式,而无法解决整个问题。只能把它们组合在一起,才能解决大问题。
问:到底什么是管道?
答:不同操作系统实现管道的方法不同,可能用存储器,也可能用临时文件。我们只要知道它从一端接收数据,在另一端发送数据就行了。
问:如果两个程序用管道相连,第二个程序要不要等第一个程序执行完后才能开始运行?
答:不需要,两个程序可以同时运行,第一个程序一发出数据,第二个程序马上就可以处理。
问:为什么小工具要使用文本?
答:文本是一种开发格式,程序员可以用文本编辑器来查看小工具的输出,并理解里面的内容,相比之下,二进制格式就难懂多了。
问:我能用管道连接多个程序吗?
答:能啊,只要在每个程序前加上一个1就行了,一连串相连的进程就叫流水线(pipeline)。
问:当我用管道连接多个进程时,<与>分别重定向哪个进程的标准输入,哪个进程的标准输出?
答:< 会把文件内容发送到流水线第一个进程的标准输入, >会捕获流水线中最后一个进程的标准输出。
问:当我在命令行中运行bermuda和geo2json程序时,它们外面的括号是必须的吗?
答:是的,这对括号保证保证了数据文件由bermuda程序的标准输入来读取。
问:最多能有几条数据流?
答:这取决于操作系统。通常情况下,一个进程最多可以有256条数据流,但请记住,数据流的数量是有限的,用完后应该关闭它们。
问:为什么FILE要大写?
答:说来话长,最早FILE是用宏定义的,而宏的名字通常都要大写。
问:我能合并两个选项吗?例如用-td now代替-d now -t。
答:可以,getopt()函数会全权处理它们。
问:我可以改变选项之间的顺序吗?
答:可以,因为我们用循环读取选项,所以-d now -t, -t -d now, -td now都一样。
问:也就是说,只要程序在命令行看到一个前缀为 - 值,就会把它当成选项处理?
答:是的,前提是它必须在命令行参数之前出现。
问:如果我想在命令行参数使用负数怎么办?像set_temperature -c -,程序会把4当做选项吗?
答:为了避免歧义,可以用--隔开参数和选项,比如set_teperature -c -- -4。getopt()看到-- 就会停止读取选项,程序会把后面的内容当成普通的命令行参数读取。
问:为什么不同操作系统的数据类型大小不同?设成一样不是更明了?
答:为了适应硬件,C语言在不同的操作系统与处理器上使用不同的数据类型大小。
问:怎么说?
答:C语言诞生之初还是8位机的天下,但现在大部分计算机都是32位和64位的,因为C语言没有指定数据类型的具体大小,所以才能与时俱进。即使新的计算机处理,C语言还是能够很好适应。
问:8位,64位到底是什么意思?
答:从技术上来讲,计算机的位数有多种含义,它既可以代表CPU指令的长度,也可以代表CPU一次从存储器读取数据的大小。实际上,位数是计算机能够处理的数值长度。
问:这和int, double的大小有什么关系?
答:如果一台计算机能够处理32位的数值,就会把基本数据类型(例如int)的大小设为32位。
问:我知道int这样的整数是怎么工作的,但float或double是怎么保存的呢?计算机如何表示有小数点的数字呢?
答:一言难尽,大部分计算机使用了IEEE发布的标准。
问:我需要理解浮点数的工作原理吗?
答:不需要,大部分程序员使用float与double时不会关注它们的细节。