关于本教程

重构C语言系列文档,把以前写的文档和一些杂七杂八的记录,整理成一个比较完整的wiki教程。

驱动完成这件事的原因有三,一是之前写了很多C语言的文档,但很久没看了,怕忘掉,不想浪费以前的文笔。二是巩固自身基础,把一些边边角角的知识点再过一遍,三是加入一些近几年工作过程中随手记录的新知识点作作为补充。

这个wiki教程最终要实现的目的是做“加法”,也就是老的知识点可以轻易地通过知识图谱找到,同时如果有新的想法,也能很容易地加入进来,让知识以网络的形式向外延伸。

这系列文档参考了以下三本书:

C语言圣经,江湖传闻学C语言只需要吃透这本书即可。

C语言面试八股必备,专门探讨C语言的各种易错知识点和奇淫技巧。

与前一本类似,但更偏重编码,适合敲代码练手。


另外提一下,个人认为,内存才是C语言的关键,尤其是对于嵌入式开发人员,了解并掌握内存具有举足轻重的作用。许多重要的概念,比如数据类型,作用域,生存周期,函数,指针,数组,都可以通过内存进行非常形象的解释。通过画内存图的方式,将不容易理解的概念具象化,对加深掌握是非常有好处的。

为了配合文档描述,本系列教程指定的开发环境是32位linux系统,之所以不用64位的系统,是因为在理解程序内存分布时,32位linux系统具有非常典型的内存分布图,相比64位系统的内存分布更加简单易懂。

程序设计概述

随便唠叨一些程序设计相关的东西,不看也没关系。

计算机与程序

从计算机运行原理的角度分析,计算机其实是非常不智能的,因为计算机所有要执行的动作,都要通过程序进行指定,没有了程序,计算机就是一堆电子元件的组合而已,即使通上电也不能执行什么操作(想想刷机把机器刷成了“砖头”就知道了)。所以,计算机的智能,反映的其实是编程人员的智慧。

再说一点,个人认为,以当前计算机的工作原理来看,人类绝无可能实现真正的人工智能,像自动驾驶这种东西想想就好了,别指望它能真正实现完全可靠的替代人类完成工作。

程序设计语言的发展

程序设计语言发展到现在,大致经历了三个阶段,分别是机器代码、汇编语言和高级语言三个阶段。计算机发展的初期,人们都是直接基于CPU的指令集编写二进制的机器代码,编写这种程序代码工作量巨大 ,不易发现和调试程序的错误,加之在一款CPU上编写的代码不能移植到另一款CPU上,所以开发效率不高。后来,人们通过使用一些具有具体意义的符号来代替CPU的指令,比如ADD表示加法指令,JPM表示跳转指令,以此发明了汇编语言。以下是一段Intel 8086 CPU的汇编程序。

start: mov ax,data  
  mov ds,ax
  lea dx,infon
  mov ah,9
  int 21h
  lea dx,buf
  mov ah,10
  int 21h
  mov cl, [buf+1]
  mov ch,0
  lea di,buf+2
  call datacate
  call ifyears
  jc a1
  lea dx,n
  mov ah,9
  int 21h
  jmp exit
a1:    lea dx,y
  mov ah,9
  int 21h
exit:  mov ah,4ch
  int 21h

汇编语言使得人们终于可以直接看懂指令的意义以及整个程序大致完成的工作。但是计算机已经不能识别汇编语言了,因为计算机只能识别由0和1组成的二进制代码。所以汇编语言需要一个叫做汇编程序的软件进行翻译,将具体的符号翻译成机器代码,计算机才可以识别。
从机器代码到汇编语言是编程语言的一大进步,汇编语言已经在朝着人的自然语言靠近了,但是汇编语言仍然具有代码逻辑复杂,无法结构化编程,与硬件高度相关,代码无法进行移植的特点,所以汇编语言仍是一种低级语言。

为了克服低级语言的缺点,人们开始创造各种高级语言,高级语言与人的自然语言更加靠近,它很接近人们使用自然语言的习惯,所以使用高级语言写出的程序很容易理解。而且,高级语言对程序的设计进行了高度的抽象与凝练,使得语言可以不依赖于具体的机器型号,程序开始很方便地可以在各种型号的计算机上进行移植。C、C++、Java、Python都是高级语言的代表。当然,随着计算机编程语言的发展,C已经被归类到中级语言的行列了。

关于C语言

C语言诞生于20世纪70年代。1972-1973年间,美国贝尔实验室的丹尼斯·里奇在B语言的基础上设计了C语言。C语言设计之初的目的是为了描述与实现当时著名的UNIX操作系统。1973年,肯·汤普逊丹尼斯·里奇用C语言重写了90%以上的UNIX系统,随着UNIX系统的广泛使用,C语言也迅速得到推广。

Dennis MacAlistair Ritchie,丹尼斯·里奇

1941.09.09-2011.10.12

C语言是一种非常优秀的语言,在那个连操作系统都是用汇编语言编写的年代时,C语言的出现可谓是具有划时代的意义。总结起来,C语言有以下几个特点:

  1. C语言是一种结构化的程序设计语言。结构化意味着在设计程序的时候,可以将程序分成不同的模块,分模块编写代码,最后将各个模块“组装”起来完成程序的设计,采用这种方式将程序细化到了人脑可以处理的难度,大大降低了程序设计与调试的难度。
  2. C语言既有高级语言那样贴近人自然语言的特点,又不失低级语言的功能。使用C语言可以直接操作计算机的内存和CPU的各种寄存器,这使得使用C语言编生成的目标代码质量高,具有非常高的执行效率。
  3. C语言存在规范的标准,使用同一标准写出的C语言代码可以无障碍地在各套CPU架构下进行移植,可移植性好也是C语言强大的体现。
    基于以上种种特点,C语言又具有“王者”或“母语”之称,许多著名的系统软件都是用C语言编写的。

数学基础

所有的科学到最后都是数学,编程尤其如此,深厚的数学功底在写代码时是非常有用的。但是很明显,不是所有的程序员都精通数学,如果一个程序员精通数学,那他还干个屁的程序员,干点其他的不好吗?所以,这里只介绍几个最基本的数学知识,目的也只是扫个盲而已。

二进制与进制转换

计算机使用2进制,但2进制位数太多不方便书写,因此在编程语言中,常用的是8进制、10进制以及16进制。在C语言中,8进制以数字0开头,16进制以0x或0X开头,16进制符号A~F不区分大小写。

不同的进制下同一个数的表示形式不一样,需要通过进制转换得到一个数对应进制的表示。一般来说,在进行进制转换时,通常会先将一个数转换成10进制形式,再转换成其他进制。N进制转换成10进制的方式是加权求和,以下是一些示例:

二进制:

(101)2 = 1*22 + 0*21 + 1*20 = (5)10

(101.1)2 = 1*22 + 0*21 + 1*20 + 1*2-1 = (5.5)10

八进制:

(074)8 = 7*81 + 4*80 = (60)10

十六进制:

(0x3FE)16 = 3*162 + 15*161 + 14*160 = (1022)10


10进制转N进制则需要使用短除法,也就是除N求余数,然后将所有的余数逆序排列即可。但对10进制小数,则需要乘以N再取整,并且结果是顺序排列,以下是10进制转2进制的示例:


Alt text

10进制整数转2进制:除2求余,逆序排列。
10进制小整数进制转2进制:乘2取整,顺序排列。


在实际使用中,一种2进制与8进制、16进制快速转换的方法也很常用,这里以2进制转16进制为例,具体的方法是:

  1. 将2进制从低到高每四位划分为一组
  2. 计算每组对应的16进制值
  3. 直接得到2进制对应的16进制值,不需要先转10进制。

示例过程如下:

111010101010101101
11,1010,1010,1010,1101
6,10,10,10,13
6,A,A,A,D
0x6AAAD
(111010101010101101)2 = 0x6AAAD


进制转换的思想也可以扩展到N进制,这点在刷题时经常遇到。

数据范围

根据排列组合的知识,x个具有n种状态的元素,它们的组合一共有x的n次方种状态。根据这点,可以用来计算二进制的数据范围,比如,8个二进制位,由于每个二进制位只具有0和1两种状态,所以8个二进制位可以表示2的8次方共256种状态,从00000000到11111111,转换成十进制是0到255,所以8位二进制可以表示的数据范围是0到255,16位,32位以此类推。

计算机存储单位转换

单位

容量

bit

位,用于存储一个二进制位

Byte

字节,8bit等于一个Byte

KB

2^10个Byte

MB

2^10个KB

GB

2^10个MB

TB

2^10个GB