课程目标:
计算机语言的发展 C语言的起源和特点 |
♥ |
---|---|
C程序的基本结构 | ♥ |
C程序的注释 | ♥♥ |
C程序的开发流程 | ♥♥♥ |
一、计算机语言的发展
机器语言:由0和1的二进制指令码组成 | 10000000 表示加法操作;10010000 表示减法操作 优点:机器能直接运行,效率高 缺点:人们无法记住复杂的二进制指令。 |
---|---|
汇编语言:用助记符号描述的指令 | 如: SUB A, B 优点:方便人们掌握一些质量,执行简单逻辑 缺点:严重依赖硬件,不同CPU架构无法执行,移植性差。无法实现复杂的逻辑 |
高级语言: - 面向过程语言:程序设计是数据被加工的过程,如 C语言 - 面向对象语言:程序设计是对象间通过发送和接受消息发生联系,如 Java、C++ |
优点:方便人们书写复杂逻辑。 缺点:效率比上面两种低 |
不管你使用任何语言,最终能被机器执行的都是机器语言,也就是高级语言和汇编语言最终都要转换为机器语言。
二、C语言的起源和特点
C语言发展历史:
- CPL语言(Combined Programming Language):1963年英国的剑桥大学推出了CPL语言,较接近硬件,难懂,实现难。
- BCPL语言(Basic CPL):1967年英国剑桥大学的Matin Richards对CPL语言作了简化,推出 BCPL 语言。
- B语言:1970年美国贝尔实验室的Ken Thompson 以BCPL语言为基础,又作了进一步简化,设计出了很简单的而且很接近硬件的B语言(取BCPL的第一个字母),并用B语言写了第一个UNIX操作系统。
- C语言:1972年至1973年间,贝尔实验室的D.M.Ritchie在B语言的基础上设计出了C语言(取BCPL的第二个字母)。
C标准:
- 1978年,Brian W. Kerni ghan和Dennis M. Ritchie (K&R),《The C Progr amming Language》 ,标准C;
- 1983年,美国国家标准化协会(ANSI),ANSI C 或者 C89;
- 1990年,国际标准化组织(ISO),C90;
- 1999年,国际标准化组织(ISO),C99;
C语言特定:
- 结构化的程序设计语言:采用自顶向下的规划(使用头文件实现)、结构化的编程(函数实现)、模块化的设计(不同源文件对应相应功能)、层次清晰、易于调试和维护。
- 功能强大:既是一种高级语言,同时也提供了丰富的低级操作,如对位、字节和地址进行操作,适合于开发各种应用软件和系统软件。
- 移植性好:只要对语言稍加修改,便可以适应不同型号机器或各类操作系统
- 标准函数库、代码和数据的分离:(我理解的这个意思是处理数据不用写在程序里面而是从文件读入,而标准函数库就是已经造好的轮子,你不必再废时间造,减少重复工作)
三、C程序的基本结构
我们以一个简单的 C 程序进行讲解:
我们在 WSL 上的 Ubuntu 平台上进行实践,建立本课程所需的文件夹。
b07@SB:~$ pwd
/home/test
b07@SB:~$ mkdir c # 创建本课程所需目录
b07@SB:~$ ls
c
为了方便管理,我们为每一章建立一个子目录,比如当前是第一章。
b07@SB:~$ cd c
b07@SB:~/c$ mkdir chapter1 # 在 c 文件夹下创建 chapter1 目录
b07@SB:~/c$ ls
chapter1
使用vi
编辑器写代码(新手先自行去学习),C语言的源代码以.c
后缀名结尾。
#include <stdio.h>
int main() {
printf("Hello world\n");
return 0;
}
每行命令解释:C 语言中以;
表示一条语句,每条语句后面都必须加上;
否则出现语法错误。
#include <stdio.h>
:是一条预处理指令,<>
表示调用系统头文件,""
表示调用自己实现的头文件。stdio.h
是标准输入输出头文件。例如输入函数scanf
默认从终端输入数据和输出函数printf
默认将结果输出在终端。int main() {...}
:main
函数整个 C 程序入口,有且只能有一个main
函数。识别函数的关键点在于返回类型 标识符() {}
,即最主要的地方在() {}
上int
表示函数返回值,即倒数第二行返回return 0;
,main
表示函数名,()
表示main
是一个函数,里面可以接受参数,本例中什么都没有表示不接受参数或者使用void
表示{...}
表示函数体,即该函数执行功能的实现。printf("Hello world\n");
:printf
是stdio.h
头文件提供的输出函数名,在其使用(args)
表示向该函数传入参数args
并执行该函数,本例中args = "Hello world\n"
是字符串,即告诉printf
函数在终端打印打印字符串Hello World
后换行。"Hello world\n"
:是字符串常量,C 语言中使用双引号括起来的为字符串,使用单引号括起来的为字符,其中\n
是转义字符表示换行,转义字符出现的目的是键盘上无法敲除换行字符(注意不要以为Enter
键就是换行)
在main
函数中return 0;
中的0
是状态值,最常见的返回状态值如下:
数值 | 含义 |
---|---|
0 | 程序正常执行 |
1 | 功能错误 |
2 | 系统找不到指定文件 |
3 | 系统找不到指定路径 |
4 | 系统无法打开文件 |
5 | 拒绝访问 |
:::tips
小技巧:如何区分函数和函数调用?
函数实现通常是返回类型 标识符(形参列表) {函数体}
;而函数调用是标识符 (形参列表)
。因此它们区别就在后者没有返回类型和**{函数体}**
部分。这一点需要和后面「函数声明」进行区分,其中 返回类型 标识符(形参列表)
叫做函数首部或者函数原型,函数声明形式就是在函数原型后面加上分号;
,即返回类型 标识符(形参列表);
告诉编译器这个函数“外貌”长什么样而不在乎“内在”。
:::
上机使用 Linux 下的 GCC 编译器将上面源程序编译为可执行程序。
:::success
b07@SB:~/c/chapter1$ ls
hello_world.c
b07@SB:~/c/chapter1$ gcc hello_world.c -o hello_world # -o 指定可执行文件名字
b07@SB:~/c/chapter1$ hello_world # 在 bash 上不可以简单通过这种方式运行
hello_world: command not found
b07@SB:~/c/chapter1$ ./hello_world # 必须指定可执行程序路径,./ 表示当前路径下
Hello world
:::
如果你使用 GCC 不指定-o
参数,那么在 Linux 上默认在当前路径下生成一个a.out
的可执行程序。
:::success
b07@SB:~/c/chapter1$ gcc hello_world.c
b07@SB:~/c/chapter1$ ls
a.out hello_world hello_world.c
b07@SB:~/c/chapter1$ ./a.out
Hello world
:::
这样会带来一个问题就是当你在当前路径下写了另一个 C 程序同样使用这样的方式进行编译后,那么新生成的可执行文件将会覆盖原来已有的a.out
文件,那么之前的程序就必须再次编译才能实现其逻辑。因此-o filename
主要是防止同名文件的覆盖。
四、C程序的注释
C 语言中有两种注释,多行注释形式为/* 待注释内容 */
,单行注释形式为// 注释内容
(在 C89 引入)。
/*
* <- 这个星星可加可不加,加了美观,偷懒也可以不加
* 开头的多行注释多用于版权信息和程序版本说明
* 或者在函数上面进行注释,对函数功能进行描述,这对逻辑很复杂的函数很重要
* 如下面对 main 函数实现逻辑进行简要的说明
*/
#include <stdio.h>
/*
* name: main
* description: print the string "Hello world" on the screen.
* args: void
* return: int
* author: sb
* date: 2028/2/3
*/
int main() {
// 这里演示使用单行注释
printf("Hello world\n"); // print the string "Hello world" on the screen.
return 0;
}
将上述代码编译后依然能够运行,因为编译器会自动忽视注释信息,且不会影响执行效率,唯一影响就是源文件容量变大了。即注释信息是给人看的,不是给机器看的,在开发大型项目或者看别人实现的代码时注释显得非常重要。
:::success
b07@SB:~/c/chapter1$ gcc hello_world.c -o hello_world
b07@SB:~/c/chapter1$ ./hello_world
Hello world
:::
注意:跨行注释不能嵌套,不允许出现/* /* 嵌套是非法的 */ */
,
/*
* /* 跨行注释是非法的,因为这个 */ 会和左侧最近的那个符合配对,从而导致错误!
* 一般的编辑器都会高亮显示错误的地方,例如此处左侧 * 都是以红色标记,
* 提醒用户代码有问题
*/
/*
* 但是多行注释里面可以嵌套单行注释 //
* 随意嵌套多少都没事,// //
*/
#include <stdio.h>
int main() {
// 这里演示使用单行注释
// 单行注释里面也可以有多行注释 /* 多行注释也可只有一行,并且嵌套在 // 注释也不会出问题的 */
printf("Hello world\n"); // print the string "Hello world" on the screen.
return 0;
}
将上述代码编译后将会出现一大堆错误:
:::danger
b07@SB:~/c/chapter1$ gcc hello_world.c -o hello_world
hello_world.c:2:50: error: stray ‘\344’ in program
2 | / 跨行注释是非法的,因为这个 / ���和左侧最近的那个符合配对,从而导致错误!
| ^
hello_world.c:2:51: error: stray ‘\274’ in program
2 | / 跨行注释是非法的,因为这个 / ���和左侧最近的那个符合配对,从而导致错误!
:::
Error 表示整个编译失败,不会生成可执行文件。本例中问题就处在多行嵌套注释。
:::tips
小技巧:如何理解单行注释和多行注释呢?
单行注释顾名思义从//
到该行结束所有内容都将被编译器“删掉”;而多行注释编译器需要找/*
和*/
,它不跟你玩嵌套,只需要找到一对就“删除”/*
到*/
内容,因此它玩连连看。
:::
五、C程序的开发流程
我们可以是使用-c
参数进行取消链接阶段,直接生成.o
的目标文件。
-c Compile or assemble the source files, but do not link. The linking stage simply is
not done. The ultimate output is in the form of an object file for each source
file.
By default, the object file name for a source file is made by replacing the suffix
.c, .i, .s, etc., with .o.
Unrecognized input files, not requiring compilation or assembly, are ignored.
使用gcc -c hello_world.c -o hello_world.o
指定输出文件为hello_world.o
,那么这个是什么文件呢?more
命令可以查看文本文件,例如查看hello_world.c
就会输出文件内容,而无法使用more hello_world.o
查看其内部内容,因为其是二进制文件,并且也无法执行!
b07@SB:~/c/chapter1$ gcc -c hello_world.c -o hello_world.o
b07@SB:~/c/chapter1$ ls
a.out hello_world hello_world.c hello_world.o
b07@SB:~/c/chapter1$ more hello_world.o
******** hello_world.o: Not a text file ********
b07@SB:~/c/chapter1$ more hello_world.c
/*
* 跨行注释是非法的,因为这个 会和左侧最近的那个符合配对,从而导致错误!
* 一般的编辑器都会高亮显示错误的地方,例如此处左侧 * 都是以红色标记,
* 提醒用户代码有问题
*/
/*
* 但是多行注释里面可以嵌套单行注释 //
* 随意嵌套多少都没事,// //
*/
#include <stdio.h>
int main() {
// 这里演示使用单行注释
// 单行注释里面也可以有多行注释 /* 不会出问题的 */
printf("Hello world\n"); // print the string "Hello world" on the screen.
return 0;
}
.o
文件是你源程序编译单独文件。由于程序中含有其它头文件和系统调用入口,这部分都需要使用链接器实现多个文件的链接合并,最终生成可执行文件。可以简单理解当前#include <stdio.h>
已经引入printf
的函数声明,但是尚未通过链接器链接到printf
函数的实现部分,因此你的代码处中printf
函数调用不知道printf
函数在何处。这也不难说明其为什么其不是可执行文件。
我们可以通过.o
文件使用链接器后生成可执行文件。
:::success
b07@SB:~/c/chapter1$ gcc hello_world.o -o hello_world
b07@SB:~/c/chapter1$ ./hello_world
Hello world
:::
在所有编译过程中,都必须经过下图三个阶段段,但是由于目前大多数编译器可以一气呵成,同时将过渡文件*.o
在编译成功后立马删除,因此我们看不到中间目标文件的生成。
那么什么时候需要获得*.o
的目标文件呢?我们将在之后讲解库文件制作过程会讲其用途。