一个简单的 C 程序
#include <stdio.h>
#include <assert.h>
/*
* 这是 main 函数 (main function)
* 一般情况下(有宿主环境)C 程序的入口点
*/
int main(void) {
// 输出一行字
puts("Hello world!");
int a, b;
scanf("%d%d", &a, &b);
assert(b != 0);
printf("%g\n", (double)a / b);
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这个程序会首先输出 Hello world!
;然后等待我们输入两个整数,检查第二个整数是否为 0(除数不能为 0):如果是 0,就输出一套错误信息;如果不是 0,就计算并输出第一个整数除以第二个整数的结果。
你可以将以上代码保存到自己的电脑里面,命名为 main.c
。像这样存储代码的文件被称为源文件。C 语言的源文件的名称应该以 .c
结尾。
1. C 项目的创建(以 Lightly 为例)
点击在线使用
点击新建项目,选择 C,C 语言标准选择 C11,项目名称自拟,其他可选项默认
经过一小段时间的初始化,工作区已经准备好了:
可以见到项目里面新建了两个文件夹,源码文件 main.c
里面生成了一个输出 hello world 的示例代码; 源码文件上面还有一个叫 .clang-format
的文件,里面有些设置项
1'. C 项目的创建(以 CLion 为例)
打开 CLion
点击新建项目,选择 C 可执行文件,C 语言标准自选,推荐选择最新的;点击创建。(我的 CLion 装了汉化插件,如果你的是英文界面也点击对应位置按钮即可)
项目创建完成后,会自动打开
main.c
文件,其中的内容是一串可以输出hello world
的示例代码。此外,项目文件夹里面还有一个CMakeLists.txt
文件,这是 CLion 项目的配置文件。
2. C 文件的编译运行(以 Lightly 为例)
- 打开
main.c
,将上方代码粘贴进去,点击绿色三角形按钮即可编译运行 - 在屏幕上输出了
hello world
2'. C 文件的编译运行(以 CLion 为例)
- 点击绿色三角形按钮即可编译运行,下方会弹出一个窗口,输出运行结果。
3. 包含标准库头文件
看以下的高亮代码行:
#include <stdio.h>
#include <assert.h>
/*
* 这是 main 函数 (main function)
* 一般情况下(有宿主环境)C 程序的入口点
*/
2
3
4
5
6
7
我们可以看到,代码的第一行和第二行具有 #include <...>
的形式,这是包含标准库头文件的写法。
- 以
#
开头的是一条预处理器指令 (Preprocessor Directive),我们将在 10 - 预处理器 这一章中详细讲解预处理器的功能。 #include
指令是把目标文件的内容“原封不动”地粘贴到此文件,我们将在 10.2#include
这一节中详细讲解#include
的用法。<stdio.h>
是标准库提供的一个头文件,需要包含它才能使用其中的内容。第 11 章 标准库概述 会提到<stdio.h>
这个头文件及其功能。
4. 注释 (Comment)
看以下的高亮代码行:
#include <stdio.h>
#include <assert.h>
/*
* 这是 main 函数 (main function)
* 一般情况下(有宿主环境)C 程序的入口点
*/
int main(void) {
// 输出一行字
puts("Hello world!");
2
3
4
5
6
7
8
9
10
11
这片代码中有两种注释:行内注释和块注释。我们将在 3.1 注释 中详细讲解。
5. main 函数
/*
* 这是 main 函数 (main function)
* 一般情况下(有宿主环境)C 程序的入口点
*/
int main(void) {
// 输出一行字
puts("Hello world!");
5
6
7
8
9
10
11
5.1 程序入口点
- 一般来说,一个 C 程序从
main()
函数开始执行,也从main()
函数结束
5.2 main 函数的几种原型
- 标准规定的:
int main(void)
- 标准规定的:
int main(int argc, char* argv[])
- 很多编译器实现的:
int main(int argc, char* argv[], char* envp[])
6. 返回值 (return value, RV)
看下面的高亮代码行:
printf("%g\n", (double)a / b);
return 0;
}
18
19
20
return
的东西是函数的返回值- 如果
main()
函数的返回值为 0,则说明程序正常退出。我们将在 21 - 程序支持 中讲解main()
函数退出时发生的一些情况,以及那时可以进行的一些操作。 - 主函数正常执行到末尾如同返回 0。
7. 库函数
看下面的高亮代码行:
// 输出一行字
puts("Hello world!");
int a, b;
scanf("%d%d", &a, &b);
assert(b != 0);
printf("%g\n", (double)a / b);
11
12
13
14
15
16
17
puts
scanf
printf
这三个是标准库提供的函数。它们的功能如下:
puts
用于输出字符串并换行。scanf
用于读取数据。printf
用于格式化输出。
除此之外,也可以定义自己的函数。我们将在 6. 函数 中详细介绍如何定义自己的函数。
8. 初识断言 (Assertion)
看下面的高亮代码行:
assert(b != 0);
printf("%g\n", (double)a / b);
17
我们可以看到,第 16 行具有 assert( ... )
的形式,它是一个断言。
8.1 什么是断言
断言是用于进行调试和错误处理的工具。
它允许程序员在代码中插入条件检查,以确保程序在运行时满足特定的前提条件。
如果断言的条件不成立,程序将终止执行并生成一条错误消息,提供关于出错位置和原因的信息。C 语言没有提供在断言不成立的时候打印自定义错误消息的接口。有个被普遍应用的技巧是使用逗号运算符额外提供错误消息。
8.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: SIGSEGV
2
8.3 用途
在开发和调试阶段用于防御性编程,缩小错误可能存在的范围,便于调试。
9. 强制类型转换
看下面的高亮代码行:
assert(b != 0);
printf("%g\n", (double)a / b);
17
第 16 行的 (double)a / b
中,a 和 b 都是整数类型:根据 C 语言的规则,把除法运算符应用到两个整数上,会得到一个整数结果(向零取整:5 / 2
等于 2
,-5 / 2
等于 -2
)。
但当我们输入 5 2
时,我们希望程序输出 2.5
,而不是 2
。(double)a
表示将 a
强制转换为 double
类型(一种浮点数类型),然后再进行除法运算。根据一般算术转换的规则,浮点数除以整数结果会得到浮点数。
习题
- 创建 C 语言项目,把本章的示例代码复制进你的项目中,并且进行编译运行。
- 修改
puts()
函数中的内容,使得程序输出Hello, C!
。 - [1.2] 实验与观察:探索整数除法 a. 暂时移除代码中的
(double)
(强制类型转换),让除法变成a / b
。 b. 运行程序,分别输入5 2
、6 3
、-5 2
,观察并记录输出结果。 c. 尝试解释为什么结果是这样。这是否验证了教程中关于“整数除法向零取整”的说法? - [1.2] 触发断言:理解
assert
a. 运行原始程序,并在第二个整数输入0
。 b. 仔细阅读并尝试理解控制台输出的错误信息。它告诉你错误发生在哪一个文件的哪一行吗?它告诉你失败的条件是什么吗? - [1.3] 破坏与修复:理解
#include
a. 将代码第一行的#include <stdio.h>
的在行首加上//
。 b. 尝试编译程序。你看到了什么样的编译错误(Compiler Error)?错误信息是否提到了puts
? c. 恢复#include <stdio.h>
,在#include <assert.h>
的行首加上//
,再次编译。这次的错误信息又提到了什么? d. 通过这个实验,你能否总结出#include
指令的作用是什么?