Skip to content
~/nihildigit
Go back

EffectiveC_01_开始使用C

在这一章中,您将开发您的第一个C程序:传统的“Hello, world!”程序。我将带您了解这个简单程序的各个方面,以及如何编译和运行C程序。

然后,我将讨论一些编辑器和编译器选项,并列出您在编写C代码时将快速熟悉的常见可移植性问题。

开发您的第一个C程序

学习C编程的最佳方式是开始编写C程序,传统上,我们从“Hello, world!”开始。要编写这个程序,您需要一个文本编辑器或集成开发环境(IDE),你可以在网上找到相关信息。

在您的文本编辑器中,输入程序:

//程序清单1-1

#include <stdio.h>
#include <stdlib.h>
int main(void) {
	puts("Hello, world!");
	return EXIT_SUCCESS;
}

我们稍后将详细讨论这个程序的每一行。现在,请将这个文件保存为hello.c
文件扩展名.c表示该文件包含C语言源代码。

编译和运行您的程序

接下来,我们需要编译和运行程序,这涉及到两个单独的步骤。您可以选择众多的C编译器,而编译程序的命令取决于您使用的编译器。
在Linux和其他类似Unix的操作系统上,您可以使用cc命令调用系统编译器。要编译您的程序,请在命令行中输入cc,然后跟上要编译的文件的名称:

% cc hello.c

如果您正确输入了程序,编译命令将在与您的源代码相同的目录中创建一个名为a.out的新文件。使用ls命令检查您的目录,您应该会看到以下内容:

% ls
a.out  hello.c

a.out文件是可执行程序,您现在可以在命令行上运行它:

% ./a.out
Hello, world!

如果一切正常,程序应该会在终端窗口中打印出Hello, world!。如果没有打印出来,请将清单1-1中的程序文本与您的程序进行比较,以确保它们相同。 cc命令有许多标志和编译器选项。例如,-o file标志允许您为可执行文件指定一个易记的名称,而不是a.out。以下编译器调用将可执行文件命名为hello

% cc -o hello hello.c
% ./hello
Hello, world!

现在我们将逐行检查hello.c程序。

预处理指令

hello.c程序的前两行使用了#include预处理指令,它的行为相当于在它所在的位置替换了它所指定的文件内容。

我们引入了<stdio.h><stdlib.h>头文件,以便访问这些头文件中声明的函数,然后就可以从程序中调用这些函数。

puts函数在<stdio.h>中声明,EXIT_SUCCESS宏在<stdlib.h>中定义。正如文件名所示:<stdio.h>包含了C标准I/O函数的声明,而<stdlib.h>包含了通用实用函数的声明。

您需要引入您在程序中使用的任何库函数的声明。

主函数

程序的主要部分,如前面的清单1-1所示,从以下部分开始:

int main(void) {

这行定义了在程序启动时调用的main函数。main函数定义了程序的主入口点,在命令行或另一个程序中调用程序后,程序会在托管环境中开始执行。

C定义了两种可能的执行环境:独立环境和托管环境。独立环境可能不提供操作系统,通常用于嵌入式编程。这些实现提供了一组最小的库函数,并且在程序启动时调用的函数的名称和类型是由实现定义的。本书主要假设使用托管环境。

我们定义main函数返回int类型的值,并在括号内放置void,以指示该函数不接受参数。int类型是一种带符号的整数类型,可以用来表示正数、负数以及零。

与其他过程式语言类似,C程序由可以接受参数并返回值的过程(称为函数)组成。每个函数都是可重复使用的工作单元,在程序中可以根据需要多次调用。

在这种情况下,main函数返回的值表示程序是否成功终止。这个特定函数执行的实际工作是打印出Hello, world!这一行:

puts("Hello, world!");

puts函数是C标准库中的一个函数,它将一个字符串参数写入stdout,通常表示控制台或终端窗口,并在输出中附加一个换行字符。"Hello, world!"是一个字符串字面量,它的行为类似于一个只读字符串。这个函数调用将Hello, world!输出到终端。

一旦您的程序完成,您会希望它退出。您可以使用return语句在main函数中返回一个整数值给主机环境或调用脚本来退出程序:

return EXIT_SUCCESS;

EXIT_SUCCESS是一个类似对象的宏,通常扩展为0,并且通常定义如下:

#define EXIT_SUCCESS 0

每个EXIT_SUCCESS都会被替换为0,然后从main返回给托管环境。调用程序的脚本可以检查其状态,以确定调用是否成功。

main函数给托管环境返回一个值的过程相当于调用C标准库的exit函数,并将其参数设置为main函数的返回值。

这个程序的最后一行是一个右花括号 },它关闭了我们从main函数的声明开始的代码块:

int main(void) {
	//省略
}

您可以将左花括号放在同一行上,也可以独立成行,如下所示:

% cc hello.c

0

这个决定严格来说是一种风格上的选择,因为空白字符(包括换行符)通常在语法上没有意义。在本书中,我通常将左花括号放在与函数声明同一行上,因为这样在风格上更加紧凑。

检查函数返回值

函数通常会返回一个计算结果或表示函数是否成功完成其任务的值。例如,我们在“Hello, world!”程序中使用的puts函数接受一个要打印的字符串,并返回一个int类型的值。如果发生写入错误,puts函数将返回宏EOF的值(一个负整数);否则,它将返回一个非负整数值。

虽然puts函数对于我们的简单程序来说可能不太可能失败并返回EOF,但这是可能的。因为puts调用可能会失败并返回EOF,这意味着你的第一个C程序存在一个bug,或者至少可以改进如下:

% cc hello.c

1

这个修改版的“Hello, world!”程序检查puts调用是否返回EOF,表示写入错误。如果函数返回EOF,程序将返回EXIT_FAILURE宏的值(非零)。否则,函数成功,程序将返回EXIT_SUCCESS(必须为0)。调用程序的脚本可以检查程序的状态以确定是否成功。

return语句后面的代码是永远不会执行的死代码,这在修订后的程序中由单行注释表示。//后面的所有内容都会被编译器忽略。

格式化输出

puts函数是将字符串写入stdout的一种简单方式,但最终您可能需要使用printf函数来打印格式化的输出,例如,以打印字符串之外的参数。

printf函数接受一个格式字符串,该字符串定义了输出的格式,然后是可变数量的参数,这些参数是您要打印的实际值。

例如,如果您想要使用printf函数来打印Hello, world!,可以这样写:

% cc hello.c

2

第一个参数是格式字符串"%s\n"

%s是一个转换说明,指示printf函数读取第二个参数(一个字符串字面值)并将其打印到stdout

\n是一个字母转义序列,用于表示非图形字符,并告诉函数在字符串之后包含一个换行符。如果没有换行序列,下一个被打印的字符(可能是命令提示符)将出现在同一行上。

这个函数调用输出以下内容:

% cc hello.c

3

要小心不要将用户提供的数据作为printf函数的第一个参数的一部分传递,因为这样做可能会导致格式化输出的安全漏洞(Seacord 2013)。

输出字符串的最简单方法是使用puts函数,如前所示。如果您在修订版的“Hello, world!”程序中使用printf而不是puts,您会发现它不再起作用,因为printf函数返回的状态与puts函数不同。如果成功,printf函数返回打印的字符数,如果发生输出或编码错误,则返回负值。您可以尝试修改“Hello, world!”程序以使用printf函数作为练习。

编辑器和集成开发环境

可以使用各种编辑器和集成开发环境(IDE)来开发C程序。可用的确切工具取决于您使用的系统。

编译器

许多C编译器可供选择,因此我不会在这里讨论它们所有。不同的编译器实现不同版本的C标准。许多嵌入式系统的编译器仅支持C89/C90。流行的Linux和Windows编译器更努力地支持C标准的现代版本,包括对C2x的支持。

可移植性

每个C编译器实现都会有一些不同。编译器不断发展,因此,例如,像GCC这样的编译器可能会全面支持C17,但正在努力支持C2x,因此它可能已经实现了一些C2x的特性,但还没有实现其他特性。因此,编译器支持各种C标准版本(包括中间版本)。C实现的整体演进较慢,许多编译器明显落后于C标准。

如果一个程序只使用标准规范中指定的语言和库的特性,那么可以认为它是严格符合规范的。这些程序旨在实现最大的可移植性。然而,由于不同编译器的实现行为差异,没有现实世界的C程序是严格符合规范的,也永远不会是(可能也不应该是)。相反,C标准允许你编写符合规范的程序,这些程序可能依赖于非可移植的语言和库特性。

通常的做法是针对单个参考实现编写代码,或者有时根据计划部署代码的平台编写多个实现。C标准确保这些实现不会有太大的差异,并允许你同时面向多个实现,而无需每次都学习一种新的语言。

C标准文档的附录J列举了五种可移植性问题,它们包括:

总结

在本章中,您学习了如何编写一个简单的C语言程序,编译它并运行它。

我们在本章中还讨论了C语言程序的可移植性。

接下来的章节将详细探讨C语言和库的特定特性,从下一章开始介绍对象、函数和类型。


Share this note on:

Previous note
EffectiveC_00_引言
Next note
cs61a_lec1_function