1.1 什么是计算机程序?
计算机的每一个操作都是根据人们事先指定的指令进行的。
- 机器语言:计算机基于二进制,从根本上说,计算机只能识别和接受由0和1组成的指令。
- 机器指令(machine instruction):计算机能直接识别的二进制代码。
- 机器语言(machine language):机器指令的集合。
- 符号语言(symbolic language):为了克服机器语言的难学、难记、难检查、难修改、难以推广使用的缺点,人们创造出了符号语言,它用一些英文字母和数字表示一个指令,例如用ADD代表“加”,SUB代表“减”,LD代表“传送”等。
- 汇编语言:(assembler language):由于计算机不能直接识别和执行符号语言的指令,需要使用一种称为汇编程序的软件,把符号语言转换为机器指令。一般,一条符号语言的指令对应转换为一条机器指令,转换过程称为“代真”或“汇编”,因此符号语言又称为符号汇编语言(symbolic assembler language)或汇编语言。
- 低级语言(low level language):机器语言和汇编语言是完全依赖于具体的机器特性的,是面向机器的语言。由于它“贴近”计算机,或者说离计算机“很近”,称为计算机低级语言。
- 高级语言(high level language):为了克服低级语言的缺点,20世纪50年代创造出了第一个计算机高级语言——FORTRAN语言。这种语言功能很强,且不依赖于具体机器,用它写出的程序对任何型号的计算机都适用(或只须做很少的修改),它与具体机器语言距离较远。
- 高级语言的翻译:用一种称为编译程序的软件把高级语言写的程序(源程序,source program)转换为机器指令的程序(目标程序,object program),然后让计算机执行机器指令程序,最后得到结果。
高级语言经历的不同发展阶段:**
- 非结构化的语言:初期语言属于非结构化的语言,编程风格比较随意,只要符合语法规则即可,没有严格的规范要求,程序中的流程可以随意跳转。(缺点:追求执行效率而采用很多小技巧,使程序变得难以阅读和维护。早期的BASIC,ALGOL,FORTRAN)。
- 结构化语言:为了解决以上问题,提出了“结构化程序设计方法”,规定程序必须由具有良好特性的基本结构(顺序结构、分子结构、循环结构)构成,程序中的流程不允许随意跳转,程序总是由上而下顺序执行各个基本结构。(优点:这种程序结构清晰,易于编写、阅读和维护。C,QBASIC和FORTRAN77)
- 面向对象的语言:近十多年来,在处理规模较大的问题时,开始使用面向对象的语言。C++,C#,Visual Basic和Java等语言是支持面向对象程序设计的语言。
:::info
本节视频:第一章 C语言概述_1计算机语言的发展
:::
1.3 C 语言发展及其特点
C 语言:是国际上广泛流行的计算机高级语言,是一种用途广泛、功能强大、使用灵活的过程性(procedural)语言编程语言,即可以用于编写应用软件,又能用于编写系统软件。C语言是一个很小的内核语言,只包括极少的与硬件相关的成分,C语言不直接提供输入输出语句,有关文件操作的语句和动态内存管理的语句等(这些操作是由编译系统所提供的库函数来实现的),C编译系统相当简洁(其实就是很甩锅,全部让编译器背上语言补充功能)。
C 语言的特点:
- 语言简洁、紧凑、使用方便、灵活
C 语言一共用37个关键字、9种控制语句,程序书写形式自由,主要用小写字母表示,压缩了一切不必要的成分。C语言程序比其他很多高级语言语言简练,源程序短,因此输入程序时工作量少。
- 运算符丰富
C 语言的运算符包含的范围很广泛,共34种运算符,C语言吧括号、赋值和强制类型转换等都作为运算符处理,从而使C语言的运算类型极其丰富,表达类型多样化。灵活使用各种运算符可以实现在其他很多高级语言中难以实现的运算。
- 数据类型丰富
C 语言提供的数据类型包括:整型、浮点型、字符型、数组类型和布尔类型等。尤其是指针类型数据,使用十分灵活和多样化,能用来实现各种复杂的数据结构(如链表、数、栈等)的运算。
- 具有结构化的控制语句(如if-else语句、while语句、do-while语句、switch-case-default语句和 for 语句)
- 用函数作为程序的模块单元,便于程序的模块化。C语言是完全模块化和结构化的语言。
- 语言限制不太严格,程序设计自由度大
数组下表越界不检查,变量类型使用比较灵活:整型量与字符型数据以及逻辑型数据可以通用(都当数字看待)“限制”与“灵活”是一对矛盾。限制严格,就失去灵活性;而强调灵活,就必须放松限制。
- C 语言允许直接访问物理地址,能进行位(bit)操作,能实现汇编语言的大部分功能,可以直接对硬件进行操作
C 语言既有高级语言的功能,又具有低级语言的许多功能,可以编写系统软件。C语言的这种双重性,使它既是成功的系统描述性语言,又是通用的程序设计语言(绝大多数高级语言没有的)。
- 用C语言编写的程序可以可移植性好
由于C的编译系统相当简洁,因此很容易移植到新的系统。而且C编译系统在新的系统在新的系统上运行时,可以直接编译“标准链接库”中的多部分功能,而不需要修改源代码,因为标准链接库时用可移植的C语言写的。
- 生成目标代码质量高,程序执行效率高
C原来是专门为编写系统软件而设计的,很多大的软件都用C语言编写,这是因为C语言的可移植性好和硬件控制能力高,表达和运算能力强。
目前C语言主要用途之一是编写“嵌入式系统程序”。由于具有上述优点,使C语言应用面十分广泛,很多应用软件软件也用C语言编写。
:::info
来自 C 程序设计师的帖子:The Development of the C Language
本节视频:第一章 C语言概述 C语言的起源、特点及C程序的基本结构
:::
Chistory.pdf
1.4 最简单的 C 语言程序
例 1.1 hello world 程序,要求在屏幕输出 This is a C program.
解题思路:在 main
函数中调用库函数 printf
输出上述字符串。
#include <stdio.h> // 预编译指令,使用printf函数必须导入标准输入输出头文件
int main() { // C程序永远是从 main 函数开始被 OS 调用,直到 main 函数执行完毕
printf("This is a C program.\n"); // 调用printf函数输出字符串
return 0; // main函数返回值,C99 建议返回0
} // 函数体结束标志,其与左括弧(left curly)配对
在 Linux 下进行编译(其它编译系统也可以的),如下输出程序说明:
:::success
b12@PC:~$ vi ./hello.c # 创建文件然后输出上述内容
b12@PC:~$ gcc -o ./hello ./hello.c # 编译成可执行文件
b12@PC:~$ ./hello # 运行可执行文件
This is a C program.
:::
#include <stdio.h>
是导入标准输入输出库函数,即日后使用printf\scanf
函数就必须导入这个头文件。int main() {..}
main 是函数名,在前面有一个int
表示函数调用返回值是整型。而返回的这个值就由第 5 行的return 0;
进行返回调用者(OS)。每个函数必须有 返回值类型 函数名(参数类型, 参数1, [参数类型, 参数2]) {函数体}- 注释分为单行注释和跨行注释:单行注释以
//
开始直到此行结束,想要多行就在下行继续使用//
;而跨行注释是/* 注释内容,可连跨多行,但是只认第一次遇到的 */,即不可嵌套 */
。特别注意在字符串即双引号内的上述两种注释方式,它不是注释而是字符串本身一部分! ```cinclude
int main() { // 单行注释 int arr[3][2] = { {85, 85}, // id:0,math,physical {12, 45}, // id:1,math,physical {55, 75} // id:2,math,physical // Note:no comma at the end }; printf(“/我肆无忌惮游荡在字符串内,嵌套我都不怕/ / 我还怕//吗?\n”); // arr[0][1]:85 printf(“arr[0][1]\n”, arr[0][1]); / 跨行注释 我在新的一行 不可以嵌套吗? / / return 0; }
上述程序在跨行处报错:
:::danger
error: expected expression before '/' token<br /> 13 | */<br /> | ^
:::
因此补充书本内容,注意在初始化那部分,特别体现出 `//` 注释的特点,它就是预编译的时候被替换掉了,完全没有影响后面数组的初始化(个人觉得这样直观,尤其是在写正则表达式或者数组初始化的原因)。
:::info
**本节视频**:[第一章 C语言概述 C程序的注释](https://www.bilibili.com/video/BV1c7411f7JU?p=6)(跨行注释不可嵌套跨行注释)
:::
<a name="Z7OEY"></a>
### 例 1.2 求两个整数之和
解题思路:使用 3 个变量,用来存放两个整数和累加结果。用赋值运算符“=”将相加结果传送给 `sum` 。
```c
#include <stdio.h>
int main() {
int a, b = 456, sum; // 定义三个整型变量,其中b定义并初始化456
printf("Uninitiated sum:%d\n", sum); // 打印不初始化的sum
a = 123; // 定义后赋值
sum = a + b; // 将123 + 456 的运算结果赋值给 sum 变量
printf("assign a + b to sum:%d\n", sum);
return 0;
}
:::warning
warning: ‘sum’ is used uninitialized in this function [-Wuninitialized]
:::
上述编译报发出警告,意思就是变量未初始化就开始使用它的值!但是编译还是成功的,即得到了可执行文件,而不是上面注释那种直接给你来了 Error。
输出结果:
:::success
Uninitiated sum:2928640
assign a + b to sum:579
:::
程序分析:
int a, b = 456, sum;
是程序(定义式)声明部分,定义了三个变量,其中 b 赋初值了,其他两个尚未赋值。printf("Uninitiated sum:%d\n", sum);
打印不初始化的 sum 使得程序编译出现 warning,说明程序可移植性或者容易出错。即上述变量未赋初值便使用。sum = a + b;
同上使用赋值操作符“=”对 sum 进行赋值,最后打印肯定不会再次警告了。
关于 printf("sum is %d\n", sum)
说明:
"sum is"
是普通字符,原样输出。%d
是格式字符串,%
是格式声明,就是代表我马上要开始格式了!d
表示 digit,即数字格式字符,要求传入参数 sum 必须是数字类型才可以对应我的格式字符。\n
是一个字符,不是两个!由\
开头的都是转义字符,即表面上看上去有几个字母构成,其实质就是一个。- 为啥出现转义字符?因为机器笨,无法像人一样手写一些,比如你让计算机初期搞个换页符,或者根号
,它根本做不到,因此想法就是转换,用一个特定的
\
+ 某独特数字 = 新字符。(造字)
- 为啥出现转义字符?因为机器笨,无法像人一样手写一些,比如你让计算机初期搞个换页符,或者根号
, sum, [...]
是输出表列,即可根据前面的格式字符的格式进行传入多个参数。
:::tips 是否可以前面的格式字符与输出表列不对应呢????不对应会报错吗?还是怎么?
printf("assign a + b to sum:%d\n", sum, a);
输出正确数字printf("assign a + b to sum:%d\n");
随意输出,测试为 assign a + b to sum:4200752
可见它既不报错也不warning,简直神奇!这是怎么回事,而且还有数字输出! :::
例 1.3 求两个整数中的较大者
#include <stdio.h>
// min函数在main函数上面先被看到
int min(int x, int y) {
return x > y ? y : x;
}
int main() {
int max(int x, int y); // 函数声明
int a, b, c;
scanf("%d,%d", &a, &b); // 输入函数
c = max(a, b); // 调用max函数
printf("max=%d\n", c);
printf("min=%d\n", min(a, b));
return 0;
}
// 求两个整数中较大的max函数
int max(int x, int y) {
int z;
if (x > y) {
z = x;
} else {
z = y;
}
return z; // 将z的值作为max函数值,返回到调用max函数的位置
}
在cmd运行(交互)即可,输出结果如下
:::success
9,-5
max=9
min=-5
:::
程序分析:此程序共有3个函数,位置从上到下依次为 min -> main -> max
,请注意在 main 函数的声明在其后面的 max,而没有声明在其前面的 min。这里涉及到作用域问题,现在不需要细究,只要知道程序编译是从上往下进行的,如果不在 main 函数中声明 max 函数,则调用的时候编译器不知道这个哪来的,必定出问题!
:::warning
warning: implicit declaration of function ‘max’ [-Wimplicit-function-declaration]
11 | c = max(a, b); // 调用max函数
| ^~~
:::
但是任然可以运行成功,这一点其实就和没有 #include <stdio.h>
然后使用 printf
等函数的隐式未定义问题,但是当今的编译器非常聪明,它会扫描整个文档看是否存在这个函数,不存在才给你真正报错。比如将 max
函数注释掉同时添加上 max
函数声明。
:::danger
b12@PC:~$ gcc -Wall -o twobig ./twobig.c
/usr/bin/ld: /tmp/cchRqEsf.o: in function main':<br />twobig.c:(.text+0x5c): undefined reference to
max’
collect2: error: ld returned 1 exit status
:::
这个错误报告的式主函数返回值为1退出,但是上面一行给出未定义的引用 max
函数式造成编译失败的原因。
- 函数声明与函数调用:
int max(int x, int y);
为函数声明,函数声明的主要目的就是编译从上往下,在调用函数处c = max(a, b);
时尚未遇到max
是什么东西,就会出现上面情况。因此只要记住,先让编译器看到你这个函数首部,然后你再调用它就知道是调用谁了。- 类似也存在变量必须先定义再使用,但是函数不同,函数声明不占内存,而变量定义就占内存,具体「定义」与「声明」之后会在作用域中指出,现在只需要简单理解为“定义就占内存,声明不占内存”。
scanf("%d,%d", &a, &b)
函数是用户交互输入的函数,标准输入就是键盘输入,同printf
函数一致,有格式化字符串,和参数表列,不过这里一定要求传入指针(地址,&是取地址符)。如果现在不明白可以不管,只要用到 scanf 函数参数表列就是传入地址即可。C 语言程序的结构
- 一个程序由一个或多个源文件组成
- 预处理指令:
#include <stdio.h>
等其他预处理指令如#define
等。C编译系统在源程序编译之前,先由一个“预处理器”对预处理指令进行预处理,对于#include指令来说,就是将stdio.h头文件的内容读进来,放在 #include
指令行,取代了#include <stdio.h>
。由预处理得到的结果与源程序其他部分一起,组成一个完整的、可以用来编译的最后的源程序,然后由编译程序对该源程序正式进行编译,才得到目标程序(就是先导入,再一同编译)。 - 全局声明:放在函数之外的数据声明。在函数外面声明的变量叫做全局变量。如果在程序开头(定义函数之前)声明的变量,则在整个源程序文件范围内有效。在函数中声明的变量是局部变量,只在函数范围内有效。
- 函数定义:定义函数的功能,以大花括号闭合。
- 函数是C程序的主要组成部分:一个 C 程序是由一个或多个函数组成的,其中必须包含一个main函数(有且仅有一个main函数)。
一个小程序只包含一个源程序文件,在一个源程序文件中包含了若干个函数(其中有一个main函数)。当程序规模较大时,所包含的函数数量较多,如果把所有的函数都放在同一个源程序文件中,则此文件显得太大,不便于编译和调试。为了便于调试和管理,可以是一个程序包含若干个源程序文件,每个源程序文件又包含若干个函数。一个源程序文件就是一个程序模块,即将一个程序分为若干个程序模块。
- 一个C程序只有一个文件,那么这个文件中必须有一个main函数。
- 一个C程序可以由几个源程序文件组成,即模块化设计,但是只有一个模块作为C程序的入口,这个入口模块必须含有一个main函数。
- 一个函数包括两个部分:
- 函数首部:即函数的第一行,包括函数名,函数类型,函数属性,函数参数(形式参数)名,参数类型。 | int | max | ( | int | x | , | int | y | ) | | —- | —- | —- | —- | —- | —- | —- | —- | —- | | 函数类型 | 函数名 | 左括号 | 函数参数类型 | 函数参数名x | 逗号分隔 | 函数参数类型 | 函数参数名y | 右括号 |
:::tips 函数名后面必须跟上一对圆括号,括号内写函数参数名及其类型。
- 无返回值:即函数类型为
void
函数不要求返回任何东西。 无函数参数:圆括号内什么都可以不写
main()
或者写main(void)
:::函数体:即函数首部下面的花括号内的部分。如果在一个函数中包括有多层花括号,则最外层的一堆花括号是函数体的范围。函数体一般包括以下两个部分:
- 声明部分:定义在本函数中所用到的变量;对本函数所调用函数进行声明(先调用后定义就必须在调用前声明。如果被调函数在本函数之前定义则不需要)。
- 执行部分:函数体内由若干个语句组成,指定在函数中所进行的操作。
在某些情况下也可以没有声明部分,甚至可以既不声明也无执行部分。如: void dump(){}
,它是一个空函数,什么也不做,但这是合法的。
- 程序总是从main函数开始执行的,而不论main函数在整个程序中的位置如何(main函数可以放在任意位置,只允许一个工程内含有唯一一个main函数)。
- 程序中对计算机的操作是由函数中的C语句完成的。如赋值、输入输出数据的操作都是由相应C语句实现的。C程序书写形式自由,一行内可以写几个语句(用
;
分割即可),一条语句可以分写在多行上,但为了清晰起见,习惯上每行只写一个语句。 - 在每个数据声明和语句的最后必须有一个分号(英文半角分号
;
)。分号是C语句的必要组成部分。 - C语言本身不提供输入输出语句。输入输出函数由库函数
scanf
和printf
等函数来完成。C对输入输出实现“函数化”。由于输入输出操作涉及具体的计算机设备,把输入输出操作用库函数实现,就可以使C语言本身的规模较小,编译程序简单,很容易在各种机器上实现,程序具有可移植性。(记住 C 语言甩锅就行!遇到不同的,都是由编译器决定) - 程序应当包含注释:一个好的、有使用价值的源程序都必须加上必要的注释,以增加程序的可读性。新手千万注意可读性大于一切! :::info 本节视频:第一章 C语言概述 第一个C程序 :::
1.5 运行 C 程序的步骤和方法
这部分内容在学习指导上有,相信大部分都是在学习 C 语言之前都已经搞定了。
1. 上机输入和编辑源程序。 1. 对源程序进行编译,先用C编译系统提供的”预处理器“对源程序的预处理指令进行编译预处理。 1. 进行连接处理:经过编译所得到的二进制目标文件(后缀为.obj)还不能供计算机直接执行。一个程序可能包含若干个源程序文件,而编译是以源程序文件为对象的,一次只能编译得到一个目标文件(目标模块),它只是整个程序的一部分。必须把所有的编译后得到的目标模块连接装配起来,再与库函数相连接成一个整体,生成一个可供计算机执行的目标程序,成为可执行程序(executive program)。 1. 运行可执行程序,得到运行结果。 |
![]() |
---|---|
:::info 本节视频:第一章 C 语言概述 C 程序的开发流程 :::
1.6 程序设计的任务
- 问题分析:在此过程中可以忽略一些次要的因素,是问题抽象化,例如用数学式表示问题的内在特性,这就是建立模型。
- 设计算法:设计出解题的方法和具体步骤。一般用流程图来表示解题的步骤。(这点很重要,涉及到程序执行效率问题)
- 编写程序:根据算法,用一种高级语言编写出源程序。(本质就是将人脑思维转为计算机思维,让计算机懂得如何做)
- 对源程序进行编辑、编译和连接,得到可执行程序。
- 运行程序,分析结果。如果有错误,需要修改。同时找到错误可以用debug调试。一次运行结果正确,不一定其他数据也正确,因此需要测试(test),就是设计多组测试数据,检查程序对不同数据的运行情况,从中尽量发现程序中存在的漏洞,并修改程序,使之能适用于各种情况,作为商品使用的程序,必须经过严格测试。(就是完善程序过程,让程序通用性变强。)
- 编写程序文档:程序是给别人用的,别人不知道你这个干嘛的,必须要一个说明书。内容主要包括:程序名称、程序功能、运行环境、程序的装入和启动、需要输入的数据,以及使用注意事项等。
程序文档式软件的一个重要组成部分,软件是计算机程序和程序文档的总称。现在大多GUI程序都有help窗口或者README,实在不行就去搜索引擎找。
总结
本章只是对C语言大概的认识,具体要点就是明白C语言是结构化,面向过程的高级语言,很多东西C语言只是做标准(叫建议),但是其很多功能都是由编译器决定,比如类型占用的内存大小,库函数都是编译器搞定的。凡是遇到想要输入输出函数,必须 #include <stdio.h>
即可,以后把这定死在你的程序第一行。
- 难点:编译器与C语言编译过程
重点掌握上面那张图,编译器先进行预处理指令,有误就停止报告,无错就编译你的程序为 f.obj
(这一点可以从gcc手动编译为 .o
),然后此时是二进制代码,没有连上 #include <stdio.h>
库函数(其实这里就是预编译的 .o
的连接操作)且操作系统不认识,因而加上一些修饰,使得能够让操作系统为你的程序开一个进程运行。