一个简单的 C 程序
这个程序会输出数组里面最大的数。
#include <assert.h>
#include <float.h>
#include <stdio.h>
/*
* 这是 main 函数 (main function)
* 一般情况下(有宿主环境)C 程序的入口点
*/
int main(void) {
// 对象声明
double max = -DBL_MAX;
double arr[6] = {
[0] = 1,
[2] = 2.26,
[3] = (double)(5 / 2),
[4] = .1,
[5] = 3.E-2
};
size_t size = sizeof(arr) / sizeof(arr[0]);
assert(size == 6);
for (size_t i = 0; i < size; ++i) {
if (arr[i] > max) {
max = arr[i];
}
}
printf("The maximum in the %zu numbers is %g", size, max);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
可能的输出(示例):
The maximum in the 4 numbers is 2.26
有些写法现在看不懂没关系,本章只要求你能编译运行,并知道它整体在做什么。后面的章节会逐步解释这些细节。
你可以将以上代码保存到自己的电脑里面,命名为 main.c。像这样存储代码的文件被称为源文件。C 语言的源文件的名称应该以 .c 结尾。
接下来,你需要把这个源文件放进一个 C 项目并编译运行。根据你使用的开发环境,选择对应的小节:
- 2.1 CLion
- 2.2 Lightly
- 2.3 VSCode 插件(即将补充)
成功运行后,我们再逐行理解这段代码。
1. 包含标准库头文件
看以下的高亮代码行:
#include <assert.h>
#include <float.h>
#include <stdio.h>
/*
* 这是 main 函数 (main function)
* 一般情况下(有宿主环境)C 程序的入口点
*/2
3
4
5
6
7
8
我们可以看到,代码的第一行和第二行具有 #include <...> 的形式,这是包含标准库头文件的写法。
- 以
#开头的是一条预处理器指令 (Preprocessor Directive),我们将在 10 - 预处理器 这一章中详细讲解预处理器的功能。 #include指令是把目标文件的内容“原封不动”地粘贴到此文件,我们将在 10.2#include这一节中详细讲解#include的用法。<stdio.h>是标准库提供的一个头文件,需要包含它才能使用其中的内容。第 11 章 标准库概述 会提到<stdio.h>这个头文件及其功能。
2. 注释 (Comment)
看以下的高亮代码行:
#include <assert.h>
#include <float.h>
#include <stdio.h>
/*
* 这是 main 函数 (main function)
* 一般情况下(有宿主环境)C 程序的入口点
*/
int main(void) {
// 对象声明
double max = -DBL_MAX;2
3
4
5
6
7
8
9
10
11
12
这片代码中有两种注释:行内注释和块注释。我们将在 3.1 注释 中详细讲解。
3. main 函数
/*
* 这是 main 函数 (main function)
* 一般情况下(有宿主环境)C 程序的入口点
*/
int main(void) {
// 对象声明
double max = -DBL_MAX;6
7
8
9
10
11
12
3.1 程序入口点
- 一般来说,一个 C 程序从
main()函数开始执行,也从main()函数结束
3.2 main 函数的几种原型
- 标准规定的:
int main(void) - 标准规定的:
int main(int argc, char* argv[]) - 很多编译器实现的:
int main(int argc, char* argv[], char* envp[])
4. 返回值 (return value, RV)
看下面的高亮代码行:
printf("The maximum in the %zu numbers is %g", size, max);
return 0;
}31
32
33
return的东西是函数的返回值。- 如果
main()函数的返回值为 0,则说明程序正常退出。我们将在 14 - 程序支持 中讲解main()函数退出时发生的一些情况,以及那时可以进行的一些操作。 - 主函数正常执行到末尾如同返回 0。
5. 库函数
看下面的高亮代码行:
printf("The maximum in the %zu numbers is %g", size, max);
return 0;31
32
printf 是标准库提供的函数,用于格式化输出。
除此之外,也可以定义自己的函数。我们将在 7. 函数 中详细介绍如何定义自己的函数。
6. 初识断言 (Assertion)
看下面的高亮代码行:
size_t size = sizeof(arr) / sizeof(arr[0]);
assert(size == 4);22
我们可以看到,第 19 行具有 assert( ... ) 的形式,它是一个断言。
6.1 什么是断言
断言是用于进行调试和错误处理的工具。
它允许程序员在代码中插入条件检查,以确保程序在运行时满足特定的前提条件。
如果断言的条件不成立,程序将终止执行并生成一条错误消息,提供关于出错位置和原因的信息。C 语言没有提供在断言不成立的时候打印自定义错误消息的接口。有个被普遍应用的技巧是使用逗号运算符额外提供错误消息。
6.2 使用断言
#include <assert.h>
int main(void){
int a = -5;
assert(a > 0);
}2
3
4
5
6
程序终止执行,因为断言的条件 a > 0 不成立。
控制台可能打印出以下内容:
***.c:4: main: Assertion `a > 0' failed.
Program terminated with signal: SIGSEGV2
6.3 用途
在开发和调试阶段用于防御性编程,缩小错误可能存在的范围,便于调试。
todo
习题
创建 C 语言项目,把本章的示例代码复制进你的项目中,并且进行编译运行。
破坏与修复:理解 #include a. 将代码第一行的 #include <stdio.h> 在行首加上 //。 b. 尝试编译程序。你看到了什么样的编译错误(Compiler Error)?错误信息是否提到了 printf? c. 恢复 #include <stdio.h>,在 #include <assert.h> 的行首加上 //,再次编译。这次的错误信息又提到了什么? d. 通过这个实验,你能否总结出 #include 指令的作用是什么?
自测题
根据 C 语言的规则,如果不使用强制类型转换,表达式 5 / 2 的结果是多少?
关于 assert(断言),以下哪些描述是正确的?(多选)
// 编译报错!
// 编译器不知道 puts 是什么
int main(void) {
puts("Hello");
return 0;
}2
3
4
5
6
// 编译通过
// <stdio.h> 提供了 puts 的声明
#include <stdio.h>
int main(void) {
puts("Hello");
return 0;
}2
3
4
5
6
7
8
关于 #include <stdio.h>,以下哪项描述是 错误 的?