《C语言入门经典(第5版)》之C语言编程总结
本章的主要内容:
● C语言标准
● 标准库的概念
● 如何创建C 程序
● 如何组织C 程序
●
如何编写在屏幕上显示文本的程序
1.1 C 语言
C
是相当灵活的,用于执行计算机程序能完成的几乎所有任务,包括会计应用程序、字处理程序、游戏、操作系统等。它不仅是更高级语言(如C++)的基础,目前还以ObjectiveC
的形式开发手机应用程序。Objective C 是标准的C 加上一小部分面向对象编程功能。C 很容易学习,因为它很简洁。因此,如果你立志成为一名程序员,最好从C
语言开始学起,能快速而方便地获得编写实际应用程序的足够知识。
C
语言由一个国际标准定义,目前,其最新版本由ISO/IEC 9899:2011 文档定义。当前的标准一般称为C11,本书介绍的语言遵循C11
标准。但要知道,C11 定义的一些语言元素是可选的。这表示,遵循C11 标准的C
编译器可能没有实现该标准中的所有功能。(编译器只是一个程序,它可以把用我们能理解的术语所编写的程序转换为计算机能理解
的术语)。本书会标识出C11
中的可选语言特性,这样读者就知道,自己的编译器可能不
支持它。
C11 编译器还有可能没有实现C11
标准强制的所有语言特性。实现新语言功能是需要时间的,所以编译器开发人员常常采用逐步接近的方式实现它们。这也是程序可能不工作的另一个原因。尽管如此,根据我的经验,C
程序不能工作的最常见原因,至少有99.9%的可能性是出现了错误。
1.2 标准库
C
的标准库也在C11 标准中指定。标准库定义了编写C 程序时常常需要的常量、符
号和函数。它还提供了基本C
语言的一些可选扩展。取决于机器的特性,例如计算机的
输入输出,由标准库以不依赖机器的形式实现。这意味着,在PC 中用C
代码把数据写
入磁盘文件的方式,与在其他计算机上相同,尽管底层的硬件处理相当不同。库提供的
标准功能包括大多数程序员都可能需要的功能,例如处理文本字符串或数学计算,这样
就免除了自己实现这些功能所需的大量精力。
标准库在一系列标准文件——头文件中指定。头文件的扩展名总是.h。为了使一组标
准功能可用于C
程序文件,只需要将对应的标准头文件包含进来,其方式在本章后面介
绍。我们编写的每个程序都会用到标准库。附录E 汇总了构成标准库的头文件。
1.3
学习C
如果对编程非常陌生,则不需要学习C 的某些方面,至少在刚开始时不需要学习。
这些功能比较特殊,或者不大常用。本书把它们放在第14
章,这里读者可以在熟悉其他
内容后,再学习它们。
所有示例的代码都可以从Apress
网站(http://www.apress/com)上下载,但建议读者自
己输入本书中的所有示例,即使它们非常简单,也要输入。自己亲自输入,以后就不容
易忘记。不要害怕用代码进行实验。犯错对编程而言非常有教育性。早期犯的错误越多,
学到的东西就越多。
1.4
创建C 程序
C 程序的创建过程有4 个基本步骤或过程:
● 编辑
● 编译
● 链接
●
执行
这些过程很容易完成(就像翻转手掌一样简单,而且可以随时翻转),首先介绍每个
过程,以及它们对创建C 程序的作用。
1.4.1
编辑
编辑过程就是创建和修改C 程序的源代码——我们编写的程序指令称为源代码。有些C
编译器带一个编辑器,可帮助管理程序。通常,编辑器是提供了编写、管理、开发与测试程序的环境,有时也称为集成开发环境(Integrated Development
Environment,IDE)。
也可以用一般的文本编辑器来创建源文件,但它们必须将代码保存为纯文本,而没有嵌入附加的格式化数据。不要使用字处理器(例如微软的Word),字处理器不适合编写程序代码,因为它们在保存文本时,会附加一些格式化信息。一般来说,如果编译器系统带有编辑器,就会提供很多更便于编写及组织程序的功能。它们通常会自动编排程序文本的格式,并将重要的语言元素以高亮颜色显示,这样不仅让程序容易阅读,还容易找到单词输入错误。
在Linux 上,最常用的文本编辑器是Vim 编辑器,也可以使用GNU Emacs 编辑器。对于Microsoft
Windows,可以使用许多免费(freeware)或共享(shareware)的程序设计编辑器。这些软件提供了许多功能,例如,高亮显示特殊的语法及代码自动缩进等功能,帮助确保代码是正确的。Emacs
编辑器也有Microsoft Windows 版本。UNIX 环境的Vi 和
VIM
编辑器也可用于Windows,甚至可以使用Notepad++(http://notepad-plus-plus.org/)。
当然,也可以购买支持C 语言的专业编程开发环境,例如微软或Borland
的相关产品,它们能大大提高代码编辑能力。不过,在付款之前,最好检查一下它们支持的C 级别是否符合当前的C
语言标准C11。因为现在很多编辑器产品主要面向C++开发人员,C 语言只是一个次要目标。
1.4.2 编译
编译器可以将源代码转换成机器语言,在编译的过程中,会找出并报告错误。这个阶段的输入是在编辑期间产生的文件,常称为源文件。编译器能找出程序中很多无效或无法识别的错误,以及结构错误,例如程序的某部分永远不会执行。编译器的输出结果称为对象代码(object
code),存放它们的文件称为对象文件(object file),这些文件的扩展名在Microsoft Windows
环境中通常是.obj,在Linux/UNIX 环境中通常是.o。编译器可以在转换过程中找出几种不同类型的错误,它们大都会阻止对象文件的创建。
如果编译成功,就会生成一个文件,它与源文件同名,但扩展名是.o 或者.obj。如果在UNIX 系统下工作,在命令行上编译C
程序的标准命令是cc(若编译器是GNU’s Not UNIX(GNU),则命令为.gcc)。下面是一个示例:
cc -c
myprog.c
其中,myporg.c 是要编译的程序,如果省略了–c
这个参数,程序还会自动链接。
成功编译的结果是生成一个对象文件。
大多数C
编译器都有标准的编译选项,在命令行(如cc myprog.c)或集成开发环境下的菜单选项(Compile 菜单选项)里都可找到。在IDE
中编译常常比使用命令行容易得多。
编译过程包括两个阶段。第一个阶段称为预处理阶段,在此期间会修改或添加代码,第二个阶段是生成对象代码的实际编译过程。源文件可以包含预处理宏,它们用于添加或修改C
程序语句。如果现在不理解它们,不必担心,本书后面将进行详细论述。
1.4.3 链接
链接器(linker)将源代码文件中由编译器产生的各种对象模块组合起来,再从C 语言提供的程序库中添加必要的代码模块,将它们组合成一个可执行的文件。链接器也可以检测和报告错误,例如,遗漏了程序的某个部分,或者引用了一个根本不存在的库组件。
实际上,如果程序太大,可将其拆成几个源代码文件,再用链接器连接起来。因为很难一次编写一个很大的程序,也不可能只使用一个文件。如果将它拆成多个小源文件,每个源文件提供程序的一部分功能,程序的开发就容易多了。这些源文件可以分别编译,更容易避免简单输入错误的发生。再者,整个程序可以一点一点地开发,组成程序的源文件通常会用同一个项目名称集成,这个项目名称用于引用整个程序。
程序库提供的例程可以执行非C 语言的操作,从而支持和扩展了C
语言。例如,库中包含的例程支持输入、输出、计算平方根、比较两个字符串,或读取日期和时间信息等操作。
链接阶段出现错误,意味着必须重新编辑源代码;反过来,如果链接成功,就会产生一个可执行文件,但这并不一定表示程序能正常工作。在Microsoft Windows
环境下,这个可执行文件的扩展名为.exe;在UNIX 环境下,没有扩展名,但它是一个可执行的文件类型。多数IDE
也有Build(建立)选项,它可一次完成程序的编译和链接。
1.4.4 执行
执行阶段就是当成功完成了前述3
个过程后,运行程序。但是,这个阶段可能会出现各种错误,包括输出错误及什么也不做,甚至使计算机崩溃。不管出现哪种情况,都必须返回编辑阶段,检查并修改源代码。
在这个阶段,计算机最终会精确地执行指令。在UNIX 和Linux 下,只要键入编译和链接后的文件名,即可执行程序。在大多数IDE
中,都有一个相应的菜单命令来运行或者执行已编译的程序。这个Run命令或者Execute 命令可能有自己的菜单,也可能位于Compile
菜单项下。在Windows环境中,运行程序的.exe 文件即可,这与运行其他可执行程序一样。
1.5 创建第一个程序
本节先浏览一下创建C
语言程序的流程,从输入代码到执行程序的所有4 个步骤。
在这个阶段,若不了解所键入的代码信息,别担心,笔者会解释每一个步骤。
试试看:C 程序示例
打开编辑器,输入下面的程序,请注意标点符号不要输错,第4
行及最后一行的括号是大括号{},而不是方括号[]或者圆括号()——这很重要。另外一定要键入斜杠(/),以后也会用到反斜杠(\)。最后别忘了行末的分号(;)。
- <span style="font-size:18px;">/* Program 1.1 Your Very First C Program - Displaying Hello World */
- #include <stdio.h>
- int main(void)
- {
- printf("Hello world! ");
- return 0;
- }</span>
在输入了上面的源代码后,将程序保存为hello.c。可以用任意名字替代hello,但扩展名必须是.c。这个扩展名在编写C
程序时是一个通用约定,它表示文件的内容是C 语言源代码。大多数C 编译器都要求源文件的扩展名是.c,否则编译器会拒绝处理它。
下面编译程序(如本章前面“编译”一节所述),链接所有必要的内容,创建一个可执行程序(如本章前面“链接”一节所述)。编译和链接一般在一个操作中完成,通常称为“构建操作”。源代码编译成功后,链接器就添加程序需要的标准库代码,为程序创建一个可执行文件。
最后,执行程序。这有几种方式,在Windows 环境下,一般只需要在Windows Explorer中双击.exe
文件,但最好打开一个命令行窗口,输入执行它的命令,因为在程序执行完毕后,显示输出的窗口就会消失。在所有的操作系统环境上,都可以从命令行上运行程序。只需要启动一个命令行会话,把当前目录改为包含程序可执行文件的目录,再输入程序名,就可以执行它了。
如果没有出现错误,就大功告成了。这个程序会在屏幕上输出如下信息:
Hello world!
1.6
编辑第一个程序
我们可以修改程序,在屏幕上输出其他信息,例如可以将程序改成:
- <span style="font-size:18px;">/*Program 1.2 Your Second C Program */
- #include <stdio.h>
- int main(void)
- {
- printf("\"If at first you don‘t succeed, try, try, try again!\"");
- return 0;
- }</span>
这个版本的输出是:
"If at first you don‘t succeed, try, try, try again!
"
在要显示的文本中,\”序列称为转义序列(escape
sequence)。文本中包含几个不同的转义序列。\”是在文本中包含双引号的特殊方式,因为双引号通常表示字符串的开头和结尾。转义序列\”使双引号出现在输出的开头和结尾。如果不使用转义序列,不仅双引号不会出现在输出中,而且程序不会编译。本章后面的“控制字符”一节将详细介绍转
义序列。
修改完源代码后,可以重新编译,链接后执行。反复练习,熟悉整个流程。
1.7 处理错误
犯错乃人之常情,没什么难为情的。幸好计算机一般不会出错,而且非常擅长于找出我们犯的错误。编译器会列出在源代码中找到的一组错误信息(甚至比我们想象的多),通常会指出有错误的语句。此时,我们必须返回编辑阶段,找出有错误的代码并更正。有时一个错误会使后面本来正确的语句也出现错误。这多半是程序的其他部分引用了错误语句定义的内容所造成的。当然,定义语句有错,但被定义的内容不一定有错。
下面看看源代码在程序中生成了一个错误时,会是什么样的情况。编辑第二个程序示例,将printf()行最后的分号去掉,如下所示:
- <span style="font-size:18px;">/*Program 1.2 Your Second C Program */
- #include <stdio.h)
- int main(void)
- {
- printf("\"If at first you don‘t succeed, try, try, try again!\"")
- return 0;
- }</span>
编译这个程序后,会看到错误信息,具体信息随编译器的不同而略有区别。下面是一个比较常见的错误信息:
- <span style="font-size:18px;">Syntax error : missing ‘;‘ before ‘}‘
- HELLO.C - 1 error(s), 0 warning(s)</span>
编译器能精确地指出错误及其出处,在这里,printf()行的结尾处需要一个分号。在开始编写程序时,可能有很多错误是简单的拼写错误造成的。还很容易忘了逗号、括号,或按错了键。没关系,许多有经验的老手也常犯这种错误。如前所述,有时一点小错误会造成大灾难,编译器会显示许多不同的错误信息。不要被错误的数量吓倒,仔细看过每一个错误信息后,返回并改掉错误部分,不懂的先不管它,然后再编译一次源文件,就会发现错误一次比一次少。
返回编辑器,重新输入分号,再编译,看看有没有其他错误,如果没有错误,程序就可以执行了。
1.8
剖析一个简单的程序
编写并编译了第一个程序后,下面是另一个非常类似的例子,了解各行代码的作用:
- <span style="font-size:18px;">/* Program 1.3 Another Simple C Program - Displaying a Quotation */
- #include <stdio.h>
- int main(void)
- {
- printf("Beware the Ides Of March!");
- return 0;
- }</span>
这和第一个程序完全相同,这里把它作为练习,用编辑器输入这个示例,编译并执行。若输入完全正确,会看到如下输出:
Beware the Ides Of
March!
1.8.1 注释
上述示例的第一行代码如下:
/* Program 1.3 Another Simple C Program -
Displaying a Quotation
*/
这不是程序代码,因为它没有告诉电脑执行操作,它只是一个注释,告诉阅读代码的人,这个程序要做什么。位于/*和*/之间的任意文本都是注释。只要编译器在源文件中找到/*,就忽略它后面的内容(即使其中的文本很像程序代码),一直到表示注释结束的*/为止。/*可以和*/放在同一行代码上,也可以放在不同的代码行上。如果忘记包含对应的
*/,编译器就会忽略/*后面的所有内容。下面使用一个注释说明代码的作者及版权所有:
/*
*
Written by Ivor Horton
* Copyright 2012
*/
也可以修饰注释,使它们比较突出:
/
******************************************* *
* This is a very important
comment *
* so please read this.
*
******************************************* * /
使用另一种记号,可以在代码行的末尾添加一个注释,如下所示:
printf("Beware the Ides of March!"); // This
line displays a quotation
代码行上两个斜杠后面的所有内容都会被编译器忽略。这种形式的注释没有前一种记号那么凌乱,尤其是在注释只占一行的情形下。应养成给程序添加注释的习惯,当然程序也可以没有注释,但在编写较长的程序时,
可能会忘记这个程序的作用或工作方式。添加足够的注释,可确保日后自己(和其他程序员)能理解程序的作用和工作方式。
下面给程序再添加一些注释:
- <span style="font-size:18px;">/* Program 1.3 Another Simple C Program - Displaying a Quotation */
- #include <stdio.h> // This is a preprocessor directive
- int main(void) // This identifies the function main()
- { // This marks the beginning of main()
- printf("Beware the Ides of March!"); // This line outputs a quotation
- return 0; // This returns control to the operating system
- } // This marks the end of main()</span>
可以看出,使用注释是一种非常有效的方式,可以解释程序中要发生的事情。注释可以放在程序中的任意位置,说明代码的一般作用,指定代码是如何工作的。
1.8.2
预处理指令
下面的代码行:
#include <stdio.h> // This is a preprocessor
directive
严格说来,它不是可执行程序的一部分,但它很重要,事实上程序没有它是不执行的。符号#表示这是一个预处理指令(preprocessing
directive),告诉编译器在编译源代码之前,要先执行一些操作。编译器在编译过程开始之前的预处理阶段处理这些指令。预处理指令相当多,大多放于程序源文件的开头。
在这个例子中,编译器要将stdio.h
文件的内容包含进来,这个文件称为头文件(headerfile),因为它通常放在程序的开头处。在本例中,头文件定义了C
标准库中一些函数的信息,但一般情况下,头文件指定的信息应由编译器用于在程序中集成预定义函数或其他全局对象,所以有时需要创建自己的头文件,以用于程序。本例要用到标准库中的printf()函数,所以必须包含stdio.h
头文件。stdio.h 头文件包含了编译器理解printf()以及其他输入/输出函数所需要的信息。名称stdio 是标准输入/输出(standard
input/output)的缩写。C
语言中所有头文件的扩展名都是.h,本书的后面会用到其他头文件。
注意:
在一些系统中,头文件名是不区分大小写的,但在#include
指令里,这些文件名通常是小写。每个符合C11 标准的C 编译器都有一些标准的头文件。这些头文件主要包含了与C标准库函数相关的声明。所有符合该标准的C
编译器都支持同一组标准库函数,有同一组标准头文件,但一些编译器有额外的库函数,它们提供的功能一般是运行编译器的计
算机所专用的。
注意:
附录E
列出了所有的标准头文件。
1.8.3 定义main()函数
下面的5 行指令定义了main()函数:
- <span style="font-size:18px;">int main(void) // This identifies the function main()
- { // This marks the beginning of main()
- printf("Beware the Ides of March!"); // This line outputs a quotation
- return 0; // This returns control to the operating system
- } // This marks the end of main()</span>
函数是两个括号之间执行某组操作的一段代码。每个C 程序都由一个或多个函数组成,每个C 程序都必须有一个main()函数——因为每个程序总是从这个函数开始执行。
因此假定创建、编译、链接了一个名为progname.exe
的文件。执行它时,操作系统会执行这个程序的main()函数。
定义main()函数的第一行代码如下:
- <span style="font-size:18px;">int main(void) // This identifies the function main()</span>
它定义了main()函数的起始,注意这行代码的末尾没有分号。定义main()函数的第一行代码开头是一个关键字int,它表示main()函数的返回值的类型,关键字int
表示main()函数返回一个整数值。执行完main()函数后返回的整数值表示返回给操作系统的一个代码,它表示程序的状态。在下面的语句中,指定了执行完main()函数后要返回的值:
- <span style="font-size:18px;">return 0; // This returns control to the operating system</span>
这个return 语句结束main()函数的执行,把值0
返回给操作系统。从main()函数返回0 表示,程序正常终止,而返回非0 值表示异常。换言之,在程序结束时,发生了不应发生的事情。
紧跟在函数名main
后的括号,带有函数main()开始执行时传递给它的信息,在这个例子里,括号内是void,表示没有给函数main()传递任何数据,后面会介绍如何将数据传递给函数main()或程序内的其他函数。
函数main()可以调用其他函数,这些函数又可以调用其他函数。对于每个被调用的函数,都可以在函数名后面的括号中给函数传递一些信息。在执行到函数体中的return语句时,就停止执行该函数,将控制权返回给调用函数(对于函数main(),则将控制权返回给操作系统)。一般函数会定义为有返回值或没有返回值。函数返回一个值时,该值总
是特定的类型。对于函数main(),返回值的类型是int,即整数。
1.8.4
关键字
在C
语言中,关键字是有特殊意义的字,所以在程序中不能将关键字用于其他目的。关键字也称为保留字。在前面的例子里,int 就是一个关键字,void 和return
也是关键字。C 语言有许多关键字,我们在学习C 语言的过程中,将逐渐熟悉这些关键字。附录C 列出了完整的C 语言关键字表。
1.8.5
函数体
main()函数的一般结构如图l-2 所示:
函数体是在函数名称后面位于起始及结束两个大括号之间的代码块。它包含了定义函数功能的所有语句。这个例子的main()函数体非常简单,只有两个语句:
- <span style="font-size:18px;">{ // This marks the beginning of main()
- printf("Beware the Ides of March!"); // This line outputs a quotation
- return 0; // This returns control to the operating system
- } // This marks the end of main()</span>
每个函数都必须有函数体,但函数体可以是空的,仅有起始及结束两个大括号,里面没有任何语句,在这种情况下,这个函数什么也不做。
这样的函数有什么用?事实上,在开发一个包含很多函数的程序时,这种函数是非常有用的。我们可以声明一些用来解决手头问题的空函数,确定需要完成的编程工作,再为每个函数创建程序代码。这个方法有助于条理分明地、系统地建立程序。
注意:
程序1.3
将大括号单独排为一行,并缩进大括号之间的代码。这么做可清楚地表示括号框起来的语句块从哪里起始和结束。大括号之间的语句通常缩进两个或多个空格,使大括号突出在前。这是个很好的编程格式,可以使语句块更容易阅读。
代码中的大括号可以用其他方式摆放。例如:
提示:
- <span style="font-size:18px;">int main(void) {
- printf("Beware the Ides of March!"); // This line outputs a quotation
- return 0;
- }</span>
无论源代码采用什么方式摆放,都要一直采用这种方式,这很重要。
1.8.6
输出信息
例子中的main()函数体包含了—个调用printf()函数的语句:
- <span style="font-size:18px;">printf("Beware the Ides of March!"); // This line outputs a quotation</span>
printf()是—个标准的库函数,它将函数名后面引号内的信息输出到命令行上(实际上是标准输出流,默认为命令行)。在这个例子中,调用这个函数会显示双引号内的一段警示语:双引号内的字符串称为字符串字面量。注意这行代码用分号作为结尾。
1.8.7
参数
包含在函数名(如上面语句中的printf()函数)后的圆括号内的项称为参数,它指定要传送给函数的数据。当传送给函数的参数多于一个时,要用逗号分开。在上面的例子中,函数的参数是双引号内的文本字符串。如果不喜欢例子中引号内的文本,可以改用自己想输出的句子。例如,使用如下语句:
- <span style="font-size:18px;">printf("Out, damned Spot! Out I say!");</span>
修改源代码后,必须再次编译及链接程序,才可执行。
注意:
与C
语言中所有可执行的语句一样,printf()行的末尾必须有分号(这与定义语句或指令语句不同)。这是一个很容易犯的错误,尤其是初次使用C
编程的人,老是忘了分号。
1.8.8 控制符
前面的程序可以改为输出两段句子。输入以下的代码:
- <span style="font-size:18px;">// Program 1.4 Another Simple C Program - Displaying a Quotation
- #include <stdio.h>
- int main(void)
- {
- printf("My formula for success?\nRise early, work late, strike oil.\n");
- return 0;
- }</span>
输出的结果是:
My formula for success?
Rise early, work late,
strike oil.
在printf()语句中,在文本的开头和第一句的后面,增加了字符\n,它是另一个转义序列,代表换行符。这样输出光标就会移动到下一行,后续的输出就会显示在新行上。反斜杠(\)在文本字符串里有特殊的意义,它表示转义序列的开始。反斜杠后面的字符表示是哪种转义序列。对于\n,n
表示换行。还有其他许多转义序列。显然,反斜杠是有
特殊意义的,所以需要一种方式在字符串中指定反斜杠。为此,应使用两个反斜杠(\\)。
输入以下的程序:
- <span style="font-size:18px;">// Program 1.5 Another Simple C Program - Displaying Great Quotations
- #include <stdio.h>
- int main(void)
- {
- printf("\"It is a wise father that knows his own child.\"\nShakespeare\n");
- return 0;
- }</span>
输出的结果如下:
"It is a wise father that knows his own
child."
Shakespeare
输出中包含双引号,因为在字符串中使用了双引号的转义序列。Shakespeare 显示在下一行,因为在\”的后面有\n 转义序列。
在输出字符串中使用转义序列\a 可以发出声音,说明发生了有趣或重要的事情。输
入以下的程序并执行:
- <span style="font-size:18px;">// Program 1.6 A Simple C Program – Important
- #include <stdio.h>
- int main(void)
- {
- printf("Be careful!!\n\a");
- return 0;
- }</span>
这个程序的输出如下所示且带有声音。仔细聆听,电脑的扬声器会发出鸣响。
Be
careful!!
转义序列\a 表示发出鸣响。表1-1 是转义序列表。
试着在屏幕上显示多行文本,在该文本中插入空格。使用 \n 可以把文本放在多个行上,使用\t
可以给文本加上空格。本书将大量使用这些转义序列。
1.8.9 三字母序列
一般可以直接在字符串中使用问号。\?转义序列存在的唯一原因是,有9
个特殊的字母序列,称为三字母序列,这是包含三个字母的序列,分别表示#、[、]、\、^、~、\、{和}:
在International
Organization for Standardization(ISO)不变的代码集中编写C
代码时,就需要它们,因为它没有这些字符。这可能不适用于你。可以完全不理会它们,除非希望编写如下语句:
- <span style="font-size:18px;">printf("What??!\n");</span>
这个语句生成的输出如下:
What|
三字母序列??!会转换为|。为了获得希望的输出,需要把上述语句写成:
- <span style="font-size:18px;">printf("What?\?!\n");</span>
现在三字母序列不会出现,因为第二个问号用其转义序列指定。使用三字母序列时,编译器会发出一个警告,因为通常是不应使用三字母序列的。
1.9
预处理器
上述示例介绍了如何使用预处理指令,把头文件的内容包含到源文件中。编译的预处理阶段可以做的工作远不止此。除了指令之外,源文件还可以包含宏。宏是提供给预处理器的指令,来添加或修改程序中的C
语句。宏可以很简单,只定义一个符号,例如INCHES_PER_FOOT,只要出现这个符号,就用12 替代。其指令如下:
- <span style="font-size:18px;">#define INCHES_PER_FOOT 12</span>
在源文件中包含这个指令,则代码中只要出现INCHES_PER_FOOT,就用12
替代它。例如:
printf("There are %d inches in a foot.\n",
INCHES_PER_FOOT);
预处理后,这个语句变成:
- <span style="font-size:18px;">printf("There are %d inches in a foot.\n", 12);</span>
INCHES_PER_FOOT 不再出现,因为该符号被#define
指令中指定的字符串替代。对于源文件中的每个符号实例,都会执行这个替代。宏也可以很复杂,根据特定的条件把大量代码添加到源文件中。这里不进一步介绍。
1.10
用C 语言开发程序
如果读者从未写过程序,对C
语言开发程序的过程就不会很清楚,但它和我们日常生活的许多事务是相同的,万事开头难。一般首先大致确定要实现的目标,接着把该目标转变成比较准确的规范。有了这个规范后,就可以制订达到最终目标的一系列步骤了。
就好比光知道要盖房子是不够的,还得知道需要盖什么样的房子,它有多大,用什么材料,要盖在哪里。这种详细规划也需要运用到编写程序上。下面介绍编写程序时需要完成的基本步骤。房子的比喻是很有帮助的,因此就利用这个比喻。
1.10.1
了解问题
第一步是弄清楚要做什么。在不清楚应提供什么设施:多少间卧房、多少间浴室、各房间多大等等之前就开始建造房子,会有不知所措之感。所有这些都会影响建造房子所需的材料和工作量,从而影响整个房子的成本。一般来说,在满足需求和完成项目的有限资金、人力及时间之间总会达成某种一致。
这和开发一个任意规模的程序是相同的。即使是很简单的问题,也必须知道有什么输入,对输入该做什么处理,要输出什么,以及输出哪种格式。输入可以来自键盘,也可以来自磁盘文件的数据,或来自电话或网络的信息。输出可以显示在屏幕上,或打印出来,也可以是更新磁盘上的数据文件。
对于较复杂的程序,需要多了解程序的各个方面。清楚地定义程序要解决的问题,对于理解制订最终方案所需的资源与努力,是绝对必要的一部分。好好考虑这些细节,还可以确定项目是否切实可行。对于新项目缺乏精准、详细的规范,常常使项目所花的时间和资金大大超出预算,因而中断项目的例子有很多。
1.10.2
详细设计
要建造房子,必须有详细的计划。这些计划能让建筑工人按图施工,并详细描述房子如何建造——具体的尺寸、要使用的材料等。还需要确定何时完成什么工作。例如,在砌墙之前要先挖地基,所以这个计划必须把工作分为可管理的单元,以便执行起来井然有序。
写程序也是一样。首先将程序分解成许多定义清楚且互相独立的小单元,描述这些独立单元相互沟通的方式,以及每个单元在执行时需要什么信息,从而开发出富有逻辑、相互独立的单元。把大型程序编写为一个大单元肯定是不可行的。
1.10.3
实施
有了房子的详细设计,就可以开始工作了。每组建筑工人必须按照进度完成他们的工作。在下一阶段开始前,必须先检查每个阶段是否正确完成。省略了这些检查,将可能导致整栋房子倒塌。
当然,假使程序很大,可以一次编写—部分。一个部分完成后,再写下—部分。每个部分都要基于详细的设计规范,在进行下一个部分之前,应尽可能详细地检查每个部分的功能。这样,程序就会逐步完成预期的任务。
大型编程项目常常涉及一组程序员。项目应分成相当独立的单元,分配给程序员组中的各个成员。这样就可以同时开发几个代码单元。如果代码单元要相互连接为一个整体,就必须精确定义代码单元与程序其余部分之间的交互。
1.10.4
测试
房子完成了,还要进行许多测试:排水设备、水电设施、暖气等。任何部分都有可能出问题,这些问题必须解决。这有时是一个反复的过程,一个地方的问题可能会造成其他地方出问题。
这个机制与写程序是类似的。每个程序模块——组成程序的单元——都需要单独测试。若它们工作不正常,就必须调试。调试(Debugging)是一个找出程序中的问题及更正错误的过程。调试的由来有个说法,曾经有人在查找程序的错误时,使用计算机的电路图来跟踪信息的来源及其处理方式,竟然发现计算机程序出现错误,是因为一只虫子在
电脑里,让里面的线路短路而发生的,后来,bug
这个词就成了程序错误的代名词。对于简单的程序,通常只要检查代码,就可以找出错误。然而一般来说,调试过程通常会使用调试器临时插入一些代码,确定在出错时会发生什么。这包括插入断点,当暂停执行,检查代码中的值。还可以单步执行代码。如果没有调试器,就要加入额外的程序代码,输出一些信息,来确定程序中事件的发生顺序,以及程序执行时生成的中间值。在大型的程序里,还需要联合测试各个程序模块,因为各个模块或许能正常工作,但并不保证它能和其他模块一起正常工作。在程序开发的这个阶段,有个专业术语叫集成测试(integration
testing)。
1.11 函数及模块化编程
到目前为止,“函数”这个词已出现过好几次了,如main()、printf()、函数体等。下面将深入研究函数是什么,为什么它们那么重要。大多数编程语言(包含C
语言)都提供了一种方法,将程序切割成多个段,各段都可以独立编写。在C
语言中,这些段称为函数。一个函数的程序代码与其他函数是相互隔绝的。函数与外界有一个特殊的接口,可将信息传进来,也可将函数产生的结果传出去。这个接口在函数的第一行即在函数名的地方指定。
图1-3 的简单程序例子由4 个函数组成,用于分析棒球分数。
这4
个函数都完成一个指定的、定义明确的工作。程序中操作的执行由一个模块main()总体掌控。一个函数负责读入及检查输入数据,另一个函数进行分析。读入及分析了数据后,第4
个函数就输出球队及球员的排名。
将程序分割成多个易于管理的小单元,对编程是非常重要的,其理由如下:
●
可以单独编写和测试每个函数,大大简化了使整个程序运转起来的过程。
● 几个独立的小函数比一个大函数更容易处理和理解。
●
库就是供人使用的函数集。因为它们是事先写好,且经过测试,能正常工作,所以可以放心地使用,无须细究它的代码细节。这就加快了开发程序的速度,因为我们只需要关注自己的代码,这是C
语言的一个基本组成部分。C 语言中丰富的函数库大大增强了C 语言的能力。
●
也可以编写自己的函数库,应用于自己感兴趣的程序类型。如果发现经常编写某个函数,就可以编写它的通用版本,以满足自己的需求,并将它加入自己的库中。以后需要用到这个函数时,就可使用它的库版本了。
●
在开发包含几千到几百万行代码的大型程序时,可以由一些程序设计团队来进行,每个团队负责一个指定的函数子组,最后把它们组成完整的程序。
第8 章将详细介绍C 函数。C
程序的结构在本质上就是函数的结构,本章的第一个例子就用到一个标准的库函数printf()。
注意:
在其他一些编程语言中,用术语“方法”表示自包含的代码单元。因此方法的含义与函数相同。
试试看:将所学的知识用于实践
下面的例子将前面学到的知识用于实践。首先,看看下面的代码,检查自己是否理解它的作用。然后输入这些代码,编译、链接并执行,看看会发生什么。
- <span style="font-size:18px;">// Program 1.7 A longer program
- #include <stdio.h> // Include the header file for input and output
- int main(void)
- {
- printf("Hi there!\n\n\nThis program is a bit");
- printf(" longer than the others.");
- printf("\nBut really it‘s only more text.\n\n\n\a\a");
- printf("Hey, wait a minute!! What was that???\n\n");
- printf("\t1.\tA bird?\n");
- printf("\t2.\tA plane?\n");
- printf("\t3.\tA control character?\n");
- printf("\n\t\t\b\bAnd how will this look when it prints out?\n\n");
- return 0;
- }</span>
输出如下:
Hi there!
This program is a bit longer than
the others.
But really it‘s only more text.
Hey, wait a minute!! What was
that???
1. A bird?
2. A plane?
3. A control character?
And how will
this look when it prints
out?
代码的说明
这个程序看起来有点复杂,这只是因为括号内的文本字符串包含了许多转义序列。每个文本字符串都由一对双引号括起来。但这个程序只是连续调用printf()函数,说明屏幕输出是由传送给printf()函数的数据所控制。
本例通过预处理指令包含了标准库中的stdio.h 文件:
#include <stdio.h> //
Include the header file for input and output
这是一个预处理指令,因为它以符号#开头。stdio.h
文件提供了使用printf()函数所需的定义。
然后,定义main()函数头,指定它返回一个整数值:
int
main(void)
括号中的void
关键字表示不给main()函数传递信息。下一行的大括号表示其下是函数体:
{
下一行语句调用标准库函数printf(),将“Hi there!”输出到屏幕上,接着空两行,
输出“This program is a bit”。
printf("Hi there!\n\n\nThis program
is a bit");
空两行是由3 个转义序列\n 生成的。转义序列\n
会把字符显示在新行上。第一个转义序列\n 结束了包含“Hi there!”的行,之后的两个转义序列\n 生成两个空行,文本“This program
is a bit”显示在第4 行上。这行代码在屏幕上生成了4 行输出。
下一个printf()生成的输出跟在上一个printf()输出的最后一个字符后面。下面的语句输出文本“ longer than the
others.”,其中的第一个字符是一个空白:
printf(" longer than the others.");
这个输出跟在上一个输出的后面,紧临bit 中的t。所以在文本的开头需要一个空格,否则计算机就会显示“This program is a
bitlonger than the others.”,这不是我们希望的结果。
下一个语句在输出前会先换行,因为双引号中文本字符串的开头是\n:
printf("\nBut really itS only more
text.\n\n\n\a\a");
显示完文本后会空两行(因为有3 个\n
转义序列),然后发出两次鸣响。下一个屏幕输出从空的第二行开始。
下一个输出语句如下:
printf("Hey, wait a minute!!
What was that???\n\n");
输出文本后空一行。其后的输出在空的这行开始。
以下3
行语句各插入一个制表符,显示一个数字后,再插入另一个制表符,之后是
一些文本,结束后换行。这样,输出更容易阅读:
printf("\t1.\tA
bird?\n");
printf("\t2.\rA plane?\n");
printf("\t3.\tA control
character?\n");
这几个语句会生成3
行带编号的输出。
下一语句先输出一个换行符,所以在前面输出的后面是一个空行,然后输出两个制表符和两个空格,接着退回两个空格,最后显示文本并换行:
printf("\n\t\t\b\bAnd
how will this look when it prints out?\n\n");
函数体中的最后一个语句如下:
return
0;
这个语句结束main()的执行,把0
返回给操作系统。结束大括号表示函数体结束:
}
注意:
输出中制表符和退格的实际效果随编译器的不同而不同。
1.12
常见错误
错误是生活中的一部分。用C
语言编写计算机程序时,必须用编译器将源代码转换成机器码,所以必须用非常严格的规则控制使用C
语言的方式。漏掉一个该有的逗点,或添加不该有的分号,编译器都不会将程序转换成机器码。
即使实践了多年,程序中也很容易出现输入错误。这些错误可能在编译或链接程序时找出。但有些错误可能使程序执行时,表面上看起来正常,却不定时地出错,这就需要花很多时间来跟踪错误了。
当然,不是只有输入错误会带来问题,具体实施时也常常会发现问题。在处理程序中复杂的判断结构时,很容易出现逻辑错误。从语言的观点看,程序是正确的,编译及运行也正确,但得不到正确的结果。这类错误最难查找。
1.13
要点
温习第一个程序是个不错的方法,图1-4 列出了重点。
1.14 小结
本章编写了几个C
程序。我们学习了许多基础知识,本章的重点是介绍一些基本概念,而不是详细探讨C 程序语言。现在读者应该对编写、编译及链接程序很有信心了。也许读者目前对如何构建C
程序只有模糊的概念。以后学了更多的C 语言知识,编写了一些程序后,就会清楚明白了。
下一章将学习较复杂的内容,而不只是用printf()输出文本。我们要处理信息,得到更有趣的结果。另外,printf()不只是显示文本字符串,它还有其他用途。
《C语言入门经典(第5版)》试读电子书免费提供,有需要的留下邮箱,一有空即发送给大家。 别忘啦顶哦!
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。