c程序设计语言(k&r)中文高清版


K&R The C Programming Language THE A N S I PROGRAMMING LANGUAGE BRIAN W. Kernighan Dennis M. RItchie SECOND EDITION PRENTICE HALL SOFTWARE SERIES 语计 言设 序 程 C WELLDONE C 程序设计语言 K&R == != >=<= >><< >< 歌诀 无极囫囵最为初,括号结构不离疏; 单符能有也能无,无极之后太极出; 两仪算子占首先,乘除余法源加减; 四相空间挪上下,化为左右合八卦; 八中余六中庸显,异同分明意境浅; 一零轮回二进制,位与异或分顺次; 二仪之末举逻辑,并且或者理明晰; 三目算符唯高妙,一事两面均虑到; 自作自受自繁难,舍己从人豁然宽; 从东往西逆流去,简符简判与简记; 有心至此余一符,诸君其中可找出? 此诀一出天下间,自始不见问谁先! 序 自 1978 年《C 程序设计语言》出版以来,计算机领域经历了一场革命。大型计算机已远大 于从前,个人计算机的能力也足以媲美十年前的大型机。在此期间,C 语言也已悄然改变,并 远远超越了其 UNIX 操作系统的编程语言这一初始角色。 C 语言的日益普及,加之这些年来的改变,以及一些非官方(组织开发的)编译器的出现, 均需要一个比本书第一版更精确、更现代的 C 语言定义被提出。1983 年,美国国家标准协会 (ANSI)成立了一个委员会,其目标是制定“一个无歧义的、与具体机器无关的 C 语言定义”, 并保留 C 语言原有的精髓。这就是 C 语言 ANSI 标准的由来。 此标准规范了在本书第一版中提及但未进行描述的构成,特别是结构赋值和枚举。它提供 了一种新的函数声明形式,允许对函数的定义与使用进行交叉检查。它详细规定了一个标准库, 库中包含一个扩充函数集,完成输入输出、内存管理、字符串操作以及其他类似的任务。标准 对原有定义中未清楚描述的特性的行为进行了精确定义,同时明确交代了语言在哪些方面仍旧 依赖于具体机器。 《C 程序设计语言》的第二版描述了 ANSI 标准定义的 C 语言。尽管我们已摘录出语言的 革新之处,但我们还是决定全部用新的形式进行撰写。对于大部分内容,这并未带来明显的差 异;最显著的改变是新的函数声明及定义形式。现代编译器已经支持这一标准的大多数特性。 我们力求保持本书第一版的简洁性。C 不是一个大型语言,因而也不适合用一本大部头来 介绍。我们改进了对关键特性的阐释,譬如 C 程序设计的核心——指针。我们还对原先的例子 进行了提炼,并在一些章节中增加了新的例子。例如,复杂声明的处理部分增加了将声明与对 应文字描述进行相互转换的函数。如前一版本,所有的例子其文本都为机器可读格式,已直接 进行过测试。 附录 A 是参考手册而不是标准本身,但我们试图以较小篇幅呈现出标准的精华内容。它的 目的是让编程者易于理解,而不是供编译器实现者参考的定义——那正是标准应当承担的角色。 附录 B 是对标准库中功能函数的总结,其同样针对编程者,而非实现者。附录 C 是对之前版本 所作变更的小结。 就如我们在第一版序中所说,C 语言“随着经验的积累其使用会愈加得心应手”。经过十几 年的体验,我们仍然这么认为。希望本书能帮助你学好并用好 C 语言。 深深感谢那些帮助我们完成本书(第二版)的朋友们。Jon Bentley、Doug Gwyn、Doug McIlroy、Peter Nelson 和 Rob Pike 对本书手稿的几乎每页都给出了透彻的评注。我们非常感谢 Al Aho、Dennis Allison、Joe Campbell、G.R.Emlin、Karen Fortgang、Allen Holub、Andrew Hume、 Dave Kristol、John Linderman、Dave Prosser、Gene Spafford 和 Chris Van Wyk,他们仔细地阅读 了本书。我们也收到了来自 Bill Cheswick、Mark Kernighan、Andy Koenig、Robin Lake、Tom London、Jim Reeds、Clovis Tondo 和 Peter Weinberger 的有益建议。Dave Prosser 解答了许多关 于 ANSI 标准的细节问题。我们大量地使用了 Bjarne Stroustrup 的 C++翻译程序来进行程序的局 部测试。Dave Kristol 为我们提供了一个 ANSI C 编译器进行最终测试。Rich Drechsler 协助进行 了大量的排版工作。 诚挚地感谢每个人。 Brian W. Kernighan Dennis M. Ritchie 引言 C 语言是一种通用的程序设计语言。它同 UNIX 系统之间具有非常密切的联系——C 语言 是在 UNIX 系统上开发的,并且,无论是 UNIX 系统本身还是其上运行的大部分程序,都是用 C 语言编写的。但是,C 语言并不受限于任何一种操作系统或机器。由于它很适合用来编写编 译器和操作系统,因此被称为“系统编程语言”,但它同样适合于编写不同领域中的大多数程序。 C 语言的很多重要概念来源于由 Martin Richards 开发的 BCPL 语言。BCPL 对 C 语言的影 响间接地来自于 B 语言,它是 Ken Thompson 为第一个 UNIX 系统而于 1970 年在 DECPDP-7 计 算机上开发的。 BCPL 和 B 语言都是“无类型”的语言。与之相对,C 语言提供了很多数据类型。其基本 类型包括字符、具有多种长度的整型和浮点数等。另外,还有通过指针、数组、结构和联合派 生的各种数据类型。表达式由运算符和操作数组成。任何一个表达式,包括赋值表达式或函数 调用表达式,都可以是一个语句。指针提供了与具体机器无关的地址算术运算。 C 语言为实现结构良好的程序提供了基本的控制流结构:语句组、条件判断(if—else)、多 路 选择(switch)、终止测试在顶部的循环(while、for)、终止测试在底部的循环(do)、提前跳出循环 (break)等。 函数可以返回基本类型、结构、联合或指针类型的值。任何函数都可以递归调用。局部变 量通常是“自动的”, 即在每次函数调用时重新创建。函数定义可以不列放在一起,而变量可以 按块结构的形式进行声明。一个 C 语言程序的不同函数可以出现在多个单独编译的不同源文件 中,变量可以仅在函数内可见,或可位于函数外且仅在单个源文件中可见,也可对于整个程序 都可见。 编译的预处理阶段将对程序文本进行宏替换、包含其他源文件以及进行条件编译。 C 语言是一种相对“低级”的语言。这种说法并没有什么贬义,它仅仅意味着 C 语言可以 处理大部分计算机能够处理的对象,比如字符、数字和地址。这些对象可以通过具体机器实现 的算术运算符和逻辑运算符进行组合和移动。 C 语言没有提供直接处理诸如字符串、集合、列表或数组等复合对象的操作。尽管可以将 结构作为整体单元进行拷贝,但语言中没有处理整个数组或字符串的操作。除了静态定义和运 用于函数局部变量的栈规则之外,C 语言没有定义任何存储器分配工具,也不提供堆和对无用 内存的回收。最后,C 语言本身没有提供输入/输出功能,没有 READ 或 WRITE 语句,也没 有内置的文件访问方法。所有这些高层的机制必须由显式调用的函数提供。大多数 C 语言的具 体实现中都已包括了一个适当的(这些函数的)标准集合。 类似地,C 语言只提供简单的单线程控制流,即测试、循环、分组和子程序,它不提供多 道程序设计、并行操作、同步和协同例程。 尽管缺少其中的某些特性看起来像是严重的缺陷(“你的意思是必须通过调用函数来比较两 个字符串?”),但是将语言保持在一个适度的规模有其实际的益处。 由于 C 语言相对较小,因 此能用较小的篇幅来描述,也能很快学会。程序员有理由期望能了解、理解并真正日常使用整 个语言。 多年以来,C 语言的定义就是《The C Programming Language》第 1 版中的参考手册。1983 年,美国国家标准协会(ANSI)成立了一个委员会以制定一个现代的、全面的 C 语言定义。最 终的定义——ANSI 标准,即“ANSI C”,于 1988 年后期完成。该标准的大部分特性已被现代 编译器所支持。 此标准基于原有的参考手册制定。语言本身只做了相对较小的改动;此标准的目的之一就 是确保现有的程序仍然有效,或者当无效时,编译器能对新的行为产生警告信息。 对大部分程序员来说,最重要的变化是新的函数声明和函数定义的语法。现在,函数声明 中可以包含函数参数的描述信息;函数定义语法也有与之相匹配的改变。这些附加的信息使得 编译器在检测因参数不匹配而导致的错误时容易了许多。根据我们的经验,这一扩充对于语言 非常有用。 新标准对语言还做了一些小范围的改进。现在,已得到广泛支持的结构赋值和枚举成为了 语言的正式部分;浮点运算可以以单精度进行计算;算术运算,特别是无符号类型的运算特性 得到了明确定义;对预处理器的描述更加详尽。这些改进于对大部分程序员的影响大都比较小。 此标准的第二个重要贡献是为 C 语言定义了一个函数库。它描述了诸如访问操作系统(如 读写文件)、格式化输入/输出、内存分配和字符串操作、以及其他类似的函数。此标准还定义 了一系列标准头文件,它们为访问函数声明和数据类型声明提供了统一方法。程序使用这一函 数库与宿主系统交互能够确保具有兼容的行为。此函数库的大部分内容高度效仿了 UNIX 系统 的“标准 I/O 库”。此函数库已在本书的第 1 版中进行了描述,并业已在其他一些系统中广泛使 用。同样,大多数程序员也不会感到这部分有太大变化。 由于大多数计算机本身直接支持 C 语言提供的数据类型和控制结构,因此只需要一个很小 的运行时库就可以实现自包含程序。由于标准库中的函数只会被显式地调用,因此不需要的库 函数即可被省略掉。大部分库函数能用 C 语言编写,而且除了其中隐藏的操作系统细节之外都 可移植。 尽管 C 语言能够运行在许多计算机上,但它不依赖任何特定的硬件系统架构。只要稍加注 意就可以编写出可移植的程序,即程序可以不加修改地运行在多种硬件上。ANSI 标准明确地提 出了可移植性问题,并预设了一个常量的集合,用于描述运行程序的机器的特性。 C 语言不是一种强类型的语言,但随着它的发展,其类型检查机制已经得到了加强。尽管 C 语言的最初定义不赞成指针和整型变量相互转换,但并没有禁止;这种做法已经淘汰很长时间 了。现在,ANSI 标准要求对变量进行恰当的声明和显式的类型转换,一些优秀的编译器已经强 制要求那样做。新的函数声明方式则是朝此方向迈进的另一步。编译器将对大部分的数据类型 错误发出警告,并且不自动执行不兼容数据类型之间的类型转换。不过,C 语言保持了其初始 的设计思想,即程序员了解他们自己在做什么,它只是要求程序员将自己的意图明确地表达出 来。 同任何其他语言一样,C 语言也有瑕疵。某些运算符的优先级不正确;语法的某些部分还 能进一步优化。尽管如此,C 语言已被证实是一种可广泛用于多种程序设计应用的相当高效的、 表达能力极强的语言。 本书按照以下结构编排:第 1 章将对 C 语言的核心部分进行简要介绍。其目的是让读者能 尽快开始编写 C 语言程序,因为我们深信,实际编写程序才是学习一种新语言的好方法。这部 分内容的介绍假定读者对程序设计的基本元素有一定的了解。我们在这部分内容中没有解释计 算机、编译等概念,也没有解释诸如 n=n+1 这样的表达式。我们将尽量在合适的地方介绍一些 实用的程序设计技术,但是,本书的中心目的并不是介绍数据结构和算法。在篇幅受限的情况 下,我们将专注于讲解语言本身。 第 2 章到第 6 章将更详细地讨论 C 语言的各个方面,且比第 1 章要正式得多,但其依然强 调完整的程序例子,而不是孤立的程序片段。第 2 章介绍基本的数据类型、运算符和表达式。 第 3 章介绍控制流,如 if-else、switch、while 和 for 等。第 4 章介绍函数和程序结构——外部变 量、作用域规则和多源文件等等内容,同时还会涉及到一些预处理器的知识。第 5 章介绍指针 和地址运算。第 6 章介绍结构和联合。 第 7 章介绍标准库。标准库提供了一个与操作系统交互的公共接口。这个函数库是由 ANSI 标准定义的,这就意味着所有支持 C 语言的机器都会支持它,因此,使用这个库执行输入、输 出或其他访问操作系统的操作的程序可以不加修改地运行在不同机器上。 第 8 章介绍 C 语言程序和 UNIX 操作系统之间的接口,我们将把重点放在输入/输出、文件 系统和存储分配上。尽管本章中的某些内容是针对 UNIX 系统所写的,但是使用其他系统的程 序员仍然能从中获益,比如深入了解如何实现标准库以及有关可移植性方面的一些建议。 附录 A 是一个语言参考手册。虽然 C 语言的语法和语义的官方正式定义是 ANSI 标准本身, 但是,ANSI 标准的文档首先是写给编译器的编写者看的,因此,对程序员来说不一定最合适。 本书中的参考手册采用了一种不很严格的形式,更简洁地对 C 语言的定义进行了介绍。附录 B 是对标准库的一个总结,它同样是为程序员而非编译器实现者准备的。附录 C 是相对于 C 语言 最初版本所做的变更的一个简要小结。但是,如果有不一致或疑问的地方,标准本身和各个特 定的编译器则是解释语言的最终权威。本书的最后是索引部分。 第 1 章 入门介绍 本章首先对 C 语言做一个简要介绍。我们希望通过实际的程序来展示 C 语言的核心元素, 而不在细节、规则以及例外情况上过多纠缠。出于这种考虑,本章并不力求内容的完整性甚至 是精确性(但是所给的例子肯定是正确的)。我们希望使读者能尽快学会编写有用的程序。为此, 本章着重介绍基础部分:变量与常量、算术运算、控制流、函数和基本输入/输出。我们有意省 略了 C 语言中编写大型程序所需要的重要特性,包括指针、结构、C 语言丰富的运算符集中的 大多数运算符、部分控制流语句以及标准库。 这一方法有其不足之处,最明显的是对任何具体语言特性的介绍都不完全,且可能会因为 教程简略而对读者造成误导。所举例子也因没有充分发挥 C 语言的能力而不是足够的简洁和优 美。我们已经尽力减小这些负面影响,但读者仍应注意。此方法的另一缺点是后续章节必须要 重复本章的一些内容。期望这些重复带给读者的帮助会胜过烦扰。 无论如何,经验丰富的程序员应能从本章的材料中推知自己在程序设计中所需要的东西, 初学者则应编写类似的小程序来增补本章所学。两种程度的读者都可以以本章为框架来学习后 续章节的详细内容。 1.1 开始 学习一种新语言的唯一方法是用它编写程序。无论是哪种语言,第一个要学习编写的程序 都一样: 打印文字 hello, world 这是一个巨大的障碍,要逾越它你必须学会创建程序文本,成功编译程序,能够加载、运行程 序,并能查看程序输出。在这些操作细节掌握之后,其余的事情就相对简单了。 C 语言中,打印“hello, world”的程序如下: #include main() { printf(“hello, world\n”); } 如何运行这个程序取决于所使用的系统。例如,在 UNIX 系统中,你需要创建一个名字以 “.c”结尾的文件来存放这个程序,如 hello.c,然后使用下面的命令对其进行编译: cc hello.c 此过程如果没有任何改动(比如遗漏了某个字符或是拼错了什么),那么编译将会安静地进行, 最后生成一个名为 a.out 的可执行文件。如果输入指令 a.out 运行该文件,它将打印出 hello, world 在其他系统中,相应规则可能会有所差别,请向特定系统的专家请教。 下面就程序本身作一些解释。一个 C 语言程序无论其大小如何,均由函数和变量构成。函 数包含若干语句,语句指定了所要完成的计算操作;变量则用于存放计算过程中使用的值。C 语言的函数类似于 Fortran 语言的子程序和函数或者是 Pascal 语言的过程和函数。本例中, 函数的名字为 main。通常函数名可由编程者任意指定,但是“main”这个名字比较特殊—— 每个程序都要从 main 函数的起始处开始执行。这意味着每个程序都必须在某处包含一个 main 函数。 第一个 C 程序 #include main() { printf(“hello, world\n”); } 包含标准库的相关信息 定义名字为 main 的函数 函数不接收任何参数 main 的所有语句被包含在一对花括号内 main 函数调用了库函数 printf 来打印这串字符 \n 代表换行符 main 函数通常会调用其它函数来协助完成程序工作,这些函数中一些由编程者自行编写, 而另一些则来自于函数库。程序的第一行 #include 告诉编译器在本程序中包含标准输入输出库的相关信息;许多 C 程序源文件的起始处都包括这 一行。标准库在第 7 章和附录 B 中进行介绍。 在函数之间交互数据的一种方法是由调用函数向被调函数提供一列数值,这些数值称为参 数。参数列表位于函数名之后,并括上一对圆括号。在本例中,main 被定义成一个不需要参数 的函数,这通过空参数表()来表示。 一个函数的全部语句括在一对花括号{}之内。本例中 main 函数只包含一条语句, printf(“hello, world\n”); 函数的调用形式为函数名加上(由一对圆括号括上的)参数列表。上述语句即调用了函数 printf,且其参数为“hello, world\n”。printf 是一个库函数,用于打印输出,此处的 输出内容即引号内的这串字符。 用双引号引上的字符序列,如“hello, world\n”,称为字符串或者是字符串常量。现阶 段我们仅将字符串作为 printf 以及其它函数的参数使用。 字符串中的序列 \n 在 C 语言中表示换行符,在打印时,它表示从下一行的最左边继续打 印。如果去掉它(值得一试☺),你会发现在输出打印完毕后并没有另起一行。要在 printf 的 参数中包含换行符,必须用 \n;如果你试图用类似下面这样的代码来达到目的, printf(“hello,world ”); C 编译器将会产生一条错误信息。 printf 不会自动添加换行符,因而可以使用多次调用分几步来构成一行输出。我们的第 一个程序也可以写成: #include main() { printf(“hello, ”); printf(“world”); printf(“\n”); } 其产生的输出完全相同。 注意 \n 表示的仅为单个字符,它是一个转义序列。像 \n 之类的转义序列提供了一种通 用的可扩展的机制,用于表示无法打印或是不可见的字符。C 语言提供的其他一些转义序列包 括表示制表符的 \t 、表示回退符的 \b 、表示双引号的 \” 以及表示反斜杠自身的 \\ 等。 2.3 节给出了转义序列的完整列表。 练习 1-1. 请读者在自己的系统上运行“hello, world”程序。尝试删除程序中的某一部分, 看看会报告什么错误信息。 练习 1-2. 在 printf 函数的参数字符串中包含 \c (c 为某个先前未曾列出的字符,可变), 请试验并找出会产生的情况。 1.2 变量与算术表达式 接下来的这个程序使用公式 oC = (5/9)(oF-32)打印下面的华氏温度至摄氏温度的对应 表: 1 -17 20 -6 40 4 60 15 80 26 100 37 120 48 140 60 160 71 180 82 200 93 220 104 240 115 260 126 280 137 300 148 这个程序依然只包含一个名字为 main 的函数。它比之前那个打印“hello, world”的程序 要长一些,但并不复杂。它引入了一些新的概念,包括注释、声明、变量、算术表达式、循环 以及格式化输出。 #include /* 按 fahr = 0, 20, ..., 300 打印华氏温度-摄氏温度对应表 */ main() { int fahr, celsius; int lower, upper, step; lower = 0; /* 温度表的下限 */ upper = 300; /* 温度表的上限 */ step = 20; /* 温度增长步幅 */ fahr = lower; while (fahr <= upper) { celsius = 5 * (fahr-32) / 9; printf("%d\t%d\n", fahr, celsius); fahr = fahr + step; } } 其中的两行 /* 按 fahr = 0, 20, ..., 300 打印华氏温度-摄氏温度对应表 */ 是程序注释,此例中的注释简要地说明了这个程序的功能。在 /* 和 */ 之间的任何字符串都 会被编译器忽略;注释可以自由地运用在程序中,以使程序更加易于理解。在空格、制表符和 换行符能够出现的地方都可以使用注释。 在 C 语言中,所有变量在使用之前都必须声明,变量声明一般放在函数的所有执行语句之 前。声明用于说明变量的属性,它包括一个类型名和一列变量,如: int fahr, celsius; int lower, upper, step; 类型 int 表明其后所列的变量都是整数;相应地,类型 float 表明其后列出的变量都是浮点 数(浮点数可能有小数部分)。int 和 float 类型的变量取值范围依赖于所使用的机器。一般而 言,int 是 16 位的,取值范围在-32768 到+32767 之间,也有 32 位的 int。典型的 float 数是 32 位的,至少有 6 位有效数字,数量级一般在 10-38 到 1038 之间。 除 int 和 float 之外,C 还提供了其他一些基本数据类型,包括: char 字符——单个字节 short 短整型 long 长整型 double 双精度浮点类型 这些数据类型对象的大小同样依赖于具体机器。此外,还存在由这些基本数据类型组成的数组、 结构和联合,以及指向这些类型的指针和返回这些类型的函数,我们会在后续相应的章节中遇 到它们。 该温度转换程序的计算部分的开头是赋值语句 lower = 0; upper = 300; step = 20; fahr = lower; 它们为变量设置了初始值。每条赋值语句都以分号结尾。 由于对照表中每一行的计算方式都相同,我们用一个重复过程来产生每行的输出;这正是 while 循环的目的: while (fahr <= upper) { ... } while 循环的执行顺序如下:首先,圆括号内的条件被检测。如果条件为真(fahr 小于或等 于 upper),则执行循环体(花括号内的三条语句)。然后,条件被再次测试,如果仍然为真, 则循环体被再次执行。直到当条件不成立时(fahr 超过 upper),循环结束,继续执行紧接在 循环语句之后的语句。在这个程序中,循环语句之后没有其它语句,于是程序终止执行。 while 的循环体可以是由花括号括起来的一条或多条语句(就像温度转换程序中的那样), 或者是一条单独的没有花括号的语句,例如: while (i < j) i = 2 * i; 在这两种情况下,我们都会将由 while 循环控制的语句缩进一个制表位(在本书之中用 4 个空 格表示),这样读者一眼就能看出哪些语句包含在循环体之内。缩进增强了程序的逻辑结构。尽 管 C 语言编译器并不关心程序看上去是什么样子,但是恰当的缩进和分隔对于提高程序的可读 性至关重要。我们推荐每行只写一条语句,并且在运算符的两边都添加空格以使运算组合显得 更加清楚。花括号的位置不那么重要,尽管人们都有各自喜欢的风格。我们从几种流行的风格 中选取了一种,你可以选择一种适合自己的风格,并一直使用它。 在上面的程序中,循环体完成了绝大部分的工作。循环体内的语句 celsius = 5 * (fahr-32) / 9; 计算出对应的摄氏温度值并赋给变量 celsius。在该表达式中,之所以写成先乘以 5 然后再除 以 9 而不是直接乘以 5/9,是因为 C 语言像许多其他的语言一样,其整数除法会进行截取处理, 即结果中的小数部分会被丢弃掉。由于 5 和 9 是整数,5 除以 9 的结果会被截取为 0,于是所 有求出的摄氏温度都会变成 0。 这个例子也进一步展示了 printf 函数的工作方式。printf 是一个通用的格式化输出函 数,我们会在第 7 章对它进行详细介绍。它的第一个参数是要打印的字符串,其中每个百分号 (%)指明了后续每个(第 2 个、第 3 个、…)参数替换到字符串中的位置,以及参数打印的格 式。例如,%d 指定的是一个整型参数,于是下面的语句 printf("%d\t%d\n", fahr, celsius); 会将整数类型变量 fahr 和 celsius 的值打印出来,并且中间有一个制表符(\t)。 printf 函数的第 1 个参数中的各个“百分号结构”依次与第 2 个参数、第 3 个参数、... 相对应;它们的个数和类型必须匹配,否则将得到错误的输出结果。 顺便指出,printf 函数并不是 C 语言的一部分。C 语言本身并没有定义输入输出功能。 printf 函数只是标准库中一个有用的函数,标准库中的函数通常都可以被 C 程序调用。而 printf 函数的行为已在 ANSI 标准中定义,因此,它的特性在所有遵循该标准的编译器和库中 都应该一样。 为把着重点放在 C 语言本身,在第 7 章之前我们不会过多地讨论输入和输出,我们特别将 格式化输入推后到第 7 章讨论。如果你需要输入数字,请参考 7.4 节中对 scanf 函数的讨论。 scanf 函数很像 printf 函数,不过它的功能是读取输入而不是打印输出。 上面的温度转换程序还有一些问题。其中比较简单的一个是程序的输出不是太好看,因为 输出的数字没有进行右对齐。这个问题好办,如果为 printf 语句中的每个%d 增加一个宽度值, 那么打印出来的数字就会在这个宽度的区域内进行右对齐。比如,我们可能通过编写 printf("%3d %6d\n", fahr, celsius); 来打印每一行,其中第一个数占 3 个数字宽度,第二个数占 6 个数字宽度,其效果如下: 0 -17 20 -6 40 4 60 15 80 26 100 37 ... 另一个严重一些的问题是,由于我们使用了整数算术运算,所得到的摄氏温度并不十分准 确。比如华氏 0o 对应的精确的摄氏温度是-17.8o,而不仅仅是-17o。为了得到更加精确的结 果,我们应该采用浮点数运算来替代整数运算。这需要对程序做几处改动,下面是改动之后的 版本: #include /* 对 fahr = 0, 20, ..., 300; 打印华氏 温度到摄氏温度的对应表。——浮点数版本 */ main() { float fahr, celsius; int lower, upper, step; lower = 0; /* 温度表的下限 */ upper = 300; /* 温度表的上限 */ step = 20; /* 温度增长步幅 */ fahr = lower; while (fahr <= upper) { celsius = (5.0/9.0) * (fahr-32.0); printf("%3.0f %6.1f\n", fahr, celsius); fahr = fahr + step; } } 这个版本和原先的版本大致相同,只是 fahr 和 celsius 被声明为浮点数类型,并且转换 公式也写得更加自然。在之前的版本中我们不能使用 5/9,因为它会被整数除法截取为 0。这 里的 5.0/9.0,由于常数中的小数点指明了其为浮点型的数,因此它表示两个浮点数的比值, 因而不会被截取。 如果一个算术运算符的操作数都是整数,那么将会执行整数运算。但如果是一个浮点操作 数和一个整数操作数,那么该整数首先会被转换成浮点数,然后再进行浮点运算。如果我们写 成 fahr - 32,那么 32 会被自动转换为浮点类型。尽管如此,对于浮点型的常量,即便它的 值是整数,显式地写成带小数点的形式可以明确它浮点类型的本质,以方便阅读者理解。 在第 2 章中,我们会详细地描述整数转化为浮点数的规则。这里只需要注意程序中的赋值 语句 fahr = lower; 和测试语句 while (fahr <= upper) 也具有同样的工作原理,int 类型的数据会首先被转换成 float 类型的数据,然后再进行对应 的操作。 printf 函数的转换格式说明%3.0f 表明该浮点数(即 fahr)在打印时至少要占 3 个字符 的宽度,且不带小数点和小数部分。%6.1f 则表明另一个数(celsius) 在打印时至少要占 6 个字符的宽度,且在小数点后面要有 1 位小数。输出的结果类似: 0 -17.8 20 -6.7 40 4.4 … 转换格式说明中的数据宽度或精度可以省略:%6f 表明这个数至少要占 6 个字符的宽度;%.2f 表明在小数点后面必须要有两个字符,但在宽度上没有限制;而%f 仅仅说明这个数要按照浮点 类型来打印。 %d 按照十进制整数格式打印 %6d 按照十进制整数格式打印,至少占据 6 个字符宽度 %f 按照浮点数格式打印 %6f 按照浮点数类型打印,至少占据 6 个字符宽度 %.2f 按照浮点数类型打印,小数点后打印 2 位数字 %6.2f 按照浮点数类型打印,至少占据 6 个字符的宽度,小数点后打印 2 位数字 此外,printf 函数还支持下列格式说明:%o 表示八进制数,%x 表示十六进制数,%c 表示字 符,%s 表示字符串,%%则表示百分号%本身。 练习 1-3. 修改温度转换程序,在输出的转换表前加一个标题。 练习 1-4. 编写一个程序,打印出摄氏温度到华氏温度的对照表。 1.3 for 语句 完成某一特定任务的程序会有很多种不同的实现方式。下面我们就将温度转换程序变化一 下: #include /* 打印华氏-摄氏温度对照表 */ main() { int fahr; for (fahr = 0; fahr <= 300; fahr = fahr + 20) printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32)); } 这个程序的执行结果与之前的程序相同,不过它看上去的确不一样。一个主要的改变是除 fahr 之外其它的变量都去掉了,fahr 也变成了 int 类型。温度的上限值、下限值和步长均以常量 的形式出现在 for 语句(对读者而言这是一个新的语言成分)中,并且计算摄氏温度的表达式 作为第 3 个参数出现在 printf 语句中,不再是一条独立的赋值语句。 最后这个改变体现了一个通用的规则:在任何可以使用某个类型的值的地方,都可以使用 计算结果为该类型的更复杂的表达式。由于 printf 语句的第 3 个参数必须是与%6.1f 匹配的 浮点数,因此任何浮点类型的表达式都可出现在这里。 for 语句是一个循环语句,是 while 语句的一般化形式。如果将它与之前的 while 相比 较,你会觉得它的操作更加清楚。圆括号里包含以分号分隔开的三部分。第一部分,初始化语 句 fahr = 0 会在正式进入循环之前执行一次。第 2 部分是用于控制循环的测试语句,或称条件语句 fahr <= 300 这个条件语句会被检测,如果成立,则循环体(这里就一条 printf 语句)被执行。然后,执行 第三部分递增语句 fahr = fahr + 20 之后,条件语句被再次测试。直到条件语句不再成立,循环才会结束。与 while 语句一样,for 语句的循环体可以是一条单独的语句,也可以是由花括号括起来的一组语句。其初始化语句、 条件语句和递增语句可以是任意的表达式。 在程序中选用 while 语句还是 for 语句是随便的,主要看使用哪一种更加清晰。for 语 句通常适合于循环程序的初始化语句和递增语句都是单条语句且彼此在逻辑上有关联的情形, 因为它把控制循环的语句都集中到了一起,比 while 语句更加紧凑。 练习 1-5. 修改温度转换程序,以相反的顺序打印温度对应表,即从 300oF 到 0oF。 1.4 符号常量 在正式结束对温度转换程序的讨论之前,我们再来看一下符号常量。把诸如 300、20 这样 的“幻数”插进程序中可不是一种好的做法,对于今后需要阅读这段代码的人而言,它们几乎 没有传达任何信息,并且难于进行系统化的修改。解决幻数问题的一种方法是给它们设定有意 义的名字。#define 语句将一个符号名字或是符号常量定义为一串特定的字符: #define 名字 替换文本 在定义之后,该名字出现的任何位置(只要不是在引号中或者是其它名字的一部分),都会被替 换为对应的替换文本。符号名字的形式和变量名相同:都是以字母开头的字母和数字序列。而 替换文本则可以是任意的字符序列,字符数量也没有限制。 #include #define LOWER 0 /* 温度表的下限 */ #define UPPER 300 /* 温度表的上限 */ #define STEP 20 /* 步长 */ /* 打印华氏-摄氏温度对照表 */ main() { int fahr; for (fahr = LOWER; fahr <= UPPER; fahr = fahr + STEP) printf("%3d %6.1f\n", fahr, (5.0/9.0)*(fahr-32)); } 程序中的 LOWER、UPPER 和 STEP 是符号常量,不是变量,因此它们不用进行声明。按惯例符 号常量都用大写字母来书写,这样可以很容易地与小写字母书写的变量名相区别。需要注意在 #define 语句的结束位置没有分号。 1.5 字符输入和输出 接下来我们将要讨论一组彼此关联的字符数据处理程序。你会发现,许多程序不过是这里 所讨论的原型程序的扩展而已。 标准库所支持的输入输出模型非常简单。无论输入自何处或者输出到哪里,输入或输出的 文本都被视作字符流。文本流是一个被划分成多行的字符序列,每一行包含零到多个字符,行 末带有一个换行符。标准库有责任让所有的输入流和输出流都符合这一模型。使用标准库的 C 程序员不用关心字符行在程序之外是如何表示的。 标准库提供了多个函数来每次读取或写出一个字符,其中最简单的函数莫过于 getchar 和 putchar。每次调用时,getchar 函数从文本流中读取下一个输入字符并返回它的值。这 就是说,执行 c = getchar() 之后,变量 c 中就保存了下一个输入字符。这些字符通常是从键盘输入的;我们会在第 7 章中 讨论从文件中输入字符的方法。 函数 putchar 在每次调用时打印一个字符: putchar(c) 会将整数变量 c 中的值按字符类型打印出来,通常会显示在屏幕上。函数 putchar 和 printf 可以交替调用,输出次序和调用的次序一致。 1.5.1 文件拷贝 有了 getchar 和 putchar,在不了解更多关于输入输出知识的情况下,就能够编写出数 量惊人的有用代码。最简单的程序例子是将输入字符一次一个地拷贝到它的输出: 读入一个字符 while (该字符不是文件结束符) 输出刚读入的字符 再读入一个字符 将上述文字转换成 C 程序就是: #include /* 将输入拷贝到输出;版本 1 */ main() { int c; c = getchar(); while (c != EOF) { putchar(c); c = getchar(); } } 其中,关系运算符 != 表示“不等于”。 无论字符在键盘和屏幕上是什么形态,它与其他任何东西一样在计算机内部都是以位模式 存储的。数据类型 char 专门用于存放这些字符数据,不过任何整数类型同样可以存放它们。 我们在这里使用 int 类型是由于一个微妙但却很重要的原因。 这个原因就是需要将表示输入结束的信息与正常的输入数据区分开来。解决办法是在没有 新的输入之后,函数 getchar 返回一个与众不同的值,这个值不会和任何实际的字符相混淆。 这个值称为 EOF,代表“end of file”。我们必须将 c 声明为足够大的数据类型,以便能够 存放 getchar 返回的任何数据。c 不能使用 char 类型,因为除了存放任何可能的 char 型数 据之外,它还必须能存放 EOF。因此我们使用了 int 类型。 EOF 是定义在 中的一个整数,它的值具体是多少并不重要,只要不同于任何 字符的值即可。通过使用符号常量,我们可以确保程序中任何部分都不依赖于具体的数值。 有经验的 C 程序员可以把这个拷贝程序写得更精练一些。C 语言中任何赋值语句,比如 c = getchar(); 都是一个表达式,并且有自身的值。它的值就是赋值之后等号左边变量的值。这就意味着,赋 值语句可以作为更大的表达式的一部分出现。如果将这个 c 的赋值语句放到 while 循环的测试 部分中去,那么程序就可以这样写: #include /* 将输入拷贝到输出; 版本 2 */ main() { int c; while ((c = getchar()) != EOF) putchar(c); } while 语句取得一个字符,将它赋予变量 c,然后检测这个字符是不是结束符 EOF。如果不是, 则执行循环体打印这个字符,然后继续循环。当达到输入末尾时,while 循环终止,main 也 随之终止。 这个版本将输入操作集中起来(只引用了一次 getchar 函数),缩短了整个程序。这使得 程序更加紧凑,并且读者一旦掌握了这种惯用法之后它会更容易阅读。你会经常见到这种风格。 (但是,过分使用这种风格可能会使代码变得难于理解,因此本书会对此加以控制)。 在条件语句中,赋值操作两边的圆括号必不可少。由于 != 的优先级高于 = ,这意味着 如果没有圆括号,比较测试 != 会在赋值操作 = 之前完成。因此语句 c = getchar() != EOF 等价于 c = (getchar() != EOF) 其结果就是根据 getchar 的调用是否遇到文件结束而将变量 c 设置为 0 或 1,这并不是我们希 望达到的效果。(关于运算优先级的更多内容在第 2 章中介绍) 练习 1-6. 验证表达式 getchar() != EOF 的值是 0 还是 1. 练习 1-7. 写一个程序,将 EOF 的值打印出来。 1.5.2 字符计数 接下来的这个程序用于统计字符的个数,它与前面的拷贝程序类似。 #include /* 统计输入中字符的个数;版本 1 */ main() { long nc; nc = 0; while (getchar() != EOF) ++nc; printf("%ld\n", nc); } 其中,语句 ++nc; 展示了一个新的运算符 ++,它表示将其作用的变量增 1。你可以用 nc = nc+1 替代它,但 ++nc 更加简洁,效率常常也更高。同样,还存在一个与之对应的运算符 --,它表示减 1。运 算 符 ++ 和 -- 既可以是前缀运算符(++nc),也可以是后缀运算符(nc++);这两种形式在表达式中 有着不同的取值,但它们都将 nc 增加了 1,这些特性将会在第 2 章中介绍。现在,我们只采用 其中的前缀形式。 此字符统计程序使用了一个 long 型变量保存计数值,而没有用 int 型。long 型(长整 型)数至少有 32 比特位。尽管在某些机器中,int 和 long 具有相同的大小,但在其他一些机 器中,int 只有 16 比特位,最大值为 32767,因此相对少的输入量就会造成 int 型的计数变 量溢出。转换格式说明%ld 告诉 printf 对应的参数是一个 long 型整数。 如果需要处理比 long 还要大的数,则可使用 double(双精度浮点)类型。我们还将用 for 语句替代 while 语句,以说明循环的另一写法: #include /* 统计输入字符的个数;版本 2 */ main() { double nc; for (nc = 0; gechar() != EOF; ++nc) ; printf("%.0f\n", nc); } printf 使用%f 来代表 float 和 double 类型。格式%.0f 强制不打印小数点和小数部分(nc 的小数部分是 0)。 程序中 for 的循环体为空,这是因为所有的工作都在测试和递增部分做了。但是 C 语言的 语法要求 for 语句必须有一个循环体。那个单独的分号就是为了满足这条规则,其称为空语句, 将它单独放在一行是为了更加醒目。 在结束对字符统计程序的讨论之前,请注意如果输入不包含任何字符,那么 while 或 for 的测试在第一次调用 getchar 后就会失败,程序的执行结果将为 0——是正确的结果。这一点 很重要。while 及 for 语句有一个很棒的特点就是它们在循环顶部进行测试——在执行循环体 之前。如果没有什么需要做(条件不成立),那就什么也不做,甚至这意味着执行可能从未经过 循环体。程序应该在出现 0 长度输入时灵活地进行处理。while 和 for 语句有助于确保程序在 边界情况时进行合理的操作。 1.5.3 行计数 接下来的这个程序用于统计输入文本的行数。正如我们之前提到的,标准库保证了输入文 本流以一连串文本行的形式出现,每行以一个换行符结束。因此,统计行数其实就是统计换行 符的个数。 #include /* 统计输入的行数 */ main() { int c, nl; nl = 0; while ((c = getchar()) != EOF) if (c == '\n') ++nl; printf("%d\n", nl); } 现在 while 的循环体是一条 if 语句,if 语句又控制着递增语句++nl。if 语句先测试圆 括号中的条件,如果条件为真,则执行紧接其后的语句(或花括号中的一组语句)。我们仍使用 缩进格式来表明语句之间的控制关系。 双等号 == 是 C 语言中表示“等于”的记号(就像 Pascal 中的 = 或者 Fortran 中 的.EQ.)。由于 C 语言用单等号 = 表示赋值操作,因此使用与之区别的 == 表示相等测试。 特别提请注意:初学者有时会在打算使用 == 时却写成 = ,在第 2 章将会看到,这样的误用 一般会得到一个合法的表达式,所以系统不会给出任何警告。 用单引号引上的单个字符代表一个整数值,这个值等于具体机器字符集中该字符对应的数 值。它被称作字符常量,尽管这只不过是一个较小整数的另一种写法而已。例如,’A’就是一 个字符常量,在 ASCII 字符集中它的值是 65——即字符 A 在机器中的内部表示。不过显然’A’ 比 65 更为可取:它的意义很明显,并且独立于特定的字符集。 字符串常量中的转义序列用作字符常量也是合法的,因此’\n’代表换行符对应的值,在 ASCII 字符集中其值为 10。读者应该特别注意,’\n’是单个字符,在表达式中它只是一个整 数;而”\n”却是一个恰巧只包含单个字符的字符串常量。有关字符串与字符的对比关系,我们 将在第 2 章中进一步讨论。 练习 1-8. 写一个程序,统计输入中的空格、制表符和换行符的个数。 练习 1-9. 写一个程序,将输入拷贝到输出,并且将输入中连续的多个空格替换为单个空格。 练习 1-10. 写一个程序,将输入拷贝到输出,将每个制表符替换为\t,将每个空格替换为\b, 将每个反斜杠替换为\\。这使得制表符和空格能够被明确地分辨出来。 1.5.4 单词计数 这个系列的第 4 个实用程序用于统计行、单词及字符的数目。这里对单词的定义比较宽松: 它是任何不包含空格、制表符及换行符的字符序列。本程序是 UNIX 中 wc 程序的一个最简化的 “光杆”版本。 #include #define IN 1 /* 在单词之内 */ #define OUT 0 /* 在单词之外 */ /* 统计输入中的行、单词和字符的个数 */ main() { int c, nl, nw, nc, state; state = OUT; nl = nw = nc = 0; while ((c = getchar()) != EOF) { ++nc; if (c == '\n') ++nl; if (c == ' ' || c == '\n' || c == '\t') state = OUT; else if (state == OUT) { state = IN; ++nw; } } printf("%d %d %d\n", nl, nw, nc); } 程序每当碰到一个单词的首字符时就会将单词数加 1。变量 state 记录了程序当前的处理 是否正处于某个单词之内;最开始程序处于“没有在处理单词”的状态,于是 state 被赋值为 OUT。就符号常量 IN 和 OUT 及其具体值 1 和 0 而言,我们更倾向于使用前者,因为它们会让 程序的可读性更好。这对于像这么丁点大的程序不会有什么差别,然而对于更大型的程序,其 带来的清晰性会非常值得从一开始就增加这一小点额外的努力。读者还会发现,当幻数只以符 号常量的形式出现时,对程序的大范围修改会更加容易。 语句 nl = nw = nc = 0; 将这三个变量的值都设为 0。这不是一种特殊用法,而是由于每个赋值语句事实上是一个带值 的表达式以及赋值操作从右至左的结合方式共同作用的结果。这个语句与如下写法 nl = (nw = (nc = 0)); 是一样的。 运算符 || 的意思是 OR(逻辑或),所以 if (c == ' ' || c == '\n' || c == '\t') 的意义是“如果 c 是空格,或者 c 是换行符,或者 c 是制表符”。(之前提到过转义序列 \t 是 制表符的可见表示形式。)还有一个与之对应的运算符 && 用于表示 AND(逻辑与),它比运算 符 || 高一个优先级。由 || 或者 && 连接起来的表达式会从左向右进行求值,其运算机制保 证整个逻辑表达式的真假一旦判定,求值就会马上停止。也就是说,如果 c 是一个空格,那么 就没必要再测试它是否是一个换行符或制表符了,所以测试就不再进行。这一特性在此处并不 是特别重要,但在更复杂的情况下其重要性将突显,我们很快就会看到这一点。 这个例子还给出了 else 部分,它指定了若 if 语句的测试条件为假(false)时所要另行 完成的操作。其一般形式为: if (表达式) 语句 1 else 语句 2 与 if-else 关联的这两条语句之中,有且只有一条会被执行。如果表达式为真,那么语句 1 被 执行;否则,语句 2 被执行。语句 1 和语句 2 既可以是一条单独的语句,也可以是放在花括号中 的多条语句。上述单词计数程序中,在 else 之后的那条 if 语句控制的就是由一对花括号括上 的两条语句。 练习 1-11. 如果由你来测试这个单词计数程序,你打算怎么做?如果程序存在错误,哪些输入 最可能测出它们呢? 练习 1-12. 写一个程序把输入的单词打印出来,每行一个。 1.6 数组 现在我们编写一个程序来统计输入中各个数字、空白符(空格、制表符和换行符)以及所 有其他字符出现的次数。这样虽然有些做作,但却可以在一个程序中展示 C 语言几个方面的内 容。 输入的字符共有 12 种类别,因此使用一个数组来保存每个数字出现的次数比使用 10 个单 独的变量更为方便。下面是该程序的一个版本: #include /* 统计数字、空白符和其它字符的个数 */ main() { int c, i, nwhite, nother; int ndigit[10]; nwhite = nother = 0; for (i = 0; i < 10; ++i) ndigit[i] = 0; while ((c = getchar()) != EOF) if (c >= '0' && c <= '9') ++ndigit[c-'0']; else if (c == ' ' || c == '\n' || c == '\t') ++nwhite; else ++nother; printf("digits ="); for (i = 0; i < 10; ++i) printf(" %d", ndigit[i]); printf(", white space = %d, other = %d\n", nwhite, nother); } 这个程序如果把自身作为输入,那么将会输出 digits = 9 3 0 0 0 0 0 0 0 1, white space = 123, other = 345 程序中的声明语句 int ndigit[10]; 将 ndigit 声明为一个由 10 个整数构成的数组。在 C 语言中,数组的下标总是从 0 开始,因 此该数组的元素分别是 ndigit[0], ndigit[1], ..., ndigit[9],这从初始化和打印 该数组的两个 for 语句中可以看出来。 数组的下标可以是任何整数表达式,其中也包括像 i 这样的整数变量以及整数常量。 这个特定的程序依赖于数字的字符表示特性。例如,测试 if (c >= '0' && c <= '9') ... 用于确定 c 中的字符是否为数字。如果是数字,那么它的值等于 c - '0' 这只有在'0', '1', ..., '9' 具有连续递增的值时才会成立。幸运的是,在所有的字符集 中这都是正确的。 根据定义,字符不过是小的整数,所以在算术表达式中 char 型的变量和常量与 int 的作 用相同。这很自然也很方便。例如,c-'0'是一个整数表达式,如果 c 存储的是'0'到'9'中的 一个字符,那么它的值就在 0 到 9 之间,这也正是数组 ndigit 的一个有效下标。 至于一个字符是数字、空白符还是其它字符,程序通过下面的序列完成判断: if (c >= '0' && c <= '9') ++ndigit[c-'0']; else if (c == ' ' || c == '\n' || c == '\t') ++nwhite; else ++nother; 模式 if (条件 1) 语句 1 else if (条件 2) 语句 2 ... ... else 语句 n 作为表述多路判定的一种方式在程序中经常出现。程序会对该模式中的条件从上到下依次测试, 直到某个条件成立为止,然后执行该条件对应的语句,此后整个判定结构结束。(同样,每个条 件对应的语句也可以是包含在花括号中的多条语句。)如果所有条件都不满足,而且存在最后一 个 else 项,则该部分的语句将被执行;如果最后的 else 及语句被省略掉(就如之前的单词 统计程序中的情况),则什么动作都不会发生。在最先的 if 和最后的 else 之间,可以存在任 意数量的 else if(条件) 语句 就编程风格而言,我们建议这种判定结构采用本节所给的格式。如果每个 if 语句都比前面 的 else 缩进一级,那么一个较长的判定序列就可能会超出页面的右边界。 在第 3 章我们将讨论 switch 语句,它提供了另一种处理多路分支的方法,其尤其适合于 判定某个整数表达式或字符表达式的值是否与一个常数集合中的某个值相匹配的情况。作为对 比,在 3.4 节中我们将给出这个程序的 switch 版本。 练习 1-13. 写一个程序,打印输入中单词长度的直方图。水平方向的直方图比较容易;垂直方 向的直方图更有难度。 练习 1-14. 写一个程序,打印输入中不同字符出现频度的直方图。 1.7 函数 C 语言的函数类似于 Fortran 语言的子程序或函数,或 Pascal 语言的过程或函数。函数 为计算的封装提供了一种便捷的方法,在使用它时不必关心它的具体实现。有了恰当设计的函 数,你就可以不用了解一个工作是如何完成的,只需要知道完成了什么就足够了。C 语言中使 用函数的方法简单、方便而且高效。你会经常看到一些定义后仅调用了一次的短函数,其目的 只是为了使某些代码段更加清晰。 到目前为止,我们只使用过类似 printf、getchar 和 putchar 等提供给我们的函数; 现在是时候写一些自己的函数了。C 语言没有提供与 Fortran 语言的 ** 类似的幂运算符,我 们将编写一个幂函数 power(m,n)并以此说明函数定义的机制。函数 power(m,n)计算整数 m 的 n 次幂(n 为正整数)。也就是说,power(2,5)的值为 32。这个函数并不是一个实用的幂 运算程序,因为它只能处理较小整数的正整数次幂,不过用于说明它完全够用了。(标准库中提 供了一个 pow(x,y)函数用于计算 xy。) 下面是函数 power 以及用于执行它的程序 main,因此整个程序结构读者立刻就能够看见。 #include int power(int m, int n); /* 测试 power 函数 */ main() { int i; for (i = 0; i < 10; ++i) printf("%d %d %d\n", i, power(2,i), power(-3,i)); return 0; } /* power: 计算 base 的 n 次幂; n >= 0 */ int power(int base, int n) { int i, p; p = 1; for (i = 1; i <= n; ++i) p = p * base; return p; } 函数定义的形式如下: 返回值类型 函数名称(可能存在的形参声明) { 声明部分 语句序列 } 不同函数的定义可以以任意次序出现在一个或多个文件中,但是单个函数定义不能分割放到多 个文件里。如果源程序分散在多个文件中,那么可能必须用更多的指令来对它进行编译和加载, 但那是操作系统的问题,而不是语言的属性。我们暂且假定这两个函数放在同一个文件中,这 样之前所学的有关运行 C 程序的知识仍然有效。 函数 power 被 main 调用了两次,都在代码行 printf("%d %d %d\n", i, power(2,i), power(-3,i)); 之中。每次调用时程序向 power 传递两个实参,power 则返回一个将被格式化并打印的整数。 在表达式中,power(2,i)就像 2 和 i 那样是一个整数。(并非所有的函数都生成一个整数值, 在第 4 章中我们将进行讨论。) 整个 power 函数的第一行 int power(int base, int n) 声明了形参的类型和名称,以及函数返回值的类型。power 所使用的参数名只在 power 内部起 作用,对其他函数不可见,也就是说其他函数可以使用相同的名字而不会发生冲突。变量 i 和 p 同样如此:power 函数中的 i 与 main 函数中的 i 互不相关。 我们通常使用形参(parameter)指代函数定义内括号中列出的变量名,而用实参 (argument)指代函数调用时使用的常量或者变量。有时也使用术语形式参数(formal argument)和实在参数(actual argument)进行同样的区分。 power 计算得到的值通过 return 语句返回给 main 函数。return 之后可以跟任意的表 达式: return 表达式; 函数不一定要返回一个值;一个不带表达式的 return 语句将导致控制权交还给调用者,但不 返回有用的值,就像碰到函数终止处的右花括号而从函数“尾部脱离”一样。调用函数也可忽 略被调函数返回的值。 可能你已经注意到了,main 的末尾处有一个 return 语句。由于 main 本身也是函数,它 也可以返回一个值给它的调用者——实际就是该程序的执行环境。典型地,返回值为 0 代表程 序正常结束,非 0 值则表示异常或错误中止的情况。为简单起见,在此之前所有的 main 函数 都省略了 return 语句,不过此后我们在其中都将加入 return 语句,以提醒读者一个程序应 当向其运行环境返回它的状态。 在 main 函数之前的声明语句 int power(int base, int n); 表明 power 是一个函数,需要两个 int 型的实参并返回一个 int 型的值。这个声明被称为函 数原型,它必须与 power 函数的定义及其使用相一致。如果某个函数的定义或使用与它的原型 不一致,则是错误的。 形参的名字不需要一致。实际上,形参名在函数原型中是可选的,因此 power 的原型也可 以写成: int power(int, int); 然而,精心选择的名字有很好的说明作用,所以我们经常会加上它们。 历史注记:ANSI C 和早期的 C 版本之间最大的变化是函数声明和定义的方式。按照 C 语 言的最初定义,power 函数会写成如下这样: /* power: 计算 base 的 n 阶幂; n >= 0 */ /* (老式版本) */ power(base, n) int base, n; { int i, p; p = 1; for (i = 1; i <= n; ++i) p = p * base; return p; } 形参的名字在圆括号中指定,而它们的类型则在函数开始的左花括号之前声明;未声明类型的 形参会被当作 int 类型。(函数体的形式不变。) 程序开始处的 power 的声明看上去会像这样: int power(); 其不允许包含形参列表,因此编译器不能轻易地检验 power 是否被正确地调用。实际上,由于 缺省情况下 power 被假定返回 int 值,这整个声明或许都会被省略掉。 新的函数原型语法使得编译器对实参数目及类型的错误检测要容易得多。老式的声明和定 义在 ANSI C 中仍然有效,至少是在一个转换时期内有效,但是我们强烈推荐读者在编译器支 持的情况下使用新的形式。 练习 1-15. 改写 1.2 节中的温度转换程序,用一个函数完成温度转换。 1.8 实参——按值调用 C 函数有一个方面可能对于习惯使用其它语言(尤其是 Fortran)的程序员不太熟悉。在 C 语言中,所有函数实参都是“按值”传递的。也就是说,被调函数所得到的是具有实参值的 临时变量,而不是原有的实参本身。这导致 C 语言的实参传递特性与 Fortran 等“按引用调 用”的语言以及 Pascal 中带有 var 选项的形参的相应特性有所差异,后两者的被调例程可以 访问原有的实参,而不仅仅是例程内部的副本。 其主要区别在于,C 语言中被调函数不能直接修改调用函数中的变量,它只能修改其私有 的临时副本。 然而,按值调用是一个优点而非缺点。由于在被调例程中参数可以方便地当作已初始化的 局部变量,因此一般会减少额外变量的使用,使程序更加紧凑。例如下面的利用了这一性质的 power 函数版本: /* power: 计算 base 的 n 次幂; n >= 0; 版本 2 */ int power(int base, int n) { int p; for (p = 1; n > 0; --n) p = p * base; return p; } 参数 n 用作一个临时变量,通过一个递减的 for 循环向下计数直至为 0;这样变量 i 就不再需 要了。函数 power 内部对 n 所做的任何操作都不会影响到调用 power 时所给的实参本身。 必要时也可以从被调函数内部来修改调用函数中变量的值。这种情况调用者必须提供这个 变量的地址(技术化的说法是“指向该变量的指针”),被调函数必须将对应参数声明为一个指 针,并通过它间接访问这个变量。我们会在第 5 章讨论指针的相关内容。 对于数组而言情况就不同了。当数组名作为参数时,传递给函数的值是这个数组的起始位 置(或称起始地址),数组的元素并不进行拷贝。通过下标的方式,函数可以访问并修改数组内 的任意元素。这是我们下一节要讨论的话题。 1.9 字符数组 字符数组是 C 语言中最常见的数组类型。为了展示字符数组的使用方法以及函数如何操作 它们,让我们来写一个程序,读入一组文本行,并打印出其中最长的一行。程序的基本框架足 够简单: while ( 还有未处理的行 ) if ( 它比之前最长的行更长 ) 保存它 保存它的长度 打印最长的行 上述框架很清晰——程序很自然地分为多个部分:一部分负责读入新的一行,一部分负责进行 测试,一部分负责保存,余下的部分负责控制整个过程。 由于逻辑功能划分很明确,程序按照这种划分来编写也较为理想。首先,我们编写一个独 立的函数 getline 用来获取输入的下一行。我们会尽量让这个函数在其它环境下也能使用。 getline 至少要能返回一个表示已到文件末尾的信号。一种更有用的设计是函数返回文本行的 长度,如果是文件末尾则返回 0。0 是一个可接受的文件末尾标记,因为它不会是有效的文本行 长度。每个文本行至少包含一个字符,只包含一个换行符的文本行长度为 1。 当找到一个行比之前读入的最长文本行更长时,必须把该行保存起来。这意味则需要另一 个函数 copy 将这个新行拷贝到一个安全的位置。 最后,我们需要一个 main 函数来控制 getline 和 copy 函数。得到的程序如下: #include #define MAXLINE 1000 /* 最大输入行长度 */ int getline(char line[], int maxline); void copy(char to[], char from[]); /* 打印最长的输入行 */ main() { int len; /* 当前行的长度 */ int max; /* 已读入的最长行的长度 */ char line[MAXLINE]; /* 当前输入行 */ char longest[MAXLINE]; /* 当前最长的文本行 */ max = 0; while ((len = getline(line, MAXLINE)) > 0) if (len > max) { max = len; copy(longest, line); } if (max > 0) /* 如果存在一行 */ printf("%s", longest); return 0; } /* getline: 将一行文本读取到 s 中, 并返回其长度 */ int getline(char s[],int lim) { int c, i; for (i=0; i #define MAXLINE 1000 /* 允许的输入行最大长度 */ int max; /* 当前最长输入行长度 */ char line[MAXLINE]; /* 当前输入行 */ char longest[MAXLINE]; /* 当前最长输入行 */ int getline(void); void copy(void); /* 打印当前最长输入行,特殊版本 */ main() { int len; extern int max; extern char longest[]; max = 0; while ((len = getline()) > 0) if (len > max) { max = len; copy(); } if (max > 0) /* there was a line */ printf("%s", longest); return 0; } /* getline:特殊版本 */ int getline(void) { int c, i; extern char line[]; for (i = 0; i < MAXLINE - 1 && (c=getchar)) != EOF && c != '\n'; ++i) line[i] = c; if (c == '\n') { line[i] = c; ++i; } line[i] = '\0'; return i; } /* copy: 特殊版本 */ void copy(void) { int i; extern char line[], longest[]; i = 0; while ((longest[i] = line[i]) != '\0') ++i; } main、getline 和 copy 中的外部变量定义在上面例子的开头几行。定义说明了它们的类 型并为之分配存储空间。在语法上,外部变量的定义就像是局部变量的定义,但由于定义位于 所有函数之外,因而成为外部变量。函数在使用外部变量之前必须知道该变量的名字,做到这 点的方法之一就是在函数中给出一个外部声明;这种声明与之前的声明一样,只是加上了关键 字 extern。 某些情况下 extern 声明能够被省略。如果一个源文件中外部变量的定义出现在某个使用 该变量的具体函数之前,那么在该函数中就无需该变量的 extern 声明。因此 main、getline 和 copy 中的 extern 声明都是多余的。事实上,普遍的做法是将所有外部变量的定义放在源 文件的起始位置,然后省略掉所有的 extern 声明。 如果程序分为多个源文件,而且变量在文件 1 中定义,并在文件 2 和文件 3 中使用,那么 在文件 2 和文件 3 中就需要用 extern 声明来关联出现的变量。惯常的做法是将变量和函数的 extern 声明放在一个单独的文件中(其历史称谓为头文件)。每个源文件通过开头的#include 语句来包含头文件。按惯例头文件的名字后缀为 .h 。例如,标准库的函数就在类似 这样的头文件中声明。这个主题将在第 4 章中详细讨论,标准库本身则将在第 7 章和附录 B 中讨论。 由于特殊版本的 getline 和 copy 没有参数,从逻辑上说它们在源文件起始处的函数原型 应该是 getline()和 copy()。但是为了兼容旧版本的 C 程序,ANSI 标准将空参数表当作旧 式声明,并关闭所有的参数表检查;而明确为空的参数表必须用关键字 void 来表示。相关内 容将在第 4 章中进一步讨论。 读者应当注意到在本节当我们提及外部变量时很谨慎地使用定义和声明这两个词。变量被 创建并分配存储的地方使用“定义”;给出变量特性但不分配存储的地方使用“声明”。 顺带提及有这样一种倾向存在:将所有用到的东西都设为外部变量,因为这看上去能简化 程序的交互——参数列表很短,而且这些变量在要用时随时可用。但是外部变量即便在你不需 要时也总是存在。过多地依赖于外部变量会使程序遍布危险,因为这将会导致程序的数据关联 不明显——变量可由一些出其不意甚至是疏忽的方式改变,而且程序也会难于修改。程序 logest-line 的第二个版本不如第一个版本,部分是由于这些原因,部分则是由于其将需要 操作的变量名字写到了函数 getline 和 copy 中,而使得这两个有用的函数失去了通用性。 至此,我们已经涵盖了那些也许会被称作 C 语言传统核心的内容。基于这些少量的语言“构 件”,读者可能编写出规模可观的有用程序。或许停下来用足够长的时间去练习编写程序会是一 个好主意。下面的程序练习比起本章先前的练习要复杂一些。 练习 1-20. 编写一个程序 detab,它将输入中的制表符替换为恰当数目的空格,使间隔达到 下一制表符停止位。假定一组固定的制表符停止位,比如每 n 列一个停止位。n 应当是一个变 量还是一个符号参数? 练习 1-21. 编写一个程序 entab,它将一连串空格替换为相同间隔的最小数目的制表符和空 格。使用与 detab 相同的制表符停止位。当单个制表符或者单个空格都能达到制表符停止位时, 选用哪一种更好? 练习 1-22. 编写一个程序,其将一个长输入行“折”为较短的几行,折行的位置为输入的第 n 列之前的最后一个非空白符之后。确保你的程序能够智能地处理很长的输入行,以及在指定列 之前没有空格或制表符的情况。 练习 1-23. 编写一个程序,用于去掉 C 程序的所有注释。注意正确地处理带引号的字符串和 字符常量。C 语言的注释不允许嵌套。 练习 1-24. 编写一个程序,其用于检查 C 程序的基本语法错误,例如不配对的圆括号、方括 号和花括号。别忘了对单引号、双引号、转义序列以及注释的处理。(如果读者想把它写成完全 通用的程序,难度会比较大。) 第 2 章 类型、运算符以及表达式 变量和常量是程序处理的基本数据对象。声明列出了所要使用的变量,说明这些变量的类 型并可能给出它们的初始值。运算符指定了这些变量所要进行的运算。表达式将一些变量和常 量结合起来生成新的数值。对象的类型决定了该对象所能被赋予的值以及能够进行的运算。以 上这些语言元素将是本章的主题。 ANSI标准对基本类型以及表达式做了许多小的增改。现在所有整数类型都具有signed(带 符号)和 unsigned(无符号)两种形式,无符号常量及十六进制字符常量也有了特定的表示 法。浮点运算可以按单精度进行;同时也有用于扩展精度的 long double 类型。多个字符串 常量可以在编译时进行拼接。枚举类型,这种已长期存在的特性正式变成了语言的一部分。对 象可以被声明为 const 类型以避免其值被改变。在算术类型之间的强制类型转换规则已被扩展 用于处理更多的类型。 2.1 变量名 尽管我们在第 1 章中没有说明,但是对变量和符号常量的命名是有一些规则要求的。名字 由字母以及数字组成;首字符必须是一个字母。下划线“_”被当作一个字母;有时候它对于提 高长变量名的可读性比较有用。但是不要让变量名以下划线开头,因为库例程常常使用以下划 线开头的名字。大写字母和小写字母会被区分,因此 x 和 X 是不同的两个名字。传统上 C 的做 法是变量名使用小写字母,而符号常量则使用全大写的字母。 内部名字(internal name)的至少前 31 个字符是有效的。对于函数名和外部变量来说, 名字的字符数量可能会少于 31 个,因为外部名字(external name)可能会被语言无法控制 的汇编器和加载器所使用。对于外部名字,ANSI 标准只保证前六个字符的唯一性且不区分大小 写。像 if、else、int、float 等关键字都是保留的:你不能够将它们用作变量名。关键字 使用的必须是小写字母。 选择与变量作用有关系的变量名是明智的做法,这样也不容易造成拼写上的混淆。我们倾 向于对本地变量使用短名字,特别是循环控制变量,而对外部变量使用较长的名字。 2.2 数据类型及大小 C 语言中只有几种基本数据类型: char 单个字节,用于存储本地字符集中的一个字符。 int 一个整数,通常为具体机器上整数的自然大小。 float 单精度浮点数。 double 双精度浮点数。 此外,还有一些能够用于这些基本类型的限定词。short 和 long 可用于整型数: short int sh; long int counter; 这种声明中的 int 可以省去(这也是典型的做法)。 引入 short 与 long 的目的是为了提供满足实际需求的不同长度的整数;int 通常是特定 机器的字长。short 常常为 16 比特,long 为 32 比特,而 int 则为 16 或者 32 比特。每个 编译器可以根据其硬件自由选择适当的大小,唯一的限制是short和int至少有16比特,long 至少有 32 比特,并且 short 不能比 int 长,int 不能比 long 长。 限定符 signed 与 unsigned 可用于 char 型或者任何整数类型。被 unsigned 限定的数 一定是正数或 0,并遵循模 2n 原则,其中 n 为该数据类型的比特位数。例如,如果 char 为 8 比特,unsigned char 型变量的值域为 0~255,而 signed char 型变量的值域为-128~127 (在采用二进制补码的机器上)。未限定的 char 型变量是有符号(signed)还是无符号 (unsigned)取决于具体机器,但是可打印字符一定是正值。 long double 类型用于指定扩展精度浮点数。与整数类似,浮点对象的大小也由具体实 现定义,float、double 和 long double 类型的对象可以表示相同的大小,也可以表示两 种或者三种不同的大小。 标准头文件 包含了与具体机器和编译器相关的所有这些 大小以及其他特性的符号常量。这些内容将在附录 B 中进行讨论。 练习 2-1. 编写一个程序来确定 char、short、int 和 long 型变量的取值范围,包括 signed 及 unsigned 类型。通过打印标准头文件中的相应值以及直接计算两种方法来完成。如果通过 计算方式,确定各种浮点类型的取值范围会更困难。 2.3 常量 如 1234 这样的整数常量是 int 型常量。long 型常量以字母 l 或 L 结尾,如 123456789L; 超过 int 型存放范围的整数常量也会被当作 long 型常量处理。无符号常量以字符 u 或 U 结尾, 后缀 ul 或 UL 则用于表示 unsigned long 型常量。 浮点常量包含一个小数点(如 123.4)或指数(如 1e-2)或两者皆有。未带后缀的浮点 常量的类型为 double;后缀 f 或 F 表示 float 常量;而后缀 l 或 L 表示 long double 常 量。 除十进制外,整数值还可以表示为八进制或十六进制。一个整数常量如果以数字 0 开头则 为八进制;以 0x 或 0X 开头则为十六进制。例如,十进制数 31 可以写成八进制数 037 或者十 六进制数 0x1f 或 0X1F。八进制和十六进制常量也可以带上表示 long 型的后缀 L 以及表示 unsigned 型的后缀 U:例如,0XFUL 即为一个值为 15(十进制)的 unsigned long 型常 量。 字符常量是一个整数,其形式为一个括上单引号的字符,如’x’。字符常量的值是具体机 器的字符集中该字符对应的数值。例如,在 ASCII 字符集中,字符常量’0’的值为 48——与数 值 0 毫无关系。如果代码使用字符’0’而不是使用 48 这样依赖于字符集的数值,那么程序将会 独立于特定的值并且更易阅读。虽然字符常量主要用来与另外的字符进行比较,但也可以像其 他任何整数一样参与数值运算。 在字符常量和字符串常量中,有些字符可能通过转义序列来表示,如 \n(换行符);这些 序列看上去像两个字符,但其仅表示单个字符。此外,字节大小的任意位模式都能用以下形式 指定: ‘\ooo’ 其中 ooo 代表一至三个八进制数字(0…7)或者 ‘\xhh’ 其中hh是一至多个 ①十六进制数字(0…9,a…f,A…F)。所以我们编写的代码可能会是: #define VTAB ‘\013’ /* ASCII 纵向制表符 */ #define BELL ‘\007’ /* ASCII 响铃符 */ 或按十六进制写为 #define VTAB ‘\xb’ /* ASCII 纵向制表符 */ #define BELL ‘\x7’ /* ASCII 响铃符 */ 下面是所有的转义序列: \a 响铃符 \\ 反斜杠 \b 回退符 \? 问号 \f 换页符 \’ 单引号 \n 换行符 \” 双引号 \r 回车符 \ooo 八进制数 \t 横向制表符 \xhh 十六进制数 \v 纵向制表符 字符常量’\0’表示一个值为 0 的字符,即空字符。我们常使用’\0’来替代 0 以强调某些 表达式的字符性质,但其数值就是 0。 常量表达式是其中只包含常量的表达式。这种表达式的求值可以在编译时完成,而不必等 到运行时才进行,因而它可用于常量能够出现的任何位置。例如: #define MAXLINE 1000 char line[MAXLINE+1]; 或 #define LEAP 1 /* 闰年 */ int days[31+28+LEAP+31+30+31+30+31+31+30+31+30+31]; 字符串常量也叫字符串字面值,是用双引号括上的由零个到多个字符组成的序列。例如: “I am a string” 或: “” /* 空字符串 */ 双引号不是字符串的一部分,它仅用于限定字符串。在字符常量中使用的转义序列同样用在字 符串中; \” 表示双引号字符。首尾相邻的字符串常量可以在编译时被拼接起来: “hello,” “ world” 等价于 “hello, world” 此特性在将长字符串拆分为若干源代码行时较为有用。 从技术角度看,字符串常量是一个字符数组。字符串的内部表示在结尾处有一个空字 符’\0’,因此存放字符串所需的物理存储比双引号内的字符数多 1。这种表示方法意味着对字 符串的长度没有限制,但程序必须扫描整个字符串以确定其长度。标准库函数 strlen(s)用于 返回其字符串参数 s 的长度,不包括终止位置的’\0’。下面是我们给出的版本: /* strlen:返回 s 的长度*/ int strlen(char s[]) { int i; ①对于 h 的数目没有限制,但如果构成的字符值超过了最大的字符,则行为是未定义的。——译注 i = 0; while (s[i] != ‘\0’) ++i; return i; } strlen 和其他字符串函数在标准头文件 中声明。 请注意区分字符常量和仅包含单个字符的字符串:’x’与”x”并不相同。前者是一个整数, 用于产生字母 x 在机器字符集中对应的数值;后者是包含一个字符(即字母 x)及一个’\0’的 字符数组。 C 语言中还有一种常量,称为枚举常量。枚举是一个由常量整数值组成的列表,例如: enum boolean { NO, YES }; 在枚举常量中,默认第一个名字的值是 0,下一个是 1,依次类推,除非名字给定了明确的值。 如果不是所有的值都被给定,未给定的值会依着最后一个给定值向后递增,如下面例子中的第 二例: enum escapes { BELL = ‘\a’, BACKSPACE = ‘\b’, TAB = ‘\t’, NEWLINE = ‘\n’, VTAB = ‘\v’, RETURN = ‘\r’ }; enum moths { JAN = 1, FEB , MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC }; /* FEB 为 2,MAR 为 3,依次类推 */ 不同枚举常量中的名字必须各不相同,同一枚举常量中各个名字的值无须不同。 枚举提供了一种将常量值和名字关联起来的便利方法。作为#define 语句的另一选择,其 优点在于常量的值可以自动生成。虽然可以声明 enum 类型的变量,但编译程序不需要检查在 这个变量中存储的值是否是该枚举的有效值;不过枚举变量提供了进行这种检查的机会,因而 常常比使用#define 更好。此外,调试程序还能以符号形式打印出枚举变量的值。 2.4 声明 所有变量在使用之前都必须声明,尽管其中一些声明可以通过上下文隐式地给出。一个声 明指定了一个类型,并包含了由一个或多个此类型的变量组成的列表。例如: int lower, upper, step; char c, line[1000]; 变量可按任何样式分布于声明之中;上述列表可等价地写成 int lower; int upper; int step; char c; char line[1000]; 后一种形式所占空间更多,但对每一声明添加注释或者以后修改都较为方便。 变量还可以在其声明中进行初始化。如果变量名之后带有一个等号和一个表达式,那么该 表达式就起到初始化单元的作用。例如: char esc = ‘\\’; int i = 0; int limit = MAXLINE+1; float eps = 1.0e-5; 如果变量不是自动变量,那么初始化仅进行一次,概念上在程序开始执行之前完成,并且 初始化单元必须是一个常量表达式。显式初始化的自动变量当其所在函数或程序块每次进入时 都要进行初始化;其初始化单元可以是任何表达式。外部变量和静态变量默认被初始化为零。 未显式初始化的自动变量的值则是未定义的(如无用数据)。 限定符 const 可被应用于任何变量的声明中,指明该变量的值不会被改变。对于数组而言, const 限定符则说明数组的元素不会被改动。 const double e = 2.71828182845905; const char msg[] = “warning: ”; const 声明也能用在数组参数上,表明此函数不会修改这个数组: int strlen(const char[]); 如果试图修改一个 const 变量,其结果由具体实现定义。 2.5 算术运算符 二元算术运算符包括 +、–、*、/ 以及模运算符 % 。整数除法会截掉任何未被整除的部 分。表达式 x % y 的结果为 x 除以 y 所得的余数,当 y 整除 x 时其值为零。例如,某年份为闰年的条件是它能被 4 整除但不能被 100 整除,或者能被 400 整除。因此 if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) printf(“%d 是闰年\n”, year); else printf(“%d 不是闰年\n”, year); 运算符 % 不能用于 float 型和 double 型。对于负操作数,/ 的截取方向和 % 结果的正负 依赖于具体机器,如同上溢或下溢时的处理方式一样。 二元运算符 + 和 – 具有相同的优先级,它们的优先级比 *、/、% 低,而 *、/、% 的 优先级又比一元运算符 + 和 – 低。算术运算符是从左至右进行结合的。 位于本章最后的表 2-1 总结了全部运算符的优先级与结合律。 2.6 关系运算符和逻辑运算符 关系运算符 > >= < <= 具有相同的优先级。优先级仅比它们低一级的是相等运算符: == != 关系运算符的优先级低于算术运算符,因此 i < lim–1 这样的表达式会像预料中那样按 i < (lim–1) 处理。 更加有趣的是逻辑运算符 && 和 || 。以 && 或 || 连接的表达式按从左至右的次序求 值,一旦获知结果的真假,求值过程就会立即终止。许多C程序依赖于这样的特性。例如,下面 的循环来自于我们在第 1 章中编写的输入函数getline①: for (i=0; i= ‘0’ && s[i] <= ‘9’; ++i) n = 10 * n + (s[i] – ‘0’); ① 此循环与 1.9 节的循环在形式上不是完全相同,但在功能上是等价的。 return n; } 正如我们第 1 章中讨论的那样,表达式 s[i] – ‘0’ 给出了 s[i] 中存放字符所对应的数,这是因为 ‘0’ 、‘1’ … 等字符的值是一个连续递增的 序列。 char 转换为 int 的另一个例子是函数 lower,它将单个字符映射为 ASCII 字符集中的小 写形式。如果该字符不是一个大写字母,那么 lower 将其原样返回。 /* lower:将 c 转换为小写形式;仅适用于 ASCII 码 */ int lower(int c) { if (c >= ‘A’ && c <= ‘Z’) return c + ‘a’ – ‘A’; else return c; } 代码对于 ASCII 码是有效的,因为 ASCII 码中大写字母和对应小写字母的距离是固定的数值, 并且每个字母表都是连续的——在 A 和 Z 之间全是字母。后者对 EBCDIC 字符集并不适用,因 此在 EBCDIC 中上述代码不会只对字母的进行转换。 在附录B中描述的标准头文件 定义了一组独立于字符集的用于字符测试和转 换的函数。例如,函数 tolower(c) 当 c 为大写时返回 c 的小写值,因此 tolower 是上面 给出的 lower 函数的一个可移植的替代者。类似地,测试 c >= ‘0’ && c <= ‘9’ 可以被替代为 isdigit(c) 从现在起,我们将使用 给出的函数。 关于字符到整数的转换有一个微妙之处:C 语言没有规定 char 型变量是有符号还是无符号 的量;当 char 转换为 int 时,究竟能否生成一个负的整数?答案随具体机器不同而变化,反 映出机器架构的差异。在一些机器上,最左端比特位为 1 的 char 型量会被转换为负整数(“符 号扩展”);而在其他机器上,会通过将 char 型量的左端添加 0 来扩展为整数,因而其值一定 为正。 C 语言的定义保证了机器的标准可打印字符集中的任何字符都不为负,因此这些字符在表 达式中总是正值。但是任意给定比特模式的字符变量可能在某些机器上为负,而在其他机器上 为正。为了程序的可移植性,如果打算将非字符数据存放在 char 型变量中,请指定 signed 或 unsigned 类型。 根据定义,如 i > j 等关系表达式以及由 && 和 || 连接的逻辑表达式如果为真,则表 达式的值为 1,如果为假则值为 0。因此下面的赋值 d = c >= ‘0’ && c <= ‘9’ 当 c 是数字时将 d 置为 1,否则置为 0。然而,isdigit 等函数却会返回任意的非零值表示真。 在 if、while、for 等语句的测试部分,“真”仅意味着“非零”,因此两者不会造成差别。 隐式的算术转换多数会像预料的那样运作。一般而言,如果类似 + 或 * 等需要两个操作 数的运算符(即二元运算符)其操作数的类型不同,那么“较低”类型会被提升为“较高”类 型,然后才进行运算;运算结果是较高的类型。附录 A 的第 6 节精确地说明了相关转换规则。 但如果没有 unsigned 型的操作数,下面这组非正式的规则就足够了: 若任一操作数是 long double 型,将另一操作数转换为 long double 型; 否则,若任一操作数是 double 型,将另一操作数转换为 double 型; 否则,若任一操作数是 float 型,将另一操作数转换为 float 型; 否则,将 char 和 short 型转换为 int 型; 然后,如果任一操作数是 long 型,将另一操作数转换为 long 型。 请注意在表达式中 float 型不会自动地转换为 double 型;这是对 C 语言最初定义的一个 改变。通常,像那些在 中的数学函数会使用双精度(即 double)。使用 float 的 主要原因是为了节省大型数组的存储空间,较少些的原因是为了节省某些双精度运算代价特高 的机器的运行时间。 当表达式中包含 unsigned 类型的操作数时,转换规则要复杂一些。具体原因是有符号值 与无符号值的比较与机器相关,这取决于各个整数类型的大小。例如,假定 int 为 16 位,long 为 32 位,那么-1L < 1U,这是因为 unsigned int 型的 1U 被提升为 signed long 类型; 但是 -1L > 1UL,这是因为-1L 被提升为 unsigned long 类型,这样它就变成了一个大的 正数。 赋值时也会进行类型转换;赋值运算右边的值会被转换为左边变量的类型,这也是结果的 类型。 字符转换为整数,无论通过符号扩展与否,其做法如前所述。 较长的整数转换成较短的整数或字符时,要把超出的高位部分去掉。于是当程序 int i; char c; i = c; c = i; 执行后,c 的值将会保持不变,无论是否进行符号扩展都是这样。然而,如果把两个赋值语句 的次序颠倒一下,则可能会丢失信息。 如果 x 是 float 型且 i 是 int 型,那么 x = i 以及 i = x 都会导致类型转换;将 float 型转换成 int 型时会截掉任何小数部分。而当 double 型转换成 float 型时,是舍入还是截 取取决于具体实现。 由于函数调用的参数是一个表达式,所以类型转换也会发生在将参数传递给函数的过程中。 在没有函数原型的情况下,char 和 short 类型将会转换成 int,float 类型将会转换成 double。这就是为什么我们之前将函数参数声明成 int 或者 double,甚至当函数被调用时传 递的参数是 char 或者 float 时也这样做。 最后,在任何表达式中都可以进行显式的类型转换(即所谓的“强制转换”),这时要使用 一个叫做强制转换(cast)的一元运算符。在如下结构中: (类型名) 表达式 表达式按上述转换规则被转换成由强制类型转换运算符所指定的类型。强制类型转换的精确含 义就像是表达式首先被赋给一个所指定类型的变量,然后再将其替换到整个结构所在的位置。 例如,库函数 sqrt 需要一个 double 类型的参数,但如果无意中给成了其他类型,那么就会 产生无意义的结果。(sqrt 在 中声明。)因而,如果 n 是一个整数,那么可以用 sqrt((double )n) 将 n 的值转换成 double 型之后再传递给 sqrt。注意,强制类型转换只是按恰当类型产生 n 的值,n 本身并未被修改。如本章末尾的表中总结的那样,强制类型转换运算符的优先级与其 他一元运算符相同。 如果参数通过函数原型进行了声明(通常会是这样),该声明会使得在函数调用时自动进行 参数的强制类型转换。这样,给出 sqrt 的一个函数原型: double sqrt(double); 调用 root2 = sqrt(2); 不需要强制转换运算符就能自动将整数 2 强制转换成 double 型的值 2.0。 标准库包含有一个可移植的伪随机数生成器以及一个用于初始化其种子的函数;前者给出 了一个强制转换的示例: unsigned long int next = 1; /* rand:返回一个伪随机整数,其值域为 0..32767 */ int rand(void) { next = next * 1103515245 + 12345; return (unsigned int)(next/65536) % 32768; } /* srand:为 rand()设置种子 */ void srand(unsigned int seed) { next = seed; } 练习 2-3. 编写函数 htoi(s),其将一个表示 16 进制数的字符串(包括可选的 0x 或 0X)转 换为对应的整数值。允许出现的数字包括 0~9、a~f 以及 A~F。 2.8 递增与递减运算符 C 语言提供了两个特殊的运算符,分别用于变量的递增和递减。递增运算符 ++ 将操作数 增 1,而递减运算符 -- 将操作数减 1。我们已频繁地使用 ++ 来递增变量,譬如 if (c == ‘\n’) ++nl; 特殊的方面是 ++ 和 -- 既可以用作前缀运算符(在变量之前,如++n),又可用作后缀运 算符(在变量之后,如 n++)。两种情况的运算效果都是将 n 递增。但是表达式 ++n 在 n 的值 被使用之前将 n 递增,而 n++ 则在 n 的值使用之后再将其递增。这意味着在需要使用表达式 值的上下文中,除对 n 的作用之外,++n 与 n++ 是不同的。如果 n 为 5,那么 x = n++; 将 x 置为 5,而 x = ++n; 将 x 置为 6。两种情况下,n 都变为 6。递增和递减运算符只能作用于变量;像 (i+j)++ 这 样的表达式是不合法的。 在只需要递增效果,而不需要表达式值的上下文中,如 if (c == ‘\n’) nl++; 前缀和后缀是相同的。但是某些情况下会特别运用其中的某一种。例如,考虑函数 squeeze(s,c),其将字符串 s 中出现的字符 c 全部去掉。 /* squeeze:删除 s 中的所有 c */ void squeeze(char s[], int c) { int i, j; for (i = j = 0; s[i] != ‘\0’; i++) if (s[i] != c) s[j++] = s[i]; s[j] = ‘\0’; } 每当出现一个不是 c 的字符,该字符即被复制到当前位置 j,此时 j 才被递增以备存放下一个 字符。该 if 语句完全等同于 if (s[i] != c) { s[j] = s[i]; j++; } 结构与之类似的另一例子来自第 1 章中我们编写的 getline 函数,我们可将 if (c == ‘\n’) { s[i] = c; ++i; } 替换为更加紧凑的 if (c == ‘\n’) s[i++] = c; 作为第三个例子,考虑标准库函数 strcat(s,t),其将字符串 t 拼接到字符串 s 的末尾。 strcat 假定 s 中有足够的空间容纳组合后的字符串。我们编写的 strcat 没有返回值;标准 库的版本会返回一个指向该组合字符串的指针。 /* strcat:将 t 拼接到 s 的末尾;s 必须够大 */ void strcat(char s[], char t[]) { int i, j; i = j = 0; while (s[i] != ‘\0’) /* 找到 s 的末尾 */ i++; while ((s[i++] = t[j++]) != ‘\0’) /* 复制 t */ ; } 随着每一字符从 t 复制到 s,后缀 ++ 分别作用于 i 和 j,以保证它们处于适当位置以备下一 轮循环处理。 练习 2-4. 编写 squeeze 函数的另一版本 squeeze(s1,s2),其从字符串 s1 中删除掉那些 但凡出现在字符串 s2 中的字符。 练习 2-5. 编写函数 any(s1,s2),其返回 s1 中首次出现字符串 s2 中任何字符的位置;如 果 s1 中不包含 s2 中的任何字符,则返回 -1 。(标准库函数 strpbrk 完成同样的工作,但 它返回一个指向该位置的指针。) 2.9 按位运算符 C 语言提供了六种用于位操作的运算符;它们只能用于整型操作数,即有符号或无符号的 char、short、int 以及 long 型整数。 & 按位与 | 按位或 ^ 按位异或 << 左移 >> 右移 ~ 按位取反(一元运算符) 按位与运算符 & 常用于屏蔽一些位;例如, n = n & 0177; 将 n 除了低 7 位之外的比特位设为 0。 按位或运算符 | 则常用于打开某些位: x = x | SET_ON; 按照 SET_ON 中为 1 的位,将 x 中对应的比特位设为 1。 按位异或运算符 ^ 当两个操作数对应比特位(的值)不同时,将该位设为 1;相同时将该 位设为 0。 读者必须将按位运算符 & 及 | 与逻辑运算符 && 及 || 区分开,后者意味着从左至右进 行逻辑真假的求值。例如,如果 x 为 1、y 为 2,那么 x & y 为 0 而 x && y 为 1。 移位运算符 << 和 >> 按其右操作数指定的位数(必须是非负值)将其左操作数进行左移 或者右移。因此 x << 2 将 x 的值左移两位,并将空出的位用 0 填充;这相当于 x 乘以了 4。 右移一个无符号数时空出的位总是用 0 填充。右移一个有符号数时,一些机器上会填充符号位 (“算术右移”),而另一些机器上则会填充 0(“逻辑右移”)。 一元运算符 ~ 将一个整数按位取反,即将每个为 1 的比特位换为 0,将为 0 的位换为 1。 例如, x = x & ~077 将 x 的最后 6 位设置为 0。注意,x & ~077 与字长无关,因而比类似 x & 0177700 等写法 更好,后者假定 x 是一个 16 比特的量。这种可移植的形式不需要额外的开销,因为 ~077 是 一个常量表达式,可以在编译时求值。 作为一些位运算符的示例,考虑函数 getbits(x,p,n),其返回 x 中从位置 p 起始的 n 个比特位(已右对齐)。我们假定位置 0 为最右端的比特位,n 和 p 都是合理的正值。例如, getbit(x,4,3)返回位置为 4、3、2 的(并经过了右对齐的)比特位。 /* getbits:获取从位置 p 起始的 n 个比特位 */ unsigned getbits(unsigned x, int p, int n) { return (x >> (p+1-n)) & ~(~0 << n); } 表达式 x >> (p+1-n)将所需位段移动到字的最右端。~0 是比特值全为 1 的数;~0 << n 将 它左移 n 位,将最右边的 n 个比特位设置为 0;最后用 ~ 取反得到最右 n 个比特位为 1 的掩 码。 练习 2-6. 编写一个函数 setbits(x,p,n,y),其将 x 从位置 p 起始的 n 个比特位设置为 y 的最右 n 个比特位,x 的其余位不变;返回 x。 练习 2-7. 编写一个函数 invert(x,p,n),其 将 x 从位置 p 起始的 n 个比特位取反(即 1 变 为 0,0 变为 1),x 的其余位不变;返回 x。 练习 2-8. 编写一个函数 right(x,n),其返回整数 x 向右循环移动 n 位所得到的值。 2.10 赋值运算符与赋值表达式 诸如 i = i + 2 等左边变量随即出现在右边的表达式可被写为如下压缩形式: i += 2 运算符 += 被称为赋值运算符。 大多数二元运算符(与 + 类似的具有左操作数和右操作数的运算符)都有对应的赋值运算 符 op= ,op 为如下运算符之一: + - * / % << >> & ^ | 如果 expr1 和 expr2 是表达式,那么 expr1 op= expr2 相当于 expr1 = (expr1) op (expr2) 只是前者的 expr1 仅会被计算 1 次。留意 expr2 周围的括号: x *= y + 1 表示 x = x * (y + 1) 而不是 x = x * y + 1 一个具体的例子是函数 bitcount,它统计其整数参数中为 1 的比特位的个数。 /* bitcount:统计 x 中为 1 的比特位 */ int bitcount(unsigned x) { int b; for (b = 0; x != 0; x >>= 1) if (x & 01) b++; return b; } 将参数 x 声明为 unsigned 类型保证了 x 向右位移时空出的比特位会被填充为 0——无论程序 运行在何种机器上。 撇开简洁性不谈,赋值运算符的优点是与人们的思维方式匹配得更好。我们会说“将 2 加到 i 中”或者“将 i 增加 2”,而不是“取 i,加上 2,然后将结果放回 i 中”。因此表达式 i += 2 比 i = i + 2 要好。此外,对于一个复杂的表达式,譬如 yyval[yypv[p3+p4] + yypv[p1+p2]] += 2 赋值运算符使得代码更易理解,读者不必费力地检查两个长表达式是否真正一致,或是奇怪为 什么它们不一样。赋值运算符甚至还能帮助编译器产生高效的代码。 我们已经见到过:赋值语句有其自己的值,并且能够出现在表达式中。最常见的例子是 while ((c = getchar()) != EOF) … 其他赋值运算符(+=、-= 等等)也能出现在表达式中,不过要少见一些。 在所有这类表达式中,赋值表达式的类型是它左操作数的类型,它的值则等于赋值完成后 的值。 练习 2-9. 在采用二进制补码的系统中,x &= (x-1) 将 x 中最右边为 1 的比特位去掉(即 置为 0)。请解释原因。利用这一经验编写一个速度更快的 bitcount 版本。 2.11 条件表达式 语句 if (a > b) z = a; else z = b; 计算 a 与 b 的最大值并放到 z 中。使用三元运算符“?:”编写的条件表达式提供了另一种编写上 述语句或者类似结构的方法。在表达式 expr1 ? expr2 : expr3 中,首先表达式 expr1 被求值,如果为非零值(真),则表达式 expr2 被求值,并作为这整个 条件表达式的值;否则 expr3 被求值并作为整个表达式的值。expr2 和 expr3 中仅有一个会被 求值。因此将 z 设置为 a 与 b 中最大值的代码为: z = (a > b) ? a : b; /* z = max(a, b) */ 应当注意条件表达式是一个真正的表达式,它可以用在任何其他表达式能用的位置。如果 expr2 和 expr3 是不同的类型,结果的类型依照本章先前讨论的转换规则确定。例如,如果 f 是 float 型而 n 是 int 型,那么表达式 (n > 0) ? f : n 无论 n 是否为正,其类型都为 float。 上述条件表达式中第一个表达式的括号是不必要的,因为 ?: 的优先级非常低,它只比赋 值运算符高一级。但是我们仍建议加上括号,因为它们让表达式的条件部分显得更加清晰。 用条件表达式常常能编写出简洁的代码。例如下面这个循环,它打印数组中的 n 个元素, 每 10 个元素一行,各列用一个空格分开,并且每行(包括最后一行)以换行符结束。 for (i = 0; i < n; i++) printf(“%6d%c”, a[i], (i%10==9 || i==n-1) ? ‘\n’ : ‘ ’); 在每 10 个元素或第 n 个元素之后会打印换行符,所有其他元素之后都带有一个空格。这可能 看起来比较复杂,但它比与之等价的 if-else 要更加紧凑。另一个较好的例子是 printf(“You have %d item%s.\n”, n, n==1 ? “” : “s”); 练习 2-10. 重写 lower 函数,它将大写字母转换为小写字母,使用一个条件表达式来替代 if-else。 2.12 算符优先级与求值次序 表 2-1 总结了所有运算符的优先级和结合率,包括那些我们还未讨论过的运算符。同一行 中的运算符拥有相同的优先级;各行按优先级递减的次序排列。例如,*、/、% 具有相同的优 先级,它们的优先级比二元运算符 + 和 – 要高。“运算符” () 代表函数调用。运算符 -> 和 . 用于访问结构的成员;它们以及运算符 sizeof(给出对象大小)将在第 6 章中介绍。第 5 章 讨论 *(通过指针间接访问)和 &(取对象的地址)。第 3 章讨论逗号运算符。 表 2-1 运算符优先级和结合率 运算符 结合率 () [] -> . 从左至右 ! ~ ++ -- + - * & (类型) sizeof 从右至左 * / % 从左至右 + - 从左至右 << >> 从左至右 < <= > >= 从左至右 == != 从左至右 & 从左至右 ^ 从左至右 | 从左至右 && 从左至右 || 从左至右 ?: 从右至左 = += -= *= /= %= &= ^= |= <<= >>= 从右至左 , 从左至右 一元运算符 +、-、* 和 & 的优先级高于相应的二元运算符 注意按位运算符 &、^ 和 | 的优先级低于 == 和 != 。这意味着像下面这样的位测试表 达式 if ((x & MASK) == 0) … 必须括上括号才能得到正确的结果。 与大多数语言类似,C 语言并未指定运算符的多个操作数的求值次序。(&&、||、?: 以及 , 是例外。)例如,类似 x = f() + g(); 这样的语句中,f 的求值可能在 g 之前,也可能在 g 之后;因此如果 f 或者 g 将改变另一方所 依赖的变量,那么 x 的值就会取决于它们求值的次序。可以将中间结果存放在临时变量中以确 保特定的求值顺序。 类似地,函数的参数之间的求值次序也是不确定的,因此语句 printf(“%d %d\n”, ++n, power(2, n)); /* 错了!*/ 用不同的编译器能产生不同的结果,这取决于 n 的递增在 power 调用之前还是之后完成。显然, 其解决办法是写成: ++n; printf(“%d %d\n”, n, power(2, n)); 函数调用、嵌套的赋值语句以及递增和递减运算符会引起“副作用”——对表达式的求值会 附带造成某些变量的改变。在任何带有副作用的表达式中,对组成表达式的变量的求值次序存 在着微妙的依赖关系。下面这个语句是一种不爽情形的典型代表: a[i] = i++; 其问题在于数组下标究竟是 i 的旧值还是新值。编译程序对此可以有不同的解释,并根据不同 解释产生不同的结果。C 语言标准有意留下了诸多此类问题未作具体规定。表达式中的副作用 (即对变量赋值)何时发生留待编译器自己考虑,因为最好的求值次序取决于具体机器的架构。 (标准明确规定了函数参数的所有副作用都必须在该函数被调用之前生效,但是这对于上面 printf 函数调用里的情况没有帮助。) 就风格而言,编写依赖于求值次序的代码对于任何语言都不是一种好的编程习惯。很自然, 知道哪些事情不能做是必要的,但如果不知道某些事情在各种机器上会如何完成,同样也不要 试图去利用某种(可能依赖于具体机器的)特定实现来做它们。 第 3 章 控制流 编程语言中的控制流语句用于规定计算指令的执行顺序。在前面的例子中我们已经遇到过 最常见的一些控制流结构;本章会对所有控制流结构进行讨论,并对之前已讨论的部分给出更 精确的描述。 3.1 语句和程序块 一个表达式(譬如 x = 0 或 i++ 或 printf(…))跟上一个分号之后就变成了一个语句, 如 x = 0; i++; printf(…); 在 C 语言中,分号是一个语句结束符,而不像在 Pascal 等语言中那样是一个语句分隔符。 花括号 { 和 } 将多个声明和语句合到一起,构成一个复合语句(或称程序块),这样多个 声明和语句在句法上等同于单个语句。函数里包含所有语句的花括号是一个明显的例子;跟在 if、else、while 以及 for 之后包含多个语句的花括号则是另一个例子。(在任意程序块中都 能声明变量,这一点会在第 4 章中探讨。)表示一个程序块结束的右花括号之后不带分号。 3.2 If-Else if-else 语句用于表述条件判定,其正式语法为 if (表达式) 语句 1 else 语句 2 其中 else 部分是可选的。表达式会被求值,如果它为真(即如果表达式的值不为零),执行语 句 1;若为假(表达式值为零)且有 else 部分,则执行语句 2。 由于 if 只是简单地测试表达式的数值,因而可以采用某些代码简写形式。最明显的是以 if (表达式) 来替代 if (表达式 != 0) 有时候这样简写清楚自然;而有时候又会让人难于理解。 由于 if-else 中的 else 部分是可选的,因此当嵌套的 if 序列中省略某个 else 时会引 起歧义。歧义的消除通过将 else 与之前离它最近且无 else 配对的 if 相匹配来完成。例如在 下面的代码中, if (n > 0) if (a > b) z = a; else z = b; 如缩进结构所示,else 语句与内部的 if 匹配。如果那不是你想要的,那么必须使用花括号来 强制得到期望的匹配: if (n > 0) { if (a > b) z = a; } else z = b; 类似下面的情况中这种歧义性尤为有害: if (n >= 0) for (i = 0; i < n; i++) if (s[i] > 0) { printf(“…”); return i; } else /* 错了!*/ printf(“错误 -- n 是一个负数\n”); 其缩进结构明确地表达了编写者希望的做法,但编译器无法得到这一信息,而将程序中的 else 与内部的 if 相匹配。这种错误会很难发现;因此当存在嵌套的 if 时使用花括号是一个好主意。 顺便提醒读者注意,代码 if (a > b) z = a; else z = b; 中在 z = a 之后有一个分号,这是因为语法上 if 之后带有一个语句,而像“z = a;”之类的 表达式语句总是以分号来结尾的。 3.3 Else-If 程序结构 if (表达式) 语句 else if (表达式) 语句 else if (表达式) 语句 else if (表达式) 语句 else 语句 相当常见,因而值得单独拿出来简要讨论一下。这种 if 语句序列是编写多路判定的最普通的方 式。其中的表达式被依次求值;如果某一表达式为真,那么与之相对应的语句就被执行,并且 整个判定序列终止。就像一直以来的那样,每个语句的代码要么是单个语句,要么是用花括号 括上的一组语句。 最后的 else 部分用于处理“以上均不是”即所有其他条件都不满足时的缺省情况。有时无 需显式的缺省处理,那样序列尾部的 else 语句 可被省略掉,或者可以用作错误检测来捕获“不可能的”条件。 这里以一个二分查找函数为例来说明三路判定的用法,该函数判定在已排好序的数组 v 中 是否存在一个特定的值 x。这 里 v 的元素必须以递增次序排列。如果 v 中存在 x,则函数返回 x 在 v 中的位置(介于 0 与 n-1 之间的一个数),否则返回 -1。 二分查找法首先将输入值 x 与数组 v 的中间元素进行比较。如果 x 小于中间值,那么集中 查找数组的前半部分,否则查找数组的后半部分。无论哪种情况,下一步都是将 x 与所选那一 半的中间元素进行比较。这种将查找范围一分为二的过程会一直持续到找到输入值或者查找范 围变空为止。 /* binsearch:在 v[0] <= v[1] <= … <= v[n-1] 中查找 x */ int binsearch(int x, int v[], int n) { int low, high, mid; low = 0; high = n – 1; while (low <= high) { mid = (low+high) / 2; if (x < v[mid]) high = mid - 1; else if (x > v[mid]) low = mid + 1; else /* 找到匹配 */ return mid; } return -1; /* 无匹配 */ } 此程序的主要判定操作是每一步中 x 是否小于、大于还是等于中间元素 v[mid];这正是 else-if 所擅长的工作。 练习 3-1. 我们的二分查找程序在循环内部进行了两次测试,其实一次测试就足够了(代价是 在循环之外要有更多的测试)。编写在循环内只有一次测试的版本,并比较两者运行时间的差别。 3.4 Switch switch 语句是一种多路判定,它测试一个表达式是否与一组整型常量中的某个值相匹配, 并根据匹配情况执行程序分支。 switch (表达式) { case 常量表达式: 语句序列 case 常量表达式: 语句序列 default: 语句序列 } 每个 case 带有一个或多个具有整型值的常量或者常量表达式的标签。如果某个 case 与待判 定表达式的值相匹配,则程序从该 case 开始执行。所有 case 的表达式(的值)必须互不相 同。在所有其他 case 都不满足匹配时,标记为 default 的 case 被执行。default 是可选 的;如果它不存在,并且没有匹配的 case,则任何操作都不会发生。所有的 case 以及 default 项可以按任意的次序排列。 在第 1 章中我们编写了一个程序,它使用一连串 if … else if … else 来统计每个数 字、空白符、以及所有其他字符出现的次数。这里的程序使用一个 switch 语句完成相同的功 能: #include main() /* 统计数字、空白符、及其他字符数 */ { int c, i, nwhite, nother, ndigit[10]; nwhite = nother = 0; for (i = 0; i < 10; i++) ndigit[i] = 0; while ((c = getchar()) != EOF) { switch (c) { case ‘0’: case ‘1’: case ‘2’: case ‘3’: case ‘4’: case ‘5’: case ‘6’: case ‘7’: case ‘8’: case ‘9’: ndigit[c-‘0’]++; break; case ‘ ’: case ‘\n’: case ‘\t’: nwhite++; break; default: nother++; break; } } printf(“digits =”); for (i = 0; i < 10; i++) printf(“ %d”, ndigit[i]); printf(“, white space = %d, other = %d\n”, nwhite, nother); return 0; } break 语句使得程序的执行立即从 switch 中退出。因为 case 只起到标签的作用,因此 在某 case 的对应代码执行完毕后,如果编程者没有采取显式的退出操作,执行将穿透到接下 来的代码。break 和 return 是退出 switch 最常用的手段。break 语句还可用于强制从 while、for 和 do 循环中立即退出,这将在本章后面的部分讨论。 穿透特性是一把双刃剑。好的方面是它允许多个 case 关联到单个操作,就如这个例子中 对数字的处理。但它同时隐含的是普通情况下每个 case 都必须以 break 结束,以免穿透到下 一个 case。从一个 case 穿透到另一个 case 的特性不够健壮,程序在修改时其完整性容易被 破坏。除了多个标签对应单一计算处理的情况之外,穿透应当少用,在使用时也应加上注释。 就编程形式而言,在最后的 case(在这里是 default)后面放置一个 break 语句是好的 做法,尽管这在逻辑上是没有必要的。某一天当在后面增加其他 case 时,这一丁点保护性的 编码可能会帮上大忙。 练习 3-2. 编写一个函数 escape(s,t),其将字符串 t 复制到字符串 s 中,并在复制过程中 将换行符和制表符之类的字符转换为可见的转义字符序列,如 \n 与 \t。请使用一个 switch 语句。再编写一个反向转换函数,其将转义字符序列转换为实际的字符。 3.5 While 循环和 For 循环 while 循环和 for 循环我们已经见到过。while 循环 while (表达式) 语句 对其中的表达式求值,如果值不为零,则执行语句并再次对表达式求值。重复这一过程直到表 达式变为零为止,然后程序从语句之后继续执行。 除 continue 的行为之外,for 循环语句 for (表达式 1; 表达式 2; 表达式 3) 语句 等价于 表达式 1; while (表达式 2) { 语句 表达式 3; } continue 语句在 3.7 节说明。 语法上 for 循环的三个组成都是表达式,最常见的情况是:表达式 1 和表达式 3 是赋值语 句或函数调用,而表达式 2 是一个关系表达式。三者中的任何一个都可被省略掉,但分号必须 保留。如果表达式 1 或表达式 3 被省略,执行会简单地穿越它们;测试部分(即表达式 2)如果 未给出,则被视为永真,因此 for (;;) { … } 是一个“无限”循环。它大概会以譬如 break 或者 return 等其他的方式终止。 用 while 还是用 for 在很大程度上是一个个人喜好问题。例如,代码 while ((c = getchar()) == ‘ ’ || c == ‘\n’ || c == ‘\t’) ; /* 略过空白字符 */ 中没有初始化或重新初始化操作,因此使用 while 最自然。 当存在简单的初始化和递增操作时更倾向于使用 for,因为它让循环控制语句集在一起且 在循环顶部就可被看见。这一点在代码 for (i = 0; i < n; i++) … 中最为明显。这是 C 语言处理某个数组前 n 个元素的惯用方法,相当于 Fortran 语言的 DO 循 环或者 Pascal 语言的 for 循环。但该类比并不完全对应,因为 C 的 for 循环可以在其内部 改变循环的索引和边界,且当循环由于任何原因而终止时,索引变量 i 的值都可保留下来。由 于 for 的组成部分是任意的表达式,因此 for 循环并非仅限于使用算术级数表达式。尽管如此, 将不相干的计算指令强加到 for 的初始化和递增部分是一种坏的编程风格,将这些部分留给循 环控制操作会更好。 更大些的例子是另一版本的 atoi 函数(其将字符串转换为其表示的数字)。这个版本比第 2 章中的版本稍微通用一些;它能处理可能存在的打头的空白符和一个+号或者-号。(第 4 章给 出的 atof 函数对浮点数进行相同的转换处理。) 程序的结构反映了输入形式: 若有空白符,略过它们 若有符号,获取符号 得到整数部分并进行转换 每个步骤处理数字的一部分,并将状态干净的内容留给下一步处理。当遇到第一个不可能是数 字部分的字符时,整个处理过程结束。 #include /* atoi:将 s 转换为整数;版本 2 */ int atoi(char s[]) { int i, n, sign; for (i = 0; isspace(s[i]); i++) /* 跳过空白字符 */ ; sign = (s[i] == ‘-’) ? -1 : 1; if (s[i] == ‘+’ || s[i] == ‘-’) /* 跳过符号 */ i++; for (n = 0; isdigit(s[i]); i++) n = 10 * n + (s[i] - ‘0’); return sign * n; } 标准库提供了一个更精巧的函数 strtol,其用于将字符串转换为长整数;参见附录 B 的第 5 节。 当存在多层嵌套循环时,让循环控制集中起来的优点愈加显著。下面的希尔排序函数用于 整数数组的排序。该排序算法由 D.L.Shell 于 1959 年发明,其基本思想是:在初始阶段对 相隔较远的元素进行比较,而不是像简单交换排序那样比较相邻的元素。这样有助于快速消除 大量乱序情况,从而减少后续阶段的工作量。比较元素的间隔会逐渐递减为 1,此刻的排序实 质上已变成一种相邻交换的方法。 /* shellsort:将 v[0]…v[n-1]按递增次序排序 */ void shellsort(int v[], int n) { int gap, i, j, temp; for (gap = n/2; gap > 0; gap /= 2) for (i = gap; i < n; i++) for (j=i-gap; j>=0 && v[j]>v[j+gap]; j-=gap) { temp = v[j]; v[j] = v[j+gap]; v[j+gap] = temp; } } 这里有三个嵌套的循环,最外层循环控制比较元素的间隔,间隔开始为 n/2,每轮除以 2 直至 变成零为止。中间的循环对元素依次处理。最内层循环以 gap 为间隔比较每对元素,并将逆序 元素颠倒过来。由于 gap 最后递减为 1,因此所有元素最终会被正确地排序。请注意 for 的通 用性是如何使得外层循环具有与其他循环相同的形式的,虽然其控制变量并不是一个等差级数。 最后的一个运算符是逗号运算符“,”,其最常见于 for 循环语句中。逗号运算符分开的一 对表达式被从左至右求值,运算结果的类型和值就是右操作数的类型和值。这样就可能在一个 for 语句中的各部分设置多个表达式,例如对两个索引进行并行处理。下面以函数 reverse(s) 为例来说明这一点,它将字符串 s 本身颠倒过来。 #include /* reverse:将字符串 s 本身颠倒过来 */ void reverse(char s[]) { int c, i, j; for (i = 0, j = strlen(s)-1; i < j; i++, j--) { c = s[i]; s[i] = s[j]; s[j] = c; } } 用于将函数参数以及声明中的变量等分隔开的逗号不是逗号运算符,也不能保证按从左至右的 顺序求值。 逗号运算符应该少用。其最适合用于相互关系较紧密的代码结构,就如 reverse 函数中的 for 循环,以及需要多个计算步骤的单个表达式宏。逗号表达式也可能适用于 reverse 函数中 的元素互换,这里互换可被当成是单个操作: for (i = 0, j = strlen(s)-1; i < j; i++, j--) c = s[i], s[i] = s[j], s[j] = c; 练习 3-3. 编写一个 expand(s1, s2),它将字符串 s1 中类似 a-z 的速记符在 s2 中扩展为 等价的完全列表 abc…xyz。函数支持大小写字符和数字,并可处理 a-b-c、a-z0-9 以及-a-z 之类的用例。打头和结尾的 - 按普通字符处理。 3.6 Do-while 循环 像我们在第 1 章中讨论的那样,while 循环和 for 循环在其顶部测试终止条件。与之相反, C 语言中的第 3 种循环——do-while——每次执行完循环体之后在底部进行测试;它的循环体 至少会执行一次。 do 循环的语法为 do 语句 while (表达式); 其中语句执行之后表达式才被求值。如果表达式为真,则语句被再次执行,重复这一过程;当 表达式变为假时,循环终止。除了测试的含义不同之外,do-while 等价于 Pascal 的 repeat-until 语句。 经验表明 do-while 的使用比 while 和 for 要少得多,但是不时仍会用到它,就像下面 的 itoa 函数那样。该函数将一个数字转换为对应的字符串(与 atoi 函数相反)。此任务可能 会比一开始想象的要稍微复杂一些,因为较容易的生成数字的方法是以相反的次序生成它们的。 我们选用的做法是先生成反向的字符串,然后再将它颠倒过来。 /* itoa:将 n 转换为字符形式存放到 s 中 */ void itoa(int n, char s[]) { int i, sign; if ((sign = n) < 0) /* 记录符号 */ n = -n; /* 使 n 变为正数 */ i = 0; do { /* 以反向次序生成数字 */ s[i++] = n % 10 + ‘0’; /* 得到下一个数 */ } while ((n /= 10) > 0); /* 删除该数 */ if (sign < 0) s[i++] = ‘-’; s[i] = ‘\0’; reverse(s); } 这里的 do-while 是必要的,或者至少是方便的,因为即使是 n 为零的情况,仍至少有一个字 符必须放到数组 s 中。我们还用花括号括上了构成 do-while 循环体的单个语句,虽然这不是 必要的,但这样做不会让某些粗心的阅读者将 while 部分误认为是一个 while 循环的开始。 练习 3-4. 按照二进制的补码表示法,我们的 itoa 版本不能处理最大的负数,即 n 的值等于 -(2 字长-1)。请解释为什么不能。请修改函数,使它无论运行在何种机器上在都能正确地打印该 值。 练习 3-5. 编写函数 itob(n, s, b),它将整数 n 转换为以 b 为底的数所对应的字符表示并 放到字符串 s 中。特别地,itob(n, s, 16)将 n 按十六进制数的格式转换到字符串 s 中。 练习 3-6. 编写函数 itoa 的另一版本,它接受的参数是三个而不是两个。第三个参数是最小 字段宽度;如果必要,转换后的数的左边必须被填充空格以达到足够的宽度。 3.7 Break 和 Continue 如果能通过顶部或底部测试以外的方式退出循环,有时会比较方便。break 语句提供了从 for、while 和 do 中提前退出的功能,这与它在 switch 中的作用一样。break 会使得最内 层的闭合循环或 switch 立即退出。 下面的函数 trim 用于把一个字符串末尾的空格、制表符和换行符去掉;当找到最右端的 一个不是空格、制表符及换行符的字符时,它使用一个 break 语句从循环中退出。 /* trim:删除字符串末尾的空格、制表符和换行符 */ int trim(char s[]) { int n; for (n = strlen(s)-1; n >= 0; n--) for (s[n] != ‘ ’ && s[n] != ‘\t’ && s[n] != ‘\n’) break; s[n+1] = ‘\0’; return n; } 函数 strlen 返回待处理字符串的长度。程序中的 for 循环从该字符串的末尾向前扫描, 查找第一个不是空格、制表符或换行符的字符。当找到一个字符,或者当 n 变为负值时(即当 整个字符串都已扫描完毕时),循环退出。读者应能验证,即使是在字符串为空或只包含空白符 的情况下,这样的处理仍然是正确的。 continue 语句与 break 语句有关联,但它没有 break 语句常用。continue 语句使得 for、while 或者 do 循环开始执行下一轮循环处理。在 while 和 do 中,这意味着立即执行 循环的测试部分;在 for 中,则意味着循环控制转移到递增步骤。continue 语句只能用于循 环,而不能用于 switch。在某个循环内的 switch 中的 continue 语句会导致循环进入下一 轮处理。 举个例子,下面的程序片段中只对数组 a 中的非负元素进行处理;负值则被跳过。 for (i = 0; i < n; i++) { if (a[i] < 0) /* 跳过负元素 */ continue; … /* 处理非负元素 */ } continue语句常用于循环中其后的部分较为复杂的情况,如果不用continue语句,则需要反 向测试,并缩进一级处理,这可能会导致程序嵌套得过深。① 3.8 Goto 与标签 C 语言提供了极易被滥用的 goto 语句,及其跳转所用的标签。正规说来,goto 语句没有 必要存在。在实践中,绝大多数情况下编写不含 goto 语句的代码都比较容易。我们在本书中 没有使用过 goto 语句。 然而,有几种情况 goto 语句可以适用。最常见的是需要取消位于某些较深的嵌套结构中 的处理,例如从两层或者更深的循环中一次性退出来。这种情况不能直接使用 break 语句,因 为它只能退出最内层的循环。这样: for ( … ) for ( … ) { … if (disaster) goto error; } … error: 错误处理部分 如果相应的错误处理代码不是很少,而且错误会在好几个地方发生,那么这种组织方式会比较 便捷。 标签具有与变量名一样的形式,但其后带有一个冒号。它可指向对应 goto 语句所在函数 的任一语句。标签的作用域就是它所在的函数。 作为另一个例子,考虑判定两个数组 a 与 b 是否存在相同元素的问题。一种可能的做法是: for (i = 0; i < n; i++) for (j = 0; j < m; j++) if (a[i] == b[j]) goto found; /* 没有找到相同的元素 */ … found: /* 找到一个:a[i] == b[j] */ … 包含 goto 语句的代码总能编写成完全不用 goto 语句的代码,尽管那样可能会增加一些 重复测试或一个额外变量作为代价。例如,上述数组查找代码可改写为: ①这句话的理解,考虑按 a[i] >= 0 {怎样怎样} 编写时的情况。译注 found = 0; for (i = 0; i < n && !found; i++) for (j = 0; j < m && !found; j++) if (a[i] == b[j]) found = 1; if (found) /* 找到一个:a[i-1] == b[j-1] */ … else /* 没有找到相同的元素 */ … 除了与这里所举例子类似的少数情况之外,使用 goto 语句的代码通常比没有 goto 语句 的代码更加难于理解和维护。尽管我们对待这个问题的态度并不武断,但是我们确实认为应该 尽可能少地使用 goto 语句。 第 4 章 函数与程序结构 函数将大的计算任务分解成若干小任务,并使得人们可在已有程序的基础上构造程序,而 无须从头做起。一个设计得当的函数可以将其余程序不需知道的操作细节隐藏起来,从而使得 整个程序更加清晰,程序改动的难度也得到降低。 C 语言的设计中考虑了函数的高效性和易用性。C 程序通常由许多小函数(而不是少量大函 数)组成。一个程序可以放在一个或多个源文件中,各个源文件可以分别编译,并与库中事先 编译好的函数一并进行加载。在这里我们不打算深入到这一过程,因为不同系统的编译加载细 节互有差异。 ANSI 标准对 C 语言所做的最明显的修改是函数声明与函数定义两方面。正如第 1 章中所 见,C 语言现在允许在声明函数时声明参数的类型。函数定义的语法也有相应改变,以使函数 声明和定义彼此匹配。这使得编译器可以检测出比原先更多的错误。此外,这还使得参数在说 明得当的情况下能够自动进行适当的强制类型转换。 ANSI 标准进一步明确了名字作用域规则,特别是要求每个外部变量只能有一个定义。初始 化的范围变得更广:现在自动数组变量和自动结构变量都可以进行初始化。 C 语言预处理器的功能也得到了增强。新的预处理器工具中包含了更完整的条件编译指令 集、一种通过宏参数创建带引号字符串的方法、以及对宏扩展过程的更好的控制。 4.1 函数基础知识 首先我们来设计并编写一个程序,它将输入中包含特定“模式”或字符串的各行打印出来(这 是 UNIX 程序 grep 的一个特例)。例如,在下面一组文本行中查找包含字母字符串“ould”的 行: Ah Love! could you and I with Fate conspire To grasp this sorry Scheme of Things entire, Would not we shatter it to bits -- and then Re-mould it nearer to the Heart's Desire! 将产生如下输出: Ah Love! could you and I with Fate conspire Would not we shatter it to bits -- and then Re-mould it nearer to the Heart's Desire! 此任务可以简洁地分为三部分: while ( 还有未处理的行 ) if ( 该行包含指定的模式 ) 打印该行 尽管将这些代码全都放到主程序 main 中肯定是可以的,但更好的方式是利用上述结构将 每一部分设计成一个独立的函数。处理三个较小的部分要比处理一个大的整体更加容易,因为 这样能够将不相关的细节隐藏在函数中,从而使不必要的相互影响的机会降到最低。并且,这 些函数还可能用于其他的程序。 我们用函数 getline 来实现“还有未处理的行”,这个函数已在第 1 章介绍过;用 printf 函数来实现“打印该行”,这个函数别人已经提供了;这意味着我们只需要编写一个判定“该行 包含指定的模式”的函数。 我们编写函数 strindex(s, t) 来解决问题。该函数返回字符串 t 出现在字符串 s 中的 起始位置或下标。当 s 中不包含 t 时,则返回-1。由于 C 语言数组的下标从 0 开始,下标的值 为 0 或正数,因此适于用 -1 之类的负数来表示失败的情况。如果以后需要更复杂的模式匹配, 只需替换 strindex 函数即可,程序的其余部分可以保持不变。(标准库中提供的库函数 strstr 的功能类似于函数 strindex,只是该库函数返回的是指针而不是下标。) 有了这些设计,编写程序的细节内容就很直接了。下面是完整的程序,读者可以看到这几 部分是怎样结合在一起的。目前查找的模式是字符串文本,这不是一种最通用的机制。关于字 符数组的初始化方式很快会回过来讨论。第 5 章会介绍如何使模式成为程序运行时可设定的参 数。getline 函数的版本也稍有不同,读者可将它与第 1 章中的版本进行比较,或许能从中得 到一些启发。 #include #define MAXLINE 1000 /* 最大输入行长度 */ int getline(char line[], int max) int strindex(char source[], char searchfor[]); char pattern[] = "ould"; /* 待查找的模式 */ /* 找出所有与模式相匹配的行 */ main() { char line[MAXLINE]; int found = 0; while (getline(line, MAXLINE) > 0) if (strindex(line, pattern) >= 0) { printf("%s", line); found++; } return found; } /* getline:取一行放到 s 中,并返回该行的长度 */ int getline(char s[], int lim) { int c, i; i = 0; while (--lim > 0 && (c = getchar()) != EOF && c != '\n') s[i++] = c; if (c == '\n') s[i++] = c; s[i] = '\0'; return i; } /* strindex:返回 t 在 s 中的位置,若未找到则返回-1 */ int strindex(char s[], char t[]) { int i, j, k; for (i = 0; s[i] != '\0'; i++) { for (j = i, k = 0; t[k] != '\0' && s[j] == t[k]; j++, k++) ; if (k > 0 && t[k] == '\0') return i; } return -1; } 每个函数定义均为如下形式: 返回类型 函数名(参数声明) { 声明和语句 } 其中多个部分可以省略,最简单的函数是: dummy( ) { } 这个函数什么也不做,也不返回任何值。像这样的“无为”函数有时在程序开发期间被用来预留 位置。如果函数定义中省略了返回类型,则默认为 int 类型。 程序可以看成变量定义和函数定义的集合。函数之间通过参数、函数返回值以及外部变量 进行交互。函数可按任意次序出现在源文件中,源程序可分成多个文件,只要不将一个函数分 在几个文件中就行。 return 语句用于从被调函数向调用者返回值,return 之后可以跟任何表达式: return 表达式; 必要时表达式会被转换为函数的返回类型。表达式常常被括上一对圆括号,但这不是必需的。 调用函数可以自己决定是否忽略掉返回值。而且,return 也不必一定带有表达式,在不 带表达式的情况下,没有值返回给调用者。而当被调用函数因执行到最后的右花括号而从“尾部 脱离”时,控制权也会不带值地返回给调用者。如果函数在一处调用时有返回值而在另一处调用 时没有返回值,这并不非法,但这可能是函数存在问题的征兆。在任何情况下,如果一个函数 没有成功地返回一个值,那么它的“值”肯定是无用的。 上面的模式查找程序从 main 中返回一个状态,即所匹配的数目。这个值可以供调用该程 序的环境使用。 不同系统对存放于多个源文件的 C 程序的编译与加载机制互有差别。例如,UNIX 系统上使 用第 1 章中提到的 cc 命令来完成这项工作。假定这三个函数分别存放在名为 main.c、 getline.c 与 strindex.c 的三个文件中,那么命令 cc main.c getline.c strindex.c 编译这三个文件,并将目标代码存放在文件 mail.o、getline.o 与 strindex.o 中,然后 再将这三个文件一起加载为可执行文件 a.out。如果有一个错误,比如说出现在文件 main.c 中,那么可以用命令 cc main.c getline.o strindex.o 对 main.c 文件重新编译,并将编译结果与之前的目标文件 getline.o 和 strindex.o 一起 加载。cc 命令使用 .c 与 .o 这两个命名约定来区分源文件与目标文件。 练习 4-1. 编写函数 strrindex(s, t),其返回字符串 t 在 s 中最右边出现的位置,如果 s 中不包含 t,则返回-1。 4.2 返回非整型值的函数 到目前为止,我们的例子函数都是没有返回值(void)或者返回 int 类型值的函数。假如 某个函数必须返回其他类型的值,那该怎么做呢?许多数学函数如 sqrt、sin 与 cos 等返回 的是 double 类型的值;另一些专用函数则返回其他类型的值。为了说明让函数返回非整数值 的方法,我们编写并使用函数 atof (s),该函数将字符串 s 转换成相应的双精度浮点数。atof 函数是 atoi 函数的扩充,第 2 章与第 3 章已讨论过 atoi 函数的几个版本。atof 函数要处理 可选的符号与小数点,并要考虑可能缺少整数部分或小数部分的情况。我们这个版本并不是一 个高质量的输入转换函数,它所占用的空间应能更节省一些。标准库中包含了一个 atof 函数, 它在头文件中声明。 首先,由于 atof 函数的返回类型不是 int,因此必须声明其返回值的类型。该类型名称 位于函数名称之前: #include /* atof:把字符串 s 转换成相应的双精度浮点数 */ double atof(char s[]) { double val, power; int i, sign; for (i = 0; isspace(s[i]); i++) /* 略过空白符 */ ; sign = (s[i] == '-') ? -1 : 1; if (s[i] == '+' || s[i] == '-') i++; for (val = 0.0; isdigit(s[i]); i++) val = 10.0 * val + (s[i] - '0'); if (s[i] == '.') i++; for (power = 1.0; isdigit(s[i]); i++) { val = 10.0 * val + (s[i] - '0'); power *= 10.0; } return sign * val / power; } 第二,也同样重要的是,调用程序必须知道 atof 函数返回的是非整数值。确保这一点的 一种方法是在调用程序中显式声明 atof 函数。下面的原始计算器程序(其仅适用于支票簿核 算)中给出了这个声明。程序按行每次读入一个数(每行一个数,数的前面可能带有正负号), 并将这些数加在一起,并在每次输入之后将当前的总和打印出来: #include #define MAXLINE 100 /* 原始计算器 */ main() { double sum, atof(char []); char line[MAXLINE]; int getline(char line[], int max); sum = 0; while (getline(line, MAXLINE) > 0) printf("\t%g\n", sum += atof(line)); return 0; } 其中,声明 double sum, atof (char []); 表明 sum 是一个 double 类型的变量,atof 是一个函数,该函数带有一个 char[] 类型的参 数并返回一个 double 类型的值。 函数 atof 的声明与定义必须一致。如果在同一源文件中,atof 函数与 main 中对它的调 用具有不一致的类型,那么编译器将会检测出这个错误。但是,假如 atof 函数是独立编译的 (这种可能性更大),那么这种不匹配的错误就不会被检测出来,atof 函数会返回 double 类 型的值,而 main 函数则会将其按 int 类型处理,最终得到的结果就会毫无意义。 按照之前我们关于声明是如何必须与定义相匹配的讨论,这似乎很令人吃惊。不匹配现象 会发生的缘由是,如果没有函数原型,则该函数会在表达式中首次出现时被隐式声明,譬如: sum += atof(line) 如果某个之前没有声明过的名字出现在某个表达式中,并且紧跟着一个左圆括号,那么根据上 下文它会被认为是一个函数名字的声明,该函数的返回值被假定为 int 类型,而对其参数则不 做任何假定。而且,如果一个函数声明不包含参数,就像 double atof(); 那么也同样意味着对于 atof 函数的参数不做任何假定,所有的参数检查都被关闭。空参数表 的这种特殊意义是为了使较老的 C 程序可以被新的编译器编译。但是,在新程序中也这样做是 不明智的。如果函数有参数,请声明它们;如果没有参数,请使用 void。 有了正确声明的 atof 函数,我们可以基于它来编写 atoi 函数(将字符串转换为 int): /* atoi:利用 atof 函数把字符串 s 转换为整数 */ int atoi(char s[]) { double atof(char s[]); return (int) atof(s); } 请注意其中的声明和 return 语句的结构。在语句 return 表达式; 中表达式的值在返回之前会被转换为所在函数的类型。因此,当函数 atof 出现在上述程序的 return 语句中时,其返回的 double 类型的值将被自动转换为 int 类型。但是,这一操作有 丢失信息的可能,因此一些编译器会对此给出警告。而此函数中的强制转换显式地表明了所要 进行的操作,屏蔽掉了相关的警告信息。 练习 4-2. 对 atof 函数进行扩充,使之可以处理形如 123.45e-6 的科学计数法,即一个浮点数,其后可能紧跟 e 或 E 及一个(可能带正负号的)指数。 4.3 外部变量 C 程序由一组外部(external)对象构成,或者是变量或者是函数。形容词“external” 与“internal”相对,internal 用于描述在函数内定义的参数和变量。外部变量在函数之外 定义,因而有可能被多个函数使用。函数自身一定是外部的,这是因为 C 语言不允许在函数中 定义其他函数。默认情况下,外部变量与函数具有如下性质:通过相同名字引用的外部对象, 所引用的是同一对象实体,即便引用它们的是独立编译的函数也是如此。(标准将这种性质称为 外部链接)。在这个意义上,外部变量类似于 Fortran 语言的 COMMON 块或 Pascal 语言中在 最外层程序块中定义的变量。我们将在后面见到如何定义只在单个源文件内可见的外部变量和 函数。 由于外部变量是全局可访问的,这就为函数之间的数据交换提供了一种可以替代函数参数 及返回值的方法。任何函数都可以通过外部变量的名字来访问该变量,只要这个名字已通过某 种方式进行了声明。 如果必须在函数之间共享大量的变量,使用外部变量要比使用长参数表更加方便和高效。 然而,正如第 1 章中所指出的,这样做必须保持谨慎,因为这可能会对程序结构造成不利的影 响,并可能导致程序中的函数出现过多的数据关联。 外部变量的用途还体现在它们更大的作用域和更长的生存期。自动变量只在函数内部起作 用;变量从其所在函数被调用时开始存在,到函数退出时消失。而外部变量是永久存在的,它 们的值在不同的函数调用之间可以保持住。因此,如果两个函数必须共享某些数据而互不调用 对方,那么将这些共享数据存放在外部变量中(而不是通过参数传入传出)常常相当方便。 我们通过一个更大的例子来验证这一点。该例子是编写一个提供加(+)、减(-)、乘(*)、 除(/)四则运算符的计算器程序。为了更易于实现,在计算器中使用逆波兰表示法来替代普通 的中辍表示法(逆波兰表示法被用于某些袖珍计算器中,以及如 Forth 和 Postscript 等一 些语言上)。 在逆波兰表示法中,每个运算符都紧跟在它的操作数之后。如中辍表达式 (1 - 2) * (4 + 5) 用逆波兰表示法表示为: 1 2 - 4 5 + * 表达式不再需要圆括号;只要知道每个运算符需要的操作数的个数,逆波兰表示法就是明确的。 程序的实现很简单。每个到来的操作数都被压入栈中;当一个运算符到来时,从栈中弹出 相应数目的操作数(对于二目运算符是两个),按该运算符进行运算,并将运算结果压回栈中。 例如,在上述用例中,首先 1 和 2 被压栈,然后它们被替换为两者之差-1;接 着 4 和 5 被压栈, 然后被替换为两者之和 9;最后,栈中的-1 与 9 被替换为它们的积-9。当到达输入行的末尾时, 弹出栈顶的值并将其打印出来。 程序的结构是一个循环,对每次循环出现的运算符或操作数执行相应的操作: while (下一个操作符或操作数不是文件结束标识符) if (数) 将它压栈 else if (运算符) 弹出操作数 进行运算 将结果压栈 else if (换行符) 弹出并打印栈顶的值 else 错误 栈的压入与弹出操作的代码很少,但加上错误检测与恢复操作之后,程序就会变得比较长, 因此最好将它们设计为独立的函数,以免整个程序通篇包含重复的代码。另外还应该有一个单 独的函数用来获取下一个输入的运算符或操作数。 到目前还没有讨论的一个主要设计决策是,栈放在哪里?即哪些函数可以直接访问它?一 种可能是把它放在主函数 main 中,并将栈及其当前位置传递给执行压入或弹出操作的函数。 但是,main 不需要知道控制栈的变量,它只进行压入和弹出操作。因此,我们决定将栈及其相 关信息放在外部变量中供 push 与 pop 函数访问,而不供 main 来访问。 将上述概要方案译成代码很容易。设想这些程序全都放在一个源文件中,那么程序看上去 会像这样: 一些#include 一些#define 用于 main 的函数声明 main() {…} 用于函数 push 和 pop 的外部变量 void push(double f) {…} double pop(void) {…} int getop(char s[]) {…} 供函数 getop 调用的函数 后面我们将会讨论如何将这些代码分为两个或多个源文件的情况。 主函数 main 是一个循环,该循环中包含了一个大的 switch 语句,处理不同种类的运算 符与操作数;这里对 switch 的使用要比 3.4 节所示的例子更为典型。 #include #include /* 为了使用 atof() 函数 */ #define MAXOP 100 /* 操作数或运算符的最大长度 */ #define NUMBER '0' /* 标示找到一个数 */ int getop(char []); void push(double); double pop(void); /* 逆波兰计算器 */ main() { int type; double op2; char s[MAXOP]; while ((type = getop(s)) != EOF) { switch (type) { case NUMBER: push(atof(s)); break; case '+': push(pop() + pop()); break; case '*': push(pop() * pop()); break; case '-': op2 = pop(); push(pop() – op2); break; case '/': op2 = pop(); if (op2 != 0.0) push(pop() / op2); else printf("error: 除零溢出\n"); break; case '\n': printf("\t%.8g\n", pop()); break; default: printf("error: 未知命令 %s\n", s); break; } } return 0; } 由于 + 与 * 运算符满足交换律,因此弹出的操作数的运算次序无关紧要,但对于 - 与 / 则 必须区分其左右操作数。下列语句 push(pop() – pop()); /* 错了!*/ 中对两次 pop 函数调用(的返回值)的运算次序没有区分清楚。为了确保正确的次序,有必要 像 main 中所做的那样将第一个值弹出到某个临时变量中。 #define MAXVAL 100 /* 栈 val 的最大深度 */ int sp = 0; /* 栈的下一个空闲位置*/ double val[MAXVAL]; /* 存放值的栈 */ /* push:把 f 压入栈中 */ void push(double f) { if (sp < MAXVAL) val[sp++] = f; else printf ("error: 栈满,不能将%g 压栈\n", f); } /* pop:弹出并返回栈顶的值 */ void pop(void) { if (sp > 0) return val[--sp]; else { printf ("error: 栈空\n"); return 0.0; } } 一个变量如果在所有函数之外定义,那么它就是外部变量。因此我们把必须被函数 push 和 pop 共享的栈和栈顶索引定义在这些函数的外部。但 main 本身并没有引用该栈或栈顶位置, 因此可对 main 隐藏掉这些内容。 现在我们来看函数 getop 的实现。该函数用于获取下一个运算符或操作数。这个任务比较 容易:跳过空格与制表符;如果下一个字符不是数字或小数点,则返回;否则,将这些数字字 符串收集起来(其中可能包含小数点),并返回 NUMBER——标示已经收集好了一个数。 #include int getch(void); void ungetch(int); /* getop:取下一个运算符或数值操作数 */ int getop(char s[]) { int i, c; while ((s[0] = c = getch()) == ' ' || c == '\t') ; s[1] = '\0'; if (!isdigit(c) && c != '.') return c; /* 不是数字 */ i = 0; if (isdigit(c)) /* 收集整数部分 */ while (isdigit(s[++i] = c = getch())) ; if (c == '.') /* 收集小数部分 */ while (isdigit(s[++i] = c = getch())) ; s[i] = '\0'; if (c != EOF) ungetch(c); return NUMBER; } 这里函数 getch 与 ungetch 的用途是什么?常有这样的情况,一个程序在读入过多输入 之前无法判定是否已读了足够的内容。一个例子是在收集用于组成数的字符时,直到看见第一 个非数字字符后,才能确定数被完整地读入了。但这时程序已经向前多读了一个字符,而该字 符不是它所要的内容。 假如可能将这个不需要的字符“反读”回去,那么这个问题就能得到解决。那样,每次程序 多读了一个字符时,就能将它压回到输入中,对其余代码而言就像这个字符并没有被读过一样。 幸运的是,模拟反取一个字符比较容易,可以编写一对相互配合的函数来完成:函数 getch 根 据情况传递下一个输入字符;而函数 ungetch 记下那些要放回输入中的字符,这样在对 getch 的后续调用中它会首先返回那些记下的字符,然后再读入新的输入。 两者的协作机制也很简单。函数 ungetch 将那些要压回的字符放入一个共享缓冲区(一个 字符数组),函数 getch 在该缓冲区不为空时就从中读取字符,而在缓冲区为空时调用函数 getchar 从输入中读字符。同时这里还必须有一个下标变量来记录缓冲区中当前字符的位置。 由于缓冲区与下标被函数 getch 与 ungetch 所共享,并且其值必须在这些函数调用之间 保持住,因此它们必然是这两个函数的外部变量。这样,函数 getch 与 ungetch 及其共享变 量可以编写为: #define BUFSIZE 100 char buf[BUFSIZE]; /* 供 unget 函数使用的缓冲区 */ int bufp = 0; /* buf 中的下一个空闲位置 */ int getch(void) /* 取一个字符(可能是压回的字符)*/ { return (bufp > 0) ? buf[--bufp] : getchar(); } void ungetch(int c) /* 将字符压回到输入中 */ { if (bufp >= BUFSIZE) printf("ungetch: 压回字符过多\n"); else buf[bufp++] = c; } 标准库中包含了函数 ungetc,其提供压回单个字符的功能,我们在第 7 章中将会讨论它。这 里我们用了一个数组而没有用单个字符,目的是为了说明一种更为通用的方法。 练习 4-3. 有了基本框架后,对计算器程序的扩展就简单了。在该程序中加入取模(%)运算 符 并增加对负数的支持。 练习 4-4. 增加下列栈操作命令:在不弹出的情况下打印栈顶元素、复制栈顶元素、交换栈顶 的两个元素。增加一个用于清空栈的命令。 练习 4-5. 增加对 sin、exp 与 pow 这类库函数的访问。参见附录 B 第 4 节中的。 练习 4-6. 增加处理变量的命令(提供 26 个具有单个字母名称的变量很容易)。增加一个变量 存放最近一次打印的值。 练习 4-7. 编写一个函数 ungets(s),其用于将整个字符串压回到输入中。ungets 函数需要 知道 buf 与 bufp 吗?它能否只使用 ungetch 函数? 练习 4-8. 假定最多只需要压回一个字符。请相应地修改 getch 与 ungetch 函数。 练习 4-9. 上面所介绍的 getch 与 ungetch 函数不能正确地处理压回的 EOF。请你决定在压 回 EOF 时这两个函数应具有什么性质,然后实现你的设计。 练习 4-10. 另一种程序构建方法是使用 getline 函数读入整个输入行;这样就没必要使用 getch 与 ungets 函数了。请运用这一方法更新计算器程序。 4.4 作用域规则 构成 C 程序的函数和外部变量不需要全部一起编译;程序的源代码可以按多个文件存放(和 编译),还可以从库中加载事先编译好的例程。这里边所要关心的问题是: 声明如何编写才能使变量在编译过程中被恰当地声明? 声明如何安排才能让程序的各个部分能够在程序加载时正确地关联起来? 声明如何组织才能只存在一份副本? 外部变量如何进行初始化? 我们通过把计算器程序重新组织成多个文件来讨论上述话题。从实际角度看,该计算器程序太 小了,不值得对其进行划分,但是它能很好地说明在较大程序中出现的对应问题。 一个名字的作用域(scope)是指程序中可以使用该名字的部分。对于在函数开头声明的 自动变量,其作用域是声明该变量名字的函数。在不同函数中声明的同一名字的局部变量彼此 互不相关。对于函数的参数同样如此,参数在效果上可看作是局部变量。 外部变量或函数的作用域是从它们在文件中被声明的位置开始到该被编译文件的末尾为 止。举例来说,如果 main、sp、val、push 以及 pop 定义在一个文件中,并按照前述程序中 所示的顺序放置,即 main() { … } int sp = 0; double val[MAXVAL]; void push(double f) { … } double pop(void) { … } 那么,对于变量 sp 和 val,在 push 和 pop 中不需要对它们进行声明就可以简单地通过名字 来使用它们。但这两个名字在 main 中不可见,push 和 pop 本身在 main 中也不可见。 另一方面,如果一个外部变量在定义之前就需要被使用,或者该外部变量定义在与使用它 的源文件不同的源文件中,那么相应的声明要强制性地使用关键词 extern [那么需要强制性 地使用“extern”声明]。 弄清楚外部变量的声明和定义二者间的区别很重要。声明只是标明了变量的属性(主要是 它的类型);而定义还会为其留出存储空间。如果代码行 int sp; double val[MAXVAL]; 出现在所有函数之外,那么它们定义了外部变量 sp 和 val 并为其留出存储空间,同时还作为 该源文件余下部分的变量声明。另一方面,代码行 extern int sp; extern double val[]; 为该源文件余下部分声明了 sp 是一个 int 型的数,val 是一个 double 型的数组(其大小在 其他地方决定)。但它们没有创建这些变量或者为变量保留存储空间。 在源程序的所有组成文件之中,一个外部变量只能有一个定义;其他文件可以通过包含该 变量的 extern 声明来访问它。(包含变量定义的文件中仍可以包含该变量的 extern 声明。) 数组的大小必须在其定义中给出,但在 extern 声明中是可选的。 外部变量的初始化只会在变量定义时进行。 假定函数 push 和 pop 在某一文件中定义,而变量 val 和 sp 在另一文件中定义和初始化 (尽管对于此程序这样的组织形式不太可能),那么为了将它们联系在一起,如下的定义和声明 是有必要的: 文件 1 中: extern int sp; extern double val[]; void push(double f) { … } double pop(void) { … } 文件 2 中: int sp = 0; double val[MAXVAL]; 由于文件 1 中的 entern 声明位于函数定义之外且早于它们,因此可被用于所有这些函数; 对于文件 1,这样一组声明就足够了。如果在一个文件中 sp 和 val 的定义位于其使用之后, 那么该文件也需要以同样的方式进行组织。 4.5 头文件 现在考虑将计算器程序分为若干个源文件,就比如每一组成部分都比现在大得多时可能会 划分成的那样。函数 main 会划分为一个文件,我们将其称为 main.c;push、pop 以及它们 使用的变量划分为第二个文件——stack.c;getop 划分为第三个——getop.c。最后,getch 与 ungetch 划分为第四个文件,getch.c;之所以将后两个函数与其他程序分开,原因是在 实际程序中它们可能来自于一个独立编译的库。 还有一件事情需要考虑——在这些文件间共享的定义和声明。我们希望尽可能将它们集中 起来,这样在程序演变时只需要维护一个副本。相应地,我们将这些公共内容放到一个头文件 calc.h 中,在必要时可以包含它。(#include 语句在 4.11 节中描述。)照此划分后的程序 看起来像如下这样: 在每个文件只可访问它所需要的信息这一期望与维护更多头文件的难度更大这一现实之间 calc.h #define NUMBER '0' void push(double); double pop(void); int getop(char []); int getch(void); void ungetch(int); main.c #include #include #include "calc.h" #define MAXOP 100 main() { … } getop.c #include #include #include "calc.h" getop() { … } stack.c #include #include "calc.h" #define MAXVAL 100 int sp = 0; double val[MAXVAL]; void push(double) { … } double pop(void) { … } getch.c #include #define BUFSIZE 100 char buf[BUFSIZE]; int bufp = 0; int getch(void) { … } void ungetch(int) { … } 需要折衷考虑。对于那些中等规模的程序,用一个头文件包含所有那些需要被程序的不同部分 共享的内容可能是最好的办法;那正是我们在这里所作的决策。而对于那些大得多的程序,则 可能会需要更多的组织方式和更多的头文件。 4.6 静态变量 stack.c 中的变量 sp 和 val 以及 getch.c 中的变量 buf 和 bufp 仅供它们各自所在的 源文件中的函数使用,而不希望被任何其他的程序访问。static 声明若应用于外部变量或者 函数,则会将所声明对象的作用域限制在该被编译源文件余下的部分之内。这样外部 static 就提供了一种隐藏名字的方法,例如 getch-ungetch 组合中的 buf 和 bufp,为了能被共享, 它们必须是外部的,但 getch 与 ungetch 的使用者则不应该见到它们。 静态存储通过在普通声明之前加上关键字 static 来进行说明。如果上述函数和变量在一 个文件中编译,即如: static char buf[BUFSIZE]; /* 供 ungetch 使用的缓冲区 */ static int bufp = 0; /* buf 的下一个空闲位置 */ int getch(void) { … } void ungetch(int c) { … } 那么其他例程都将无法访问 buf 和 bufp,并且这两个名字不会与同一程序的其他文件中的相 同名字发生冲突。同样地,通过静态声明的方式,可以将 push 和 pop 用于栈操作的变量 sp 和 val 隐藏起来。 外部静态声明多用于变量,但也可用于函数。通常情况下,函数名是全局的,对于整个程 序的任何部分都可见。但是如果一个函数被声明为静态的,那么它在包含该声明的文件之外是 不可见的。 静态声明还可用于内部变量。内部静态变量就像自动变量一样,是某个特定函数的局部变 量,但与自动变量不同,它们是一直存在着的,而不是随着每次函数调用而产生和消亡。这意 味着内部静态变量提供了仅在单个函数内使用的、持续的存储空间。 练习 4-11. 修改 getop 函数,使其不再需要使用 ungetch 函数。提示:使用一个内部静态 变量。 4.7 寄存器变量 register 声明用于提示编译器其声明的变量将被极频繁地使用。其思想是应将 register 变量放到机器的寄存器中,这样可能得到更小、执行速度更快的程序。但是编译器 可以无条件地忽略掉这一提示。 register 声明的形式如下: register int x; register char c; register 声明只能应用在自动变量和函数的形式参数上。后者的形式如下: f(register unsigned m, register long n) { register int i; … } 受底层硬件实际情况影响,寄存器变量在实际应用中存在限制。每个函数只有少许变量可 以被存放在寄存器中,且只允许一些特定类型的变量。然而,过多的寄存器声明并没有害处, 这是由于关键字 register 在声明过多或者是声明不允许类型的情况下会被忽略掉。另外,无 论一个寄存器变量是否真正存放在寄存器中,取这个变量的地址都是不可能的(取址是第 5 章 所涵盖的话题)。对寄存器变量的数目和类型的限制因具体机器而不同。 4.8 程序块结构 C 语言不是 Pascal 等语言意义上的块结构语言,它不允许在函数中定义函数,但是,其 允许在函数中按块结构的形式定义变量。变量的声明(包括初始化)不是只能跟在函数开头的 左花括号之后,而是可以跟在任意引入复合语句的左花括号的后面。以这种方式声明的变量可 以屏蔽掉位于该程序块之外的同名变量,且在与左花括号匹配的右花括号出现之前一直存在。 例如,在程序 if (n > 0) { int i; /* 声明一个新的变量 i */ for (i = 0; i < n; i++) … } 之中,变量 i 的作用域是 if 语句 “为真”的分支;这个 i 与该程序块之外的任何 i 都不相关。 在程序块中声明与初始化的自动变量在每次进入该程序块时都会进行初始化。静态变量只在首 次进入程序块时进行初始化。 自动变量(包括形式参数)也会屏蔽掉与之同名的外部变量和函数。对于如下声明: int x; int y; f(double x) { double y; … } 在函数 f 之内出现的 x 引用的是函数参数,是一个 double 型变量;而在函数 f 之外,x 引用 的是 int 型的外部变量。变量 y 的情况同样如此。 就编程风格而言,最好避免使用那些会屏蔽外层作用域中名字的变量名;否则极易引起混 淆和错误。 4.9 初始化 初始化的概念之前已多次提及,但一直是其他话题里的附带内容。本节在已讨论过各种存 储类型的基础上总结一些初始化的规则。 在没有显式初始化的情况下,外部变量与静态变量默认被初始化为 0;而自动变量与寄存 器变量则具有未定义的初值(即无用数据)。 标量变量可在定义时被初始化,其通过在变量名后带一个等号和一个表达式来完成: int x = 1; char squote = ‘\’’; long day = 1000L * 60L * 24L; /* 一天的毫秒数 */ 对于外部变量和静态变量,其初始化值必须是一个常量表达式;初始化只进行一次(概念上是 在程序开始执行前完成)。对于自动变量和寄存器变量,每次进入其所在函数或程序块时都会对 其进行初始化。 对于自动变量和寄存器变量,其初始化值并不限于常量:它可以是包含任意已定义值(甚 至函数调用)的表达式。例如,3.3 节中的二分查找程序可以被编写为: int binsearch(int x, int v[], int n) { int low = 0; int high = n – 1; int mid; … } 代替之前的 int low, high, mid; low = 0; high = n – 1; 在效果上,自动变量的初始化相当于赋值语句的简写形式。倾向于哪种形式很大程度上是个人 喜好问题。我们通常采用显式赋值的形式,因为声明中的初始化值更难察看,且离变量使用的 位置更远。 数组的初始化可以通过在其声明后带上用花括号括上的一列用逗号分隔开的初始化值来完 成。例如,以每月的天数来初始化数组 days: int days [] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; 当数组的大小缺省时,编译器会统计初始化值的个数来得出数组的长度(在本例中为 12)。 如果初始化值的个数少于数组既定的大小,外部、静态及自动数组变量所缺少的元素将被 设置为零;个数多于既定大小则是错误的。不能指定单个初始化值对多个数组元素的重复初始 化,也无法在不提供前面所有元素初始化值的情况下直接对数组中间的元素进行初始化。 字符数组的初始化是一个特例;其可以用一个字符串替代上述使用花括号和逗号的记法: char pattern[] = “ould”; 是下面这种更长但等价的语句的简写形式: char pattern[] = { ‘o’, ‘u’, ‘l’, ‘d’, ‘\0’ }; 此例中,数组的大小为 5(4 个字符加上 1 个结束符‘\0’)。 4.10 递归 C 函数可以递归调用,即一个函数可以直接或间接地调用它自己。考虑将一个数打印为字 符串的情况,正如之前提到的,数字生成次序的不对:先得到的是低位数字,然后才是高位数 字;而它们必须以相反的次序打印出来。 解决这个问题有两种方法。一种是将所生成的数字存放在一个数组中,然后按照相反的次 序打印它们,这是 3.6 节中函数 itoa 所采用的方法。另一种是采用递归,此方法中函数 printd 首先调用其自身来处理所有打头的数字,然后再打印结尾的数字。同样,这个版本在处理最大 负数时也会出错。 #include /* printd:将 n 按十进制数进行打印 */ void printd(int n) { if (n < 0) { putchar(‘-’); n = -n; } if (n / 10) printd(n / 10); putchar(n % 10 + ‘0’); } 当函数递归调用自身时,每次调用都会得到一个新的全部自动变量的集合,该集合与之前 调用获得的变量集合彼此独立。这样,在 printd(123) 中,第一个 printd 接到的参数 n 为 123,它将 12 传给第二个 printd,后者再将 1 传给第三个 printd。第三层的 printd 打 印 1,然后返回第二层;该层的 printd 打印 2,然后返回第一层;第一层的 printd 打印 3 并终止执行。 递归的另一个较好的例子是快速排序(quicksort)。该算法是 C. A. R. Hoare 于 1962 年发明的。对于一个给定的数组,选取一个元素,并以该元素为界将其他元素分为两个子集, 一个子集的所有元素都小于选定元素,另一个子集的所有元素都大于或等于该元素,然后对两 个子集递归执行同一过程。当某个子集的元素少于 2 个时,该子集就不需要再进行排序,于是 递归就终止了。 我们的快速排序版本不是最快的,但它是其中最简单的版本之一。我们选取每个(子)数 组的中间元素进行划分。 /* qsort:将 v[left] … v[right] 按递增次序排序 */ void qsort(int v[], int left, int right) { int i, last; void swap(int v[], int i, int j); if (left >= right) /* 假如数组元素少于 2 个 */ return; /* 则不做任何操作 */ swap(v, left, (left + right)/2); /* 将划分元素移动 */ last = left; /* 至 v[0] */ for (i = left+1; i <= right; i++) /* 对其余元素进行划分 */ if (v[i] < v[left]) swap(v, ++last, i); swap(v, left, last); /* 将划分元素移回适当位置 */ qsort(v, left, last-1); qsort(v, last+1, right); } 由于交换操作在 qsort 中出现了 3 次,因此我们将其作为一个独立的函数。 /* swap:将 v[i]与 v[j]互换 */ void swap(int v[], int i, int j) { int temp; temp = v[i]; v[i] = v[j]; v[j] = temp; } 标准库中包含了 qsort 的一个版本,它可以对任意类型的对象排序。 递归程序不能节省存储空间,因为它必须在某处维护一个存放待处理值的栈。它也不会执 行得更快。但是相对于与之等价的非递归程序,递归代码更加紧凑,编写它和理解它常常要容 易得多。递归对于处理像树之类递归定义的数据结构尤其方便;在 6.5 节我们会见到一个非常 好的例子。 练习 4-12. 运用 printd 的思想编写一个递归版本的 itoa 函数;即通过递归调用将一个整数 转换为字符串形式。 练习 4-13. 编写一个递归版本的 reverse(s)函数,该函数将字符串 s 本身颠倒过来。 4.11 C 预处理器 C 语言的预处理器提供了一定的语言功能,预处理从概念上讲是编译过程中单独执行的第 一个步骤。其中两个最常用的特性是#include(用于在编译过程中包含某个文件的内容)和 #define(以任意设定的字符序列替代一个标记)。在本节中描述的其他特性还包括条件编译及 带参数的宏。 4.11.1 文件包含 文件包含功能使得处理大量#define 语句以及声明更加方便。任何形如: #include “文件名” 或 #include <文件名> 的源代码行都会被替换为由文件名指定的文件的内容。如果是文件名由引号引上,那么通常从 包含该文件的源程序所存放的位置开始查找该文件;如果没有查找到,或者文件名由尖括号括 上,则按照 C 语言的具体实现所定义的规则查找该文件。被包含的文件本身也可以包含 #include 代码行。 源文件的开始处常常有多个#include 行,用来包含常用的#define 语句和 extern 声明, 或者用于访问如等头文件内库函数的函数原型声明。(严格地说,这些要包含的内 容并不一定以文件的形式存在,头文件的访问方式依赖于具体实现。) #include 是大型程序中将声明紧密关联在一起的常用手段。它保证提供给所有源文件的 是一致的定义和变量声明,这样可以消除掉那些与一致性有关的棘手错误。很自然地,当某个 包含文件发生变化时,所有依赖于它的文件都必须重新编译。 4.11.2 宏替换 形如 #define 名字 替换文本 的宏定义给出了最简单的一种宏替换——其后出现的标记名字都将被替代为替换文本。 #define 中的名字具有与变量名相同的形式;替换文本则是任意的。通常替换文本就是宏代码 行的后面部分,但是一个长的宏定义可能连续存放为多个行(通过每行的末尾放置一个 \ 来表 示续行)。#define 定义的名字的作用域是从其定义位置开始到该被编译文件的末尾为止。宏 定义中可以使用之前的宏定义。宏只对标记进行替换,而在带引号的字符串中不会发生替换。 例如,如果 YES 是一个宏定义名字,对于 printf(“YES”)或者 YESMAN 都不会进行宏替换。 任何宏定义的名字都可以被配上任意的替代文本。例如, #define forever for (;;) /* 无限循环 */ 定义了一个新“关键字”forever,表示一个无限循环。 还可以定义带参数的宏,这样宏的不同调用能具有不同的替换文本。例如,定义一个称为 max 的宏: #define max(A, B) ((A) > (B) ? (A) : (B)) 尽管它看上去像一个函数调用,但 max 在使用时会扩展为代码行。每次出现的形式参数(在这 里是 A 或 B)都会被替换为相应的实在参数。因此代码行 x = max(p+q, r+s); 将被替换为 x = ((p+q) > (r+s) ? (p+q) : (r+s)); 由于对所有实在参数的处理都是一致的,因此这个宏能用于任何数据类型;对于不同的数据类 型,max 宏只需要一种,但 max 函数则需要有多种。 如果仔细考虑上述对 max 的扩展,你会发现一些陷阱。其中表达式的求值进行了两次;这 对于包含递增操作符或输入及输出等有附带效应的表达式是一件坏事。例如, max(i++, j++) /* 错了!*/ 会将较大的值递增两次。还要适当小心地用括号来确保求值的既定顺序;请考虑下面的宏 #define square(x) x * x /* 错了! */ 在调用 suqare(z+1) 时会出现什么样的情况。 但宏是有价值的,一个实用的例子来自于,该头文件中的 getchar 和 putchar 常被定义为宏,目的是避免因每处理一个字符都要进行一次函数调用而带来的运行时开销。 中的函数常常也是通过宏实现的。 可以使用 #undef 取消名字的宏定义,这通常用于保证某个例程是一个真正的函数而不是 宏: #undef getchar int getchar(void) { … } 带引号的字符串中的形式参数不会被替代。然而,如果替换文本中的某个参数名之前带有 一个 #,此组合在该形参被替换为实参时将被扩展为带引号的字符串。这一方法可以与字符串 连接配合使用,例如构建一个打印调试信息的宏: #define dprint(expr) printf(#expr “ = %g\n”, expr) 当该宏被调用时,譬如 dprint(x/y); 它被扩展为 printf(“x/y” “ = %g\n”, x/y); 而这两个字符串是相连接的,因此效果等同于 printf(“x/y = %g\n”, x/y); 实在参数中的 ” 将被替换为 \”, \ 将被替换为 \\,因此扩展后得到的是合法的字符串常量。 预处理器的操作符 ## 提供了一种将实在参数拼接起来的方法。如果一个形参在替换文本 中与 ## 相邻,那么该形参会被替换为对应的实参,而该 ## 及其周边的空白符都会被移除, 所得结果将被重新扫描。例如,下面的宏将它的两个参数拼接起来: #define paste(front, back) front ## back 这样 paste(name, 1) 就创建出标记 name1。 嵌套使用 ## 的规则比较晦涩;进一步的细节请参见附录 A。 练习 4-14. 定义一个宏 swap(t, x, y),它将类型为 t 的两个参数 x、y 彼此互换。(使用 程序块结构会有所帮助。) 4.11.3 条件包含 预处理本身可以通过(在预处理过程中求值的)条件语句来进行控制。这提供了一种可根 据编译过程中的条件取值来选择性地包含代码的方法。 #if 语句对一个整型常量表达式求值(表达式中不得包含 sizeof、强制类型转换以及枚 举常量),如果表达式的值不为零,那么从该行后一直到 #endif 、#elif 或者 #else 之间 的行都会被包含到代码中。(预处理语句#elif 的作用类似于 else if。)在#if 语句中,可以 使用表达式 defined(名字),如果名字已被定义,则该表达式取值为 1,否则为 0。 例如,为了确保文件 hdr.h 的内容只会被包含一次,可以像下面这样将文件内容用条件语 句包裹起来: #if !defined(HDR) #define HDR /* hdr.h 的内容放在这里 */ #endif hdr.h 文件在第一次被包含时会定义名字 HDR;而之后的包含会因发现该名字已定义而直接跳 转到#endif 处。类似做法能用于避免对文件的多次包含。如果一致地使用这种做法,那么每 个头文件自身都可以包含它所依赖的任何头文件,而头文件的使用者不必处理其间的相互依赖 关系。 下面的预处理序列通过检测名字 SYSTEM 来决定包含哪个版本的头文件: #if SYSTEM == SYSV #define HDR “sysv.h” #elif SYSTEM == BSD #define HDR “bsd.h” #elif SYSTEM == MSDOS #define HDR “msdos.h” #else #define HDR “default.h” #endif #include HDR #ifdef 与#ifndef 语句专用于测试某个名字是否已被定义。前面有关#if 的第一个例子 可以被编写为: #ifndef HDR #define HDR /* hdr.h 的内容放在这里 */ #endif 第 5 章 指针与数组 指针是存放某个变量的地址的变量。C 语言中大量采用了指针,一部分原因是在某些情况 下指针是表示计算的唯一方法,另一部分原因是使用指针编写代码通常比用其他方法更加简洁 高效。指针与数组存在着紧密的联系;本章还将探究它们的这种联系并说明如何利用这一特性。 与 goto 语句一样,指针也被认为是构建无法理解的程序的一种诡异途径。这的确是非常 可能的,如果它们没有被小心地使用,极易出现未指向所期望地址的指针。然而,在一定规则 下使用指针却能够让代码变得简单明了,这也是我们试图展现的方面。 ANSI C 的一个主要变化是明确了指针的操控规则,将优秀程序员已经运用的以及优秀编 译器已经增强的特性正式确立下来。此外,void * 类型(空指针)替代 char * 成为合法的 通用指针类型。 5.1 指针和地址 我们以一个描述内存组织的简化图例开始讨论。典型的机器拥有一列连续编号或编址的内 存单元,这些单元可单独操控,也可按连续的单元组操控。普遍的情况是单个字节是 char,双 字节单元可当作一个 short 型整数,而连续四个字节则构成一个 long 型整数。指针是能够存 放一个地址的存储单元组(通常为双字节或 4 字节)。这样,假如 c 是一个 char,p 是一个指 向 c 的指针,则这种情况可表示如下: ... ... ... p: c: 一元运算符 & 用于取一个对象的地址,因此语句 p = &c; 将 c 的地址赋给变量 p,并 称 p“指向”c。& 运算符只能应用于内存中的对象——变量以及数组 元素。它不能被用于表达式、常量或者寄存器变量。 一元运算符 * 是间接(indirection)或解引用(dereferencing)运算符;当其应 用于指针时即访问此指针所指向的对象。假设 x 与 y 是整数,ip 是指向整数的指针。下面的代 码说明了如何声明一个指针以及 & 和 * 的用法: int x = 1, y = 2, z[10]; int *ip; /* ip 是指向 int 型的指针 */ ip = &x; /* ip 指向了 x */ y = *ip; /* y 的值变为 1 */ *ip = 0; /* x 的值变为 0 */ ip = &z[0]; /* ip 指向了 z[0] */ x、y 与 z 的声明我们从一开始就已经见过了。指针 ip 的声明方式 int *ip; 的目的是便于记忆;它表明表达式 *ip 是一个 int 型数。这种声明变量的语法模仿了含有此 变量的表达式的语法。同样的理由也用于函数的声明之中。例如: double *dp, atof(char *); 表明表达式 *dp 与 atof(s)的值都为 double 类型,且 atof 的参数是指向 char 的指针。 读者可能会发现一个隐含的规则,即指针必须指向一个特定类型的对象:每个指针都指向 某个具体的数据类型。(存在一个例外:一个“指向 void 的指针”用于保存任何类型的指针,但 其无法解引用自身。我们将在 5.11 节介绍这个概念。) 假如 ip 指向整数 x,那么在任何出现 x 的上下文中,x 的位置都能够用*ip 替换,因此 *ip = *ip + 10; 将 *ip 的值增加了 10。 一元运算符 * 和 & 的优先级较算术运算符高,因此赋值语句 y = *ip + 1 首先取出 ip 指向的对象,增加 1,并将结果赋给 y;而 *ip += 1 将 ip 指向的对象增 1,其等同于 ++*ip 及 (*ip)++ 最后这个例子中括号是必须的;否则表达式会将 ip 本身(而不是 ip 所指向的对象)增 1,这 是由于 * 和 ++ 等一元运算符是由右至左接合的缘故。 最后,由于指针也是一种变量,它们也可不经过解引用而直接使用。例如 iq 是另一个指向 int 的指针, iq = ip 将 ip 的内容复制到 iq 中,从而使 iq 指向 ip 所指的对象。 5.2 指针和函数参数 由于 C 中的函数采用按值传递参数,因此没有直接的途径可以让被调函数改变调用者函数 的变量。例如,排序程序可能使用一个 swap 函数互换两个乱序的元素。使用语句 swap(a, b); 无法达到目的。这里 swap 函数的定义是 void swap(int x, int y) /* 错了!*/ { int temp; temp = x; x = y; y = temp; } 由于是按值调用,swap 不能改变调用它的程序中的参数 a 与 b。上面的函数仅仅交换了 a 和 b 的副本。 获得所期望效果的方式是让调用者传递需修改变量的指针: swap(&a, &b); 运算符 & 得到变量的地址,因此 &a 即为 a 的指针。这里 swap 函数的参数被声明为指针, 并通过它们间接访问真正需要操作的变量。 void swap(int *px, int *py) /* 互换 *px 与 *py */ { int temp; temp = *px; *px = *py; *py = temp; } 用图表示即: 调用者程序内: a: b: swap函数内: px: py: 指针参数使得一个函数能够访问及修改调用它的函数中的对象。例如,考虑函数 getint, 它转换输入的任意格式的字符流,将其分解为整数值,每次调用得到一个整数。getint 需要 返回它找到的数值,并当所有输入处理完毕时提示文件结束(EOF)。这两种值只能通过不同的 路径传输,因为无论使用什么值代表 EOF,都可能与输入的整数值相同。 一个解决方案是让 getint 用函数返回值传递文件结束状态,并用一个指针参数存放转换 的整数返回给调用者。此方案也被用于 scanf;参见 7.4 节。 下面的循环把调用 getint 所得到的整数填到一个数组中: int n, array[SIZE], getint(int *); for (n = 0; n < size && getint(&array[n]) != EOF; n++) ; 每次调用将 array[n]设置为本次在输入中找到的整数,然后将 n 递增。注意其中的关键是将 array[n]的地址传送给 getint。否则无法让 getint 将转换后的整数传回给调用者。 本书的 getint 版本返回 EOF 表示文件结束,返回零表示本次输入不是数字,返回正值表 示输入包含了一个有效的数字。 #include int getch(void); void ungetch(int); /* getint:取得输入中整数并放到 *pn 中 */ int getint(int *pn) { int c, sign; while (isspace(c = getch())) /* 略过空白符 */ ; if (isdigit(c) && c != EOF && c != ‘*’ && c != ‘-’) { ungetch(c); /* 它不是数字 */ return 0; } sign = (c == ‘-’) ? -1 : 1; if (c == ‘+’ || c == ‘-’) c = getch(); for (*pn = 0; isdigit(c); c = getch()) *pn = 10 * *pn + (c – ‘0’); *pn *= sign; if (c != EOF) ungetch(c); return c; } 在整个 getint 中,*pn 都作为一个普通的 int 变量使用。我们还使用了 getch 和 ungetch (在 4.3 节中描述),这样一个必须额外读出的字符能被压回输入中。 练习 5-1. 本节所给代码中 getint 将其后未带数字的 + 或 – 按有效的表达式 0 处理。将其 修正,并将这一字符压回输入中。 练习 5-2. 编写 getint 的浮点数变体 getfloat。getfloat 的函数返回值应为什么类型? 5.3 指针和数组 C 语言中指针和数组之间有很强的联系,强到应当将它们一起讨论。任何用数组下标来完 成的操作都同样可以用指针来完成。通常使用指针速度会更快,但在某种程度上也更难于理解 ——至少对于新手来说是这样。 声明 int a[10]; 定义了一个大小为 10 的数组 a,也就是包含了 10 个连续对象的“整块儿”,对象分别称为 a[0]、 a[1]、…、a[9]。 a: a[0] a[1] a[9] 符号 a[i]代表了数组中的第 i 个元素。如果 pa 是一个整数指针,声明为 int *pa; 那么赋值语句 pa = &a[0]; 将 pa 设为指向 a 的第 0 个元素;即 pa 包含了 a[0]的地址。 a: a[0] pa: 那现在赋值语句 x = *pa; 会将 a[0]的内容复制到 x。 如果 pa 指向了一个数组中的某一具体元素,那么根据定义,“pa + 1”将指向下一个元素, “pa + i”将指向 pa 之后的第 i 个元素,而“pa - i”指向 pa 之前的第 i 个元素。因此,如 果 pa 指向 a[0],那么 *(pa + 1) 代表了 a[1]的内容,pa + i 是 a[i]的地址,*(pa + i)是 a[i]的内容。 a: a[0] pa: pa + 1: pa + 2: 无论数组 a 中的变量的类型和大小如何,上述规则都是成立的。“将指针加 1”以及指针的 所有其他算术运算的意义就是:pa + 1 指向 pa 的下一个对象,pa + i 指向 pa 的向后的第 i 个对象。 数组下标与指针运算之间的关联相当紧密。根据定义,数组类型的变量或表达式的值是此 数组的第 0 个元素的地址。因此,如下赋值 pa = &a[0]; 之后 pa 与 a 拥有同样的值。由于数组名称等同于其起始元素的位置,赋值 pa=&a[0]也可被 写为 pa = a; a[i]也可以被写成*(a+i),这至少在初次见到时会令人十分惊讶。C 语言对 a[i] 求值 时会立即将它转换为*(a+1);这两种形式是等价的。将运算符 & 应用到等式两边,则得到&a[i] 与 a+i 也是等价的:a+i 是 a 之后第 i 个元素的地址。反过来,如果 pa 是一个指针,在表达 式中它也可以与下标一起使用;pa[i]等同于*(pa+i)。简言之,“数组+下标”表达式与“指针 +偏移”表达式是等价的。 数组名与指针间有一个区别必须牢记:指针是一个变量,所以 pa=a 和 pa++都是合法的; 但数组名却不是变量,因而 a=pa 和 a++等写法都是不合法的。 当一个数组名被传递给函数时,实际传递的是起始元素的位置,在该函数中,对应参数是 一个局部变量,因此一个数组名参数是一个指针,即一个存放地址的变量。我们能够利用这一 事实编写另一个版本的 strlen,其用于计算一个字符串的长度。 /* strlen:返回字符串 s 的长度 */ int strlen(char *s) { int n; for (n = 0; *s != ‘\0’; s++) n++; return n; } 由于 s 是一个指针,对其递增是完全合法的;s++不会对 strlen 函数的调用者中的字符串产 生影响,只会增加 strlen 自己的指针拷贝。这就意味着像 strlen(“hello, world”); /* 字符串常量 */ strlen(array); /* 字符数组 char array[100] */ strlen(ptr); /* 字符指针 char *ptr */ 等调用都能工作。 作为函数定义中的形参, char s[]; 与 char *s; 是等同的;我们更倾向于使用后者,因为其更明显地标明此参数是一个指针。当数组名被传递 给函数时,函数可根据便利程度来认为传递的是数组还是指针,并照此使用。甚至可以两种形 式都使用,假如这样看起来清晰恰当。 通过传递数组内元素的起始位置,可以仅传递部分数组给函数。例如,a 是一个数组, f(&a[2]) 及 f(a+2) 都向函数 f 传递以 a[2]作为起始地址的部分数组。而 f 的参数声明可以写作 f(int arr[]) { … } 或 f(int *arr) { … } 因此对于 f 而言,参数所对应的实际只是数组的一部分没有关系。 如果确定元素存在,也可以向前索引一个数组;像 p[-1]、p[-2]等等在语法上都是合法 的,它们对应于 p[0]之前紧挨着的元素。当然,引用数组边界外的对象则是非法的。 5.4 地址运算 如果 p 是指向数组某个元素的指针,则 p++ 使 p 指向下一个元素;p+=i 使 p 指向当前 元素之后的第 i 个元素。它们及其他类似的语句是形式最简单的指针/地址运算。 C 语言的地址运算方法是一致且有规律的;指针、数组、地址运算整合在一起是 C 语言的 一大优点。下面我们通过编写一个初级的存储分配器来进行说明。第一个是 alloc(n),它返 回指针 p,p 指向连续的 n 个字符空间。该空间可被 alloc 的调用者用于存放字符。第二个是 afree(p),它释放用 alloc 申请的存储,使之能够被重新(申请)使用。这些程序之所以“初 级”是由于 afree 的调用顺序必须与 alloc 的顺序相反。也就是说,由 alloc 和 afree 管理 的存储是一个栈——一个后进先出的队列。标准库提供的类似函数 malloc 和 free 则没有这 个限制;在 8.7 节我们将介绍如何实现它们。 最容易的实现方法是让 alloc 管理一个较大的字符数组,我们将其称为 allocbuf。此 数 组由 alloc 和 afree 私有使用。由于这两个函数的参数用的是指针,而不是数组下标,其他 程序都无需知道该数组的名字,因此可以在包含 alloc 和 afree 的那个源文件中用 static 声明该数组,这样它就不会被外部程序见到。实际实现中,这个数组甚至可以没有名字,它可 能由(通过 malloc 调用或者向操作系统请求而获得的)匿名存储块的指针所替代。 另一所需的信息是 allocbuf 已被使用的量。我们用一个称为 allocp 的指针指向头一个 空闲元素。当 alloc 接到 n 个字符的申请时,它检查 allocbuf 是否剩余有足够的空间。如 果有,alloc 返回 allocp 的当前值(譬如,空闲块的起始位置),并将其增加 n,指向后面的 空闲区域。如果没有足够空间,则 alloc 返回零值。假如 p 在 allocbuf 的范围之内,afree(p) 的工作只是将 allocp 设置为 p。 调用alloc之前: allocbuf: 调用alloc之后: allocbuf: allocp: 使用中 空闲 allocp: 使用中 空闲 #define ALLOCSIZE 10000 /* 可用空间的大小 */ static char allocbuf[ALLOCSIZE]; /* 用于分配的存储 */ static char *allocp = allocbuf; /* 头一个空闲的位置 */ char *alloc(int n) /* 返回指向 n 个字符的指针 */ { if (allocbuf + ALLOCSIZE – allocp >= n) { /* 满足 */ allocp += n; return allocp – n; /* 分配前 p 所指的位置 */ } else /* 没有足够空间 */ return 0; } void afree(char *p) /* 释放 p 指向的存储 */ { if (p >= allocbuf && p < allocbuf + ALLOCSIZE) allocp = p; } 通常指针可以像任何其他变量一样初始化,虽然有意义的值只有零值或者之前定义的某个 恰当类型的数据的地址。声明 static char *allocp = allocbuf; 定义 allocp 为字符型指针,并初始化为指向 allocbuf 的开头,这也是程序一开始时的头一 个空闲位置。由于数组名也是第零个元素的地址,这个声明也可以被写为 static char *allocp = &allocbuf[0]; 条件测试 if (allocbuf + ALLOCSIZE – allocp >= n) { /* 满足 */ 检查是否有足够的空间满足 n 个字符的请求。如果足够,allocp 的新值最多为 allocbuf 的 末尾元素多 1。如果请求可以被满足,alloc 返回指向一整段字符的起始位置的指针(注意这 个函数声明本身)。如果不满足,alloc 的返回必须表示已经没有剩余空间了。C 语言保证零值 决不会是一个有效的数据地址,因此返回一个零值可以作为异常事件的信号,在本例中即表示 空间不足。 指针和整数不能互换。零值是唯一的例外:零值常量可以被赋给一个指针,而指针可以与 零值常量相比较。零值常常用符号常量NULL所替换,更明晰的标示出这是用于指针的特殊赋值。 NULL定义在 中 ①。从现在起我们将使用NULL来表示指针零值。 条件测试如 if (allocbuf + ALLOCSIZE – allocp >= n) { /* 满足 */ 和 if (p >= allocbuf && p < allocbuf + ALLOCSIZE) 展示了指针运算的几个重要方面。首先,指针可以在某些情况下进行比较。如果 p 和 q 分别指 向属于同一数组的元素,则关系运算符如==、!=、<、>= 等等都能正确地工作。例如,若 p 指向的元素在 q 指向的元素之前,则 p < q 为真。任何指针与零值进行相等或不相等的比较都是有意义的。但是没有指向同一个数组的指 针相互进行比较或者运算的行为则是未定义的。(存在一个例外:超过数组末尾的第一个元素可 以用于指针运算。) 其次,我们已经看到指针和整数可以进行加减,表达式 p + n 表示 p 当前所指向对象之后的第 n 个对象的地址;无论 p 所指对象是什么类型都是这样。n 会 根据 p 所指对象的大小(这取决于 p 的声明)进行扩展。例如,若 int 是 4 个字节,那么 int 型指针运算中 n 将扩大 4 倍。 指针减法也是有效的:如果 p 和 q 指向属于同一数组的元素并且 p < q,那么 q – p + 1 就是就是从 p 至 q 的元素的数目。这一特性可以被用于编写另一个版本的 strlen 函数: /* strlen:返回字符串 s 的长度 */ int strlen(char *s) { char *p = s; while (*p != ‘\0’) p++; return p - s; } 在函数的声明部分,p 被初始化为 s,即指向字符串的首字符。在 while 循环中,字符被依次 检查,直到碰见末尾的‘\0’。由于 p 是指向字符的指针,每次 p++ 使 p 前进指向下一个字符, p – s 给出了 p 前进的字符数——这正是字符串的长度。(字符串中的字符数可能太大而不能 用一个 int 来存放。头文件 中定义了一个 ptrdiff_t 类型,足够容纳两个指 针的(正负)差值。然而,假如我们非常谨慎,我们应该使用 size_t 作为 strlen 的返回类 型,使此程序与标准库的版本一致。size_t 是由 sizeof 运算符返回的无符号整数类型。) 指针运算是一致的:如果我们处理的是比字符占用更多的存储空间的浮点数,并且 p 是一 ① 根据标准,NULL 在中定义,尽管它也出现在如等头文件中。 个浮点指针,那么 p++将指向下一个浮点数。这样我们只需要将 alloc 和 afree 中所有的 char 都替换为 float,就能编写出另一个用于处理浮点数而不是字符的 alloc 版本。所有的指针操 作都自动将所指对象的大小考虑在内。 有效的指针运算包括将指针(值)赋给同一类型的指针,指针和整数进行加减,指向属于 同一数组的元素的指针相比较或相减,以及指针赋零值或与零值比较。所有其他的指针运算都 是非法的。例如两个指针相加、相乘或相除,指针与浮点数或双精度数相加,甚至将某个类型 的指针(void * 除外)在没有强制转换的情况下赋给另一个类型的指针等等都是不合法的。 5.5 字符指针与函数 字符串常量,如 “I am a string” 是一个字符数组。在内部表示中,此数组用空字符’\0’结束,这样程序才能找到它的结尾。数 组的存储长度也因此比双引号间的字符数目大 1。 字符串常量最常见的或许是用作函数的参数,就像 printf(“hello, world\n”); 当类似这样的一个字符串出现在程序中时,对它的访问通过一个字符指针进行;printf 接收 到的是指向该字符数组起始位置的指针。就是说,字符串常量是通过其第一个元素的指针来访 问的。 字符串常量不必一定用作函数参数。如果 pmessage 声明为 char *pmessage; 那么语句 pmessage = “now is the time”; 将这个字符串数组的指针赋给 pmessage。这并非对字符串的拷贝,复制的只是指针。C 语言 没有提供任何将字符串作为一个单元处理的操作。 下面的定义存在一个重要的区别: char amessage[] = “now is the time”; /* 数组 */ char *pmessage = “now is the time”; /* 指针 */ amessage 是一个数组,大小刚好足够容纳用于初始化它的对应字符串和’\0’。此数组包含的 字符可能变化,但 amessage 始终对应着同一个存储空间。而后者 pmessage 是一个指针,其 被初始化为指向一个字符串常量;这个指针之后可能被修改而指向不同的地方,但如果你试图 修改后者初始化字符串的内容,则结果是未定义的。 pmessage: amessage: now is the time\0 now is the time\0 我们将通过对标准库的两个有用函数的几个修改版本的考察,来详细说明指针和数组的多 个方面。第一个函数是 strcpy(s,t),其将字符串 t 复制到字符串 s 中。用 s = t 来表达当 然很好,但这样复制的不是字符串而是指针。为了复制字符串,我们需要一个循环。第一个是 数组版本: /* strcpy:将 t 复制到 s;数组下标版本 */ void strcpy(char *s, char *t) { int i; i = 0; while ((s[i] = t[i]) != ‘\0’) i++; } 对应地,下面是 strcpy 的指针版本: /* strcpy:将 t 复制到 s;指针版本 1 */ void strcpy(char *s, char *t) { while ((*s = *t) != ‘\0’) { s++; t++; } } 由于参数是按值传递的,strcpy 可按其想要的任何方式使用参数 s 和 t。这里将它们看作初 始化好的指针比较方便,它们沿数组一次前进一个字符,直到 t 的结束符‘\0’被复制到 s 为止。 实际中,strcpy 不会像上述程序那样编写。有经验的 C 编程者会更喜欢 /* strcpy:将 t 复制到 s;指针版本 2 */ void strcpy(char *s, char *t) { while ((*s++ = *t++) != ‘\0’) ; } 这个程序在循环的条件检测部分递增 s 和 t。*t++的值为 t 增加之前所指向的字符;后缀 ++ 在 字符被获取之后才改变 t。同样地,该字符被存放到原 s 的位置后 s 才被递增。该字符还作为 与‘\0’相比较的值来控制循环。其最终效果就是字符从 t 复制到 s,直到包含结尾的‘\0’为止。 作为最后一步精简,请注意与‘\0’的比较其实是多余的,因为该判定只是“这个表达式是 否为零”。因此该函数可能会按以下方式编写: /* strcpy:将 t 复制到 s;指针版本 3 */ void strcpy(char *s, char *t) { while (*s++ = *t++) ; } 这种表示法尽管初看起来可能显得难于理解,但其方便性是很明显的。应该掌握这种惯用法, 因为你经常会在 C 程序中见到它。 标准库()中的 strcpy 将目的字符串作为函数值返回。 我们考察的第二个程序是 strcmp(s, t),其按照字母序比较字符串 s 和 t,并 在 s 小于、 等于或大于 t 时返回负数、零或者正数。返回值是 s 和 t 第一个不同的字符相减的差值。 /* strcmp:如果 st 返回>0 */ int strcmp(char *s, char *t) { int i; for (i = 0; s[i] == t[i]; i++) if (s[i] == ‘\0’) return 0; return s[i] – t[i]; } 对应 strcmp 的指针版本: /* strcmp:如果 st 返回>0 */ int strcmp(char *s, char *t) { for ( ; *s == *t; s++, t++) if (*s == ‘\0’) return 0; return *s – *t; } 由于 ++ 和 -- 既是前缀运算符又是后缀运算符,因此也会出现 * 和 ++ 及 -- 的其他 组合,尽管要少见一些。例如: *--p 首先将 p 减 1,然后再获取 p 所指向的字符。实际上,下面这对表达式 *p++ = val; /* 将 val 压入栈 */ val = *--p; /* 将栈顶元素弹出到 val 中 */ 是栈压入和弹出的标准惯用法;见 4.3 节。 头文件包含有本节所述的函数的声明,以及标准库中各种类型的其他字符串 处理函数的声明。 练习 5-3. 编写 strcat 函数的指针版本。此函数我们在第 2 章描述过:strcat(s, t)将字 符串 t 复制到 s 的末尾处。 练习 5-4. 编写函数 strend(s, t),如果字符串 t 出现在字符串 s 的末尾处,函数返回 1, 否则返回 0。 练习 5-5. 编写库函数 strncpy、strncat 和 strncmp,这些函数只处理它们字符串参数的 最多前 n 个字符。例如,strncpy(s, t, n)将 t 的最多前 n 个字符复制到 s 中。它们的全 面描述参见附录 B。 练习 5-6. 以指针来替代数组下标,重写之前章节和练习中的相应程序。可能的好选择包括 getline(第 1、4 章),atoi、itoa 及其变体(第 2、3、4 章),以 及 strindex 和 gettop (第 4 章)。 5.6 指针数组;指向指针的指针 由于指针本身也是变量,它们可以像其他变量那样存放在数组中。让我们编写一个将一组 文本行按字母序排序的程序来说明这一点。这是 UNIX 程序 sort 去掉其他功能的版本。 在第 3 章我们给出了一个 Shell 函数 sort 用于整数数组排序,并在第 4 章用快速排序对 其进行了改进。在这里相同的算法一样有效,只是现在我们必须处理文本行,它们的长度各异, 且与整数不同,不能用单个操作来比较或移动。我们需要一个数据表示法来高效便利地处理变 长的文本行。 我们引入指针数组来解决此问题。如果待排序的文本行首尾相连存放在一个大的字符数组 中,则每一行可通过指向它的首字符的指针来访问。这些指针可以存放在一个数组中。两个文 本行可通过将其指针传递到 strcmp 来进行比较。当两个乱序的行需要交换时,交换的是指针 数组中对应的指针,而不是交换文本行本身。 jklmnopqrst abc defghi jklmnopqrst abc defghi 这样就消除了移动文本行本身而带来的复杂存储管理和高开销这一对共生问题。 排序过程共分为三个步骤: 读入输入的所有文本行 将它们排序 将它们顺序打印出来 通常情况下,最好是将程序按照这种自然划分而分为多个函数,并通过一个主程序来控制其他 函数。让我们将排序步骤稍稍推后,先集中讨论数据结构和输入输出部分。 输入函数需要收集和保存每一行字符,并建立一个指向这些行的指针数组。它还需累计输 入行数,因为排序和打印需要这一信息。由于输入函数只能处理有限数量的输入行,它可返回 某个非法行数(如-1)来表示出现了过多输入的情况。 输出函数仅需按照指针数组中的顺序打印文本行。 #include #include #define MAXLINES 5000 /* 能被排序的最大行数 */ char *lineptr[MAXLINES]; /* 指向文本行的指针 */ int readlines(char *lineptr[], int nlines); void writelines(char *lineptr[], int nlines); void qsort(char *lineptr[], int left, int right); /* 将输入行排序 */ main() { int nlines; /* 读入的输入行数 */ if ((nlines = readlines(lineptr, MAXLINES)) >= 0) { qsort(lineptr, 0, nlines-1); writelines(lineptr, nlines); return 0; } else { printf(“error: input too big to sort\n”); return 1; } } #define MAXLEN 1000 /* 任一输入行的最大长度 */ int getline(char *, int); char *alloc(int); /* readlines:读取输入行 */ int readlines(char *lineptr[], int maxlines) { int len, nlines; char *p, line[MAXLEN]; nlines = 0; while ((len = getline(line, MAXLEN)) > 0) if (nlines >= maxlines || (p = alloc(len)) == NULL) return -1; else { line[len-1] = ‘\0’; /* 删除换行符 */ strcpy(p, line); lineptr[nlines++] = p; } return nlines; } /* writelines:写输出行 */ void writelines(char *lineptr[], int nlines) { int i; for (i = 0; i < nlines; i++) printf(“%s\n”, lineptr[i]); } 函数 getline 来自 1.9 节。 一个主要的新事物是 lineptr 的声明: char *lineptr[MAXLINES] 说明 lineptr 是一个包含 MAXLINES 个元素的数组,每个元素都是指向字符的指针。也就是 说,lineptr[i]是一个字符型指针,*lineptr[i]是其指向的字符,即所保存的第 i 个文本 行的首字符。 由于 lineptr 是数组的名字,它可以按照指针来对待,就像我们在之前的例子中所做的那 样,因此 writelines 可以改写为: /* writelines:写输出行 */ void writelines(char *lineptr[], int nlines) { while (nlines-- > 0) printf(“%s\n”, *lineptr++); } 初始的*lineptr 指向第一行;每增 1 就使它前进到下一行的指针,同时 nlines 计数减 1。 输入和输出搞定之后,我们进一步考虑排序部分。第 4 章的快速排序(quick-sort)需 要微小的改变:声明需要修改,比较操作必须通过调用 strcmp 来完成。算法则保持不变,这 让我们对程序仍能工作有了一定的把握。 /* qsort:将 v[left]…v[right]排为递增次序 */ void qsort(char *v[], int left, int right) { int i, last; void swap(char *v[], int i, int j); if (left >= right) /* 如果数组元素小于 2 个 */ return; /* 则什么也不做 */ swap(v, left, (left + right)/2); last = left; for (i = left+1; i <= right; i++) if (strcmp(v[i], v[left]) < 0) swap(v, ++last, i); swap(v, left, last); qsort(v, left, last-1); qsort(v, last+1, right); } 同样地,swap 程序只需要一小点改变: /* swap:将 v[i]和 v[j]互换 */ void swap(char *v[], int i, int j) { char *temp; temp = v[i]; v[i] = v[j]; v[j] = temp; } 由于 v(指代 lineptr)的任何元素都是字符指针,因此 temp 必须也是,这样才能够相互复 制。 练习 5-7. 重写 readlines,将文本行存放在由 main 提供的数组中,而不是调用 alloc 来 获得存储。这个程序会比改写之前快多少? 5.7 多维数组 C 语言提供了矩形多维数组,尽管实际中它们用得远比指针数组要少。在本节中我们将展 示它们的一些特性。 考虑日期转换的问题,将某月某日转换为某年的第几天,以及反向的转换。例如 3 月 1 日 是非闰年的第 60 天,是闰年的第 61 天。我们定义两个函数进行转换:day_of_year 将某月 某日转换为某年的第几天,而 month_day 将某年的第几天转换为某月某日。由于后一个函数 要计算两个值,其参数月和日应为指针: month_day(1988, 60, &m, &d) 将 m 置为 2,d 置为 29(2 月 29 日)。 这两个函数都需要同样的信息,一份每月天数的表(“九月里有三十天……”)。由于闰年和 非闰年有不同的每月天数,将它们分作二维数组的两行比在计算过程中考虑 2 月份的变化要容 易一些。用于执行转换的数组和函数如下: static char daytab[2][13] = { {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} }; /* day_of_year:从月/日转换为一年的第几天 */ int day_of_year(int year, int month, int day) { int i, leap; leap = year%4 == 0 && year%100 != 0 || year%400 == 0; for (i = 1; i < month; i++) day += daytab[leap][i] return day; } /* month_day:将一年的第几天转换为月/日 */ void month_day(int year, int yearday, int *pmonth, int *pday) { int i, leap; leap = year%4 == 0 && year%100 != 0 || year%400 == 0; for (i = 1; yearday > daytab[leap][i]; i++) yearday -= daytab[leap][i]; *pmonth = i; *pday = yearday; } 回忆逻辑表达式的运算,例如上面闰年的计算,其结果不是 0(假)就是 1(真),因而它可被 用作数组 daytab 的下标。 数组 daytab 必须在 day_of_year 和 month_day 的外部声明,这样两个函数都能使用 它。我们将 daytab 设为 char 类型,是为了说明 char 类型用于存放非字符的小整数也是合 法的。 daytab 是我们碰到的第一个二维数组。在 C 中,二维数组实际上是一个一维数组,其每 个元素都是一个数组。因而下标被写为: daytab[i][j] /* [行][列] */ 而不是 daytab[i, j] /* 错 */ 除了这一标记上的区别,二维数组的处理方式与其他语言基本一致。元素按行存放,所以当元 素按照存储顺序访问时,最右边的下标(即列)变化得最快。 数组以一列用花括号括起来的初始值进行初始化;二维数组的各行用对应的子列初始化。 我们让数组 daytab 用一列 0 开头,这样月份就可以使用自然的 1 到 12 而不是 0 到 11。由 于 这里空间不是首要考虑的问题,这样做比调整下标更加直观。 如果要将一个二维数组传递给函数,函数的参数声明中必须包括数组的列数;而行数则无 关,因为就像之前那样,传递的是数组行的指针,这里每一行是包含 13 个 int 的一维数组。 在这个具体例子中,它是指向包含 13 个 int 的数组对象的指针。因此,如果要将数组 daytab 传递给函数 f,f 的声明将会是: f(int daytab[2][13]) { … } 由于行数无关紧要,也可以是 f(int daytab[][13]) { … } 或者是 f(int (*daytab)[13]) { … } 此声明表示其参数是指向包含 13 个整数的数组的指针。这里圆括号是必须的,因为括号 [] 比 * 的优先级高。没有圆括号,声明 int *daytab[13] 就是一个包含 13 个整数指针的数组。更一般地,只有数组的第一个维度(下标)是可以不指定 的,而其他维度都必须指定。 5.12 节有对复杂声明的进一步讨论。 练习 5-8. 在 day_of_year 和 month_day 中没有错误检测,请修正这一缺陷。 5.8 指针数组的初始化 考虑函数 month_name(n)的编写,该函数返回包含第 n 月的名字的字符串。这里应用内 部静态数组十分理想。month_name 包含一个私有的字符串数组,并返回指向正确名字串的指 针。本节说明这个名字数组如何进行初始化。 其语法与之前的初始化类似: /* month_name:返回第 n 个月的名字 */ char *month_name(int n) { static char *name[] = { “Illegal month”, “January”, “February”, “March”, “Apirl”, “May”, “June”, “July”, “August”, “September”, “October”, “November”, “December” }; return (n < 1 || n>12) ? name[0] : name[n]; } 声明 name 是一个字符指针数组,与排序用例中的 lineptr 是同一类型。初始化值是一列字符 串,每个串被分配到数组中的相应位置。第 i 个串中的字符被放在某个地方,而指向它们的指 针存放在 name[i]中。由于数组 name 的大小没有指定,编译器累计初始化值的个数并填入确 切的数值。 5.9 指针与多维数组的比较 C 语言初学者有时会混淆二维数组与指针数组的区别,例如前一个例子中的 name。假设有 定义 int a[10][20]; int *b[10]; 那么语法上 a[3][4]和 b[3][4]都合法地引用了一个 int。但 a 是一个真正的二维数组:200 个 int 大小的空间已被预留了出来,传统的矩形下标计算公式 20×行+列 用于得到元素 a[行][列]。而对于 b,该定义仅仅分配了 10 个指针,且没有初始化它们;初始化必须显式地 完成,或者通过静态分配或者通过代码初始化。假设 b 每个元素指向一个 20 个元素的数组, 那么将被预留的有 200 个 int,以及指针的 10 个单元。指针数组的一个重要优点是数组的每 行可以有不同的长度。这样,b 的每个元素不必都指向具有 20 个元素的向量;可以有些指向 2 个元素,有些 50 个元素,有些则根本没有元素。 尽管我们只以整数进行了讨论,但指针数组最常用于存放长度各异的字符串,就像在函数 month_name 中的那样。指针数组的声明和图示如下: char *name[] = { “Illegal month”, “Jan”, “Feb”, “Mar” }; Illegal month\0 Feb\0 Jan\0 Mar\0 name: 与二维数组相较: char aname[][15] = { “Illegal month”, “Jan”, “Feb”, “Mar” }; aname: 0 15 30 45 Illegal month\0 Jan\0 Feb\0 Mar\0 练习 5-9. 重写函数 day_of_year 和 month_day,使用指针替代下标。 5.10 命令行参数 在支持 C 语言的环境中,存在一种在程序开始执行时将命令行参数传递给它的方法。当 main 被调用时,它有两个参数。第一个(传统上称为 argc,参数计数)是运行程序时命令行 参数的个数;第二个(argv,参数向量)是一个指针,指向包含参数字符串的数组,其中每个 串对应一个参数。我们惯常使用多级指针来操作这些字符串。 最简单的示例是程序 echo,它将其命令行参数用空格分隔并回显在一行上。即,命令 echo hello, world 打印输出为 hello, world 按照常规,argv[0]是被调用程序的名字,所以 argc 至少为 1。如果 argc 是 1,那么在程序 名之后没有命令行参数。在上面的例子中,argc 为 3,argv[0]、argv[1]和 argv[2]分别 是“echo”、“hello,”和“world”。 可选参数的第一个是 argv[1],最后一个是 argv[argc - 1];此外,标准要求 argv[argc]是一个空指针。 echo\0 argv: 0 hello,\0 world\0 echo 的第一个版本将 argv 看作一个字符指针数组: #include /* 回显命令行参数;版本 1 */ main(int argc, char *argv[]) { int i; for (i = 1; i < argc; i++) printf(“%s%s”, argv[i], (i < argc-1) ? “ ” : “”); printf(“\n”); return 0; } 由于 argv 是指向一个指针数组的指针,我们可以操作指针而不用数组下标。下面这个变体基 于递增 argv,它是指向字符指针的指针,同时 argc 计数递减: #include /* 回显命令行参数;版本 2 */ main(int argc, char *argv[]) { while (--argc > 0) printf(“%s%s”, *++argv, (argc > 1) ? “ ” : “”); printf(“\n”); return 0; } 由于 argv 是指向参数字符串数组起始位置的指针,将它加 1(++argv)使它指向 argv[1] 而不是 argv[0]。每次递增将它顺移至下一个参数;*argv 则为指向对应参数的指针。与此同 时,argc 被递减;当它变为 0 时,就完成了所有参数的打印。 我们可将 printf 的声明写为另一形式: printf((argc > 1) ? “%s ” : “%s”, *++argv); 此语句说明 printf 的格式化参数也可以是表达式。 作为第二个例子,我们将 4.1 节的模式查找程序进行一些加强。如果读者还记得,我们把 搜索模式深放在程序内部,这显然不是一种令人满意的安排。我们以 UNIX 程序 grep 为范本 修改程序,使其可通过命令行的第一个参数来指定待匹配的模式。 #include #include #define MAXLINE 1000 int getline(char *line, int max); /* find:打印与第 1 个参数指定模式相匹配的行 */ main(int argc, char *argv[]) { char line[MAXLINE]; int found = 0; if (argc != 2) printf(“Usage: find pattern\n”); else while (getline(line, MAXLINE) > 0) if (strstr(line, argv[1]) != NULL) { printf(“%s”, line); found++; } return found; } 标准库函数 strstr(s, t) 返回字符串 s 中首次出现字符串 t 的位置的指针,或在未出现时 返回 NULL。它在 中声明。 这一命令行参数模型现在可被着意加强来进一步说明指针的构造。假设我们希望有两个可 选参数。第一个表示“打印所有不匹配模式的行”;第二个表示“在每一个打印行之前加上该行 的行号”。 在 UNIX 系统上 C 程序有一个共同的约定:用减号开头的参数是一个可选标记或参数。如 果我们选择 -x(表示“除外”)提示反意执行,以及 -n(“编号”)来要求加上行号,那么命 令 find -x -n 模式 将会打印所有不匹配模式的行,并在前面加上该行的行号。 可选参数应该允许以任意的次序出现,而程序的其他部分应该与参数出现的个数无关。并 且,如果可选参数能够合并则会便于使用,就如 find -xn 模式 这是对应的程序: #include #include #define MAXLINE 1000 int getline(char *line, int max); /* find:打印与第一个(非可选)参数相匹配的行 */ main(int argc, char *argv[]) { char line[MAXLINE]; long lineno = 0; int c, except = 0, number = 0, found = 0; while (--argc > 0 && (*++argv)[0] == ‘-’) while (c = *++argv[0]) switch (c) { case ‘x’: except = 1; break; case ‘n’: number = 1; break; default: printf(“find: 非法选项 %c\n”, c); argc = 0; found = -1; break; } if (argc != 1) printf(“用法:find –x –n 模式\n”); else while (getline(line, MAXLINE) > 0) { lineno++; if ((strstr(line, *argv) != NULL) != except) { if (number) printf(“%ld:”, lineno); printf(“%s”, line); found++; } } return found; } 在每一个可选参数前 argc 被递减同时 argv 被递增。在循环的最后,如果没有错误,argc 表示有多少参数没有处理,同时 argv 指向第一个未处理的参数。因此 argc 应该为 1,而*argv 则应该指向待匹配模式。请注意*++argv 是一个指向参数串的指针,所以(*++argv)[0]是该 参数串的第一个字符。(另一种有效形式是**++argv。)因为[]比*和++结合得更紧,所以圆 括号是必须的;否则表达式将被看作*++(argv[0])。事实上这正是我们在内层循环中使用的 形式,其任务是沿着特定的参数字符串前进,在内层循环中,表达式*++argv[0]增加的是指 针 argv[0]! 很少有人使用比它们更加复杂的指针表达式,如果有这种情况,将它们分作两步或三步会 更直观一些。 练习 5-10. 编写程序 expr,它对来自命令行的一个逆波兰式求值,这里每个运算符或操作数 都是分开的参数,例如: expr 2 3 4 + * 对 2×(3+4) 求值。 练习 5-11. 修改程序 entab 和 detab(第 1 章作为练习编写的程序),使其接受一组制表符 停止位作为参数。如果没有参数则使用缺省的制表设置。 练习 5-12. 扩展 entab 和 detab,使其接受缩略形式 entab -m +n 其表示从第 m 列开始,每 n 列一个制表符停止位,选择(对使用者而言)方便的缺省行为。 练习 5-13. 编写程序 tail,它打印其输入的最后 n 行。n 设有缺省值,比如说为 10,但它 可通过一个可选参数来改变,这样 tail –n 打印最后的 n 行。无论输入以及 n 值是如何的不合理,此程序都应该具有合理的行为。编写此 程序让它最好地利用可获得的存储;文本行应该像 5.6 节的排序程序中那样存放,而不是放在 固定大小的二维数组中。 5.11 函数指针 在 C 语言中,函数本身不是变量,但定义函数的指针是可以的,它可被赋值、放在数组内、 传递给函数、由函数返回等等。我们将通过修改本章先前编写的排序程序来对此进行阐述。修 改后的函数如果给出可选参数 -n,它将以数值序代替字母序进行输入行的排序。 一个排序通常包括三个部分——用于确定任意两个对象次序的比较部分,用于倒转它们的 次序的交换部分,以及进行比较和交换直到所有对象已经有序的排序算法部分。排序算法独立 于比较和交换操作,所以通过传递不同的比较和交换程序给排序算法程序,我们就可以安排不 同准则进行排序。这正是我们在新排序程序中所采用的方法。 像之前那样,两行的字母序比较通过 strcmp 来完成;我们还需要一个 numcmp 例程基于 数值来比较两行并且返回与 strcmp 相同类别的比较结果标志。这些函数在 main 函数之前声 明,一个指向其中某一恰当函数的指针被传递到 qsort 中。我们略去了参数的错误处理,这样 我们可以集中精力考虑主要问题。 #include #include #define MAXLINES 5000 /* 可被排序的最大行数 */ char *lineptr[MAXLINES]; /* 指向文本行的指针 */ int readlines(char *lineptr[], int nlines); void writelines(char *lineptr[], int nlines); void qsort(void *lineptr[], int left, int right, int (*comp)(void *, void *)); int numcmp(char *, char *); /* 将输入行进行排序 */ main(int argc, char *argv[]) { int nlines; /* 输入的行数 */ int numeric = 0; /* 如果按数值排序则为 1 */ if (argc > 1 && strcmp(argv[1], “-n”) == 0) numeric = 1; if ((nlines = readlines(lineptr, MAXLINES)) >= 0) { qsort((void **) lineptr, 0, nlines-1, (int (*)(void*, void*))(numeric ? numcmp : strcmp)); writelines(lineptr, nlines); return 0; } else { printf(“input too big to sort\n”); return 1; } } 在对 qsort 的调用中,strcmp 和 numcmp 是函数的地址。由于已经知道它们是函数,& 运算 符不是必需的,就像在数组名之前不需要用 & 一样。 改写后的 qsort 可以处理任意的数据类型,而不是仅能处理字符串。按照 qsort 函数原 型的表述,qsort 的参数包括一个指针数组、两个整数、以及一个有 2 个指针参数的函数。通 用指针类型 void * 用于这些指针参数。任何指针都可被转换为 void * 并转换回来,且不丢 失信息,所以我们可用强制转换为 void * 的参数来调用 qsort。比较函数的参数使用了这种 特意的函数参数变换方式。通常说来这对实际的表述没有影响,但需要确认编译器认为这些都 OK。 /* qsort:将 v[left]…v[right]按递增次序排列 */ void qsort(void *v[], int left, int right, int (*comp)(void *, void *)) { int i, last; void swap(void *v[], int, int); if (left >= right) /* 如果数组包含元素小于两个 */ return; /* 则什么也不做 */ swap(v, left, (left + right)/2); last = left; for (i = left+1; i <= right; i++) if ((*comp)(v[i], v[left]) < 0) swap(v, ++last, i); swap(v, left, last); qsort(v, left, last-1, comp); qsort(v, last+1, right, comp); } 这些声明应该仔细研究一下。qsort 的第四个参数是 int (*comp)(void *, void *) 它表示 comp 是一个函数,该函数有两个 void * 参数,并返回一个 int 值。 在代码行 if ((*comp)(v[i], v[left]) < 0) 中 comp 的使用与是符合其声明的:comp 是一个函数的指针,*comp 是该函数,而 (*comp)(v[i], v[left]) 则是对此函数的调用。这里括号是需要的,这样成员才能够按正确的方式关联;如果没有它们, int *comp(void *, void *) /* 错了!*/ 表示 comp 是一个函数,其返回一个 int 指针,和原意大不相同。 我们已经给出了 strcmp,它对两个字符串进行比较。这里介绍 numcmp,其比较两个字符 串的前导数值(该值通过调用 atof 计算得到): #include /* numcmp:按数值比较 s1 与 s2 */ { double v1, v2; v1 = atof(s1); v2 = atof(s2); if (v1 < v2) return -1; else if (v1 > v2) return 1; else return 0; } 用于交换两个指针的 swap 函数和本章之前所展示的完全一样,只是声明变成了 void *。 void swap(void *v[], int i, int j) { void *temp; temp = v[i]; v[i] = v[j]; v[j] = temp; } 还有多种选项可以加到这个排序程序中,其中一些构成了有挑战性的习题。 练习 5-14. 修改排序程序使其可处理标记 -r,它表明按逆序(降序)进行排序。请确认 -r 可 以与 -n 一起工作。 练习 5-15. 增加 -f 选项,将大小写合并考虑,这样在排序时大小写不作区分;例如,a 和 A 是相等的。 练习 5-16. 增加 -d(“字典序”)选项,使程序仅比较字母、数字和空格。请确保它可以与 -f 协同工作。 练习 5-17. 增加字段处理功能,这样可以按多个字段对行进行排序,每个字段根据独立的选项 集完成排序。(本书的索引以 –df 对索引类型排序,并以 –n 对页码排序。) 5.12 复杂声明 C 语言中声明所用的语法有时会倍受批评,尤其是那些包含函数指针的声明语法。该语法 试图让一个声明与对应的用法相匹配;简单情况下它工作得很好,但较复杂的情况下则可能令 人困惑,这可能是由于声明不能按照从左到右的顺序理解,也可能是因为使用了太多的圆扩号。 声明 int *f(); /* f:函数返回指向 int 型的指针 */ 与 int (*pf)(); /* pf:指向返回 int 的函数的指针 */ 之间的区别就说明了这个问题:* 是一个前缀运算符,它的优先级低于 () ,因此必须用圆括 号来强制恰当的结合。 虽然真正复杂的声明在实际中极少出现,但是,知道如何理解它们并且在必要时如何创建 它们仍然重要。合成声明的一个好方法是使用 typedef 分步骤完成。typedef 将在 6.7 节讨 论。作为另一种选择,本节将给出一对程序,它们将有效的 C 语言声明和对应的文字描述进行 互相转换。转换的文字描述可以从左至右地解读。 第一个程序 dcl 要复杂一些。它将 C 语言声明转换为文字描述,就如下面的例子: char **argv argv: pointer to pointer to char /* 指向字符指针的指针 */ int (*daytab)[13] daytab: pointer to array[13] of int /* 指向含 13 个 int 元素的数组的指针 */ int *daytab[13] daytab: array[13] of pointer to int /* 包含 13 个指向 int 的指针元素的数组 */ void *comp() comp: function returning pointer to void /* 返回 void 型指针的函数 */ void (*comp)() comp: pointer to function returning void /* 指向返回 void 的函数的指针 */ char (*(*x())[])() x: function returning pointer to array[] of pointer to function returening char /* x 是一个函数,其返回值为指向数组的指针, 该数组元素是指向返回 char 的函数的指针 */ char (*(*x[3])())[5] x: array[3] of pointer to function returning pointer to array[5] of char /* x 是包含 3 个元素的数组,该数组元素是指向一个函数的指针, 该函数返回值为指向包含 5 个 char 元素的数组的指针 */ dcl 基于声明符所用语法编写,该语法的精确描述在附录 A 的 8.5 节;这里是一个简化形 式: dcl: optional *’s direct-dcl direct-dcl: name (dcl) direct-dcl() direct-dcl[optional size] 用文字描述就是:dcl 是一个 direct-dcl,该 direct-dcl 也可能前缀有一个或多个 * 。 direct-dcl 是 name(名字),或者是用圆括号括起来的 dcl,或者是其后紧跟一个圆括号的 direct-dcl,或者是其后紧跟一个方括号(括号内可能包含大小)的 direct-dcl。 这个语法可用来解析声明。例如,考虑如下声明: (*pfa[])() pfa 将被辨识为一个 name,即一个 direct-dcl。那么 pfa[]同样是一个 direct-dcl。这 样*pfa[]就被辨识为一个 dcl,因此 (*pfa[]) 是一个 direct-dcl。这样 (*pfa[])() 就是一个 direct-dcl 也即一个 dcl。我们还可使用一个解析树来演示这一解析过程(其中 direct-dcl 被缩写为 dir-dcl): ( * pfa [ ] ) ( ) name dir-dcl dir-dcl dcl dir-dcl dir-dcl dcl dcl 程序的核心是一对函数——dcl 和 dirdcl,它们根据上述语法对声明进行解析。由于 该语法是递归定义的,当这对函数辨识声明的片段时将互相递归调用;这样的程序被称为递归 下降解析器。 /* dcl:解析一个声明 */ void dcl(void) { int ns; for (ns = 0; gettoken() == ‘*’; ) /* 计算 * 的个数 */ ns++; dirdcl(); while (ns-- > 0) strcat(out, “ pointer to”); } /* dirdcl:解析一个直接声明 */ void dirdcl(void) { int type; if (tokentype == ‘(’) { /* ( dcl ) */ dcl(); if (tokentype != ‘)’) printf(“error: missing )\n”); } else if (tokentype == NAME) /* 变量名字 */ strcpy(name, token); else printf(“error: expected name or (dcl)\n”); while ((type=gettoken()) == PARENS || type == BRACKETS) if (type == PARENS) strcat(out, “ function returning”); else { strcat(out, “ array”); strcat(out, token); strcat(out, “ of”); } } 由于这些程序的目的在于演示,而不要求尽善尽美,因此 dcl 有明显的限制。它仅可处理 如 char 或 int 这样的简单数据类型。它没有处理函数中的参数类型,或者如 const 这样的限 定符。不合逻辑的空白符会造成解析错乱。错误恢复方面它没有做太多工作,所以无效声明也 会造成解析错乱。这些方面的改进将留作练习。 下面是全局变量和主函数: #include #include #include #define MAXTOKEN 100 enum { NAME, PARENS, BRACKETS }; void dcl(void); void dirdcl(void); int gettoken(void); int tokentype; /* 解析中最新遇到的标记的类型 */ char token[MAXTOKEN]; /* 最新遇到的标记字符串*/ char name[MAXTOKEN]; /* 标识符名称 */ char datatype[MAXTOKEN]; /* 数据类型 = char、int 等等 */ char out[1000]; /* 输出字符串 */ main() /* 将声明转换为文字 */ { while (gettoken() != EOF) { /* 输入行中的第 1 个标记 */ strcpy(datatype, token); /* 是数据类型 */ out[0] = ‘\0’; dcl(); /* 解析输入行的剩余部分 */ if (tokentype != ‘\n’) printf(“syntax error\n”); /* 语法出错 */ printf(“%s: %s %s\n”, name, out, datatype); } return 0; } 函数 gettoken 跳过空格符和制表符,找到输入中的下一个标记;“标记”可以是名字、一 对圆括号、一对方括号(可能包含数字),或者是其他的任意单个字符。 int gettoken(void) /* 返回紧接着的标记 */ { int c, getch(void); void ungetch(int); char *p = token; while ((c = getch()) == ‘ ’ || c == ‘\t’) ; if (c == ‘(’) { if ((c = getch()) == ‘)’) { strcpy(token, “()”); return tokentype = PARENS; } else { ungetch(c); return tokentype = ‘(’; } } else if (c == ‘[’) { for (*p++ = c; (*p++ = getch()) != ‘]’; ) ; *p = ‘\0’; return tokentype = BRACKETS; } else if (isalpha(c)) { for (*p++ = c; isalnum(c = getch()); ) *p++ = c; *p = ‘\0’; ungetch(c); return tokentype = NAME; } else return tokentype = c; } getch 和 ungetch 在第 4 章中讨论过。 反向的转换要简单一些,尤其是不需要担心生成多余的圆括号。程序 undcl 将类似“x 是 一个函数,该函数返回一个指向数组的指针,该数组元素是指向返回 char 的函数的指针”的文 字描述,我们将其表述为 x () * [] * () char 转换为 char (*(*x())[])() 这种缩写的输入语法使得我们可以复用 gettoken 函数。undcl 也使用了与 dcl 相同的外部 变量。 /* undcl:将文字描述转换为声明 */ main() { int type; char temp[MAXTOKEN]; while (gettoken() != EOF) { strcpy(out, token); while ((type = gettoken()) != ‘\n’) if (type == PARENS || type == BRACKETS) strcat(out, token); else if (type == ‘*’) { sprintf(temp, “(*%s)”, out); strcpy(out, temp); } else if (type == NAME) { sprintf(temp, “%s %s”, token, out); strcpy(out, temp); } else printf(“invalid input at %s\n”, token); printf(“%s\n”, out); } return 0; } 练习 5-18. 使 dcl 可以从输入错误中恢复。 练习 5-19. 修改 undcl 使其不会增加冗余的圆括号到声明中。 练习 5-20. 扩展 dcl,使其可处理包含函数参数类型、const 等限定符等等的声明。 第 6 章 结构 结构是一个或多个变量的集合,这些变量的类型可能不同,为方便而将它们并为一组用一 个单独的名字操控。(某些语言中结构被称为“记录(record)”,例如著名的 Pascal。)结构 有助于组织复杂的数据,尤其在大型程序中,因为它们允许将相关联的一组变量作为一个单元 使用,而不是作为分离的对象处理。 职员薪资表是说明结构的一个传统例子:雇员用一组属性来描述,例如名字、住址、社会 保障号码、薪金等等。其中一些属性可能又是结构:名字有多个组成部分;相应地,住址、甚 至薪金也是这样。另一个对 C 语言更典型的例子来自图形学:一个点是一对坐标值,一个矩形 是两个点等等。 ANSI 标准作出的主要修改是定义了结构赋值——结构可被复制、赋值、传递给函数、以及 被函数返回。这一特性大多数编译器已经支持多年,现在被精确地定义下来。结构和数组类型 的自动变量现在也能够被初始化了。 6.1 结构的基础知识 让我们创建几个适用于图形学的结构。最基本的对象是点,我们假定它有 x 坐标值和 y 坐 标值,且都是整数。 x y (4,3) (0,0) 这两个分量可以被放到一个像这样声明的结构中: struct point { int x; int y; }; 关键字 struct 引入结构声明,结构声明是被花括号括上的一列声明。在关键字 struct 之后可带有一个被称为结构标记的可选名字(在这里即 point)。该标记命名了该类型的结构, 并在此后可做为声明中花括号部分的缩写使用。 在结构内命名的变量被称为成员。结构成员、标记和普通变量(即非成员变量)可以拥有 相同的名字而不冲突,因为它们总是能够通过上下文进行区分。并且,相同的成员名字可以出 现在不同的结构中,尽管就编程风格而言通常只有关系紧密的对象才使用相同的名字。 一个 struct 声明定义了一个类型。在标志成员列表结束的右花括号后可以带有一列变量, 就像任何基本类型那样。譬如, struct { … } x, y, z; 与 int x, y, z; 在语法上可以类比:两者都将 x、y 和 z 声明为所命名的类型,并且为它们预留了存储空间。 不带变量列表的结构声明并不预留存储空间;它只是描述了结构的模版(即一个结构的形 态),然而如果结构声明已被加上标记,该标记则可用于此后结构实例的定义。例如,按照上面 的 point 声明, sturct point pt; 定义了一个 struct point 类型的结构变量 pt。一个结构可通过在其定义之后紧随的初始值 列表来进行初始化。每一初始值都是赋给结构成员的常量表达式: struct point maxpt = { 320, 200 }; 一个自动结构变量也可以通过赋值或者调用某个返回同类型结构的函数来进行初始化。 在表达式中,通过如下构成形式对某个特定结构的成员进行引用: 结构名称.成员 结构成员运算符“.”连接结构名称和成员名称。例如,打印 pt 这个点的坐标可以用 printf(“%d, %d”, pt.x, pt.y) 计算从原点 (0,0) 至 pt 的距离可以用 double dist, sqrt(double); dist = sqrt((double)pt.x * pt.x + (double)pt.y * pt.y); 结构可以嵌套。矩形的一种表示方法就是代表其对角的两个点: x y pt2 pt1 struct rect { struct point pt1; struct point pt2; }; 结构 rect 包含了两个 point 结构。如果我们将 screen 声明为 struct rect screen; 则 screen.pt1.x 引用了 screen 的成员 pt1 的 x 坐标。 6.2 结构和函数 对结构的操作中,仅有“以结构为单元进行复制或赋值”、“用 & 获取结构地址”、以 及“ 访 问结构成员”等操作是合法的。复制和赋值也包括向函数传递参数以及从函数返回值。结构不 可以进行比较。可以用一个成员常值列表初始化结构;一个自动结构变量也可以通过赋值进行 初始化。 我们通过编写一些操控点和矩形的函数来深入了解结构。至少有三种可能的编写方法:分 别传递结构的组件、传递整个结构、或者传递结构的指针。每种都有其优点和缺点。 第一个函数,makepoint,其输入两个整数并返回一个 point 结构: /* makepoint:以 x 和 y 为坐标构造一个点 */ struct point makepoint(int x, int y) { struct point temp; temp.x = x; temp.y = y; return temp; } 注意参数名和同名的成员之间没有冲突;实际上名字的重复强调了它们的联系。 现在 makepoint 可用于动态地初始化任何结构,或者为函数提供结构参数: struct rect screen; struct point middle; struct point make point(int, int); screen.pt1 = makepoint(0, 0); screen.pt2 = makepoint(XMAX, XMAX); middle = makepoint((screen.pt1.x + screen.pt2.x)/2, (screen.pt1.x + screen.pt2.x)/2); 接下来是对点进行算术运算的函数集。例如: /* addpoint:两个点相加 */ struct point addpoint(struct point p1, struct point p2) { p1.x += p2.x; p1.y += p2.y; return p1; } 在这里参数和返回值都是结构。我们之所以递增 p1 中的成员而不是显式地使用一个临时变量, 目的是强调结构参数像其他参数那样是按值传递的。 另一个例子是函数 ptinrect,其测试一个点是否在一个矩形之内,这里我们采用了惯例, 即矩形包含它的左侧边和底边,而不包含顶边和右侧边。 /* ptinrect:如果 p 在 r 之内,返回 1,否则返回 0 */ int ptinrect(struct point p, struct rect r) { return p.x >= r.pt1.x && p.x < r.pt2.x && p.y >= r.pt1.y && p.y < r.pt2.y; } 这里假定矩形按照规范形式表述,即 pt1 的坐标值小于 pt2 的坐标值。接下来的函数返回一个 确保为规范形式的矩形: #define min(a, b) ((a) < (b) ? (a) : (b)) #define max(a, b) ((a) > (b) ? (a) : (b)) /* canonrect:将矩形的坐标规范化 */ struct rect canonrect(struct rect r) { struct rect temp; temp.pt1.x = min(r.pt1.x, r.pt2.x); temp.pt1.y = min(r.pt1.y, r.pt2.y); temp.pt2.x = max(r.pt1.x, r.pt2.x); temp.pt2.y = max(r.pt1.y, r.pt2.y); return temp; } 如果需要将一个大型结构传递给一个函数,通常传递指针要比复制整个结构的效率更高。 结构指针就像其他变量的指针一样。声明 struct point *pp; 表示 pp 是一个 struct point 结构类型的指针。如果 pp 指向一个 point 结构,那么*pp 即 是这个结构,(*pp).x 和(*pp).y 则是其成员。要使用 pp,我们可能编写如下代码: struct point origin, *pp; pp = &origin; printf(“origin is (%d, %d)\n”, (*pp).x, (*pp).y); (*pp).x 中的圆括号是必须的,因为结构成员运算符 . 的优先级高于 * 。表达式 *pp.x 的 意义是 *(pp.x) ,由于这里 x 并不是一个指针,因此该表达式是非法的。 结构指针使用得相当频繁,以至于 C 语言提供了另一种简写记法。如果 p 是一个结构指针, 那么 p->结构成员 即引用了指定的成员。(运算符 -> 为一个负号紧接着 > 号。)所以上面的代码可以改写为 printf(“origin is (%d, %d)\n”, pp->x, pp->y); 运算符 . 和 -> 都为自左向右结合,因此假如有 struct rect r, *rp = &r; 那么以下四个表达式是等价的: r.pt1.x rp->pt1.x (r.pt1).x (rp->pt1).x 结构运算符 . 和 -> 与用于函数调用的 () 和用于下标的 [] 一起,处于运算符优先等 级的顶端,因而结合得非常紧密。例如,给定声明 struct { int len; char *str; } *p; 则 ++p->len 递增的是 len,而不是 p,因为其隐含的带圆括号的表达式是++(p->len)。圆括号可用于改 变结合关系:(++p)->len 在访问 len 之前先递增 p,而(p++)->len 在之后才递增 p。(最 后这组圆括号不是必需的。) 同样地,*p->str 获取 str 指向的内容;*p->str++ 在访问它指向的内容之后将 str 递增(就像*s++ 那样);(*p->str)++递增 str 指向的内容;而 *p++->str 在访问 str 指向的内容之后将 p 递增。 6.3 结构数组 请考虑编写一个程序来计算每个 C 关键字出现的次数。我们需要一个字符串数组来存放关 键字的名字,和一个整数数组来存放计数。一种可能是使用两个并列的数组,keyword 和 keycount,即如 char *keyword[NKEYS]; int keycount[NKEYS]; 但正是数组并列这一事实提示了另一种不同的构成方式——结构数组。每个关键字项都是一对 变量: char *word; int count; 因此有一组这样的变量对。结构声明 struct key { char *word; int count; } keytab[NKEYS]; 声明了一个结构类型 key,定义了此结构类型的数组 keytab,并为它们预留了存储空间。此 数组的每个元素都是一个结构。这一声明也可写为 struct key { char *word; int count; }; struct key keytab[NKEYS]; 由于结构 keytab 包含了一组名字常值,最容易的方法是将其设为外部变量,并在定义时 一次性初始化完毕。此结构的初始化类似于早先的例子——定义之后跟随着花括号内的一组初 始化值: struct key { char *word; int count; } keytab[] = { “auto”, 0, “break”, 0, “case”, 0, “char”, 0, “const”, 0, “continue”, 0, “default”, 0, /* … */ “unsigned”, 0, “void”, 0, “volatile”, 0, “while”, 0, }; 与结构成员对应,初始化值两两成对而列。若按每“行”(即每个结构)将初始化值用花括号括 起来则会更加精确。如 { “auto”, 0 }, { “break”, 0 }, { “case”, 0 }, … 但是当初始化值是简单变量或字符串,且所有值都给出时,内部的花括号不是必需的。与通常 一样,如果数组 keytab 提供了初始化值并且 [] 内为空,那么其项数将通过计算确定。 此关键字计算程序首先是 keytab 的定义。主函数通过重复调用函数 getword 来读取输 入,每次调用获取一个词。每个词都在 keytab 中进行查找,使用的是在第 3 章中编写的二分 查找函数。关键字列表必须按照升序排列。 #include #include #include #define MAXWORD 100 int getword(char *, int); int binsearch(char *, struct key *, int); /* 计算 C 的关键字 */ main() { int n; char word[MAXWORD]; while (getword(word, MAXWORD) != EOF) if (isalpha(word[0])) if((n = binsearch(word, keytab, NKEYS)) >= 0) keytab[n].count++; for (n = 0; n < NKEYS; n++) if (keytab[n].count > 0) printf(“%4d %s\n”, keytab[n].count, keytab[n].word); return 0; } /* binsearch:在 tab[0]至 tab[n-1]中查找关键字 */ int binsearch(char *word, struct key tab[], int n) { int cond; int low, high, mid; low = 0; high = n - 1; while (low <= high) { mid = (low + high) / 2; if ((cond = strcmp(word, tab[mid].word)) < 0) high = mid - 1; else if (cond > 0) low = mid + 1; else return mid; } return -1; } 稍后我们将描述函数 getword;现在只用知道 getword 每次调用找到一个词,并将其拷贝到 该函数第一参数所指定的数组中。 常量 NKEYS 为 keytab 中关键字的个数。虽然可以用手工计数,但通过机器来做要容易和 安全得多,在关键字列表可能改变时尤其如此。一种可能是用一个空指针作为初始化列表的结 尾,这样可按 keytab 循环直到找到结尾。 但是这不是必需的,由于数组的大小在编译时会完全确定。数组的大小是其一项的大小乘 以项数,所以关键字数组的项数就是 keytab 的大小 / 结构 key 的大小 C 提供了一个叫做 sizeof 的编译时用的一元运算符,其可用于计算任一对象的大小。表达式 sizeof 对象 以及 sizeof (类型名) 生成一个整数,其等于所指定对象或类型的按字节计的大小。(严格地说,sizeof 产生一个类 型为 size_t 的无符号整数值,该类型在头文件 中定义。)对象可以是变量、数 组或者结构;类型名可以是像 int 或 double 等的基本类型名,或者是结构或指针等派生类型 名。 在我们的例子中,关键字的数目就是数组的大小除以其中一个元素的大小。此计算方法被 用在一个设置 NKEYS 值的 #define 声明里: #define NKEYS (sizeof keytab / sizeof(struct key)) 它的另一种写法是用数组大小除以一个指定元素的大小: #define NKEYS (sizeof keytab / sizeof keytab[0]) 这样写的好处是假如类型变化也不需要进行改变。 sizeof 不能被用于 #if 行中,因为预处理器并不解析类型名。但 #define 中的表达 式却不由预处理器处理,因此这里的代码是合法的。 现在考虑 getword 函数。我们编写了一个比本程序所需功能更为通用的 getword 函数, 但其并不复杂。getword 从输入中获取最新到来的“词”,这里的词可以是以字母开头的由字母 或数字组成的串,或者是单个非空白字符。函数返回值为该词的第一个字符、或者为表示文件 结束的 EOF、或者是非字母的字符本身。 /* getword:从输入中得到接下来的词或字符 */ int getword(char *word, int lim) { int c, getch(void); void ungetch(int); char *w = word; while (isspace(c = getch())) ; if (c != EOF) *w++ = c; if (!isalpha(c)) *w = ‘\0’; return c; } for ( ; --lim > 1; w++) if (!isalnum(*w = getch())) { ungetch(*w); break; } *w = ‘\0’; return word[0]; } getword 使用了第 4 章编写的 getch 和 ungetch。当一个字母数字标记收集结束时,getword 已经多取了一个字符,于是调用 ungetch 将该字符推回输入以便于下一次调用。getword 还 使用 isspace 来跳过空白符、使用 isalpha 来识别字母、使用 isalnum 来识别字母和数字; 它们都来自于标准头文件 。 练习 6-1. 我们的 getword 版本不能正确地处理下划线、字符串常量、注释、以及预处理控 制行。请编写一个更好的版本。 6.4 结构指针 为了阐明对结构指针与结构数组的一些相关考虑,让我们再次编写关键字计数程序,这次 用指针来替代数组下标。 外部声明 keytab 不用改变,但是 main 和 binsearch 都需要修改。 #include #include #include #define MAXWORD 100 int getword(char *, int); struct key *binsearch(char *, struct key *, int); /* 计算 C 的关键字;指针版本 */ main() { char word[MAXWORD]; struct key *p; while (getword(word, MAXWORD) != EOF) if (isalpha(word[0])) if((p = binsearch(word, keytab, NKEYS)) != NULL) p->count++; for (p = keytab; p < keytab + NKEYS; p++) if (p->count > 0) printf(“%4d %s\n”, key->count, p->word); return 0; } /* binsearch:在 tab[0]至 tab[n-1]中查找关键字 */ struct key *binsearch(char *word, struct key *tab, int n) { int cond; struct key *low = &tab[0]; struct key *high = &tab[n]; struct key *mid; while (low < high) { mid = low + (high - low) / 2; if ((cond = strcmp(word, mid->word)) < 0) high = mid; else if (cond > 0) low = mid + 1; else return mid; } return NULL; } 这里有几件事情值得一提。首先,binsearch 的声明必须标明它返回一个 struct key 指针,而不是返回整数;这在 binsearch 和其函数原型中都要进行声明。如果 binsearch 找到了关键字,它返回指向该关键字的指针;如果没有找到,则返回 NULL。 其次,keytab 的元素现在通过指针来访问。这一点要求 binsearch 有明显的改变。 low 和 high 的初始化值现在分别是指向表开头的指针和指向表末尾之后第一个位置的指 针。 中间元素的计算不再是 mid = (low + high) / 2 /* 错误 */ 那么简单了,因为两个指针相加是非法的。但是,它们相减却是合法的,high – low 即为元 素的个数,于是 mid = low + (high - low) / 2 使 mid 指向 low 与 high 中间的位置。 最重要的改变是调整算法来确保其不会产生非法指针或者访问超出数组范围的元素。问题 在于 &tab[-1] 和 &tab[n] 都在数组 tab 的范围之外。前者是完全非法的,而解引用后者 是非法的。然而,C 语言的定义保证了涉及数组尾部之后第一个元素的指针运算(即&tab[n]) 会正常地工作。 在 main 中我们用 for (p = keytab; p < keytab + NKEYS; p++) 如果 p 是指向结构的指针,对 p 的运算将考虑到结构的大小,因此 p++ 使得 p 增加恰当的大 小而指向结构数组的下一个元素,测试部分也会让循环正确地停止。 然而,不要假定结构的大小就等于其成员的大小之和。由于不同对象的对齐要求,结构中 可能存在未命名的“空洞”。例如,如果 char 是 1 字节、int 是 4 字节,那么结构 struct { char c; int i; }; 或许需要 8 个字节,而不是 5 个。sizeof 运算符返回正确的值。 最后,附带一个关于程序格式的问题:当函数返回像结构指针这样的复杂类型时,就如 struct key * binsearch(char *word, struct key *tab, int n) 语句中的函数名在文本编辑器中会较难浏览和查找,另一种时常使用的风格是: struct key * binsearch(char *word, struct key *tab, int n) 这是个人喜好问题;选择你所喜欢的形式并一直使用它。 6.5 自引用结构 假设我们需要处理更为通用的问题——统计输入中所有单词出现的次数。由于单词列表无 法事先知道,我们不能方便地将其排序并使用二分查找;也不能在每个单词到来时使用线性查 找,看它是否已经出现过,这样程序会非常耗时。(更精确地说,其运行时间很可能随着输入单 词数的增加而线性增长。)那我们如何组织数据才能高效地处理一个任意单词的列表呢? 一个解决方案是在每个单词到来时将其按序排放到正确的位置,从而始终将目前已出现的 单词保持有序。这不应该通过在一个线性数组中平移单词来完成,虽然可以那样做,但也会非 常耗时。我们代之使用一种称为二叉树的数据结构。 这颗树对于每个不同的单词都有一个“节点”;每个节点包含 一个指向单词文本的指针 一个出现次数的计数 一个指向左孩子节点的指针 一个指向右孩子节点的指针 没有节点会有多于两个孩子;它或许只有一个或零个孩子。 节点按这样的形式组织:任一节点的左子树仅包含那些(按字母序)小于该节点单词的单 词,而右子树则仅包含比其大的单词。下面就是句子“now is the time for all good men to come to the aid of their party”通过插入每个遇到的单词而构建成的树: now is the for men of time all good party their to aid come 为了确认一个新碰到的单词是否已经存在于这棵树中,需要自根开始,将此新单词与该节点的 单词相比较。假如匹配,问题的答案是肯定的。如果该新单词较树中的单词小,则到节点的左 孩子继续查找,否则到其右孩子处查找。如果要查找的方向没有孩子,则该新单词不在树中, 这个空位就是加入该新单词的恰当位置。此过程是递归的,因为自任一节点的查找也包括了在 它某一孩子中的查找。相应地,插入和打印采用递归过程也会最自然。 回过来考虑节点的描述问题,节点可方便地表述为包含 4 个成员的结构: struct tnode { /* 树的节点 */ char *word; /* 指向单词的文本 */ int count; /* 出现的次数 */ struct tnode *left; /* 左孩子 */ struct tnode *right; /* 右孩子 */ }; 节点的这种递归声明或许看起来令人提心吊胆,但它是正确的。一个结构包含它自己的实例是 非法的,但 struct tnode *left; 将 left 声明为 tnode 的指针,而不是 tnode 本身。 偶尔地,人们会需要自引用结构的一种变体:两个结构相互引用。其构建方式如下: struct t { … struct s *p; /* p 指向一个 s */ }; struct s { … struct t *q; /* q 指向一个 t */ }; 有了如 getword 等几个已经编写过的函数支持,整个程序的代码出人意料地小。主函数使 用 getword 读取单词,并使用 addtree 将它们放置到树中。 #include #include #include #define MAXWORD 100 struct tnode *addtree(struct tnode *, char *); void treeprint(struct tnode *); int getword(char *, int); /* 单词频度统计 */ main() { struct tnode *root; char word[MAXWORD]; root = NULL; while (getword(word, MAXWORD) != EOF) if (isalpha(word[0])) root = addtree(root, word); treeprint(root); return 0; } addtree 是递归函数。单词被 main 函数递交到树的顶层(根)。在每一层,该单词与存 放在节点内的单词相比较,并通过递归调用 addtree 下传到其左子树或右子树,最后该单词要 么与树中已有的某单词相匹配(在这种情况下计数会被递增),要么碰到一个空指针,这表明必 须创建一个节点并将其加入此树中。如果创建了新的节点,addtree 会返回一个指向它的指针, 该指针会被加到它的父节点中。 struct tnode *talloc(void); char *strdup(char *); /* addtree:将一个包含 w 的节点加到 p 或 p 之下的位置 */ struct tnode *addtree(struct tnode *p, char *w) { int cond; if (p == NULL) { /* 出现了新单词 */ p = talloc(); /* 创建一个新节点 */ p->word = strdup(w); p->count = 1; p->left = p->right = NULL; } else if ((cond = strcmp(w, p->word)) == 0) p->count++; /* 重复出现的单词 */ else if (cond < 0) /* 小于则进入左子树 */ p->left = addtree(p->left, w); else /* 大于则进入右子树 */ p->right = addtree(p->right, w); return p; } 新节点的存储空间通过函数 talloc 获得,该函数返回一个指针,指向适合存放单个树节 点的空间。新单词通过 strdup 拷贝到一个隐藏空间。(我们很快就会讨论这些函数。)节点上 的计数被初始化,两个孩子被设为空。这部分代码仅在新节点被加入时在树的叶节点处执行。 我们省略了 strdup 及 talloc 的返回值出错检查(这样做并不明智)。 treeprint 按序打印这棵树;对于每个节点,它打印左子树(所有小于此节点单词的单词), 然后是此节点的单词本身,然后是右子树(所有较大的单词)。如果你还拿不准递归是如何工作 的,那么请模拟如上所述的 treeprint 对树的操作。 /* treeprint:按序打印树 p */ void treeprint(struct tnode *p) { if (p != NULL) { treeprint(p->left); printf(“%4d %s\n”, p->count, p->word); treeprint(p->right); } } 一个实用的注释:如果由于单词没有以随机的大小次序到达而使树变得“不平衡”,那么程 序运行的时间会大大增加。最坏的情况是,假如单词已经有序,那么此程序就是对线性查找的 一个高代价的模拟。存在一些不受此最坏情况影响的二叉树通用构建法则,但此处我们不作讨 论。 在我们结束这个例子之前,还值得简短讨论一下与存储空间分配器相关的一个枝节问题。 很显然,在一个程序中只期望存在一个存储空间分配器,即便它要分配不同类型的对象。但是 假如一个分配器需要处理,举例来说,对字符指针的请求和对 struct tnode 指针的请求,就 出现了两个问题:第一,如何满足多数实际机器都存在的要求,即某些类型的对象必须满足一 些对齐限制(例如,整数常常必须位于偶地址)?第二,什么样的声明才能应对一个分配器必 须按需返回不同类型的指针这一实际问题? 对齐要求一般容易满足,这要以一些空间浪费作为代价,以确保分配器总是返回满足所有 对齐限制的指针。第 5 章的 alloc 不提供任何对齐保证,因此我们将使用标准库函数 malloc, 它可以做到这点。在第 8 章中我们将展示一种实现 malloc 的方法。 类似malloc的函数的类型声明对于任何严格对待类型检查的语言来说都是一个恼人的问 题。在C中,正确的方法是将malloc声明为返回一个void型指针,并显式地将指针强制转换为 所期望的类型 ①。malloc以及相关的函数在标准头文件 中声明。这样talloc 就可被编写为: #include /* talloc:构建一个 tnode */ struct tnode *talloc(void) { return (struct tnode *) malloc(sizeof(struct tnode)); } ① 就 1988-1989 ANSI/ISO 标准而言,void * 到 ALMOSTANYTYPE * 的转换是自动的,因此这种强制转换并 不必要;而且如果 malloc 或其替代者未声明为返回 void *,显式的强制转换还会掩盖这一不经意的错误。另一 方面,在 ANSI 标准之前,这种强制转换则是必须的。 strdup 所做的只是将其参数给出的字符串复制到一个安全的地方,此安全空间通过调用 malloc 获得: char *strdup(char *s) /* 构建一个 s 的副本 */ { char *p; p = (char *) malloc(strlen(s)+1); /* +1 为了存放‘\0’ */ if (p != NULL) strcpy(p, s); return p; } 若没有空间可以分配,malloc 返回 NULL;strdup 将此值传回,把错误处理留给它的调用者。 通过调用 malloc 获得的存储空间可以通过调用 free 释放以供重新分配;请参见 7、8 两章。 练习 6-2. 编写一个程序,它读取一个 C 程序并按字母序将变量名分组打印出来,前 6 个字符 相同(而之后有所不同)的变量分为一组。不要计算字符串和注释内的单词。将 6 改为一个参 数,可以通过命令行进行设置。 练习 6-3. 编写一个交叉引用程序,打印一个文档中所有单词的列表,并打印每个单词出现的 行的行数列表。去掉如“the”、“and”等干扰性单词。 练习 6-4. 编写一个程序,将其输入中的不同单词按出现频度降序排列打印出来。每个单词之 前是它出现的次数。 6.6 表的查询 在本节我们将编写一个表查询软件包的内部构成程序,以此阐述结构的更多方面。这是有 可能在微处理器的或编译器的符号表管理程序中找到的典型代码。例如,考虑 #define 语句, 当碰到如 #define IN 1 的行时,名字 IN 以及替换文本 1 被存放到一个表中。之后,当名字 IN 出现在如 state = IN; 的声明中时,它必须被替换为 1。 有两个函数用于操作名称和替换文本行。install(s, t) 将名字 s 和替换文本 t 记录在 表中;s 和 t 都是字符串。lookup(s) 在该表中查找 s,并返回 s 所在位置的指针,如果它 不在表中,则返回 NULL。 所用算法是哈希查找法——到来的名字被转换为一个小的非负整数,该整数作为一个指针 数组的下标。每个数组元素指向一个(描述名字的块的)链表的开头,该链表中的名字都具有 对应的哈希值(即该下标)。如果没有名字被哈希为该下标值,那么对应的数组元素为 NULL。 0 0 0 0 name defn name defn 0 name defn 块列表中的块是一个结构,其包含了名字的指针、替换文本的指针以及列表中后续块的指 针。后续指针为空则标志这个列表结束。 struct nlist { /* 表项:*/ struct nlist *next; /* 链表中的后续项 */ char *name; /* 所定义的名字 */ char *defn; /* 替换文本 */ }; 指针数组即 #define HASHSIZE 101 static struct nlist *hashtab[HASHSIZE]; /* 指针表 */ lookup 和 install 都要用到哈希函数,该函数通过将字符串的每一字符值与由该字符之 前其余字符混杂而成的值相加,最后将所得混合值按数组大小取模,并返回余值。这不是可能 的最优哈希函数,但是它既短小又有效。 /* hash:形成字符串 s 的 hash 值 */ unsigned hash(char *s) { unsigned hashval; for (hashval = 0; *s != ‘\0’; s++) hashval = *s + 31 * hashval; return hashval % HASHSIZE; } 无符号运算保证了哈希值是非负值。 此哈希过程产生出数组 hashtab 中的一个起始下标;如果被哈希的字符串会在某个位置找 到,那么它一定位于此处起始的块列表中。这个查找通过 lookup 来执行,如果 lookup 找到 了已经存在的项,它返回指向该项的指针,否则返回 NULL。 /* lookup:在 hashtab 中查找 s */ struct nlist *lookup(char *s) { struct nlist *np; for (np = hashtab[hash(s)]; np != NULL; np = np->next) if (strcmp(s, np->name) == 0) return np; /* 找到 */ return NULL; /* 未找到 */ } lookup 中的 for 循环是遍历链表的标准惯用法: for (ptr = head; ptr != NULL; ptr = ptr->next) … install 使用 lookup 来确认待加入的名字是否已经存在;如果存在,新的定义将替换掉 旧的定义。否则,一个新项将被创建。如果由于任何理由没有保存新项的空间,install 将返 回 NULL。 struct nlist *lookup(char *); char *strdup(char *); /* install:将 (name, defn) 放到 hashtab 中 */ struct nlist *install(char *name, char *defn) { struct nlist *np; unsigned hashval; if ((np = lookup(name)) == NULL) { /* 未找到 */ np = (struct nlist *) malloc(sizeof(*np)); if (np == NULL || (np->name = strdup(name)) == NULL) return NULL; hashval = hash(name); np->next = hashtab[hashval]; hashtab[hashval] = np; } else /* 已经有了 */ free((void *) np->defn); /* 将原有的 defn 释放 */ if ((np->defn = strdup(defn)) == NULL) return NULL; return np; } 练习 6-5. 编写一个 undef 函数,它从 lookup 与 install 维护的表中删除某个名字及定义。 练习 6-6. 基于本节的函数,实现一个简单的(譬如没有参数)适用于 C 程序的 #define 处 理器版本。你可能还会发现 getch 和 ungetch 有帮助。 6.7 类型定义(Typedef) C 语言提供了一个称为 typedef 的功能用于创建新的数据类型名。例如,声明 typedef int Length; 使得名字 Length 成为 int 的同义词。此 Length 类型可被用于声明、强制转换等等,用法与 int 类型完全一致: Length len, maxlen; Length *lengths[]; 类似地,声明 typedef char *String; 使 String 成为 char *(即字符指针)的同义词,随后它即可用于声明和强制转换: String p, lineptr[MAXLINES], alloc(int); int strcmp(String, String); p = (String) malloc(100); 注意 typedef 所声明的类型出现在变量名的位置,而不是直接跟在关键字 typedef 之后。 typedef 在语法上与 extern、static 等存储类相似。我们让 typedef 定义的名字用大写 开头,以示差别。 作为一个更复杂的例子,我们可将本章先前所述的树节点用 typedef 来定义: typedef struct tnode *Treeptr; typedef struct tnode { /* 树节点 */ char *word; /* 指向单词的文本 */ int count; /* 出现次数 */ Treeptr left; /* 左孩子 */ Treeptr right; /* 右孩子 */ } Treenode; 这里创建了两个新的类型关键字 Treenode(一个结构)和 Treeptr(该结构的指针)。这样 函数 talloc 就可改写为: Treeptr talloc(void) { return (Treeptr) malloc(sizeof(Treenode)); } 必须强调的是 typedef 声明并没有创造任何意义上的新类型;它只是为现有的类型增加了 一个新名字。它也没有创造任何新的语义:用这种方式声明的变量与由显式拼写的类型所声明 的变量的特性完全一致。从效果上看,typedef 与 #define 类似,不同之处是由于它可以被 编译器解释,因而可以应付那些超出预处理器能力的文本替换。例如, typedef int (*PFI)(char *, char *); 创建了类型 PFI,它表示“指向返回 int 的(且拥有两个 char * 参数的)函数的指针”,其 可被用于第 5 章的排序程序这样的上下文中,譬如 PFI strcmp, numcmp; 撇开纯粹的审美因素,采用 typedef 有两个主要的理由。第一个是将程序参数化以应对移 植性问题。假如 typedef 用于那些可能与机器相关的数据类型,那么当程序被移植时只有 typedef 的定义需要改变。一种普遍情况是使用 typedef 名字代表不同的整数类型,这样可 以为每类主机选择一个恰当的 short、int 和 long 的集合。像来自标准库中的 size_t 和 ptrdiff_t 类型就是实际的例子。 采用 typedef 的第二个目的是为程序提供更好的说明性——一个叫做 Treeptr 的类型会 比一个只是声明为某复杂结构的指针类型要更容易理解。 6.8 联合(Union) 联合是一个变量,它可以(在不同的时间)包含不同类型和大小的对象,并由编译器来追 踪大小和对齐要求。联合提供了一种在单个存储区域操控不同类型的数据的途径,且不需要在 程序中嵌入任何与具体机器相关的信息。它相当于 Pascal 中的可变记录。 在编译器的符号表管理器中有可能会找到一个例子:假如一个常量可以是一个 int、或一 个 float、或一个字符指针;特定常量的值必须存放在正确类型的变量中,然而假如一个值无 论何种类型都占用相同大小的存储空间,那么仍然是最方便的表管理方式。这正是联合的目的 ——一个单独的变量可以合法地包含多种类型中的任意一个。联合的语法基于结构: unison u_tag { int ival; float fval; char *sval; } u; 变量 u 将会大到足以包含这三个类型中最大的一个;其实际的大小依赖于具体实现。u 可 以被赋给这些类型中的任意一种,然后用于表达式,只要它的用法是一致的:从 u 获取的类型 必须是最新存放的类型。跟踪最新存放在联合中的是哪种类型是编程人员的责任;假如联合以 一种类型存放并以另一种类型获取,则其结果依赖于具体实现。 在语法上,联合的成员就像结构一样通过 联合名.成员 或 联合指针->成员 进行访问。如果变量 utype 被用于跟踪 u 的当前存放的类型,那么或许会看到如下这样的代码: if (utype == INT) printf(“%d\n”, u.ival); else if (utype == FLOAT) printf(“%f\n”, u.fval); else if (utype == STRING) printf(“%s\n”, u.sval); else printf(“bad type %d in utype\n”, utype); 联合可以出现在结构和数组中,反之亦然。对结构中的联合(或者反过来)的成员的访问 方式与嵌套结构的访问方式相同。例如,在如下定义的结构数组中 struct { char *name; int flags; int utype; union { int ival; float fval; char *sval; } u; } symtab[NSYM]; 对成员 ival 的引用通过 symtab[i].u.ival 完成,而对字符串 sval 的首字符的引用通过下面两者之一完成。 *symtab[i].u.sval symtab[i].u.sval[0] 就效果而言,联合就是一个其所有成员相对于基本位置的偏移都是零的结构,该结构大到 足以容纳其中“最宽”的成员,并且其对齐方式适合于联合中所有的类型。对结构的操作同样允 许用于联合:可以按整个单元进行赋值或复制、获取地址、以及访问成员。 联合只可以使用其第一个成员类型的值进行初始化;即上面描述的联合 u 只能使用整数值 进行初始化。 第 8 章中的存储空间分配器展示了联合是如何能够用来强制一个变量对齐到某个特定类型 的存储空间边界上的。 6.9 位域(Bit-fields) 当存储空间极为宝贵时,可能有必要将几个对象压缩到单个机器字中;一种普遍的用法是 类似编译器符号表等应用中的按位标记集合。一些外部强制数据格式(譬如对硬件设备的接口) 也常常要求具有获取一个字的片段的能力。 想象某个编译器操作符号表时的片段。程序中的每个标识符都附带有一定信息,例如,它 是否是一个关键字,是否是外部变量且(或)是静态变量,等等。将这些信息编码的最紧凑方 式就是在单个 char 或 int 之内的一组按位标记。 做到这样的常用方法是定义一组与相关比特位对应的“掩码”,就如 #define KEYWORD 01 #define EXTERNAL 02 #define STATIC 04 或者 enum { KEYWORD = 01, EXTERNAL = 02, STATIC = 04 }; 这些数字必须是 2 的幂次。这样对这些位的访问就变成了使用第 2 章所描述的移位、掩码、反 码等运算符“按位操控”的问题。 某些惯用法出现得很频繁: flags |= EXTERNAL | STATIC; 将 flags 中的 EXTERNAL 位与 STATIC 位打开,而 flags &= ~(EXTERNAL | STATIC); 将它们关闭,以及 if ((flags & (EXTERNAL | STATIC)) == 0) … 当这两个位都关闭时为真。 尽管这些惯用法很容易被掌握,但另一种选择是 C 语言提供的可直接定义和访问字内位域 的能力,而无需使用位逻辑运算符。位域(bit-field),或简称为域(field),是位于一个 我们称之为“字”的存储单元内的一组相邻的比特位,该存储单元字由具体实现定义。定义和 访问域的语法基于结构。例如,上面 #define 的符号表可以被替换为三个域的定义: struct { unsigned int is_keyword : 1; unsigned int is_extern : 1; unsigned int is_static : 1; } flags; 此处定义了一个称为 flags 的变量,它包含三个 1 比特位的域。冒号之后的数代表了该域的比 特位宽度。这些域被声明为 unsigned int 以确保它们是无符号的量。 单个域的引用方式与其他结构成员一样:flags.is_keyword、flags.is_extern 等 等。域就像小的整数,并且就像其他整数一样可以参与算术表达式的运算。于是之前的例子就 可以更自然地写成: flags.is_extern = flags.is_static = 1; 用于打开这两个位; flags.is_extern = flags.is_static = 0; 用于关闭这两个位;而 if (flags.is_extern == 0 && flags.is_static == 0) … 用于测试它们。 关于域的几乎所有的内容都依赖于具体实现。一个域是否可以跨字的边界由具体实现定义。 域不一定要有名字;未命名域(只有冒号和宽度)用于空间填充。特殊宽度 0 可以被用于强制 对齐到下一个字的边界。 域在一些机器中从左至右分配,而在另一些机器中从右至左分配。这意味着尽管域对于维 护内部定义的数据结构很有用,但当获取外部定义的数据的片断时,哪一端先出现的问题需要 仔细考虑;依赖于这些因素的程序是不可移植的。域可以仅用 int 进行声明;但为了移植性, 请显式地指定 signed 或者 unsigned。它们不是数组,也没有地址,因此 & 运算符不能作用 于它们。 第 7 章 输入与输出 输入/输出功能并不是 C 语言本身的组成部分,所以到目前为止,我们并没有过多地强调它 们。然而,程序与所在环境之间的交互方式比之前展示的要复杂许多。本章将描述标准库—— 一组为 C 程序提供输入/输出、字符串处理、存储管理、数学运算以及其他一些功能服务的函数 集合。讨论的重点将放在输入/输出上。 ANSI 标准精确地定义了这些库函数,所以在任何可使用 C 语言的系统中这些函数形式都相 互兼容。如果程序与系统交互的部分仅仅使用了标准库提供的功能,那么这些程序不经修改就 可以从一个系统移植到另一个系统。 这些库函数的特性分别声明在十几个头文件中,我们已经见过其中一些,如 。我们不打算将整个标准库都罗列于此,因为我们更关心如何使 用标准库编写 C 程序。附录 B 对标准库进行了详细的描述。 7.1 标准输入/输出 正如第 1 章所述,标准库实现了一个简单的文本输入/输出模型。文本流由一连串文本行组 成,每行以一个换行符结尾。如果系统不是这种运作方式,标准库将负责使该系统看上去像是 这样运作的。例如,标准库可能会在输入端将回车符和新行符转换为换行符,而在输出端进行 逆向转换。 最简单的输入机制是使用 getchar 函数从标准输入(一般为键盘)中一次读取一个字符: int getchar(void) getchar 函数在每次被调用时返回下一个输入字符;若遇到文件末尾,则返回 EOF。符号常量 EOF 在 中定义,其值一般为-1,但应当使用 EOF 而不是-1 来测试文件是否结束, 这样可使程序与 EOF 的特定值无关。 在许多环境中,可通过重定向符 < 实现输入重定向,以一个文件替代键盘输入:如果程序 prog 使用了 getchar,那么命令行 prog 文件名 重定向到 一个文件:例如,如果程序 prog 使用了 putchar,那么命令行 prog >outfile 会使得 prog 将写到标准输出的数据转而写入文件 outfile 中。如果系统支持管道,那么 prog | anotherprog 将把 prog 的标准输出连接至 anotherprog 的标准输入。 函数 printf 也向标准输出上输出数据。程序可能交替调用 putchar 和 printf,输出将 按照调用的顺序依次产生。 每一个使用输入/输出库函数的源文件都必须在首次使用之前包含如下代码行: #include 当文件名用尖括号 < 和 > 括上时,预处理器将在某些标准位置查找这个头文件(例如,在 UNIX 系统中,典型的位置是在目录/usr/include 中)。 许多程序仅从一个输入流中读取数据并只向一个输出流写出数据。对于这类程序,使用函 数 getchar、putchar 和 printf 来实现输入/输出可能已完全够用,用于起步当然也足够了, 这当重定向被用于将某一程序的输出连接至另一程序的输入的情况时尤其如此。例如,考察下 面的 lower 程序,它将输入转换为小写字母的形式: #include #include main() /* lower:将输入转换为小写形式 */ { int c; while ((c = getchar()) != EOF) putchar(tolower(c)); return 0; } 函数 tolower 在头文件 中定义;它将大写字母转换为小写形式,并将其余 字符原样返回。正如之前提及,头文件 中的 getchar 和 putchar 以及 中的 tolower 等“函数”常常为宏,这样可避免为每个字符都进行函数调用的开 销。我们将在 8.5 节介绍它们的实现方法。无论 中的函数在给定机器上如何实现, 使用这些函数的程序都不必了解字符集的知识。 练习 7-1. 编写一个程序,根据程序调用名(放在 argv[0]中),实现将大写字母转换为小写 字母或将小写字母转换为大写字母的功能。 7.2 格式化输出——printf 函数 输出函数 printf 用于将内部数值转换为字符的形式。在前面的章节中我们曾非正式地使 用过这个函数。本节涵盖了它最典型的一些用法,但并不完全;关于 printf 的完整描述参见 附录 B。 int printf(char *format, 参数 1, 参数 2, …) 函数 printf 通过输出格式 format 的控制,将其参数进行转换与格式化,并在标准输出上打 印出来。它的返回值为所打印的字符数。 格式化字符串包含两种类型的对象:普通字符和转换规格说明。在输出时普通字符原样不 动复制到输出流中,而转换规格说明并不直接输出到输出流中,而是用于控制 printf 中后续 参数的转换和打印。每一个转换规格说明都由一个百分号 % 引入,并以一个转换字符结束。在 字符%和转换字符中间可能依次包含如下成分: 负号,用于指定被转换的参数按照左对齐的形式输出。 数,用于指定最小字段宽度。被转换后的变元将在不小于最小字段宽的宽度中打印出 来。如果必要,字段左面(或右面,如果使用左对齐)多余的字符位置用空格填充以 保证最小字段宽度。 小数点,用于将字段宽和精度分开。 数,用于表示精度,即指定一个字符串中所要打印的最大字符数,或一个浮点数小数 点后的位数,或一个整数最少输出的数字数目。 字母 h 或 l, h 表示将整数作为 short 类型打印,l 表示将整数作为 long 类型打印。 表 7-1 中列出了所有转换字符。如果 % 后面的字符不是一个转换规格说明,则该行为是 未定义的。 表 7-1 printf 函数的基本转换字符 字符 变元类型;输出形式 d, i int 类型;十进制数 o unsigned int 类型;无符号八进制数(不以 0 开头) x, X unsigned int 类型;无符号十六进制数(不以 0x 或 0X 开头),10~15 这六个数分别用 a~f 或 A~F 表示 u unsigned int 类型;无符号十进制数 c int 类型;单个字符 s char*类型;打印字符串的字符直至遇到空字符(‘\0’)或已打印了 由精度指定的字符数为止 f double 类型;十进制小数表示法:[-]m.dddddd,其中 d 的个数由 精度指定(缺省为 6) e, E double 类型;指数形式:[-]m.dddddd e±xx 或[-]m.dddddd E ±xx,其中 d 的数目由精度指定(缺省为 6) g, G double 类型;如果指数小于-4 或大于等于精度值,则用%e 或%E 格 式输出,否则用%f 格式输出。尾部的 0 和小数点不打印 p void * 类型;指针(取决于具体实现) % 不转换参数;打印一个百分号% 转换规格说明中,宽度或精度可用星号 * 表示,此时,宽度或精度的值通过转换下一个参 数(必须为 int 类型)来计算。例如,为了从字符串 s 中最多打印 max 个字符,可用如下语句: printf(“%.*s”, max, s); 格式转换的大部分内容已经在先前几章中有过阐述,但与字符串相关的精度部分我们还未 介绍。下表展示了在打印字符串“hello,world”(12 个字符)时各种转换规格说明所产生的 效果。我们在字段的前后加上冒号,这样可以清晰地显示出字段的宽度。 :%s: :hello, world: :%10s: :hello, world: :%.10s: :hello, wor: :%-10s: :hello, world: :%.15s: :hello, world: :%-15s: :hello, world : :%15.10s: : hello, wor: :%-15.10s: :hello, wor : 警告:函数 printf 用它的第一个参数来判断其后的参数数目以及它们的类型。如果参数 不够或者参数类型错误,那么函数处理将会混乱,使用者也会得到错误的结果。请注意如下两 个调用之间的差别: printf(s); /* 如果字符串 s 含有字符%,输出将出错 */ printf(“%s”, s); /* 安全做法 */ 函数 sprintf 进行与函数 printf 同样的转换,但它将输出存放在一个字符串中: int sprintf(char *string, char *format, 参数 1, 参数 2, …) 与之前一样,sprintf 函数根据 format 对参数序列进行格式化,只不过 sprintf 将结 果放到 string 中而不是标准输出中。string 的大小必须足以存放输出结果。 练习 7-2. 编写一个程序,以合理的方式打印任意输入的内容。该程序至少应能根据用户习惯 以八进制或十六进制打印非显示字符,并将过长的文本行进行拆分。 7.3 变长参数表 本节以实现 printf 函数的一个迷你版本为例,介绍如何以可移植的方式编写能处理可变 长度的参数表的函数。由于重点在于参数的处理,因此函数 minprintf 只处理格式字符串和 参数表,格式转换则通过调用 printf 实现。 函数 printf 的正确说明形式如下: int printf(char *fmt, …) 其中,声明 … 表示对应参数的数量和类型可能会变化。声明 … 只能在参数表的最后位置出现。 由于不必让 minprintf 像函数 printf 那样返回实际输出的字符数,我们将它声明为如下形 式: void minprintf(char *fmt, …) 关键技巧在于 minprintf 如何遍历一个甚至连名字也没有的参数表。标准头文件 中包含了一组宏定义,它们对如何依次处理参数表进行了定义。此头文件的实现 因机器不同而变化,但其提供的接口是一致的。 类型 va_list 用于声明一个依次引用每个参数的变量。在函数 minprintf 中,该变量被 称为 ap,意思是“argument pointer(参数指针)”。宏 va_start 将 ap 初始化,使其指 向第一个无名参数。在 ap 被使用之前,这个宏必须被调用一次。参数表中必须至少包括一个有 名参数;va_start 用最后一个有名参数来确定起始位置。 每调用一次 va_arg,它就返回一个参数,并将 ap 指向下一个参数。va_arg 使用一个类 型名以确定返回何种类型的对象以及 ap 移动的步长。最后,必须在函数返回之前调用 va_end, 完成必要的清理工作。 上述特性构成了实现我们的简化 printf 的基础: #include /* minprintf:带有可变参数表的迷你 printf 函数 */ void minprintf (char *fmt, …) { va_list ap; /* 依次指向每一个无名参数 */ char *p, *sval; int ival; double dval; va_start(ap, fmt); /* 使 ap 指向第一个无名参数 */ for (p = fmt; *p; p++) { if (*p != ‘%’) { putchar(*p); continue; } switch (*++p) { case ‘d’: ival = va_arg(ap, int); printf(“%d”, ival); break; case ‘f’: dval = va_arg(ap, double); printf(“%f”, dval); break; case ‘s’: for (sval = va_arg(ap, char *); *sval; sval++) putchar(*sval); break; default: putchar(*p); break; } } va_end(ap); /* 完成之后的清理工作 */ } 练习 7-3. 改写 minprintf 函数,使其能处理 printf 函数的更多功能。 7.4 格式化输入——scanf 函数 scanf 是与输出函数 printf 相对应的输入函数,它从反方向提供了许多与 printf 同样 的转换功能,其函数原型如下: int scanf(char *format, …) scanf 从标准输入中读取字符序列,根据 format 中的转换规格说明对其进行解析,并将 结果存放在其余的参数中。格式参数 format 将在下面讨论;其余每个参数都必须是指针,用 于指明对应的转换输入应该存放的位置。与 printf 一样,本节只总结了 scanf 的一些最有用 的特性,并未列出全部内容。 当scanf的格式串全匹配光或者当一些输入无法与控制说明相匹配时,它就停止运行,并 返回成功匹配并赋值的输入项的个数。返回值可作为判断已获得输入值的输入项数的依据。如 果是文件结尾,则返回EOF;注意这与返回 0 值不同,0 表示下一个输入字符和格式串中的第一 个说明不匹配。下次对此函数的调用将从上次转换 ①的最后一个字符的下一字符开始继续搜索。 还存在一个输入函数 sscanf,其用于从一个字符串(而不是标准输入)中读取字符序列: int sscanf(char *string, char *format, 参数 1, 参数 2, …) 它按照 format 中的格式从 string 中读取字符序列,并通过参数 1、参数 2 等等存放转换结果。 这些参数必须是指针。 输入格式串一般都包含转换规格说明,用以控制输入的转换。格式化串可能包含如下成分: ① 此处的“转换”可理解为“匹配成功”,译者注。 空格或制表符,在处理过程中将被忽略。 普通字符序列(不包括%),用于匹配输入流中下面尚待读入的非空白字符序列。 转换规格说明,由一个%、一个可选的*(赋值屏蔽字符)、一个可选的整数(用于指定 字段最大宽度)、一个可选的 h、l 或 L(用于指明转换对象的宽度)以及一个转换字符 组成。 每一转换规格说明用于依次指导每一输入字段的转换。通常转换结果会放到相应参数所指向的 变量中。但是,如果转换规格说明中包含表示赋值屏蔽的字符 * ,则会跳过该输入字段,不进 行赋值。输入字段被定义为一个由非空白符组成的字符串,字段范围或者直至下一个空白符, 或者直至字段所指定的最大宽度。这意味着 scanf 会越过行边界查找它的输入,因为换行符也 是空白符(空白符包括空格、横向制表符、换行符、回车符、纵向制表符以及换页符)。 转换字符用于指明如何解释输入字段。按照 C 语言的按值调用的语义,对应的参数必须是 一个指针。转换字符如表 7-2 中所示: 表 7-2. scanf 函数的基本的转换字符 字符 输入数据;参数类型 d 十进制整数;int *类型 i 整数;int *类型。可以是八进制(以 0 开头)或十六进制整数(以 0x 或 0X 开头) o 无符号八进制整数(以 0 开头或不以 0 开头);unsigned int *类型 u 无符号十进制整数;unsigned int *类型 x, X 无符号十六进制整数(以或不以 0x 或 0X 开头);unsigned int *类型 c 字符;char *类型。将下(几)个输入字符(缺省字符数为 1)存放到指 定的位置。该转换规格说明不跳过空白符,若要读取下一个非空白字符, 使用%1s s 字符串(不加引号);char *类型,指向一个字符数组,其足以存放该字 符串以及在该串末尾加上的一个空字符(’\0’) e, f, g 可带前缀正负号、小数点及指数部分的十进制浮点数;float *类型 % 字符%本身;不进行任何赋值操作 在转换字符 d、i、o、u 及 x 之前可以加上字符 h 或 l。h 用于表明参数列表中的对应项 是一个指向 short 而不是 int 的指针,l 则表明对应项为指向 long 的指针。类似地,转换字 符 e、f 及 g 之前可以加上 l,表明参数列表中的对应项是一个指向 double 而不是 float 的 指针。 作为第一个例子,下面用函数 scanf 进行输入转换来改写第 4 章的原始计算器程序: #include main() /* 原始计算器程序 */ { double sum, v; sum = 0; while (scanf(“%lf”, &v) == 1) printf(“\t%.2f\n”, sum += v); return 0; } 假设我们希望读取包含如下日期形式的输入行: 25 Dec 1988 则相应的 scanf 语句为: int day, year; char monthname[20]; scanf(“%d %s %d”, &day, monthname, &year); monthname 没有加取址运算符&,因为数组名本身就是指针。 普通字符(串)也可出现在 scanf 的格式串中,但其必须与输入中同样的字符(串)相匹 配。因此可用下列 scanf 语句读取形如 mm/dd/yy 的日期数据: int day, month, year; scanf(“%d/%d/%d”, &month, &day, &year); scanf 忽略格式字符串中的空格和制表符;此外,在查找输入值时,它也会略过输入中的 空白符(空格、制表符、换行符等等)。为了读取没有固定格式的输入,通常最好是每次读入一 行,然后用 sscanf 逐个读入。例如,假设我们希望读取可能包含上述任一形式的日期的行时, 可以编写如下代码: while (getline(line, sizeof(line)) > 0) { if (sscanf(line, “%d %s %d”, &day, monthname, &year) == 3) printf(“valid: %s\n”, line); /* 形如 25 Dec 1988 */ else if (sscanf(line, “%d/%d/%d”, &month, &day, &year) == 3) printf(“valid: %s\n”, line); /* 形如 mm/dd/yy */ else printf(“invalid: %s\n”, line); /* 无效日期形式 */ } scanf 可与其他输入函数混合调用。scanf 调用之后下一输入函数的调用将从该 scanf 未读取的首个字符处开始读取数据。 最后提醒读者:scanf 和 sscanf 函数的参数都必须是指针。最常见的错误是将输入语句 写成 scanf(“%d”, n); 其正确形式应为 scanf(“%d”, &n); 这类错误在编译阶段通常检测不到。 练习 7-4. 参照上一节的 minprintf 函数,编写一个自己的 scanf 版本。 练习 7-5. 改写第 4 章的后缀计算器,用 scanf 和(或)sscanf 完成输入及数字转换。 7.5 文件访问 到目前为止,我们所讨论的例子都是从标准输入读取数据并向标准输出写入数据。标准输 入和标准输出是由程序所在的操作系统为程序自动定义的。 下面这一步要编写一个程序,对尚未与之关联的文件进行访问。cat 是一个可说明这种操 作需求的程序,它把一组有名文件的内容串联起来并输出到标准输出。cat 可用于将文件打印 到屏幕上,也可作为那些不能通过名字访问文件的程序的通用输入收集器。例如,命令 cat x.c y.c 将文件 x.c 和 y.c 的内容(而不包含任何其他内容)打印到标准输出上。 问题在于怎样使得这些有名文件能够被读取——即怎样将用户所用的外部文件名字和读取 这些文件的语句联系起来。 方法很简单:一个文件在可被读写之前,必须用库函数 fopen 打开。fopen 用诸如 x.c 或 y.c 这样的外部名字与操作系统进行某些必要的处理和协商(我们不必关心其细节),并返 回一个之后可用于文件读写的指针。 该指针称为文件指针,它指向一个包含文件信息的结构,例如:缓冲区的位置、缓冲区中 当前所指向的字符位置、文件是正在读或还是正在写、文件是否出错或是否已经达到文件末尾 等等。用户不必知道这些细节,因为 的定义中包括了一个称为 FILE 的结构声明, 使用文件指针只需声明即可,例如: FILE *fp; FILE *fopen(char *name, char *mode); 上述语句表示 fp 是一个指向 FILE 的指针,fopen 返回一个指向 FILE 的指针。注意 FILE 像 int 一样是类型名,而不是结构标记,它由 typedef 定义。(fopen 在 UNIX 系统中的实现细 节将在 8.5 节讨论。) 程序可以这样调用 fopen 函数: fp = fopen(name, mode); fopen 的第一个参数是一个字符串,包含待打开文件的名字。第二个参数是访问模式,它也是 一个字符串,表示调用者希望如何使用文件。允许的访问模式包括读(“r”)、写(“w”)以及 追加(“a”)。一些系统还区分文本和二进制文件,对后者的访问必须在模式串中添加字符“b”。 如果以写或追加方式打开一个不存在的文件,那么如有可能,系统将创建该文件。当以写 方式打开一个已存在的文件时,该文件的原有内容将被丢弃,而以追加方式打开时文件原有内 容将被保留。试图读一个不存在的文件会导致错误,也有其他原因可能导致错误,比如试图读 一个无读取权限的文件。如果有错误发生,fopen 将返回 NULL。(错误可以更精确地进行识别; 参见附录 B 第 1 节末尾关于错误处理函数的讨论。) 一旦文件被打开,接下来就需要某种途径来读写文件了。有几种可能的方法,getc 和 putc 是其中最简单的。getc 返回文件中的下一个字符,它需要文件指针来告知是哪个文件。 int getc(FILE *fp) getc 返回 fp 所指向的输入流中的下一个字符;如果到达文件末尾或发生错误,则返回 EOF。 putc 是一个输出函数: int putc(int c, FILE *fp) putc 将字符 c 写入 fp 指向的文件并返回所写入的字符;如果有错误出现,则返回 EOF。正如 getchar 和 putchar 一样,getc 和 putc 可以是宏而不是函数。 当一个 C 程序开始运行时,操作系统环境负责打开 3 个文件并提供相应的文件指针。这些 文件是标准输入、标准输出和标准错误,对应的文件指针分别为 stdin、stdout 和 stderr, 它们在 中声明。通常情况下,stdin 指向键盘,而 stdout 和 stderr 则指向 显示器;但正如 7.1 节中所述,stdin 和 stdout 可以被重定向到文件或管道。 getchar 和 putchar 可以用 getc、putc、stdin 及 stdout 定义如下: #define getchar() getc(stdin) #define putchar(c) putc((c), stdout) 为了格式化文件的输入输出,可以使用函数fscanf和fprintf。它们与scanf和printf 的区别仅在于它们的第一个参数是指向待读写文件的指针,格式串则是第二个参数。 int fscanf(FILE *fp, char *format, …) int fprintf(FILE *fp, char *format, …) 掌握上述预备知识之后,我们现在就能编写将一组文件串联起来的 cat 程序了。该程序的 设计与其他许多程序类似:若命令行中带有参数,则将它们作为文件名依次处理,若没有参数, 则使用标准输入。 #include /* cat:将一组文件串联起来,版本 1 */ main(int argc, char *argv[]) { FILE *fp; void filecopy(FILE *, FILE *); if (argc == 1) /* 如果未带参数,则复制标准输入 */ filecopy(stdin, stdout); else while (--argc > 0) if ((fp = fopen(*++argv, “r”)) == NULL) { printf(“cat: can’t open %s\n”, *argv); return 1; } else { filecopy(fp, stdout); fclose(fp); } return 0; } /* filecopy:将文件 ifp 复制到 ofp */ void filecopy(FILE *ifp, FILE *ofp) { int c; while ((c = getc(ifp)) != EOF) putc(c, ofp); } 文件指针 stdin 与 stdout 是 FILE *类型的对象。但它们是常量而不是变量,因而不可能对 其进行赋值。 函数 int fclose(FILE *fp) 的功能与 fopen 相反,它断开由 fopen 建立的文件指针和外部名字之间的连接,释放文件指 针以供其他文件使用。由于大多数操作系统对于一个程序可同时打开的文件数都存在一定限制, 所以当文件指针不再需要时释放它是一个好的思路,就像我们在 cat 程序中所做的那样。对输 出文件使用 fclose 还有另一原因:它将缓冲区中 putc 正在收集的输出数据刷到文件中。当 一个程序正常终止时,系统自动对该程序打开的每个文件调用 fclose 函数。(如果不再需要使 用 stdin 与 stdout,可以将其关闭。它们也能通过库函数 freopen 重新指定。) 7.6 错误处理——stderr 和 exit 函数 cat 程序对错误的处理并不理想。假如由于某些原因而造成某个文件无法访问,那么相应 的诊断信息将会在连接输出的末尾处打印出来。如果程序输出至屏幕,这种处理方式尚可接受, 但当输出至一个文件或通过管道向另一程序输入时则会出现问题。 为了更好地处理这类情况,程序配备了与 stdin 和 stdout 类似、被称为 stderr 的第二 种输出流。即便重定向了标准输出,写入 stderr 的输出通常仍会显示到屏幕上。 下面我们改写 cat 程序,使其将错误信息写到标准错误 stderr 上。 #include /* cat:将多个文件拼接到一起,版本 2 */ main(int argc, char *argv[]) { FILE *fp; void filecopy(FILE *, FILE *); char *prog = argv[0]; /* 程序名,供错误处理使用 */ if (argc == 1) /* 如果没有参数,则从标准输入拷贝 */ filecopy(stdin, stdout); else while (--argc > 0) if ((fp = fopen(*++argv, “r”)) == NULL) { fprintf(stderr, “%s: can’t open %s\n”, prog, *argv); exit(1); } else { filecopy(fp, stdout); fclose(fp); } if (ferror(stdout)) { fprintf(stderr, “%s: error writing stdout\n”, prog); exit(2); } exit(0); } 此程序通过两种途径报错。首先,将 fprintf 生成的诊断信息输出到 stderr,于是诊断 信息就会显示到屏幕而不是消失在管道或进入某个输出文件中。诊断信息包含了 argv[0]中的 程序名,这样当此程序和其他程序一起使用时就可以辨别出错误的来源。 其次,此程序使用了标准库函数 exit,当它被调用时将终止程序执行。任何调用上述程序 的进程都能获得该程序调用 exit 时的参数,因此可通过另一个将该程序作为子进程的程序来 测试该程序是否成功执行。按照惯例,返回 0 值表示一切正常,而非 0 值通常表示出现了异常 情况。exit 为每个已打开的输出文件调用 fclose 函数,将所有缓冲的输出刷入相应的文件中。 在主程序 main 中,语句 return expr 等价于 exit(expr)。但是 exit 有其优点:它 可被其他函数调用,并可用类似第 5 章中介绍的模式查找函数来找到这些调用。 如果流 fp 中发生了错误,函数 ferror 返回一个非 0 值。 int ferror(FILE *fp) 尽管输出错误很少遇到,但它们确实会发生(例如,当磁盘满时),因此一个产品级程序应当检 测这样的错误。 函数 feof(FILE *)与 ferror 类似,如果遇到文件结尾,它返回一个非 0 值。 int feof(FILE *fp) 本节编写的小程序旨在说明问题,因此我们不太关心它的退出状态,但对于任何正式的程 序而言,都应特别注意返回合理有用的状态值。 7.7 行输入与行输出 标准库提供了一个输入函数 fgets,它和之前几章中用到的函数 getline 类似: char *fgets(char *line, int maxline, FILE *fp) fgets 从 fp 所指向的文件中读取下一行输入(包括换行符),并将其存放到字符数组 line 中; 最多可读取 maxline-1 个字符,读取的行以‘\0’结尾。通常情况下,fgets 返回 line,但 如果遇到了文件末尾或发生了错误,则返回 NULL(我们编写的 getline 函数返回行的长度, 这个值更有用,当它为 0 时意味着到达了文件末尾)。 对于输出,函数 fputs 将一个字符串(不一定包含换行符)写入一个文件中: int fputs(char *line, FILE *fp) 如果发生错误,该函数返回 EOF,否则返回一个非负值。 库函数 gets 和 puts 的功能与 fgets 和 fputs 类似,但它们是对 stdin 和 stdout 进 行操作。有一点注意不要混淆,gets 在读入字符串时将丢弃末尾的‘\n’,而 puts 则要在写 出字符串的末尾添加‘\n’。 类似 fgets 和 fputs 这样的函数并不特别,下列代码就拷贝自作者所用系统中标准库内 fgets 和 fputs 的代码: /* fgets:从 iop 所指向的文件中最多读取 n 个字符 */ char *fgets(char *s, int n, FILE *iop) { register int c; register char *cs; cs = s; while (--n > 0 && (c = getc(iop)) != EOF) if ((*cs++ = c) == ‘\n’) break; *cs = ‘\0’; return (c == EOF && cs == s) ? NULL : s; } /* fputs:将字符串 s 输出到 iop 所指向的文件 */ int fputs(char *s, FILE *iop) { int c; while (c = *s++) putc(c, iop); return ferror(iop) ? EOF : 0; } ANSI C 标准规定:ferror 函数在遇到错误时返回非 0 值;而 fputs 函数在遇到错误时返回 EOF,否则返回一个非负整数值。 使用 fgets 很容易实现我们的 getline 函数: /* getline:读入一行并返回其长度 */ int getline(char *line, int max) { if (fgets(line, max, stdin) == NULL) return 0; else return strlen(line); } 练习 7-6. 编写一个程序,比较两个文件并打印它们第一个不相同的行。 练习 7-7. 修改第 5 章的模式查找程序,使其从一组有名文件中读取输入,并在没有文件作为 参数时从标准输入中读取输入。当找到一个匹配行时,是否应该将对应的文件名打印出来? 练习 7-8. 编写一个程序,打印一组文件;每个文件从新的一页开始打印,包括文件标题和每 页的页数。 7.8 其他函数 标准库提供了功能宽泛的函数集合。本节将对其中最常用的函数进行一个简要概述。更多 细节和许多这里没有介绍的函数请参见附录 B。 7.8.1 字符串处理函数 前面已经提到过字符串函数 strlen、strcpy、strcat 和 strcmp,它们都定义在头文 件 中。在下列函数中,s 与 t 的类型为 char *,而 c 与 n 的类型为 int。 strcat(s,t) 将字符串 t 连接到字符串 s 的末尾 strncat(s,t,n) 将字符串 t 中前 n 个字符连接到字符串 s 的末尾 strcmp(s,t) 视字符串 s 小于、等于或大于字符串 t 而返回负整数、0 或正整数 strncmp(s,t,n) 与 strcmp 相同,但只处理前 n 个字符 strcpy(s,t) 将字符串 t 复制到字符串 s strncpy(s,t,n) 将字符串 t 中最多前 n 个字符复制到字符串 s strlen(s) 返回字符串 s 的长度 strchr(s,c) 返回字符串 s 中首次出现 c 的位置指针,若未找到则返回 NULL strrchr(s,c) 返回字符串 s 中末次出现 c 的位置指针,若未找到则返回 NULL 7.8.2 字符类测试和转换函数 头文件 中的一些函数用于字符的测试和转换。在下列函数中,c 是可用于表 示 unsigned char(无符号字符)或 EOF 的一个 int 型数。函数的返回值类型为 int。 isalpha(c) 若 c 是字母,则返回非 0 值,否则返回 0 isupper(c) 若 c 是大写字母,则返回非 0 值,否则返回 0 islower(c) 若 c 是小写字母,则返回非 0 值,否则返回 0 isdigit(c) 若 c 是数字,则返回非 0 值,否则返回 0 isspace(c) 若 c 是空格、横向制表符、换行符、回车符和纵向制表符,则返回 非 0 值,否则返回 0 toupper(c) 返回 c 的大写形式 tolower(c) 返回 c 的小写形式 7.8.3 ungetc 函数 标准库提供了一个称为 ungetc 的函数,它是第 4 章中编写的 ungetch 函数的一个功能 上限制更严的版本。 int ungetc(int c, FILE *fp) 该函数将字符 c 写回文件 fp。如果执行成功则返回 c,否则返回 EOF。每个文件只能接收一个 写回字符。ungetc 函数可以和诸如 scanf、getc 或 getchar 等任何输入函数配合使用。 7.8.4 命令执行函数 函数 system(char *s)执行包含在字符串 s 中的命令,然后继续执行当前程序。s 的内 容很大程度上取决于所用的操作系统。例如,在 UNIX 系统中,语句 system(“date”); 将执行程序 date,它在标准输出上打印当天的日期和时间。system 函数返回一个来自被执行 命令的与系统相关的整数状态。在 UNIX 系统中,该返回状态是 exit 所返回的数值。 7.8.5 存储管理函数 函数 malloc 和 calloc 用于动态分配内存块。语句 void *malloc(size_t n) 在分配成功时,返回一个指向 n 字节大小的未初始化的存储空间的指针,否则返回 NULL。而 语 句 void *calloc(size_t n, size_t size) 在分配成功时,返回一个指向足以容纳由 n 个指定大小的对象组成的数组的存储空间的指针, 否则返回 NULL。该存储空间被初始化为全 0。 由 malloc 或 calloc 函数返回的指针能自动地根据所分配对象的类型进行适当的对齐调 整,但它必须强制转换为恰当的类型,例如: int *ip; ip = (int *)calloc(n, sizeof(int)); free(p)函数用于释放 p 所指向的存储空间,其中 p 是此前通过调用 malloc 或 calloc 函数而得到的指针。存储空间的释放顺序没有什么限制,但释放一个不是通过调用 malloc 或 calloc 函数而得到的指针所指向的存储空间是一个可怕的错误。 使用已经释放的存储空间同样是错误的。下面所示的是一个典型的不正确的代码段,它是 用于释放链表中每项所占存储空间的循环: for (p = head; p != NULL; p = p->next) /* 错了!*/ free(p); 正确的处理方法是在释放之前将所有必要的信息先保存起来: for (p = head; p != NULL; p = q) { q = p->next; free(p); } 8.7 节给出了一个类似于 malloc 函数的存储分配程序的实现,该程序分配的存储块可以 以任意顺序释放。 7.8.6 数学函数 头文件 中声明了二十多个数学函数,下面所列的是一些经常使用的函数。每 个函数带有一个或两个 double 型参数,并且返回一个 double 类型的值。 sin(x) x 的正弦函数,其中 x 用弧度表示 cos(x) x 的余弦函数,其中 x 用弧度表示 atan2(y,x) y/x 的反正切函数,该函数值用弧度表示 exp(x) 指数函数 ex log(x) x 的自然对数(以 e 为底)函数(x>0) log10(x) x 的常用对数(以 10 为底)函数(x>0) pow(x, y) xy sqrt(x) x 的平方根函数(x≥0) fabs(x) x 的绝对值函数 7.8.7 随机数发生器函数 函数 rand()用于生成介于 0 和 RAND_MAX 之间的伪随机整数序列,其中 RAND_MAX 在头 文件 中定义。下面是一种生成大于等于 0 但小于 1 的随机浮点数的方法: #define frand() ((double)rand() / (RAND_MAX+1.0)) (如果所用函数库中已提供了一个用于生成浮点随机数的函数,那么它可能比上面这个函数具 有更好的统计特性。) 函数 srand(unsigned)设置 rand 所用的随机数生成算法的种子值。2.7 节中给出了遵 循标准的 rand 和 srand 函数的可移植的实现。 练习 7-9. 诸如 isupper 这样的函数能够以节省空间或者节省时间的方式实现。试探究这两 种可能性。 ᑣՓ⫼᭛ӊ 0 ԰Ў䕧ܹˈ᭛ӊ 1 ੠ 2 ԰Ў䕧ߎˈᅗህϡӮⶹ䘧⿟ᑣⱘ䕧ܹҢા䞠ᴹˈᑊ䕧ߎ ϟˈ᭛ӊ䌟ؐⱘᬍব䛑ϡᰃ⬅⿟ᑣᅠ៤ⱘˈ㗠ᰃ⬅ shell ᅠ៤ⱘDŽা㽕⿟މⱘ⡍ᗻDŽ೼ӏԩᚙ ҡϢᰒ⼎఼Ⳍ݇㘨ˈ䖭ḋˈߎ䫭ֵᙃӮ䕧ߎࠄᰒ⼎఼ϞDŽϢㅵ䘧Ⳍ݇ⱘ䕧ܹˋ䕧ߎг᳝㉏Ԑ ϟˈshell ᡞ᭛ӊᦣ䗄ヺ 0 ੠ 1 ⱘ咬䅸䌟ؐᬍবЎᣛᅮⱘ᭛ӊDŽ䗮ᐌˈ᭛ӊᦣ䗄ヺ 2މ䖭⾡ᚙ prog < ೛దำߑ੠ > ೛ѻำߑ੠ ⿟ᑣⱘՓ⫼㗙ৃ䗮䖛<੠>䞡ᅮ৥⿟ᑣⱘ I/O˖ Ё䇏ˈᇍ 1 ੠ 2 䖯㸠ݭˈህৃҹ䖯㸠䕧ˋ䕧ߎ㗠ϡᖙ݇ᖗᠧᓔ᭛ӊⱘ䯂乬DŽ 䫭䇃DŽབᵰ⿟ᑣҢ᭛ӊ 0ޚ䕧ߎ੠ᷛޚ䕧ܹˈᷛޚⱘ᭛ӊᦣ䗄ヺߚ߿Ў 0ˈ1ˈ2ˈձ⃵㸼⼎ᷛ ⡍߿ⱘᅝᥦDŽᔧੑҸ㾷䞞⿟ᑣ˄े“shellā˅䖤㸠ϔϾ⿟ᑣⱘᯊ׭ˈᅗᇚᠧᓔ 3 Ͼ᭛ӊˈᇍᑨ њخ಴Ў໻໮᭄ⱘ䕧ܹˋ䕧ߎᰃ䗮䖛䬂Ⲭ੠ᰒ⼎఼ᴹᅲ⦄ⱘˈЎњᮍ֓䍋㾕ˈUNIX ᇍℸ ӊᦣ䗄ヺᓩ⫼᭛ӊˈ ᣛ䩜៪ MS•DOS Ёⱘ᭛ӊহᶘDŽ˅㋏㒳䋳䋷㓈ᡸᏆᠧᓔ᭛ӊⱘ᠔ֵ᳝ᙃˈ⫼᠋⿟ᑣা㛑䗮䖛᭛ ᑧЁⱘ᭛ӊޚ䗮䖛᭛ӊᦣ䗄ヺᷛ䆚᭛ӊˈ㗠ϡᰃ䗮䖛᭛ӊৡᷛ䆚᭛ӊDŽ˄᭛ӊᦣ䗄ヺ㉏ԐѢᷛ ৥⿟ᑣ䖨ಲϔϾᇣⱘ䴲䋳ᭈ᭄ˈ䆹ᭈ᭄⿄Ў᭛ӊᦣ䗄ヺDŽӏԩᯊ׭ᇍ᭛ӊⱘ䕧ܹˋ䕧ߎ䛑ᰃ ㋏㒳ẔᶹԴⱘᴗ࡯˄䆹᭛ӊᰃ৺ᄬ೼˛ᰃ৺᳝䆓䯂ᅗⱘᴗ䰤˛˅ˈབᵰϔߛℷᐌˈ᪡԰䞛㒳ᇚ Ꮖᄬ೼ⱘݙᆍDŽܜ߯ᓎ䆹᭛ӊˈгৃ㛑䳔㽕϶ᓗ䆹᭛ӊЁॳܜབᵰᰃݭϔϾ᭛ӊˈ߭ৃ㛑䳔㽕 ᇚ䖭Ͼᛣ೒䗮ⶹ㋏㒳ˈ䆹䖛⿟⿄Ўӕࢗ᭛ӊDŽܜϟˈ೼䇏៪ݭ᭛ӊПࠡˈᖙ乏މ䗮ᐌᚙ ᥹ষህৃҹ໘⧚໪ೈ䆒໛੠⿟ᑣП䯈ⱘ᠔᳝䗮ֵDŽ ᭛ӊˈ಴ℸˈ᠔᳝ⱘ䕧ܹˋ䕧ߎ䛑㽕䗮䖛䇏᭛ӊ៪ݭ᭛ӊᅠ៤DŽгህᰃ䇈ˈ䗮䖛ϔϾऩϔⱘ ೼ UNIX ᪡԰㋏㒳Ёˈ᠔᳝ⱘ໪ೈ䆒໛˄ࣙᣀ䬂Ⲭ੠ᰒ⼎఼˅䛑㹿ⳟ԰ᰃ᭛ӊ㋏㒳Ёⱘ 8.1. ᭛ӊᦣ䗄ヺ ᑧDŽޚ䕧ܹ੠䕧ߎⱘ㋏㒳䇗⫼ˈᑊҟ㒡བԩ䗮䖛ᅗӀᅲ⦄ᷛ ᑧߑ᭄ⱘᅲ⦄ᖙ乏䗮䖛ᆓЏ㋏㒳ᦤկⱘࡳ㛑ᴹᅲ⦄DŽ᥹ϟᴹⱘ޴㡖ᇚҟ㒡 UNIX ㋏㒳Ё⫼Ѣ ޚ㄀ 7 ゴҟ㒡ⱘ䕧ܹˋ䕧ߎ᥹ষᇍӏԩ᪡԰㋏㒳䛑ᰃϔḋⱘDŽ೼ӏԩ⡍ᅮⱘ㋏㒳Ёˈᷛ ݙᆍ㽕∖䇏㗙ᇍ UNIX ㋏㒳ⱘ໪䚼⡍ᗻ᳝ϔᅮⱘњ㾷DŽ ߚ䜡DŽ݊Ёˈࠡϸ䚼ߚⱘټᴀゴⱘݙᆍࣙᣀ 3 ϾЏ㽕䚼ߚˈ䕧ܹˋ䕧ߎǃ᭛ӊ㋏㒳੠ᄬ ᑧDŽޚЎ෎⸔ᓎゟ䍋ᴹⱘˈ᠔ҹˈᄺдᴀゴЁⱘ⿟ᑣ䖬ᇚ᳝ࡽѢ᳈དഄ⧚㾷ᷛ ߑ᭄ᑧᰃҹ UNIX ㋏㒳ޚЁⱘҷⷕ݋᳝ⳌԐᗻˈাᰃϔѯ㒚㡖Ϟ᳝ऎ߿㗠ᏆDŽ಴Ў ANSI C ᷛ ㋏㒳ϞՓ⫼ C 䇁㿔ˈᴀゴⱘ՟ᄤгᇚӮᐂࡽԴᇍ C 䇁㿔⿟ᑣ䆒䅵᳝᳈⏅ܹⱘњ㾷DŽϡৠ㋏㒳 ᑧЁ≵᳝ⱘᶤѯࡳ㛑DŽԚᰃˈेՓ䇏㗙ᰃ೼݊ᅗ᪡԰ޚ䇗⫼ҹ㦋ᕫ᳔催ⱘᬜ⥛ˈ៪㗙䆓䯂ᷛ ࡽѢ㋏㒳׳བᵰ䇏㗙Փ⫼ⱘᰃ UNIXˈᴀゴᇚӮᇍԴ᳝Ⳉ᥹ⱘᐂࡽˈ䖭ᰃ಴Ўˈ៥Ӏ㒣ᐌ䳔㽕 ᭄ˈᅗӀৃҹ㹿⫼᠋⿟ᑣ䇗⫼DŽᴀゴᇚҟ㒡བԩ೼ C 䇁㿔⿟ᑣЁՓ⫼ϔѯ䞡㽕ⱘ㋏㒳䇗⫼DŽ UNIX ᪡԰㋏㒳䗮䖛ϔ㋏߫ⱘຂහԸဈᦤկ᳡ࡵˈ䖭ѯ㋏㒳䇗⫼ᅲ䰙Ϟᰃ᪡԰㋏㒳ݙⱘߑ ㄀8ゴ UNIX ㋏㒳᥹ষ } ( int getchar(void /* getchar: unbuffered single character input */ #include "syscalls.h" 䕧ܹDŽކ䇏ܹϔϾᄫヺᴹᅲ⦄᮴㓧 䕧ܹޚputchar ㄝⱘ催㑻ߑ᭄DŽ՟བˈҹϟᰃ getchar ߑ᭄ⱘϔϾ⠜ᴀˈᅗ䗮䖛↣⃵Ңᷛ Ўњ᳈དഄᥠᦵ᳝݇ὖᗉˈϟ䴶ᴹ䇈ᯢབԩ⫼ read ੠ write ᵘ䗴㉏ԐѢ getcharǃ Ͼ䕗ᇣⱘᄫ㡖᭄ˈwrite ݡᣝ䖭Ͼᄫ㡖᭄ݭˈℸৢݡ䇗⫼ read ᇚ䖨ಲ 0DŽ ᭄ˈ߭ᇍ read ⱘᶤ⃵䇗⫼Ӯ䖨ಲϔסᰃϔϾ䕗ড়䗖ⱘ᭄ؐDŽབᵰ᭛ӊ໻ᇣϡᰃ BUFSIZ ⱘ খ᭄ BUFSIZ гᏆ㒣೼ syscalls.h ༈᭛ӊЁᅮНDŽᇍѢ᠔Փ⫼ⱘ᪡԰㋏㒳ᴹ䇈ˈ䆹ؐ ⱘDŽޚ⿟ᑣ䛑ᇚࣙ৿䆹༈᭛ӊDŽϡ䖛ˈ䆹᭛ӊⱘৡᄫϡᰃᷛ ៥ӀᏆ㒣ᇚ㋏㒳䇗⫼ⱘߑ᭄ॳൟ䲚Ёᬒ೼ϔϾ༈᭛ӊ syscalls.h Ёˈ಴ℸˈᴀゴЁⱘ } return 0; write(1, buf, n); while ((n = read(0, buf, BUFSIZ)) > 0) int n; char buf[BUFSIZ]; { main() /* copy input to output */ #include "syscalls.h" ࠄӏԩ᭛ӊ៪䆒໛DŽ ໡ࠊ⿟ᑣ೼ࡳ㛑ϞⳌৠDŽ⿟ᑣৃҹᇚӏᛣ䕧ܹ໡ࠊࠄӏᛣ䕧ߎˈ಴Ў䕧ܹˋ䕧ߎৃҹ䞡ᅮ৥ 㒧ড়ҹϞⱘ䅼䆎ˈ៥Ӏৃҹ㓪ݭϔϾㅔऩⱘ⿟ᑣˈᇚ䕧ܹ໡ࠊࠄ䕧ߎˈ䖭Ϣ㄀ 1 ゴЁⱘ ᇥњDŽޣⱘؐDŽ⫼᳈໻ⱘؐ䇗⫼䆹ߑ᭄ৃҹ㦋ᕫ᳈催ⱘᬜ⥛ˈ಴Ў㋏㒳䇗⫼ⱘ⃵᭄ ៪ᰃ㉏ԐѢ 1024̚4096 䖭ḋⱘϢ໪ೈ䆒໛ⱘ⠽⧚ഫ໻ᇣⳌᑨˈ˅ކߎ៪ݭܹ 1 Ͼᄫヺ˄᮴㓧 ೼ϔ⃵䇗⫼Ёˈ䇏ߎ៪ݭܹⱘ᭄᥂ⱘᄫ㡖᭄ৃҹЎӏᛣ໻ᇣDŽ᳔ᐌ⫼ⱘؐЎ 1ˈे↣⃵䇏 䫭䇃DŽ ӊᯊˈ䖨ಲؐᰃᅲ䰙ݭܹⱘᄫ㡖᭄DŽབᵰ䖨ಲؐϢ䇋∖ݭܹⱘᄫ㡖᭄ϡⳌㄝˈ߭䇈ᯢথ⫳њ ᵰ䖨ಲؐЎ 0ˈ߭㸼⼎Ꮖࠄ䖒᭛ӊⱘ㒧ሒ˗བᵰ䖨ಲؐЎ•1ˈ߭㸼⼎থ⫳њᶤ⾡䫭䇃DŽ೼ݭ᭛ ↣Ͼ䇗⫼䖨ಲᅲ䰙Ӵ䕧ⱘᄫ㡖᭄DŽ೼䇏᭛ӊᯊˈߑ᭄ⱘ䖨ಲؐৃ㛑ӮᇣѢ䇋∖ⱘᄫ㡖᭄DŽབ int n_written = write(int fd, char *buf, int n); int n_read = read(int fd, char *buf, int n); ᑣЁᄬᬒ䇏៪ݭⱘ᭄᥂ⱘᄫヺ᭄㒘ˈ㄀ϝϾখ᭄ᰃ㽕Ӵ䕧ⱘᄫ㡖᭄DŽ ੠ write 䆓䯂䖭ϸϾ㋏㒳䇗⫼DŽ䖭ϸϾߑ᭄Ёˈ㄀ϔϾখ᭄ᰃ᭛ӊᦣ䗄ヺˈ㄀ѠϾখ᭄ᰃ⿟ 䕧ܹϢ䕧ߎᰃ䗮䖛read੠write㋏㒳䇗⫼ᅲ⦄ⱘDŽ೼C䇁㿔⿟ᑣЁˈৃҹ䗮䖛ߑ᭄read 8.2. Ԣ㑻 I/O——read ੠ write ࠄા䞠এDŽ ⠜ᴀЁ߭೼ЁᅮНDŽ ೼ System V UNIX ㋏㒳Ёˈ䖭ѯᐌ䞣೼༈᭛ӊЁᅮНˈ㗠೼ Berkeley˄BSD˅ O_RDWR ҹ䇏ݭᮍᓣᠧᓔ᭛ӊ O_WRONLY ҹাݭᮍᓣᠧᓔ᭛ӊ O_RDONLY ҹা䇏ᮍᓣᠧᓔ᭛ӊ ൟⱘؐˈᅗ䇈ᯢҹԩ⾡ᮍᓣᠧᓔ᭛ӊˈЏ㽕ⱘ޴Ͼؐབϟ᠔⼎˖ Ϣ fopen ϔḋˈখ᭄ name ᰃϔϾࣙ৿᭛ӊৡⱘᄫヺІDŽ㄀ѠϾখ᭄ flags ᰃϔϾ int ㉏ fd = open(name, flags, perms); int open(char *name, int flags, int perms); int fd; #include ϔϾ int ㉏ൟⱘ᭄ؐDŽ㗠ৢ㗙䖨ಲϔϾ᭛ӊᣛ䩜DŽབᵰথ⫳䫭䇃ˈopen ᇚ䖨ಲ•1DŽ open Ϣ㄀ 7 ゴ䅼䆎ⱘ fopen ⳌԐˈϡৠⱘᰃˈࠡ㗙䖨ಲϔϾ᭛ӊᦣ䗄ヺˈᅗҙҙাᰃ ഄᠧᓔDŽ㋏㒳䇗⫼ open ੠ creat ⫼Ѣᅲ⦄䆹ࡳ㛑DŽ 䫭䇃᭛ӊ໪ˈ݊ᅗ᭛ӊ䛑ᖙ乏೼䇏៪ݭПࠡᰒᓣޚ䕧ߎ੠ᷛޚ䕧ܹǃᷛޚ䰸њ咬䅸ⱘᷛ 8.3. openǃcreatǃclose ੠ unlink ᅲ⦄ⱘDŽ #undef 乘໘⧚ᣛҸপ⍜ৡᄫ getchar ⱘᅣᅮНˈ಴Ў೼༈᭛ӊЁˈgetchar ᰃҹᅣᮍᓣ ϟ㓪䆥䖭ѯ⠜ᴀⱘ getchar ߑ᭄ˈህ᳝ᖙ㽕⫼މབᵰ㽕೼ࣙ৿༈᭛ӊⱘᚙ } return (••n >= 0) ? (unsigned char) *bufp++ : EOF; } bufp = buf; n = read(0, buf, sizeof buf); if (n == 0) { /* buffer is empty */ static int n = 0; static char *bufp = buf; static char buf[BUFSIZ]; { int getchar(void) /* getchar: simple buffered version */ #include "syscalls.h" getchar ⱘ㄀ѠϾ⠜ᴀϔ⃵䇏ܹϔ㒘ᄫヺˈԚ↣⃵া䕧ߎϔϾᄫヺDŽ ೼䖨ಲ䇁হЁᇚ c 䕀ᤶЎ unsigned char ㉏ൟৃҹ⍜䰸ヺোᠽሩ䯂乬DŽ ݊Ёˈc ᖙ乏ᰃϔϾ char ㉏ൟⱘব䞣ˈ಴Ў read ߑ᭄䳔㽕ϔϾᄫヺᣛ䩜㉏ൟⱘখ᭄˄&c˅DŽ } return (read(0, &c, 1) == 1) ? (unsigned char) c : EOF; char c; ⊼ᛣˈߑ᭄ error ㉏ԐѢߑ᭄ printfˈ೼䇗⫼ᯊৃᏺব䭓খ᭄㸼DŽϟ䴶䗮䖛 error ߑ 㦋ᕫϔϾᏆᄬ೼᭛ӊⱘ῵ᓣˈᑊᇚℸ῵ᓣ䌟ؐ㒭ᅗⱘࡃᴀDŽ 䆹⿟ᑣ߯ᓎⱘ䕧ߎ᭛ӊ݋᳝೎ᅮⱘᴗ䰤 0666DŽ߽⫼ 8.6 㡖Ёᇚ㽕䅼䆎ⱘ stat ㋏㒳䇗⫼ˈৃҹ } return 0; error("cp: write error on file %s", argv[2]); if (write(f2, buf, n) != n) while ((n = read(f1, buf, BUFSIZ)) > 0) argv[2], PERMS); error("cp: can't create %s, mode %03o", if ((f2 = creat(argv[2], PERMS)) == •1) error("cp: can't open %s", argv[1]); if ((f1 = open(argv[1], O_RDONLY, 0)) == •1) error("Usage: cp from to"); if (argc != 3) char buf[BUFSIZ]; int f1, f2, n; { main(int argc, char *argv[]) /* cp: copy f1 to f2 */ void error(char *, ...); #define PERMS 0666 /* RW for owner, group, others */ #include "syscalls.h" #include #include Ⳃᷛ᭛ӊⱘᴗ䰤ϡᰃ䗮䖛໡ࠊ㦋ᕫⱘˈ㗠ᰃ䞡ᮄᅮНⱘDŽ 䆌⫼Ⳃᔩ԰Ў㄀ѠϾখ᭄ˈᑊϨˈܕ᭛ӊDŽ៥Ӏ㓪ݭⱘ䖭Ͼ⠜ᴀҙҙা㛑໡ࠊϔϾ᭛ӊˈϡ ϟ䴶䗮䖛ϔϾㅔ࣪ⱘ UNIX ⿟ᑣ cp 䇈ᯢ creat ⱘ⫼⊩DŽ䆹⿟ᑣᇚϔϾ᭛ӊ໡ࠊࠄ঺ϔϾ ៤ਬা㛑䖯㸠䇏੠ᠻ㸠᪡԰DŽ ⱘᴗ䰤ˈ՟བˈ0755 䇈ᯢ᭛ӊⱘ᠔᳝㗙ৃҹᇍᅗ䖯㸠䇏ǃݭ੠ᠻ㸠᪡԰ˈ㗠᠔᳝㗙㒘੠݊Ҫ ݊Ҫ៤ਬᇍ᭛ӊⱘ䇏ǃݭ੠ᠻ㸠䆓䯂DŽ಴ℸˈ䗮䖛ϔϾ 3 ԡⱘܿ䖯ࠊ᭄ህৃᮍ֓ഄ䇈ᯢϡৠ ӊ㋏㒳Ёˈ↣Ͼ᭛ӊᇍᑨϔϾ 9 ↨⡍ⱘᴗ䰤ֵᙃˈᅗӀߚ߿᥻ࠊ᭛ӊⱘ᠔᳝㗙ǃ᠔᳝㗙㒘੠ བᵰ㽕߯ᓎⱘ᭛ӊϡᄬ೼ˈ߭ creat ⫼খ᭄ perms ᣛᅮⱘᴗ䰤߯ᓎ᭛ӊDŽ೼ UNIX ᭛ ⱘ᭛ӊϡӮᇐ㟈䫭䇃DŽ Ꮖ᳝ⱘݙᆍDŽՓ⫼ creat ߯ᓎϔϾᏆᄬ೼ܜcreat ᇚᡞ䆹᭛ӊⱘ䭓ᑺ៾ᮁЎ 0ˈҢ㗠϶ᓗॳ བᵰ creat ៤ࡳഄ߯ᓎњ᭛ӊˈᅗᇚ䖨ಲϔϾ᭛ӊᦣ䗄ヺˈ৺߭䖨ಲ•1DŽབᵰℸ᭛ӊᏆᄬ೼ˈ fd = creat(name, perms); int creat(char *name, int perms); ᭛ӊ៪㽚ⲪᏆ᳝ⱘᮻ᭛ӊˈབϟ᠔⼎˖ བᵰ⫼ open ᠧᓔϔϾϡᄬ೼ⱘ᭛ӊˈ߭ᇚᇐ㟈䫭䇃DŽৃҹՓ⫼ creat ㋏㒳䇗⫼߯ᓎᮄ ೼ᴀゴⱘ䅼䆎Ёˈopen ⱘখ᭄ perms ⱘؐྟ㒜Ў 0DŽ fd = open(name, O_RDONLY,0); ҹՓ⫼ϟ߫䇁হᠧᓔϔϾ᭛ӊҹᠻ㸠䇏᪡԰˖ৃ ;(lseek(fd, 0L, 0 㢹㽕䖨ಲ᭛ӊⱘᓔྟ໘˄ेড㒩˅ˈ߭ৃҹՓ⫼ϟ߫䇗⫼˖ lseek(fd, 0L, 2); “aā˅ˈ߭೼ݭ᪡԰Пࠡᖙ乏Փ⫼ϟ߫㋏㒳䇗⫼ᡒࠄ᭛ӊⱘ᳿ሒ˖ ӊⱘሒ䚼⏏ࡴݙᆍ˄೼ UNIX shell⿟ᑣЁՓ⫼䞡ᅮ৥ヺ>>៪೼㋏㒳䇗⫼ fopen ЁՓ⫼খ᭄ ⫼Ѣᣛᅮ offset Ң᭛ӊᓔྟǃҢᔧࠡԡ㕂៪Ң᭛ӊ㒧ᴳ໘ᓔྟㅫ䍋DŽ՟བˈЎњ৥ϔϾ᭛ ⱘԡ㕂㗠㿔ⱘDŽ䱣ৢ䖯㸠ⱘ䇏ݭ᪡԰ᇚҢℸԡ㕂ᓔྟˈorigin ⱘؐৃҹЎ 0ǃ1 ៪ 2ˈߚ߿ ᇚ᭛ӊᦣ䗄ヺЎ fd ⱘ᭛ӊⱘᔧࠡԡ㕂䆒㕂Ў offsetˈ݊Ёˈoffset ᰃⳌᇍѢ orgin ᣛᅮ long lseek(int fd, long offset, int origin); ᛣ⿏ࡼԡ㕂㗠ϡᅲ䰙䇏ݭӏԩ᭄᥂˖ ᪡԰ⱘԡ㕂ПৢDŽԚᰃˈ᳝ᯊ׭䳔㽕ҹӏᛣ乎ᑣ䆓䯂᭛ӊˈ㋏㒳䇗⫼ lseek ৃҹ೼᭛ӊЁӏ 䕧ܹˋ䕧ߎ䗮ᐌᰃ乎ᑣ䖯㸠ⱘ˖↣⃵䇗⫼ read ੠ write 䖯㸠䇏ݭⱘԡ㕂㋻䎳೼ࠡϔ⃵ 8.4. 䱣ᴎ䆓䯂——lseek 䞡ݭ㄀ 7 ゴⱘ cat ⿟ᑣˈᑊ䗮䖛ᅲ偠↨䕗ϸϾ⠜ᴀⱘⳌᇍᠻ㸠䗳ᑺDŽ ᑧЁࡳ㛑ㄝӋⱘߑ᭄ˈޚ㒗д 8•1 ⫼ readǃwriteǃopen ੠ close ㋏㒳䇗⫼ҷ᳓ᷛ removeDŽ ᑧߑ᭄ޚߑ᭄ unlink(char *name)ᇚ᭛ӊ name Ң᭛ӊ㋏㒳Ёߴ䰸ˈᅗᇍᑨѢᷛ Џ⿟ᑣЁ䖨ಲˈ᠔᳝ᠧᓔⱘ᭛ӊᇚ㹿݇䯁DŽ ऎDŽབᵰ⿟ᑣ䗮䖛 exit ߑ᭄䗔ߎ៪Ңކfclose ߑ᭄ⳌᇍᑨˈԚᅗϡ䳔㽕⏙⋫˄flush˅㓧 ᑧЁⱘޚᠧᓔ᭛ӊП䯈ⱘ䖲᥹ˈᑊ䞞ᬒℸ᭛ӊᦣ䗄ヺˈҹկ݊ᅗ᭛ӊՓ⫼DŽclose ߑ᭄Ϣᷛ ⧚䆌໮᭛ӊˈ䙷Мᅗᖙ乏䞡⫼᭛ӊᦣ䗄ヺDŽߑ᭄ close˄int fd ˅⫼ᴹᮁᓔ᭛ӊᦣ䗄ヺ੠Ꮖ ϔϾ⿟ᑣৠᯊᠧᓔⱘ᭛ӊ᭄ᰃ᳝䰤ࠊⱘ˄䗮ᐌЎ 20˅DŽⳌᑨഄˈབᵰϔϾ⿟ᑣ䳔㽕ৠᯊ໘ } exit(1); va_end(args); fprintf(stderr, "\n"); vprintf(stderr, fmt, args); fprintf(stderr, "error: "); va_start(args, fmt); va_list args; { void error(char *fmt, ...) /* error: print an error message and die */ #include #include ߑ᭄㉏ԐDŽ va_start ᅣ䖯㸠߱ྟ࣪DŽৠḋˈvfprintf ੠ vsprintf ߑ᭄ߚ߿Ϣ fprintf ੠ sprintf ᭄Ϣ printf ߑ᭄㉏Ԑˈ᠔ϡৠⱘᰃˈᅗ⫼ϔϾখ᭄পҷњব䭓খ᭄㸼ˈϨℸখ᭄䗮䖛䇗⫼ ᑧߑ᭄ vprintf ߑޚ᭄ⱘᅲ⦄䇈ᯢབԩՓ⫼ printf ߑ᭄ᆊᮣⱘ঺ϔϾ៤ਬ vprintfDŽᷛ ;[ extern FILE _iob[OPEN_MAX } FILE; int fd; /* file descriptor */ int flag; /* mode of file access */ char *base; /* location of buffer */ char *ptr; /* next character position */ int cnt; /* characters left */ typedef struct _iobuf { #define OPEN_MAX 20 /* max #files open at once */ #define BUFSIZ 1024 #define EOF (•1) #define NULL 0 䆹㑺ᅮDŽ ᑧߑ᭄䛑䙉ᕾޚさDŽ᠔᳝ⱘᷛކ⫼ⱘৡᄫҹϟߦ㒓ᓔྟˈ಴ℸϔ㠀ϡӮϢ⫼᠋⿟ᑣЁⱘৡᄫ ᑧЁ݊ᅗߑ᭄᠔ՓޚᑧЁⱘ݊ᅗߑ᭄ࣙ৿DŽ೼ϟ䴶䖭↉݌ൟⱘҷⷕ↉Ёˈাկᷛ ᭄ⱘ⿟ᑣ䛑ᖙ乏೼⑤᭛ӊЁࣙ৿䖭Ͼ༈᭛ӊ˄䗮䖛#include ᣛҸࣙ৿༈᭛ӊ˅DŽℸ᭛ӊг㹿 䕧ܹˋ䕧ߎᑧЁߑޚᦣ䗄᭛ӊⱘ᭄᥂㒧ᵘࣙ৿೼༈᭛ӊЁˈӏԩ䳔㽕Փ⫼ᷛ ⢊ᗕⱘᷛᖫㄝDŽ ऎЁϟϔϾᄫヺⱘᣛ䩜˗᭛ӊᦣ䗄ヺ˗ᦣ䗄䇏ˋݭ῵ᓣⱘᷛᖫ˗ᦣ䗄䫭䇃ކ఼˗ϔϾᣛ৥㓧 ऎЁ࠽ԭⱘᄫヺ᭄ⱘ䅵᭄ކऎⱘᣛ䩜ˈ䗮䖛ᅗৃҹϔ⃵䇏ܹ᭛ӊⱘϔ໻ഫݙᆍ˗ϔϾ䆄ᔩ㓧 ކ᭛ӊᣛ䩜ᰃϔϾᣛ৥ࣙ৿᭛ӊ৘⾡ֵᙃⱘ㒧ᵘⱘᣛ䩜ˈ䆹㒧ᵘࣙ৿ϟ߫ݙᆍ˖ϔϾᣛ৥㓧 ᑧЁⱘ᭛ӊϡᰃ䗮䖛᭛ӊᦣ䗄ヺᦣ䗄ⱘˈ㗠ᰃՓ⫼᭛ӊᣛ䩜ᦣ䗄ⱘDŽޚ៥Ӏಲᖚϔϟˈᷛ 䍋ᴹՓ⫼DŽ ᑧߑ᭄ fopen ੠ getc ⱘϔ⾡ᅲ⦄ᮍ⊩Ў՟ᴹ䇈ᯢབԩᇚ䖭ѯ㋏㒳䇗⫼㒧ড়ޚϟ䴶ҹᷛ 8.5. ᅲ՟——fopen ੠ getc ߑ᭄ⱘᅲ⦄ Ϩ೼থ⫳䫭䇃ᯊ䖨ಲϔϾ䴲 0 ؐDŽ ᑧߑ᭄ fseek Ϣ㋏㒳䇗⫼ lseek ㉏Ԑˈ᠔ϡৠⱘᰃˈࠡ㗙ⱘ㄀ϔϾখ᭄ᰃ FILE * ㉏ൟˈޚᷛ lseek ㋏㒳䇗⫼䖨ಲϔϾ long ㉏ൟⱘؐˈℸؐ㸼⼎᭛ӊⱘᮄԡ㕂ˈ㢹থ⫳䫭䇃ˈ߭䖨ಲ•1DŽ } return •1; else return read(fd, buf, n); if (lseek(fd, pos, 0) >= 0) /* get to pos */ { int get(int fd, long pos, char *buf, int n) /*get: read n bytes from position pos */ #include "syscalls.h" 䇃ˈ߭䖨ಲ•1DŽ བˈϟ䴶ⱘߑ᭄ᇚҢ᭛ӊⱘӏᛣԡ㕂䇏ܹӏᛣ᭄Ⳃⱘᄫ㡖ˈᅗ䖨ಲ䇏ܹⱘᄫ㡖᭄ˈ㢹থ⫳䫭 Փ⫼ lseek ㋏㒳䇗⫼ᯊˈৃҹᇚ᭛ӊ㾚ЎϔϾ໻᭄㒘ˈ݊ҷӋᰃ䆓䯂䗳ᑺӮ᜶ϔѯDŽ՟ ϔ㟈DŽ 䇋⊼ᛣˈখ᭄ 0L гৃݭЎ(long)0ˈ៪ҙҙݭЎ 0ˈԚᰃ㋏㒳䇗⫼ lseek ⱘໄᯢᖙ乏ֱᣕ /* if (fp >= _iob + OPEN_MAX) /* no free slots break; /* found free slot */ if ((fp•>flag & (_READ | _WRITE)) == 0) for (fp = _iob; fp < _iob + OPEN_MAX; fp++) return NULL; if (*mode != 'r' && *mode != 'w' && *mode != 'a') FILE *fp; int fd; { FILE *fopen(char *name, char *mode) #define PERMS 0666 /* RW for owner, group, others */ #include "syscalls.h" #include ᭛ӊᯊ⬅ߑ᭄_fillbuf ᅠ៤ⱘDŽ ऎⱘߚ䜡ᰃ೼㄀ϔ⃵䇏ކऎぎ䯈ˈ㓧ކ㕂ˈ䆒㕂ᷛᖫԡҹᣛ⼎Ⳍᑨⱘ⢊ᗕDŽᅗϡߚ䜡ӏԩ㓧 ϟ䴶៥Ӏᴹⴔ᠟㓪ݭߑ᭄ fopenDŽfopen ߑ᭄ⱘЏ㽕ࡳ㛑ᰃᠧᓔ᭛ӊˈᅮԡࠄড়䗖ⱘԡ ݊Ёࣙ৿њ䆓䯂䫭䇃䕧ߎǃ᭛ӊ㒧ᴳ⢊ᗕ੠᭛ӊᦣ䗄ヺⱘᅣDŽ ऎ⒵ᯊˈᅗᇚ䇗⫼ߑ᭄_flushbufDŽℸ໪ˈ៥Ӏ䖬೼ކⱘ᪡԰Ϣ getc ߑ᭄䴲ᐌ㉏Ԑˈᔧ㓧 ሑㅵ೼䖭䞠៥Ӏᑊϡᛇ䅼䆎ϔѯ㒚㡖ˈԚ⿟ᑣЁ䖬ᰃ㒭ߎњ putc ߑ᭄ⱘᅮНˈҹ㸼ᯢᅗ ㉏ൟDŽҹ⹂ֱ᠔᳝ⱘᄫヺЎℷؐDŽ ऎˈ䞡ᮄ߱ྟ࣪㒧ᵘⱘݙᆍˈᑊ䖨ಲϔϾᄫヺDŽ䖨ಲⱘᄫヺЎ unsignedކ㓧ܙ฿ _fillbuf Ͼ䭓ⱘ#define 䇁হৃ⫼ড᭰ᴴߚ៤޴㸠DŽ˅Ԛᰃˈབᵰ䅵᭄ؐবЎ䋳ؐˈgetc ህ䇗⫼ߑ᭄ ᇚᣛ䩜⿏ࠄϟϔϾԡ㕂ˈ✊ৢ䖨ಲᄫヺDŽ˄ࠡ䴶䆆䖛ˈϔˈ1 ޣᇚ䅵఼᭄ܜᅣ getc ϔ㠀 #define putcher(x) putc((x), stdout) #define getchar() getc(stdin) ? *(p)•>ptr++ = (x) : _flushbuf((x),p)) #define putc(x,p) (••(p)•>cnt >= 0 \ ? (unsigned char) *(p)•>ptr++ : _fillbuf(p)) #define getc(p) (••(p)•>cnt >= 0 \ #define fileno(p) ((p)•>fd) #define ferror(p) ((p)•>flag & _ERR) != 0) #define feof(p) ((p)•>flag & _EOF) != 0) int _flushbuf(int, FILE *); int _fillbuf(FILE *); }; _ERR = 020 /* error occurred on this file */ _EOF = 010, /* EOF has occurred on this file */ _UNBUF = 04, /* file is unbuffered */ _WRITE = 02, /* file open for writing */ _READ = 01, /* file open for reading */ enum _flags { #define stderr (&_iob[2]) #define stdout (&_iob[1]) define stdin (&_iob[0])# ᳔ৢϔӊџᚙ֓ᰃབԩᠻ㸠䖭ѯߑ᭄DŽ៥Ӏᖙ乏ᅮН੠߱ྟ᭄࣪㒘_iob Ёⱘ stdinǃ } return (unsigned char) *fp•>ptr++; } return EOF; fp•>cnt = 0; fp•>flag |= _ERR; else fp•>flag |= _EOF; if (fp•>cnt == •1) if (••fp•>cnt < 0) { fp•>cnt = read(fp•>fd, fp•>ptr, bufsize); fp•>ptr = fp•>base; return EOF; /* can't get buffer */ if ((fp•>base = (char *) malloc(bufsize)) == NULL) if (fp•>base == NULL) /* no buffer yet */ bufsize = (fp•>flag & _UNBUF) ? 1 : BUFSIZ; return EOF; if ((fp•>flag&(_READ|_EOF_ERR)) != _READ) int bufsize; { int _fillbuf(FILE *fp) /* _fillbuf: allocate and fill input buffer */ #include "syscalls.h" ऎᏆ㒣ߚ䜡DŽކЁⱘ㄀ϔϾᄫヺDŽ䱣ৢ䖯㸠ⱘ_fillbuf 䇗⫼Ӯথ⦄㓧 ऎކऎˈ䆒㕂䅵᭄ؐ੠ᣛ䩜ˈᑊ䖨ಲ㓧ކℸ㓧ܙ฿ ऎৢˈ_fillbuf 䇗⫼ readކᓎゟ㓧 ᮍᓣ䖯㸠ⱘ䆱˅DŽކऎ˄བᵰ䇏᪡԰ᰃҹ㓧ކ䆩೒ߚ䜡ϔϾ㓧 _fillbufDŽབᵰ_fillbuf থ⦄᭛ӊϡᰃҹ䇏ᮍᓣᠧᓔⱘˈᅗᇚゟे䖨ಲ EOF˗৺߭ˈᅗᇚ ᇍѢᶤϔ⡍ᅮⱘ᭛ӊˈ㄀ϔ⃵䇗⫼ getc ߑ᭄ᯊ䅵᭄ؐЎ 0ˈ䖭ḋህᖙ乏䇗⫼ϔ⃵ߑ᭄ 䆌ৠᯊ䖯㸠䇏੠ݭⱘ+ᷛᖫDŽܕUNIX ㋏㒳Ё䖭⾡ᮍᓣᰃ≵᳝ᛣНⱘDŽৠᯊˈᅗгϡ㛑䆚߿ ᇥҷⷕDŽ⡍߿ᰃˈ䆹⠜ᴀⱘ fopen ϡ㛑䆚߿㸼⼎Ѡ䖯ࠊ䆓䯂ᮍᓣⱘ b ᷛᖫˈ䖭ᰃ಴Ўˈ೼ C ⱘ᠔᳝䆓䯂῵ᓣˈԚᰃˈࡴܹ䖭ѯ῵ᓣᑊϡ䳔㽕๲ࡴ໮ ޚ䆹⠜ᴀⱘ fopen ߑ᭄≵᳝⍝ঞᷛ } return fp; fp•>flag = (*mode == 'r') ? _READ : _WRITE; fp•>base = NULL; fp•>cnt = 0; fp•>fd = fd; return NULL; if (fd == •1) /* couldn't access name */ fd = open(name, O_RDONLY, 0); } else lseek(fd, 0L, 2); fd = creat(name, PERMS); if ((fd = open(name, O_WRONLY, 0)) == •1) else if (*mode == 'a') { fd = creat(name, PERMS); if (*mode == 'w') return NULL; ⬭԰㒗дDŽމUNIX ㋏㒳ⱘⳂᔩ㒧ᵘⳌৠⱘ㋏㒳Ϟᅲ⦄䖭ѯߑ᭄DŽ݊ᅗᚙ 㓪োⱘ䆓䯂DŽ៥Ӏᇚ߽⫼ℸ᥹ষ㓪ݭ fsize ⿟ᑣˈ✊ৢ䇈ᯢབԩ೼Ϣ Version 7੠System V ߑ᭄ opendirǃreaddir ੠ closedirˈᅗӀᦤկϢ㋏㒳᮴݇ⱘᇍⳂᔩ乍Ёⱘৡᄫ੠ i 㒧⚍ ⾏ߎϡৃ⿏ỡⱘ䚼ߚˈ៥Ӏᡞӏࡵߚ៤ϸ䚼ߚDŽ໪ሖᅮНњϔϾ⿄Ў Dirent ⱘ㒧ᵘ੠ 3 Ͼ 䘫ធⱘᰃˈ೼ϡৠ⠜ᴀⱘ㋏㒳ЁˈⳂᔩⱘḐᓣ੠⹂ߛⱘݙᆍᰃϡϔḋⱘDŽ಴ℸˈЎњߚ ৡ੠ i 㒧⚍㓪োDŽ ᭛ӊⱘ i 㒧⚍ᰃᄬᬒ䰸᭛ӊৡҹ໪ⱘ᠔᳝᭛ӊֵᙃⱘഄᮍDŽⳂᔩ乍䗮ᐌҙࣙ৿ϸϾᴵⳂ˖᭛ӊ ᭛ӊৡ߫㸼੠ϔѯᣛ⼎᭛ӊԡ㕂ⱘֵᙃDŽĀԡ㕂āᰃϔϾᣛ৥݊ᅗ㸼˄े i 㒧⚍㸼˅ⱘ㋶ᓩDŽ ಲ乒 UNIX ᭛ӊ㋏㒳ⱘ㒧ᵘDŽ೼ UNIX ㋏㒳ЁˈⳂᔩህᰃ᭛ӊˈᅗࣙ৿њϔϾܜ៥Ӏ佪 ᔦ䇗⫼㞾䑿DŽབᵰੑҸ㸠Ё≵᳝ӏԩখ᭄ˈ߭ fsize ⿟ᑣ໘⧚ᔧࠡⳂᔩDŽ 㸠খ᭄㸼Ёᣛᅮⱘ᠔᳝᭛ӊⱘ䭓ᑺDŽབᵰ݊ЁϔϾ᭛ӊᰃⳂᔩˈ߭ fsize ⿟ᑣᇚᇍℸⳂᔩ䗦 ҹϟᇚ䗮䖛⿟ᑣ fsize 䇈ᯢ䖭ϔ⚍DŽfsize ⿟ᑣᰃ ls ੑҸⱘϔϾ⡍⅞ᔶᓣˈᅗᠧॄੑҸ ݋ԧⱘ㋏㒳᳝݇ˈ៥Ӏ䳔㽕ᦤկϔ⾡Ϣ㋏㒳᮴݇ⱘ䆓䯂᭛ӊֵᙃⱘ䗨ᕘDŽ ⫮㟇㦋প᭛ӊৡг䳔㽕Փ⫼㋏㒳䇗⫼ˈ՟བ೼ MS•DOS ㋏㒳ЁेབℸDŽ᮴䆎ᅲ⦄ᮍᓣᰃ৺ৠ ᰃˈབᵰ䳔㽕㦋প᭛ӊⱘ݊ᅗֵᙃˈ↨བ䭓ᑺㄝˈህ䳔㽕Փ⫼㋏㒳䇗⫼DŽ೼݊ᅗϔѯ㋏㒳Ёˈ ⬅Ѣ UNIX ЁⱘⳂᔩህᰃϔ⾡᭛ӊˈ಴ℸˈls া䳔㽕䇏ℸ᭛ӊህৃ㦋ᕫ᠔᳝ⱘ᭛ӊৡDŽԚ ᳝㉏Ԑⱘࡳ㛑DŽ ӊৡҹঞ݊ᅗϔѯৃ䗝ֵᙃˈབ᭛ӊ䭓ᑺǃ䆓䯂ᴗ䰤ㄝㄝDŽMS•DOS ᪡԰㋏㒳Ёⱘ dir ੑҸг ⱘ݋ԧݙᆍDŽⳂᔩ߫㸼⿟ᑣ֓ᰃ݊ЁⱘϔϾ՟ᄤˈ↨བ UNIX ੑҸ lsˈᅗᠧॄϔϾⳂᔩЁⱘ᭛ ៥Ӏᐌᐌ䖬䳔㽕ᇍ᭛ӊ㋏㒳ᠻ㸠঺ϔ⾡᪡԰ˈҹ㦋ᕫ᭛ӊⱘֵ᳝݇ᙃˈ㗠ϡᰃ䇏প᭛ӊ 8.6. ᅲ՟——Ⳃᔩ߫㸼 㛑໳णৠᎹ԰DŽކⱘ㓧 ؐᰃϔϾ int ㉏ൟⱘ⢊ᗕ㗠䴲ԡ㕂ؐDŽ㓪ݭߑ᭄ fseekˈᑊ⹂ֱ䆹ߑ᭄ϢᑧЁ݊ᅗߑ᭄Փ⫼ ㉏ԐѢߑ᭄ lseekˈ᠔ϡৠⱘᰃˈ䆹ߑ᭄Ёⱘ fp ᰃϔϾ᭛ӊᣛ䩜㗠ϡᰃ᭛ӊᦣ䗄ヺˈϨ䖨ಲ int fseek(FILE *fp, long offset, int origin) ᑧߑ᭄ޚ㒗д 8•4 ᷛ 㒗д 8•3 䆒䅵ᑊ㓪ݭߑ᭄_flushbufǃfflush ੠ fcloseDŽ ⷕⱘ䭓ᑺ੠ᠻ㸠䗳ᑺDŽ 㒗д 8•2 ⫼ᄫ↉ҷ᳓ᰒᓣⱘᣝԡ᪡԰ˈ䞡ݭ fopen ੠_fillbuf ߑ᭄DŽ↨䕗Ⳍᑨҷ ᮍᓣⱘݭ᪡԰DŽކᠻ㸠㓧 䆹㒧ᵘЁ flag 䚼ߚⱘ߱ؐ㸼ᯢˈᇚᇍ stdin ᠻ㸠䇏᪡԰ǃᇍ stdout ᠻ㸠ݭ᪡԰ǃᇍ stderr }; { 0, (char *) 0, (char *) 0, _WRITE, | _UNBUF, 2 } { 0, (char *) 0, (char *) 0, _WRITE, 1 }, { 0, (char *) 0, (char *) 0, _READ, 0 }, FILE _iob[OPEN_MAX] = { /* stdin, stdout, stderr */ stdout ੠ stderr ؐ˖ /* #define S_IFCHR 0020000 /* character special #define S_IFDIR 0040000 /* directory */ #define S_IFMT 0160000 /* type of file: */ 䳔㽕໘⧚᭛ӊ㉏ൟⱘ᳝݇䚼ߚ˖ st_mode 乍ࣙ৿њᦣ䗄᭛ӊⱘϔ㋏߫ᷛᖫˈ䖭ѯᷛᖫ೼ЁᅮНDŽ៥Ӏা ЁᅮНˈ⿟ᑣЁᖙ乏ࣙ৿ℸ᭛ӊDŽ 䆹㒧ᵘЁ໻䚼ߚⱘؐᏆ೼⊼䞞Ё䖯㸠њ㾷䞞DŽdev_t ੠ ino_t ㄝ㉏ൟ೼༈᭛ӊ }; time_t st_ctime; /* time originally created */ time_t st_mtime; /* time last modified */ time_t st_atime; /* time last accessed */ off_t st_size; /* file size in characters */ dev_t st_rdev; /* for special files */ short st_gid; /* owners group id */ short st_uid; /* owners user id */ short st_nlink; /* number of links to file */ short st_mode; /* mode bits */ ino_t st_ino; /* inode number */ dev_t st_dev; /* device of inode */ { struct stat /* inode information returned by stat */ ⱘ䖨ಲؐⱘ㒧ᵘDŽ䆹㒧ᵘⱘϔϾ݌ൟᔶᓣབϟ᠔⼎˖ 㒧ᵘ stbufDŽ༈᭛ӊЁࣙ৿њᦣ䗄 statܙ฿ᅗ⫼᭛ӊ name ⱘ i 㒧⚍ֵᙃ stat(name, &stbuf); int stat(char *, struct stat *); struct stat stbuf; char *name; བϟ᠔⼎˖ ㋏㒳䇗⫼ stat ҹ᭛ӊৡ԰Ўখ᭄ˈ䖨ಲ᭛ӊⱘ i 㒧⚍Ёⱘ᠔ֵ᳝ᙃ˗㢹ߎ䫭ˈ߭䖨ಲ•1DŽ void closedir(DIR *dfd); Dirent *readdir(DIR *dfd); DIR *opendir(char *dirname); } DIR; Dirent d; /* the directory entry */ int fd; /* file descriptor for the directory */ typedef struct { /* minimal DIR: no buffering, etc. */ } Dirent; char name[NAME_MAX+1]; /* name + '\0' terminator */ long ino; /* inode number */ typedef struct { /* portable directory entry */ /* system•dependent */ #define NAME_MAX 14 /* longest filename component; */ ᅗᇚ㹿 readdir ੠ closedir Փ⫼DŽ᠔᳝䖭ѯֵᙃᄬᬒ೼༈᭛ӊ dirent.h ЁDŽ ᅮDŽopendir 䖨ಲϔϾᣛ৥⿄Ў DIR ⱘ㒧ᵘⱘᣛ䩜ˈ䆹㒧ᵘϢ㒧ᵘ FILE ㉏Ԑˈއⱘؐ⬅㋏㒳 㒧ᵘ Dirent ࣙ৿ i 㒧⚍㓪ো੠᭛ӊৡDŽ᭛ӊৡⱘ᳔໻䭓ᑺ⬅ NAMZ_MAX 䆒ᅮˈNAME_MAX Ў fsize ߑ᭄ᇍ↣ϾⳂᔩ䛑㽕䇗⫼ dirwalk ߑ᭄ˈ᠔ҹ䖭ϸϾߑ᭄ᰃⳌѦ䗦ᔦ䇗⫼ⱘDŽ ᠧᓔⳂᔩˈᕾ⦃䘡ग़݊Ёⱘ↣Ͼ᭛ӊˈᑊᇍ↣Ͼ᭛ӊ䇗⫼䆹ߑ᭄ˈ✊ৢ݇䯁Ⳃᔩ䖨ಲDŽ಴ܜ ߑ᭄ dirwalk ᰃϔϾ䗮⫼ⱘߑ᭄ˈᅗᇍⳂᔩЁⱘ↣Ͼ᭛ӊ䛑䇗⫼ߑ᭄ fcn ϔ⃵DŽᅗ佪 } printf("%8ld %s\n", stbuf.st_size, name); dirwalk(name, fsize); if ((stbuf.st_mode & S_IFMT) == S_IFDIR) } return; fprintf(stderr, "fsize: can't access %s\n", name); if (stat(name, &stbuf) == •1) { struct stat stbuf; { void fsize(char *name) /* fsize: print the name of file "name" */ void dirwalk(char *, void (*fcn)(char *)); int stat(char *, struct stat *); 㑻DŽܜⱘӬ 㑻ԢѢ==䖤ㅫヺܜS_IFDIR ᴹ߸ᅮ᭛ӊᰃϡᰃϔϾⳂᔩDŽᣀোᰃᖙ乏ⱘˈ಴Ў&䖤ㅫヺⱘӬ ߑ᭄໘⧚ᅗ᠔ࣙ৿ⱘ᠔᳝᭛ӊDŽ⊼ᛣབԩՓ⫼᭛ӊЁⱘᷛᖫৡ S_IFMT ੠ 䇗⫼ dirwalkܜߑ᭄ fsize ᠧॄ᭛ӊⱘ䭓ᑺDŽԚᰃˈབᵰℸ᭛ӊᰃϔϾⳂᔩˈ߭ fsize 佪 } return 0; fsize(*++argv); while (••argc > 0) else fsize("."); if (argc == 1) /* default: current directory */ { main(int argc, char **argv) /* print file name */ void fsize(char *) #include "dirent.h" #include /* structure returned by stat */ #include /* typedefs */ #include /* flags for read and write */ #include "syscalls.h" #include #include Џ⿟ᑣ main ໘⧚ੑҸ㸠খ᭄ˈᑊᇚ↣Ͼখ᭄Ӵ䗦㒭ߑ᭄ fsizeDŽ ⧚ⳂᔩЁⱘ᭛ӊDŽ⬅Ѣ䆹Ⳃᔩৃ㛑ࣙ৿ᄤⳂᔩˈ಴ℸ䆹䖛⿟ᰃ䗦ᔦⱘDŽ ᔩˈህᕜᆍᯧ㦋ᕫ䆹᭛ӊⱘ䭓ᑺˈᑊⳈ᥹䕧ߎDŽԚᰃˈབᵰ᭛ӊᰃϔϾⳂᔩˈ߭ᖙ乏䗤Ͼ໘ ϟ䴶៥Ӏᴹⴔ᠟㓪ݭ⿟ᑣ fsizeDŽབᵰ⬅ stat 䇗⫼㦋ᕫⱘ῵ᓣ䇈ᯢᶤ᭛ӊϡᰃϔϾⳂ /* ... */ #define S_IFREG 0010000 /* regular */ define S_IFBLK 0060000 /* block special */# ;(* int fstat(int fd, struct stat ㉏ԐˈԚᅗҹ᭛ӊᦣ䗄ヺ԰Ўখ᭄˅ˈ✊ৢߚ䜡ϔϾⳂᔩ㒧ᵘˈᑊֱᄬֵᙃ˖ ᠧᓔⳂᔩˈ偠䆕ℸ᭛ӊᰃϔϾⳂᔩ˄䇗⫼㋏㒳䇗⫼ fstatˈᅗϢ statܜopendir ߑ᭄佪 ЁⱘⳂᔩֵᙃˈབϟ᠔⼎˖ ϞᦤկϔϾ opendirǃreaddir ੠ closedir ⱘ᳔ㅔऩ⠜ᴀDŽҹϟⱘߑ᭄䗖⫼Ѣ Version 7 ⱘџᚙህᰃ೼ᶤϾ݋ԧⱘ㋏㒳خࠄ⦄೼䖭ϔℹЎℶˈҷⷕϢⳂᔩⱘḐᓣ᮴݇DŽϟϔℹ㽕 ᖙ乏䏇䖛ᅗӀˈ৺߭ᇚӮᇐ㟈᮴䰤ᕾ⦃DŽ ⱘ᭛ӊˈ䆹ߑ᭄ᇚ䖨ಲ NULLDŽ↣ϾⳂᔩ䛑ࣙ৿㞾䑿“.ā੠⠊Ⳃᔩ“..āⱘ乍Ⳃˈ೼໘⧚ᯊ ↣⃵䇗⫼ readdir 䛑ᇚ䖨ಲϔϾᣛ䩜ˈᅗᣛ৥ϟϔϾ᭛ӊⱘֵᙃDŽབᵰⳂᔩЁᏆ≵᳝ᕙ໘⧚ } closedir(dfd); } } (*fcn)(name); sprintf(name, "%s/%s", dir, dp•>name); else { dir, dp•>name); fprintf(stderr, "dirwalk: name %s %s too long\n", if (strlen(dir)+strlen(dp•>name)+2 > sizeof(name)) continue; /* skip self and parent */ || strcmp(dp•>name, "..")) if (strcmp(dp•>name, ".") == 0 while ((dp = readdir(dfd)) != NULL) { } return; fprintf(stderr, "dirwalk: can't open %s\n", dir); if ((dfd = opendir(dir)) == NULL) { DIR *dfd; Dirent *dp; char name[MAX_PATH]; { void dirwalk(char *dir, void (*fcn)(char *)) /* dirwalk: apply fcn to all files in dir */ define MAX_PATH 1024# ϡ䳔㽕ࣙ৿ⳌᑨⱘໄᯢDŽ݊⃵ˈ᳝ৃ㛑ЎϢ㋏㒳Ⳍ݇ⱘᇍ䈵߯ᓎϔϾϢ㋏㒳᮴݇ⱘ᥹ষDŽᷛ ༈᭛ӊЁˈՓ⫼ᅗӀⱘ⿟ᑣা䳔㽕೼᭛ӊЁࣙ৿䖭ѯ༈᭛ӊेৃˈ㗠ޚᙃⱘ㸼⼎ҙߎ⦄೼ᷛ Ā㋏㒳⿟ᑣāˈᅗӀҙҙՓ⫼⬅᪡԰㋏㒳㓈ᡸⱘֵᙃDŽᇍѢ䖭ḋⱘ⿟ᑣˈᕜ䞡㽕ⱘϔ⚍ᰃˈֵ 䆌໮⿟ᑣᑊϡᰃˈܜሑㅵ fsize ⿟ᑣ䴲ᐌ⡍⅞ˈԚᰃᅗⱘ⹂䇈ᯢњϔѯ䞡㽕ⱘᗱᛇDŽ佪 } return NULL; } return &d; d.name[DIRSIZ] = '\0'; /* ensure termination */ strncpy(d.name, dirbuf.d_name, DIRSIZ); d.ino = dirbuf.d_ino; continue; if (dirbuf.d_ino == 0) /* slot not in use */ == sizeof(dirbuf)) { while (read(dp•>fd, (char *) &dirbuf, sizeof(dirbuf)) static Dirent d; /* return: portable structure */ struct direct dirbuf; /* local directory structure */ { Dirent *readdir(DIR *dp) /* readdir: read directory entries in sequence */ #include /* local directory structure */ readdir ߑ᭄ᇚ㽚Ⲫࠡϔ⃵䇗⫼㦋ᕫⱘֵᙃDŽ ੠Ⳃᔩৡᬒ೼ϔϾ static ㉏ൟⱘ㒧ᵘЁˈᑊ㒭⫼᠋䖨ಲϔϾᣛ৥ℸ㒧ᵘⱘᣛ䩜DŽ↣⃵䇗⫼ Փ⫼˄಴Ўߴ䰸њϔϾ᭛ӊ˅ˈ߭ᅗⱘ i 㒧⚍㓪োЎ 0ˈᑊ䏇䖛䆹ԡ㕂DŽ৺߭ˈᇚ i 㒧⚍㓪ো ᳔ৢˈߑ᭄ readdir Փ⫼ read ㋏㒳䇗⫼䇏প↣ϾⳂᔩ乍DŽབᵰᶤϾⳂᔩԡ㕂ᔧࠡ≵᳝ } } free(dp); close(dp•>fd); if (dp) { { void closedir(DIR *dp) /* closedir: close directory opened by opendir */ closedir ߑ᭄⫼Ѣ݇䯁Ⳃᔩ᭛ӊᑊ䞞ᬒݙᄬぎ䯈˖ } return dp; dp•>fd = fd; return NULL; || (dp = (DIR *) malloc(sizeof(DIR))) == NULL) || (stbuf.st_mode & S_IFMT) != S_IFDIR || fstat(fd, &stbuf) == •1 if ((fd = open(dirname, O_RDONLY, 0)) == •1 DIR *dp; struct stat stbuf; int fd; { DIR *opendir(char *dirname) opendir: open a directory for readdir calls */ */ ぎ䯆ഫࣙ৿ϔϾᣛ৥䫒㸼ЁϟϔϾഫⱘᣛ䩜ǃϔϾഫ໻ᇣⱘ䆄ᔩ੠ϔϾᣛ৥ぎ䯆ぎ䯈ᴀ ៪ long ㉏ൟDŽ ೼ᶤѯᴎ఼Ёˈ᳔ফ䰤ⱘ㉏ൟᰃ double ㉏ൟ˗㗠೼঺໪ϔѯᴎ఼Ёˈ᳔ফ䰤ⱘ㉏ൟᰃ int ೼ᶤϾ⡍ᅮⱘഄഔЁˈ߭݊ᅗ᠔᳝ⱘ㉏ൟгৃҹᄬᬒ೼ℸഄഔЁDŽټབᵰ᳔ফ䰤ⱘ㉏ൟৃҹᄬ ֱᄬⱘᇍ䈵ⱘᇍ唤㽕∖DŽ㱑✊ᴎ఼㉏ൟ৘ᓖˈԚᰃˈ↣Ͼ⡍ᅮⱘᴎ఼䛑᳝ϔϾ᳔ফ䰤ⱘ㉏ൟ˖ ぎ䯈⒵䎇ᇚ㽕ټ៥Ӏ೼㄀ 5 ゴЁ᳒ᦤߎњ䖭ḋⱘ䯂乬ˈे⹂ֱ⬅ malloc ߑ᭄䖨ಲⱘᄬ ৺ぎ䯆DŽ ໮ⱘ⹢⠛DŽ಴Ўぎ䯆ഫ䫒㸼ᰃҹഄഔⱘ䗦๲乎ᑣ䫒᥹೼ϔ䍋ⱘˈ᠔ҹᕜᆍᯧ߸ᮁⳌ䚏ⱘഫᰃ ぎ䯈ϡӮ᳝໾ټᬒഫⳌ䚏ⱘӏϔ䖍ᰃϔϾぎ䯆ഫˈ߭ᇚ䖭ϸϾഫড়៤ϔϾ᳈໻ⱘഫˈ䖭ḋᄬ ㋶ぎ䯆ഫ䫒㸼ˈҹᡒࠄৃҹᦦܹ㹿䞞ᬒഫⱘড়䗖ԡ㕂DŽབᵰϢ㹿䞞᧰ܜ䞞ᬒ䖛⿟гᰃ佪 ϔϾ䎇໳໻ⱘഫˈ߭৥᪡԰㋏㒳⬇䇋ϔϾ໻ഫᑊࡴܹࠄぎ䯆ഫ䫒㸼ЁDŽ ߭ᇚᅗߚ៤ϸ䚼ߚ˖໻ᇣড়䗖ⱘഫ䖨ಲ㒭⫼᠋ˈ࠽ϟⱘ䚼ߚ⬭೼ぎ䯆ഫ䫒㸼ЁDŽབᵰᡒϡࠄ ഫDŽབᵰ䆹ഫᙄདϢ䇋∖ⱘ໻ᇣⳌヺড়ˈ߭ᇚᅗҢ䫒㸼Ё⿏䍄ᑊ䖨ಲ㒭⫼᠋DŽབᵰ䆹ഫ໾໻ˈ ЎĀ佪⃵䗖ᑨā˄first fit˅˗ϢПⳌᇍⱘㅫ⊩ᰃĀ᳔Շ䗖ᑨā˄best fit˅ˈᅗᇏᡒ⒵䎇ᴵӊⱘ᳔ᇣ ᔧ᳝⬇䇋䇋∖ᯊˈmalloc ᇚᠿᦣぎ䯆ഫ䫒㸼ˈⳈࠄᡒࠄϔϾ䎇໳໻ⱘഫЎℶDŽ䆹ㅫ⊩⿄ ೒ 8•1 㾕೒ 8•1˅DŽ ഄഔⱘछᑣ㒘㒛ˈ᳔ৢϔഫ˄᳔催ഄഔ˅ᣛ৥㄀ϔഫ˄খټぎ䯈ⱘᣛ䩜DŽ䖭ѯഫᣝ✻ᄬټᄬ ҹぎ䯆ഫ䫒㸼ⱘᮍᓣ㒘㒛ˈ↣Ͼഫࣙ৿ϔϾ䭓ᑺǃϔϾᣛ৥ϟϔഫⱘᣛ䩜ҹঞϔϾᣛ৥㞾䑿 ぎ䯈ټ䗮䖛݊ᅗᮍᓣ⬇䇋ぎ䯈˅ˈ᠔ҹˈmalloc ㅵ⧚ⱘぎ䯈ϡϔᅮᰃ䖲㓁ⱘDŽ䖭ḋˈぎ䯆ᄬ ৥᪡԰㋏㒳⬇䇋ぎ䯈DŽ಴Ў⿟ᑣЁⱘᶤѯഄᮍৃ㛑ϡ䗮䖛 malloc 䇗⫼⬇䇋ぎ䯈˄гህᰃ䇈ˈ ぎ䯈ˈ㗠ᰃ೼䳔㽕ᯊټmalloc ᑊϡᰃҢϔϾ೼㓪䆥ᯊህ⹂ᅮⱘ೎ᅮ໻ᇣⱘ᭄㒘Ёߚ䜡ᄬ 乬ˈৠᯊгሩ⼎њ㒧ᵘǃ㘨ড়੠ typedef ⱘᅲ䰙ᑨ⫼DŽ ぎ䯈DŽ䖭ѯ⿟ᑣ䇈ᯢњ䗮䖛ϔ⾡Ϣ㋏㒳᮴݇ⱘᮍᓣ㓪ݭϢ㋏㒳᳝݇ⱘҷⷕᯊᑨ㗗㰥ⱘ䯂ټᄬ 䰤ࠊˈৃҹҹӏᛣ⃵ᑣ䇗⫼ malloc ੠ freeDŽmalloc ೼ᖙ㽕ᯊ䇗⫼᪡԰㋏㒳ҹ㦋প᳈໮ⱘ ߚ䜡⿟ᑣDŽᴀ㡖ᇚ㽕㓪ݭⱘ⠜ᴀ≵᳝ټ៥Ӏ೼㄀ 5 ゴ㒭ߎњϔϾࡳ㛑᳝䰤ⱘ䴶৥ᷜⱘᄬ ߚ䜡⿟ᑣټ8.7. ᅲ՟——ᄬ 㒗д 8•5 ׂᬍ fsize ⿟ᑣˈᠧॄ i 㒧⚍乍Ёࣙ৿ⱘ݊ᅗֵᙃDŽ ᑧЁⱘߑ᭄ህᰃᕜདⱘ՟ᄤDŽޚ ( void *malloc(unsigned nbytes /* malloc: general•purpose storage allocator */ static Header *freep = NULL; /* start of free list */ static Header base; /* empty list to get started */ DŽܗぎ䯈ˈे↨ᣛ৥༈䚼ⱘᣛ䩜໻ϔϾऩټ䯆ᄬ ϟˈ䖨ಲ㒭⫼᠋ⱘᣛ䩜䛑ᣛ৥ഫݙⱘぎމ߱ྟഫⱘ༈䚼া䳔㽕ׂᬍ size ᄫ↉ेৃDŽ೼ӏԩᚙ ᓔྟDŽ䆹ㄪ⬹ৃҹֱ䆕䫒㸼ᰃഛࣔⱘDŽབᵰᡒࠄⱘഫ໾໻ˈ߭ᇚ݊ሒ䚼䖨ಲ㒭⫼᠋ˈ䖭ḋˈ ϟˈᔧ䇋∖ぎ䯆ぎ䯈ᯊˈ䛑ᇚ᧰㋶ぎ䯆ഫ䫒㸼DŽ᧰㋶ҢϞϔ⃵ᡒࠄぎ䯆ഫⱘഄᮍ˄freep˅ މᇚ߯ᓎϔϾ䗔࣪ⱘぎ䯆ഫ䫒㸼ˈᅗাࣙ৿ϔϾ໻ᇣЎ 0 ⱘഫˈϨ䆹ഫᣛ৥ᅗ㞾ᏅDŽӏԩᚙ ব䞣 base 㸼⼎ぎ䯆ഫ䫒㸼ⱘ༈䚼DŽ㄀ϔ⃵䇗⫼ malloc ߑ᭄ᯊˈfreep Ў NULLˈ㋏㒳 ৃ㛑䗮䖛ᣛ䩜ㅫᴃ䖤ㅫ䅵ㅫ݊໻ᇣDŽ ݊Ёⱘ size ᄫ↉ᰃᖙ䳔ⱘˈ಴Ў⬅ malloc ߑ᭄᥻ࠊⱘഫϡϔᅮᰃ䖲㓁ⱘˈ䖭ḋህϡ ೒ 8•2 malloc 䖨ಲⱘഫ 䫒㸼DŽ೒ 8•2 㸼⼎⬅ malloc 䖨ಲⱘഫDŽ ぎ䯈П໪ݭ᭄ܹ᥂ˈ߭ৃ㛑Ӯ⸈ണഫټぎ䯈䖯㸠ӏԩ᪡԰ˈԚᰃˈབᵰ೼ߚ䜡ⱘᄬټᕫⱘᄬ 䚼ⱘ size ᄫ↉ЁDŽmalloc ߑ᭄䖨ಲⱘᣛᓩᇚᣛ৥ぎ䯆ぎ䯈ˈ㗠ϡᰃഫⱘ༈䚼DŽ⫼᠋ৃᇍ㦋 Ѣ༈䚼ᴀ䑿DŽᅲ䰙ߚ䜡ⱘഫⱘ໻ᇣᇚ㹿䆄ᔩ೼༈⫼ˈܗDŽᅲ䰙ߚ䜡ⱘഫᇚ໮ࣙ৿ϔϾऩס᭄ ೼ malloc ߑ᭄Ёˈ䇋∖ⱘ䭓ᑺ˄ҹᄫヺЎऩԡ˅ᇚ㹿㟡ܹˈҹֱ䆕ᅗᰃ༈䚼໻ᇣⱘᭈ 唤㽕∖DŽ ϟ⒵䎇ᇍމ೼䆹㘨ড়ЁˈAlign ᄫ↉∌䖰ϡӮ㹿Փ⫼ˈᅗҙҙ⫼Ѣᔎࠊ↣Ͼ༈䚼೼᳔ണⱘᚙ typedef union header Header; }; Align x; /* force alignment of blocks */ } s; unsigned size; /* size of this block */ union header *ptr; /* next block if on free list */ struct { union header { /* block header */ typedef long Align; /* for alignment to long boundary */ Ў᳔ফ䰤ⱘ㉏ൟ˖ ᅮ long ㉏ൟ؛ⱘ༈䚼㒧ᵘҹঞϔϾᇍ唤㽕∖᳔ফ䰤ⱘ㉏ൟⱘᅲ՟ˈ೼ϟ䴶䖭↉⿟ᑣЁˈ៥Ӏ Ϩ༈䚼Ꮖℷ⹂ഄᇍ唤DŽ䖭ᰃ䗮䖛ϔϾ㘨ড়ᅲ⦄ⱘˈ䆹㘨ড়ࣙ৿᠔䳔ˈס乏ᰃ༈䚼໻ᇣⱘᭈ᭄ 䑿ⱘᣛ䩜DŽԡѢഫᓔྟ໘ⱘ᥻ࠊֵᙃ⿄ЎĀ༈䚼ĀDŽЎњㅔ࣪ഫⱘᇍ唤ˈ᠔᳝ഫⱘ໻ᇣ䛑ᖙ ; up = (Header *) cp return NULL; if (cp == (char *) •1) /* no space at all */ cp = sbrk(nu * sizeof(Header)); nu = NALLOC; if (nu < NALLOC) Header *up; char *cp, *sbrk(int); { static Header *morecore(unsigned nu) /* morecore: ask system for more memory */ #define NALLOC 1024 /* minimum #units to request */ ಴ℸˈা᳝೼ϔ㠀ᣛ䩜䯈ⱘ↨䕗᪡԰᳝ᛣНⱘᴎ఼Ϟˈ䆹⠜ᴀⱘ malloc ߑ᭄ᠡ㛑໳⿏ỡDŽ 䆌ᣛ৥ৠϔϾ᭄㒘ⱘᣛ䩜䯈ⱘ↨䕗DŽܕᑊ≵ֱ᳝䆕䖭ϔ⚍ˈᅗাޚ㸠᳝ᛣНⱘ↨䕗DŽANSI ᷛ ᅮˈ⬅ sbrk 䇗⫼䖨ಲⱘᣛ৥ϡৠഫⱘ໮Ͼᣛ䩜П䯈ৃҹ䖯؛ⱘϡৠⱘᕅડDŽԚᰃˈ䖭䞠ҡ✊ ㉏ൟˈҹ֓Ϣ䖨ಲؐ䖯㸠↨䕗DŽ㗠Ϩˈᔎࠊ㉏ൟ䕀ᤶՓᕫ䆹ߑ᭄ϡӮফϡৠᴎ఼Ёᣛ䩜㸼⼎ 䯆ぎ䯈ˈሑㅵ䖨ಲ NULL ৃ㛑᳈དϔѯˈԚ sbrk 䇗⫼䖨ಲ•1DŽᖙ乏ᇚ•1 ᔎࠊ䕀ᤶЎ char * ぎ䯈DŽབᵰ≵᳝ぎټUNIX ㋏㒳䇗⫼ sbrk(n)䖨ಲϔϾᣛ䩜ˈ䆹ᣛ䩜ᣛ৥ n Ͼᄫ㡖ⱘᄬ ぎ䯈ᦦܹࠄぎ䯆ऎඳЁDŽټ ᥂䳔㽕ߚ៤䕗ᇣⱘഫDŽ೼䆒㕂ᅠ size ᄫ↉Пৢˈmorecore ߑ᭄䇗⫼ free ߑ᭄ᡞ໮ԭⱘᄬ DŽ䖭Ͼ䕗໻ⱘഫᇚḍܗᠻ㸠䆹᪡԰ˈ෎Ѣ䖭Ͼ㗗㰥ˈmorecore ߑ᭄䇋∖㟇ᇥ NALLOC Ͼऩ ぎ䯈ᰃϔϾᓔ䫔ᕜ໻ⱘ᪡԰ˈ಴ℸˈ៥ӀϡᏠᳯ↣⃵䇗⫼ malloc ߑ᭄ᯊ䛑ټ৥㋏㒳䇋∖ᄬ ぎ䯈ˈ݊ᅲ⦄㒚㡖಴㋏㒳ⱘϡৠ㗠ϡৠDŽ಴Ўټߑ᭄ morecore ⫼Ѣ৥᪡԰㋏㒳䇋∖ᄬ } } return NULL; /* none left */ if ((p = morecore(nunits)) == NULL) if (p == freep) /* wrapped around free list */ } return (void *)(p+1); freep = prevp; } p•>s.size = nunits; p += p•>s.size; p•>s.size •= nunits; else { /* allocate tail end */ prevp•>s.ptr = p•>s.ptr; if (p•>s.size == nunits) /* exactly */ if (p•>s.size >= nunits) { /* big enough */ for (p = prevp•>s.ptr; ; prevp = p, p = p•>s.ptr) { } base.s.size = 0; base.s.ptr = freeptr = prevptr = &base; if ((prevp = freep) == NULL) { /* no free list yet */ nunits = (nbytes+sizeof(Header)•1)/sizeof(header) + 1; unsigned nunits; Header *moreroce(unsigned); Header *p, *prevp; } 䫒㸼Ё⏏ࡴϔϾ䴭ᗕ៪໪䚼᭄㒘DŽ ⬅ malloc ੠ free 㓈ᡸⱘぎ䯆ഫ䫒㸼ЁDŽ䗮䖛Փ⫼ bfreeˈ⫼᠋ৃҹ೼ӏᛣᯊࠏ৥ぎ䯆ഫ 㒗д 8•8 㓪ݭߑ᭄ bfree(p, n)ˈ䞞ᬒϔϾࣙ৿ n Ͼᄫヺⱘӏᛣഫ pˈᑊᇚᅗᬒܹ ߭䅸Ў㹿䞞ᬒⱘഫࣙ৿ϔϾ᳝ᬜⱘ䭓ᑺᄫ↉DŽᬍ䖯䖭ѯߑ᭄ˈՓᅗӀ݋᳝䫭䇃Ẕᶹⱘࡳ㛑DŽ ぎ䯈ⱘ䇋∖ᯊˈᑊϡẔᶹ䇋∖䭓ᑺⱘড়⧚ᗻ˗㗠 freeټ㒗д 8•7 malloc ᥹ᬊᇍᄬ ߑ᭄DŽ ぎ䯈䛑㹿߱ྟ࣪Ў 0DŽ䗮䖛䇗⫼៪ׂᬍ malloc ߑ᭄ᴹᅲ⦄ callocټⱘᇍ䈵ˈϨ᠔᳝ߚ䜡ⱘᄬ ᑧߑ᭄ calloc(n, size)䖨ಲϔϾᣛ䩜ˈᅗᣛ৥ n Ͼ䭓ᑺЎ sizeޚ㒗д 8•6 ᷛ DŽމ䖭⾡䗮⫼ᮍ⊩г䗖⫼Ѣ݊ᅗᚙ ߚ䜡ˈԚᰃˈټ㟇ৃҹ໘⧚䆒䅵ϡ໳དⱘ㋏㒳᥹ষ䯂乬DŽ㱑✊䖭䞠᠔䆆ⱘݙᆍা⍝ঞࠄᄬ⫮ خᅮ sbrk 䖨ಲⱘᰃড়䗖ⱘᣛ䩜˅䯂乬DŽ㉏ൟⱘᔎࠊ䕀ᤶՓᕫᣛ䩜ⱘ䕀ᤶᰃᰒᓣ䖯㸠ⱘˈ䖭ḋ ؛˄њഄഔⱘᇍ唤އⳌ݇ⱘ䚼ߚˈᑊᇚ䖭䚼ߚ⿟ᑣ᥻ࠊࠄ᳔ᇥ䞣DŽtypedef ੠ union ⱘՓ⫼㾷 ߚ䜡Ңᴀ䋼ϞᰃϢᴎ఼Ⳍ݇ⱘˈԚᰃˈҹϞⱘҷⷕ䇈ᯢњབԩ᥻ࠊϢ݋ԧᴎ఼ټ㱑✊ᄬ } freep = p; p•>s.ptr = bp; } else p•>s.ptr = bp•>s.ptr; p•>s.size += bp•>s.size; if (p + p•>size == bp) { /* join to lower nbr */ bp•>s.ptr = p•>s.ptr; } else bp•>s.ptr = p•>s.ptr•>s.ptr; bp•>s.size += p•>s.ptr•>s.size; if (bp + bp•>size == p•>s.ptr) { /* join to upper nbr */ break; /* freed block at start or end of arena */ if (p >= p•>s.ptr && (bp > p || bp < p•>s.ptr)) for (p = freep; !(bp > p && bp < p•>s.ptr); p = p•>s.ptr) bp = (Header *)ap • 1; /* point to block header */ Header *bp, *p; { void free(void *ap) /* free: put block ap in free list */ ᕜㅔऩˈা䳔㽕䆒㕂ᣛ䩜ᣛ৥ℷ⹂ⱘԡ㕂ˈᑊ䆒㕂ℷ⹂ⱘഫ໻ᇣህৃҹњDŽ ϟˈབᵰ㹿䞞ᬒⱘഫϢ঺ϔぎ䯆ഫⳌ䚏ˈ߭ᇚ䖭ϸϾഫড়ᑊ䍋ᴹDŽড়ᑊϸϾഫⱘ᪡԰މ⾡ᚙ ᡒৃҹᦦܹぎ䯆ഫⱘഄᮍDŽ䆹ԡ㕂ৃ㛑೼ϸϾぎ䯆ഫП䯈ˈгৃ㛑೼䫒㸼ⱘ᳿ሒDŽ೼ӏԩϔ ៥Ӏ᳔ৢᴹⳟϔϟ free ߑ᭄DŽᅗҢ freep ᣛ৥ⱘഄഔᓔྟˈ䗤Ͼᠿᦣぎ䯆ഫ䫒㸼ˈᇏ } return freep; free((void *)(up+1)); up•>s.size = nu; ᄫ䴶ؐЁDŽ ⊼䞞ҹᄫヺ/*ᓔྟˈҹ*/㒧ᴳDŽ⊼䞞ϡ㛑໳ጠ༫ˈгϡ㛑໳ߎ⦄೼ᄫヺІᄫ䴶ؐ៪ᄫヺ A.2.2 ⊼䞞 Ёৃ㛑ᵘ៤䆄োⱘ᳔䭓ⱘᄫヺІDŽ བᵰࠄᶤϔᄫヺЎℶⱘ䕧ܹ⌕㹿ߚ䱨៤㢹ᑆ䆄োˈ䙷МˈϟϔϾ䆄োህᰃৢ㓁ᄫヺᑣ߫ 䱨䆄োˈ಴ℸᇚ㹿ᗑ⬹DŽⳌ䚏ⱘᷛ䆚ヺǃ݇䬂ᄫ੠ᐌ䞣П䯈䳔㽕⫼ぎⱑヺᴹߚ䱨DŽ ぎḐˈ῾৥ࠊ㸼ヺ੠㒉৥ࠊ㸼ヺǃᤶ㸠ヺˈᤶ义ヺ੠⊼䞞˄㒳⿄ぎⱑヺ˅೼⿟ᑣЁҙ⫼ᴹߚ C 䇁㿔Ё᳝݅ 6 ㉏䆄ো˖ᷛ䆚ヺǃ݇䬂ᄫǃᐌ䞣ǃᄫヺІᄫ䴶ؐǃ䖤ㅫヺ੠݊ᅗߚ䱨ヺDŽ A.2.1 䆄ো ⿟ᑣ㹿ᔦ㑺៤ϔϾ䆄োᑣ߫DŽ ヺ#ᓔ༈ⱘ㸠ЁⱘᣛҸˈᑊ䖯㸠ᅣᅮН੠ᅣᠽሩDŽ೼乘໘⧚˄ᇚ೼ A.12 㡖Ёҟ㒡˅ᅠ៤ৢˈ 䰊↉ᅠ៤ˈ䖭䚼ߚݙᆍᇚ೼ A.12 㡖Ёҟ㒡DŽ㗏䆥ⱘ㄀ϔ䰊↉ᅠ៤Ԣ㑻ⱘ䆡⊩䕀ᤶˈᠻ㸠ҹᄫ translation unit˅㒘៤DŽ⿟ᑣⱘ㗏䆥ߚ޴Ͼ˄ܗ೼᭛ӊЁⱘϔϾ៪໮Ͼ㗏䆥ऩټ⿟ᑣ⬅ᄬ A.2 䆡⊩㾘߭ 㓪䆥఼ᬃᣕⱘ䇁㿔П䯈ⱘᏂ߿DŽ C 䇁㿔Ϣᴀк㄀ 1 ⠜ᅮНⱘ C 䇁㿔៪݊ᅗ ޚᴀ᠟ݠЁˈ䇈ᯢ䚼ߚⱘ᭛ᄫᣛߎњ ANSI ᷛ 乘໘⧚఼ⱘᅮНг≵᳝ᔶᓣ࣪DŽ ㋴ⱘੑৡৃ㛑᳝ѯϡৠˈ䆡⊩䆄ো੠ܗᰃⳌৠⱘˈԚᰃˈ݊Ёᇥ䞣ޚᴀ᠟ݠ㒭ߎⱘ䇁⊩Ϣᷛ ෎ᴀ㉏ԐˈϢᴀкⱘ㄀ 1 ⠜г㉏ԐˈԚᰃᇍ㒚㡖ⱘ㒘㒛᳝ѯϡৠDŽޚ䆹᠟ݠⱘ㒘㒛Ϣᷛ ᏆDŽ ⱘϔϾ㾷䞞㗠ޚᴀ䑿ˈ㗠ҙҙাᰃᇍᷛޚݠ԰Ў C 䇁㿔ⱘᣛफҟ㒡㒭䇏㗙ˈԚᅗ↩コϡᰃᷛ ⹂ഄᇚ䆹᠟ޚ∖C ⿟ᑣ䆒䅵䇁㿔ˈX3.159•1989āDŽሑㅵ៥ӀᏆሑ᳔໻ࡾ࡯ˈ࡯——ޚᙃ㋏㒳ᷛ োЎĀ㕢೑೑ᆊֵޚᴀ᠟ݠᦣ䗄ⱘ C 䇁㿔ᰃ 1988 ᑈ 10 ᳜ 31 ᮹ᦤѸ㒭 ANSI ⱘ㤝Ḝˈᡍ A.1 ᓩ㿔 䰘ᔩAখ㗗᠟ݠ ᳝ৢ㓔Ϩᰃक䖯ࠊ㸼⼎ˈ߭݊㉏ൟᕜৃ㛑ᰃ intǃlong int ៪ unsigned long intDŽབ ᭈൟᐌ䞣ⱘ㉏ൟৠᅗⱘᔶᓣǃؐ੠ৢ㓔᳝݇˄᳝݇㉏ൟⱘ䅼䆎ˈখ㾕 A.4 㡖˅DŽབᵰᅗ≵ ߭㸼⼎ᅗᰃϔϾ䭓ᭈൟ᭄˗㢹ҹᄫ↡ UL Ўৢ㓔ˈ߭㸼⼎ᅗᰃϔϾ᮴ヺো䭓ᭈൟ᭄DŽ ᭈൟᐌ䞣㢹ҹᄫ↡ u ៪ U Ўৢ㓔ˈ߭㸼⼎ᅗᰃϔϾ᮴ヺো᭄˗㢹ҹᄫ↡ l ៪ L Ўৢ㓔ˈ a˄៪ A˅ࠄ f˄៪ F˅ⱘᄫ↡ˈᅗӀߚ߿㸼⼎᭄ؐ 10 ࠄ 15DŽ 䖯ࠊᐌ䞣ϡࣙᣀ᭄ᄫ 8 ੠ 9ˈҹ 0x ੠ 0X ᓔ༈ⱘ᭄ᄫᑣ߫㸼⼎क݁䖯ࠊ᭄ˈक݁䖯ࠊ᭄ࣙ৿Ң ᭈൟᐌ䞣⬅ϔІ᭄ᄫ㒘៤DŽབᵰᅗҹ᭄ᄫ 0 ᓔ༈ˈ߭Ўܿ䖯ࠊ᭄ˈ৺߭Ўक䖯ࠊ᭄DŽܿ 1. ჼ໸Ш२ ਛࡨШ२ ׼ԤШ२ ᆓ׸Ш२ ჼ໸Ш२ ᐌ䞣˖ ᐌ䞣᳝໮⾡㉏ൟDŽ↣⾡㉏ൟⱘᐌ䞣䛑᳝ϔϾ᭄᥂㉏ൟDŽ෎ᴀ᭄᥂㉏ൟᇚ೼ A.4.2 㡖䅼䆎DŽ A.2.5 ᐌ䞣 ྸ࠼Ϣಾॴd ēຣၽڶߏᆓӬҶร΄ರဈڑ࿫ဈĢentry ႛ࠼΄ͬঝนַڜ΄໭႙ަԅēຣྸ܊͑ ಾԛ 1 ߏᆓ constcsigned ۤ volatile ಾ ANSI γᅹᄯ໭႙ަԅĢenum ۤ voidڑ˖䇈ᯢ ᶤѯᅲ⦄䖬ᡞ fortran ੠Ϩ asm ֱ⬭Ў݇䬂ᄫDŽ do if static while default goto sizeof volatile continue for signed void const float short unsigned char extern return union case enum register typedef break else long switch auto double int struct ϟ߫ᷛ䆚ヺ㹿ֱ⬭԰Ў݇䬂ᄫˈϨϡ㛑⫼Ѣ݊ᅗ⫼䗨˖ A.2.4 ݇䬂ᅛ ϔѯˈᅲ⦄ৃ㛑া䅸Ў䖭ѯᷛ䆚ヺⱘࠡ 6 Ͼᄫヺᰃ᳝ᬜⱘˈ㗠Ϩ᳝ৃ㛑ᗑ⬹໻ᇣݭⱘϡৠDŽ ৡ੠݊ᅗ᠔᳝≵᳝໪䚼䖲᥹˄খ㾕 A.11.2 㡖˅ⱘৡᄫDŽᏺ᳝໪䚼䖲᥹ⱘᷛ䆚ヺⱘ䰤ࠊ᳈ϹḐ 31 Ͼᄫ↡ᰃ᳝ᬜⱘˈ೼ᶤѯᅲ⦄Ёˈ᳝ᬜⱘᄫヺ᭄ৃ㛑᳈໮DŽݙ䚼ᷛ䆚ヺࣙᣀ乘໘⧚఼ⱘᅣ ↡DŽ໻ݭᄫ↡੠ᇣݭᄫ↡ᰃϡৠⱘDŽᷛ䆚ヺৃҹЎӏᛣ䭓ᑺDŽᇍѢݙ䚼ᷛ䆚ヺᴹ䇈ˈ㟇ᇥࠡ ᷛ䆚ヺᰃ⬅ᄫ↡੠᭄ᄫᵘ៤ⱘᑣ߫DŽ㄀ϔϾᄫヺᖙ乏ᰃᄫ↡ˈϟߦ㒓“_āг㹿ⳟ៤ᰃᄫ A.2.3 ᷛ䆚ヺ l ៪ L ৢ㓔㸼ᯢᅗᰃ long double ㉏ൟ˗≵᳝ৢ㓔߭㸼ᯢᰃ double ㉏ൟDŽ ᭄䚼ߚ˄Ԛϡ㛑ϸ㗙䛑≵᳝˅DŽ⍂⚍ᐌ䞣ⱘ㉏ൟ⬅ৢ㓔⹂ᅮˈF ៪ f ৢ㓔㸼⼎ᅗᰃ float ㉏ൟ˗ ߫㒘៤DŽৃҹ≵᳝ᭈ᭄䚼ߚ៪ᇣ᭄䚼ߚ˄Ԛϡ㛑ϸ㗙䛑≵᳝˅ˈ䖬ৃҹ≵᳝ᇣ᭄⚍៪㗙 e ੠ᣛ ᭄੠ϔϾৃ䗝ⱘ㸼⼎㉏ൟⱘৢ㓔˄े fǃFǃl ៪ L Пϔ˅㒘៤DŽᭈ᭄੠ᇣ᭄䚼ߚഛ⬅᭄ᄫᑣ ⍂⚍ᐌ䞣⬅ᭈ᭄䚼ߚǃᇣ᭄⚍ǃᇣ᭄䚼ߚǃϔϾ e ៪ EǃϔϾৃ䗝ⱘᏺヺোᭈൟ㉏ൟⱘᣛ 3. ׼ԤШ२ ಾนॴζವཐᄽԅဴཝd ࢶྻဈ char फ໸ࠩ໻Ω৲ē႙ަ wchar_t ԅᅖྑંԅނ๦૘ൎဈԅᆓ׸ۤڳඹШெࣣຏēਥ 䇈ᯢ˖੶໔ᅧ࿌༝ॹಾ໭႙ަԅēඋιಾಥঢࠩᄥᆓ׸ԅζವdࣴႺᆓ׸ྙಾ໭႙ަԅd ߭㒧ᵰᰃ᳾ᅮНⱘDŽ 䞣ৃҹՓ⫼ܿ䖯ࠊ៪क݁䖯ࠊ䕀Нᄫヺᑣ߫˗Ԛᰃˈབᵰؐ䍙䖛 wchar_t ৃҹ㸼⼎ⱘ㣗ೈˈ ༈᭛ӊЁDŽϢ䗮ᐌⱘᄫヺᐌ䞣ϔḋˈᆑᄫヺᐌޚ䖭ᰃϔ⾡ᭈൟ㉏ൟˈᅮН೼ᷛ ⱘᐌ䞣㽕ҹϔϾࠡᇐヺ L ᓔ༈˄՟བ L'x'DŽ˅ˈ⿄Ўᆑᄫヺᐌ䞣DŽ䖭⾡ᐌ䞣ⱘ㉏ൟЎ wchar_tDŽ ೼ C 䇁㿔ⱘᶤѯᅲ⦄Ёˈ䖬᳝ϔϾᠽሩⱘᄫヺ䲚ˈᅗϡ㛑⫼ char ㉏ൟ㸼⼎DŽᠽሩ䲚Ё ߭݊㸠Ўᰃ᳾ᅮНⱘDŽ ᅗ㹿ᔎࠊ䕀ᤶЎ char ㉏ൟϔḋDŽབᵰ\ৢ䴶㋻䎳ⱘᄫヺϡ೼ҹϞᣛᅮⱘᄫヺЁˈڣሩˈህད ᰃᏺヺোⱘˈ߭ᇚᇍᄫヺؐ䖯㸠ヺোᠽخࠊ៪क݁䖯ࠊ䕀Нᄫヺˈབᵰᅲ⦄Ёᇚ㉏ൟ char ⳟ ؐDŽ᭄ᄫⱘϾ᭄≵᳝䰤ࠊˈԚབᵰᄫヺؐ䍙䖛᳔໻ⱘᄫヺؐˈ䆹㸠Ўᰃ᳾ᅮНⱘDŽᇍѢܿ䖯 ߫\xhh Ёˈড᭰ᴴৢ䴶㋻䎳 x ҹঞक݁䖯ࠊ᭄ᄫˈ䖭ѯक݁䖯ࠊ᭄⫼ᴹᣛᅮ᠔ᳳᳯⱘᄫヺⱘ ᅮ᠔ᳳᳯⱘᄫヺⱘؐDŽ\0˄݊ৢ≵᭄᳝ᄫ˅֓ᰃϔϾᐌ㾕ⱘ՟ᄤˈᅗ㸼⼎ᄫヺ NULDŽ䕀Нᑣ 䕀Нᑣ߫\ooo ⬅ড᭰ᴴৢ䎳 1 Ͼǃ2 Ͼ៪ 3 Ͼܿ䖯ࠊ᭄ᄫ㒘៤ˈ䖭ѯܿ䖯ࠊ᭄ᄫ⫼ᴹᣛ ડ䪗ヺ BEL \a ᤶ义ヺ FF \f क݁䖯ࠊ᭄ hh \xhh ಲ䔺ヺ CR \r ܿ䖯ࠊ᭄ ooo \ooo ಲ䗔ヺ BS \b ঠᓩো "\" 㒉৥ࠊ㸼ヺ VT \v ऩᓩো '\' ῾৥ࠊ㸼ヺ HT \t 䯂ো ?\? ᤶ㸠ヺ NL(LF) \n ড᭰ᴴ \\\ ᄫヺ˖ ᄫヺᐌ䞣ϡࣙᣀᄫヺ'੠ᤶ㸠ヺˈৃҹՓ⫼ҹϟ䕀Нᄫヺᑣ߫㸼⼎䖭ѯᄫヺҹঞ݊ᅗϔѯ 㸠ᯊᴎ఼ᄫヺ䲚Ёℸᄫヺᇍᑨⱘ᭄ؐˈ໮ᄫヺᐌ䞣ⱘؐ⬅݋ԧᅲ⦄ᅮНDŽ ᄫヺᐌ䞣ᰃ⫼ऩᓩোᓩ䍋ᴹⱘϔϾ៪໮Ͼᄫヺᵘ៤ⱘᑣ߫ˈབ'x'DŽऩᄫヺᐌ䞣ⱘؐᰃᠻ 2. ᆓ׸Ш२ ᅷಾ໭႙ަԅd܊ࠧ΄ࢡᆳಾ long फ໸dU 䇈ᯢ˖ANSI γᅹᄯēჼ໸Ш२ԅफ໸Αԛ 1 ͑ྑ؏ၶԄտdၽԛ 1 ͑ᄯēӖԅჼ໸Ш२ long intDŽ ៪ ungigned long int DŽབᵰᅗⱘৢ㓔Ў l ៪ Lˈ߭݊㉏ൟᕜৃ㛑ᰃ long int ៪ unsigned int ៪ unsigned long int DŽབᵰᅗⱘৢ㓔Ў u ៪ Uˈ߭݊㉏ൟᕜৃ㛑ᰃ unsigned int ᵰᅗ≵᳝ৢ㓔Ϩᰃܿ䖯ࠊ៪क݁䖯ࠊ㸼⼎ˈ߭݊㉏ൟᕜৃ㛑ᰃ intǃunsigned int ǃlong ᅮ঺ϔ԰⫼ඳЁⱘৠϔϾৡᄫᰃ৺އϾ䖲᥹DŽ԰⫼ඳे⿟ᑣЁৃҹ䆓䯂ℸৡᄫⱘऎඳˈ䖲᥹ ᅮњᷛ䆚ᇍ䈵Ёؐⱘ৿НDŽৡᄫ䖬݋᳝ϔϾ԰⫼ඳ੠ϔއऎඳⱘ⫳ᄬᳳˈ㉏ൟټⳌ݇㘨ⱘᄬ ᅮњϢ䆹ᷛ䆚ᇍ䈵އ㉏ټ㉏੠㉏ൟDŽᄬټԡ㕂DŽᇍᅗⱘ㾷䞞ձ䌪ѢϸϾЏ㽕ሲᗻ˖ᄬټϾᄬ ៤ਬ៪㘨ড়៤ਬ˗ᵮВᐌ䞣˗㉏ൟᅮНৡ˗ᷛোҹঞᇍ䈵ㄝDŽᇍ䈵᳝ᯊг⿄Ўব䞣ˈᅗᰃϔ ᷛ䆚ヺг⿄Ўৡᄫˈৃҹᣛҷ໮⾡ᅲԧ˖ߑ᭄ǃ㒧ᵘᷛ䆄ǃ㘨ড়ᷛ䆄੠ᵮВᷛ䆄˗㒧ᵘ A.4 ᷛ䆚ヺⱘ৿Н ໿ມ಴ζӒѻࣿॴdۦࠒۤ މ䇈ᯢ˖ူ·ೠԛ 1 ͑ٓѻԅဴ֥ൎϢලԅಾēҮ҉ٓѻԅဴ֥ߜζӒ಴ၮസ׸ԅညຕ 㸼⼎ϔϾᣀ೼㢅ᣀোЁⱘ㸼䖒ᓣˈ䆹㸼䖒ᓣᰃৃ䗝ⱘDŽA.13 㡖ᇍ䇁⊩䖯㸠њᘏ㒧DŽ { 㸼䖒ᓣ opt } ҹᬒ೼ϔ㸠Ёˈᑊҹⷁ䇁“one ofāᷛ䆚DŽৃ䗝ⱘ㒜㒧ヺ៪䴲㒜㒧ヺᏺ᳝ϟᷛ“optāDŽ՟བ˖ ϟˈϔ㒘ᄫヺ䭓ᑺ䕗ⷁⱘ׭䗝乍ৃމ㸼ϡDŽ໮Ͼ׭䗝㉏߿䗮ᐌ߫೼ϡৠⱘ㸠ЁˈԚ೼ϔѯᚙ ೼ᴀ᠟ݠ⫼ࠄⱘ䇁⊩ヺোЁˈ䇁⊩㉏߿⫼Ὃԧঞ᭰ԧᄫ㸼⼎DŽ᭛ᄫ੠ᄫヺҹᠧᄫൟᄫԧ A.3 䇁⊩ヺো ԅd ၭ༘ອঃᆓ׸ґᆓੋᄔࠩ໻ॕࠄdࣚᆓ׸ᆓ׸ґᆓੋᄔྙಾ ANSI γᅹᄯ໭႙ަރґᆓੋᄔྻ ᆓ׸؟cࠬᄘ༉דՇ՛ಾ ANSI γᅹᄯ໭႙ަԅġᆓ׸ґᆓੋᄔϢΡࠩ໻௕ڟ䇈ᯢ˖ຏॹ 䖯㸠䖲᥹ⱘ㸠Ўᰃ᳾ᅮНⱘDŽ ᄫヺІᄫ䴶ؐⱘ㉏ൟЎ“wchar_t ㉏ൟⱘ᭄㒘āDŽᇚ᱂䗮ᄫヺІᄫ䴶ؐ੠ᆑᄫヺᄫヺІᄫ䴶ؐ Ϣᄫヺᐌ䞣ϔḋˈᠽሩᄫヺ䲚ЁⱘᄫヺІᄫ䴶ؐгҹࠡᇐヺ L 㸼⼎ˈབ L"…"DŽᆑᄫヺ ӀDŽ ᄫヺІᄫ䴶ؐϡࣙ৿ᤶ㸠ヺ੠ঠᓩোᄫヺˈԚৃҹ⫼Ϣᄫヺᐌ䞣Ⳍৠⱘ䕀Нᄫヺᑣ߫㸼⼎ᅗ ೼ᄫヺІⱘৢ䴶๲ࡴϔϾぎᄫ㡖\0ˈ䖭ḋˈᠿᦣᄫヺІⱘ⿟ᑣ֓ৃҹᡒࠄᄫヺІⱘ㒧ᴳԡ㕂DŽ ៥ӀৃҹᡞⳌ䚏ⱘᄫヺІᄫ䴶ؐ䖲᥹ЎϔϾऩϔⱘᄫヺІDŽᠻ㸠ӏԩ䖲᥹᪡԰ৢˈ䛑ᇚ ヺІᄫ䴶ؐˈ߭㸠Ўᰃ᳾ᅮНⱘDŽ Ѣ݋ԧⱘᅲ⦄DŽབᵰ⿟ᑣ䆩೒ׂᬍᄫއ䖯㸠߱ྟ࣪DŽᇍⳌৠⱘᄫヺІᄫ䴶ؐᰃ৺䖯㸠ऎߚপ ㉏Ў static˄খ㾕 A.4 㡖˅ˈᅗՓ⫼㒭ᅮⱘᄫヺټ“…”DŽᄫヺІⱘ㉏ൟЎĀᄫヺ᭄㒘āˈᄬ ᄫヺІᄫ䴶ؐ˄string literal˅г⿄ЎᄫヺІᐌ䞣ˈᰃ⫼ঠᓩোᓩ䍋ᴹⱘϔϾᄫヺᑣ߫ˈབ A.2.6 ᄫヺІᄫ䴶ؐ ໄᯢЎᵮВヺⱘᷛ䆚ヺᰃ int ㉏ൟⱘᐌ䞣˄খ㾕 A.8.4 㡖 ˅DŽ 4. ਛࡨШ२ ᅷಾ໭႙ަԅd܊䇈ᯢ˖׼ԤШ२ԅ Ёⱘӏԩ㉏ൟ䛑ৃ㛑ᰃৠНⱘˈԚ㊒ᑺҢࠡࠄৢᰃ䗦๲ⱘDŽ ऩ㊒ᑺ⍂⚍᭄˄float˅ǃঠ㊒ᑺ⍂⚍᭄˄double˅੠໮㊒ᑺ⍂⚍᭄˄long double˅ 㸼⼎ᰃⳌৠⱘDŽ ೼Ⳍᑨⱘ᮴ヺোᇍ䈵Ёⱘؐⱘᄤ䲚ˈᑊϨˈ䖭ϸϾ䲚ড়ⱘ䞡঴䚼ߚⱘټ䋳ؐⱘ䲚ড়ᰃৃҹᄬ ೼ᏺヺোᇍ䈵Ёⱘ䴲ټⱘѠ䖯ࠊԡ᭄ˈ䖭ḋˈᇍ᮴ヺো᭄ⱘㅫᴃ䖤ㅫ∌䖰ϡӮ⑶ߎDŽৃҹᄬ ҹ݇䬂ᄫ unsigned ໄᯢⱘ᮴ヺোᭈ᭄䙉ᅜㅫᴃ῵ 2n ⱘ㾘߭ˈ݊Ёˈn ᰃ㸼⼎Ⳍᑨᭈ᭄ 䰸䴲⡍߿䇈ᯢˈint ㉏ൟ䛑㸼⼎ᏺヺো᭄DŽ Փᕫϔ㠀ᭈൟ˄int˅Ϣⷁᭈൟ˄short int˅៪䭓ᭈൟ˄long int˅݋᳝ৠḋⱘ໻ᇣDŽ ぎ䯈˗Ԛᰃ݋ԧⱘᅲ⦄ৃҹټ৘⾡⡍⅞ⱘ⫼䗨DŽ䕗䭓ⱘᭈ᭄㟇ᇥ㽕ऴ᳝Ϣ䕗ⷁᭈ᭄ϔḋⱘᄬ ᅮⱘ㞾✊䭓ᑺⳌৠDŽ݊ᅗ㉏ൟⱘᭈൟৃҹ⒵䎇އ䗮 int ᇍ䈵ⱘ䭓ᑺϢ⬅ᆓЏᴎ఼ⱘԧ㋏㒧ᵘ 䰸 char ㉏ൟ໪ˈ䖬᳝ 3 ⾡ϡৠ໻ᇣⱘᭈൟ㉏ൟ˖short intǃint ੠ long intDŽ᱂ ႙ަԅd 䇈ᯢ˖·ೠԅԛ 1 ͑ᄯਠပ unsigned char फ໸ēӬუᄵဈ֥۳Шߎdsigned char ಾ໭ 䋳ⱘDŽҹ signed char ᰒᓣໄᯢⱘᏺヺোᄫヺϢ᱂䗮ᄫヺгऴ⫼ৠḋ໻ᇣⱘぎ䯈DŽ ҹ unsigned char ໄᯢⱘ᮴ヺোᄫヺϢ᱂䗮ᄫヺऴ⫼ৠḋ໻ᇣⱘぎ䯈ˈԚ݊ؐᘏᰃ䴲 োˈৠ݋ԧⱘᅲ⦄᳝݇DŽ ೼ char ㉏ൟⱘব䞣ЁˈԚ݊পؐ㣗ೈˈ⡍߿ᰃ݊ؐᰃ৺ᏺヺټؐDŽ݊ᅗ㉏ൟⱘᇍ䈵гৃҹᄬ ೼ϔϾ char ㉏ൟⱘᇍ䈵Ёˈ߭䆹ᇍ䈵ⱘؐㄝѢᄫヺⱘᭈൟ㓪ⷕؐˈᑊϨᰃ䴲䋳ټᶤϾᄫヺᄬ ᠻ㸠ᄫヺ䲚ЁⱘӏԩᄫヺDŽབᵰᄫヺ䲚ЁⱘټໄᯢЎᄫヺ˄char˅ⱘᇍ䈵㽕໻ࠄ䎇ҹᄬ ㉏ൟⱘ᳔໻ؐ੠᳔ᇣؐDŽ䰘ᔩ B 㒭ߎⱘ᭄ؐ㸼⼎᳔ᇣⱘৃ᥹ফ䰤ᑺDŽ ༈᭛ӊЁᅮНњᴀഄᅲ⦄Ё↣⾡ޚ෎ᴀ㉏ൟࣙᣀ໮⾡DŽ䰘ᔩ B Ёᦣ䗄ⱘᷛ A.4.2 ෎ᴀ㉏ൟ ᯢᯊˈᇍ䈵ᇍᭈϾ⿟ᑣᴹ䇈ᰃܼሔৃ䆓䯂ⱘˈᑊϨ݋᳝໪䚼䖲᥹DŽ ㉏៪䗮䖛݇䬂ᄫ extern 䖯㸠ໄټᇍ䈵ˈ䖭⾡㉏ൟⱘᇍ䈵ᇚ݋᳝ݙ䚼䖲᥹DŽᔧⳕ⬹ᰒᓣⱘᄬ ⱘሔ䚼ܗৠϔ㑻ⱘᇍ䈵ᘏᰃ䴭ᗕⱘDŽৃҹ䗮䖛 static ݇䬂ᄫᇚᇍ䈵ໄᯢЎᶤϾ⡍ᅮ㗏䆥ऩ ⷕⱘ⿟ᑣഫ˅ݙˈ䴭ᗕᇍ䈵⫼݇䬂ᄫ static ໄᯢDŽ೼᠔᳝⿟ᑣഫ໪䚼ໄᯢϨϢߑ᭄ᅮН೼ ೼䗔ߎ੠ݡ䖯ܹߑ᭄៪⿟ᑣഫᯊ݊ؐᇚֱᣕϡবDŽ೼ϔϾ⿟ᑣഫ˄ࣙᣀᦤկߑ᭄ҷˈމ⾡ᚙ 䴭ᗕᇍ䈵ৃҹᰃᶤϾ⿟ᑣഫⱘሔ䚼ᇍ䈵ˈгৃҹᰃ᠔᳝⿟ᑣഫⱘ໪䚼ᇍ䈵DŽ᮴䆎ᰃાϔ ೼ᴎ఼ⱘᖿ䗳ᆘᄬ఼Ё˄བᵰৃ㛑ⱘ䆱˅DŽټ㉏ᇍ䈵ˈᑊϨᇚ㹿ᄬټⱘᇍ䈵гᰃ㞾ࡼᄬ ㉏ᇍ䈵DŽໄᯢЎ registerټབᵰՓ⫼њ auto 䰤ᅮヺˈ߭⿟ᑣഫЁⱘໄᯢ⫳៤ⱘ䛑ᰃ㞾ࡼᄬ ㉏䇈ᯢヺˈ៪㗙ټA.9.3 㡖˅ᴹ䇈ᰃሔ䚼ⱘˈ೼䗔ߎ⿟ᑣഫᯊ䆹ᇍ䈵ᇚ⍜༅DŽབᵰ≵᳝Փ⫼ᄬ ㉏ᇍ䈵ᇍѢϔϾ⿟ᑣഫ˄খ㾕ټ㉏DŽ㞾ࡼᄬټᅮњᇍ䈵ⱘᄬއѯ݇䬂ᄫ੠ໄᯢⱘϞϟ᭛݅ৠ ㉏˄static˅DŽໄᯢᇍ䈵ᯊՓ⫼ⱘϔټ㉏˄automatic˅੠䴭ᗕᄬټ㉏ߚЎϸ㉏˖㞾ࡼᄬټᄬ ㉏ټA.4.1 ᄬ ᣛ৥ৠϔϾᇍ䈵៪ߑ᭄DŽ԰⫼ඳ੠䖲᥹ᇚ೼ A.11 㡖Ё䅼䆎DŽ ↣ᇚ䇈ᯢ䖭⾡䕀ᤶѻ⫳ⱘ㒧ᵰDŽA.6.5 㡖ᇚ䅼䆎໻໮᭄᱂䗮䖤ㅫヺ᠔㽕∖ⱘ䕀ᤶˈ៥Ӏ೼䆆㾷 ḍ᥂᪡԰᭄ⱘϡৠˈᶤѯ䖤ㅫヺӮᓩ䍋᪡԰᭄ⱘؐҢᶤ⾡㉏ൟ䕀ᤶЎ঺ϔ⾡㉏ൟDŽᴀ㡖 A.6 䕀ᤶ 䳔㽕䇈ᯢℸ䖤ㅫヺᰃ৺䳔㽕ϔϾᎺؐ᪡԰᭄ҹঞᅗᰃ৺ѻ⫳ϔϾᎺؐDŽ ᴹ⑤Ѣ䌟ؐ㸼䖒ᓣ E1=E2ˈ݊ЁˈᎺ᪡԰᭄ E1 ᖙ乏ᰃϔϾᎺؐ㸼䖒ᓣDŽᇍ↣Ͼ䖤ㅫヺⱘ䅼䆎 ᰃϔϾᣛ䩜㉏ൟⱘ㸼䖒ᓣˈ*E ߭ᰃϔϾᎺؐ㸼䖒ᓣˈᅗᓩ⫼⬅ E ᣛ৥ⱘᇍ䈵DŽৡᄫĀᎺؐ” ㉏ⱘᷛ䆚ヺ֓ᰃᎺؐ㸼䖒ᓣⱘϔϾᯢᰒⱘ՟ᄤDŽᶤѯ䖤ㅫヺৃҹѻ⫳ᎺؐDŽ՟བˈབᵰ Eټᄬ ऎඳˈᎺؐ˄lvalue˅ᰃᓩ⫼ᶤϾᇍ䈵ⱘ㸼䖒ᓣDŽ݋᳝ড়䗖㉏ൟϢټᇍ䈵ᰃϔϾੑৡⱘᄬ A.5 ᇍ䈵੠Ꮊؐ পؐⱘ㣗ೈˈгϡᕅડ݊ㅫᴃሲᗻDŽ䰤ᅮヺᇚ೼ A.8.2 㡖Ё䅼䆎DŽ ҹׂᬍ˗ໄᯢЎ volatile ⱘᇍ䈵㸼ᯢᅗ݋᳝ϢӬ࣪Ⳍ݇ⱘ⡍⅞ሲᗻDŽ䰤ᅮヺ᮶ϡᕅડᇍ䈵 ᇍ䈵ⱘ㉏ൟৃҹ䗮䖛䰘ࡴⱘ䰤ᅮヺ䖯㸠䰤ᅮDŽໄᯢЎ const ⱘᇍ䈵㸼ᯢℸᇍ䈵ⱘؐϡৃ A.4.4 ㉏ൟ䰤ᅮヺ ϟˈ䖭ѯᵘ䗴ᇍ䈵ⱘᮍ⊩ৃҹ䗦ᔦՓ⫼DŽމϔ㠀ᚙ · ৃҹࣙ৿໮Ͼϡৠ㉏ൟᇍ䈵ЁӏᛣϔϾᇍ䈵ⱘ㘨ড় · ࣙ৿ϔ㋏߫ϡৠ㉏ൟᇍ䈵ⱘ㒧ᵘ · ᣛ৥㒭ᅮ㉏ൟᇍ䈵ⱘᣛ䩜 · 䖨ಲ㒭ᅮ㉏ൟᇍ䈵ⱘߑ᭄ · 㒭ᅮ㉏ൟᇍ䈵ⱘ᭄㒘 ൟৃҹ᳝᮴䰤໮Ͼ˖ 䰸෎ᴀ㉏ൟ໪ˈ៥Ӏ䖬ৃҹ䗮䖛ҹϟ޴⾡ᮍ⊩ᵘ䗴⌒⫳㉏ൟˈҢὖᗉᴹ䆆ˈ䖭ѯ⌒⫳㉏ A.4.3 ⌒⫳㉏ൟ void ㉏ൟ䇈ᯢϔϾؐⱘぎ䲚ড়ˈᅗᐌ㹿⫼ᴹ䇈ᯢϡ䖨ಲӏԩؐⱘߑ᭄ⱘ㉏ൟDŽ ㉏ൟ floatǃdouble ੠ long double 㒳⿄Ў⍂⚍㉏ൟ˄floating type˅DŽ ㉏ൟǃ৘⾡໻ᇣⱘ int ㉏ൟ˄᮴䆎ᰃ৺ᏺヺো˅ҹঞᵮВ㉏ൟ䛑㒳⿄Ўᭈൟ㉏ൟ˄integral type˅DŽ ಴ЎҹϞ䖭ѯ㉏ൟⱘᇍ䈵䛑ৃҹ㹿㾷䞞Ў᭄ᄫˈ᠔ҹˈৃҹᇚᅗӀ㒳⿄Ўㅫᴃ㉏ൟDŽchar ЁⱘϔϾˈ៪㗙䌟ؐϡᰃϔϾৠ㉏ൟⱘ㸼䖒ᓣˈ߭㓪䆥఼䗮ᐌӮѻ⫳䄺ਞֵᙃDŽ 㾕 A.8.4 㡖˅DŽᵮВ㉏ൟ㉏ԐѢᭈൟDŽԚᰃˈབᵰᶤϾ⡍ᅮᵮВ㉏ൟⱘᇍ䈵ⱘ䌟ؐϡᰃ݊ᐌ䞣 ᵮВᰃϔϾ݋᳝ᭈൟؐⱘ⡍⅞ⱘ㉏ൟDŽϢ↣ϾᵮВⳌ݇㘨ⱘᰃϔϾੑৡᐌ䞣ⱘ䲚ড়˄খ ຣၽಾϢອලԅd 䇈ᯢ˖long double ಾ໭႙ަԅफ໸dၽԛ 1 ͑ᄯēlong float ူ double फ໸ԉޮēӬ double ㉏ൟDŽ བᵰӏԩϔϾ᪡԰᭄Ў long double ㉏ൟˈ߭ᇚ঺ϔϾ᪡԰᭄䕀ᤶЎ longˈܜ佪 䕀ᤶDŽ ᳝᪡԰᭄䕀ᤶЎৠϔ݀݅㉏ൟˈᑊҹℸ԰Ў㒧ᵰⱘ㉏ൟDŽ䖭⾡ᮍᓣⱘ䕀ᤶ⿄Ў᱂䗮ㅫᴃ㉏ൟ 䆌໮䖤ㅫヺ䛑Ӯҹ㉏Ԑⱘᮍᓣ೼䖤ㅫ䖛⿟Ёᓩ䍋䕀ᤶˈᑊѻ⫳㒧ᵰ㉏ൟDŽ݊ᬜᵰᰃᇚ᠔ A.6.5 ㅫᴃ㉏ൟ䕀ᤶ ᵰৃ㛑ᰃϟϔϾ䕗催៪䕗Ԣⱘৃ㸼⼎ؐDŽབᵰ㒧ᵰ೼ৃ㸼⼎㣗ೈП໪ˈ߭݊㸠Ўᰃ᳾ᅮНⱘDŽ Ͼ䕗催㊒ᑺⱘ⍂⚍㉏ൟؐ䕀ᤶЎ䕗Ԣ㊒ᑺⱘ⍂⚍㉏ൟᯊˈབᵰᅗⱘؐ೼ৃ㸼⼎㣗ೈݙˈ߭㒧 ᇚϔϾ㊒ᑺ䕗Ԣⱘ⍂⚍ؐ䕀ᤶЎⳌৠ៪᳈催㊒ᑺⱘ⍂⚍㉏ൟᯊˈᅗⱘֱؐᣕϡবDŽᇚϔ A.6.4 ⍂⚍㉏ൟ НⱘDŽ ߭㒧ᵰৃ㛑ᰃϟϔϾ䕗催៪䕗Ԣⱘৃ㸼⼎ؐDŽབᵰ䆹ؐ䍙ߎৃ㸼⼎ⱘ㣗ೈˈ߭݊㸠Ўᰃ᳾ᅮ ᔧᡞᭈൟؐ䕀ᤶЎ⍂⚍㉏ൟᯊˈབᵰ䆹ؐ೼䆹⍂⚍㉏ൟৃ㸼⼎ⱘ㣗ೈݙԚϡ㛑㊒⹂㸼⼎ˈ ݊㸠Ўᰃ᳾ᅮНⱘDŽ⡍߿ᰃˈᇚ䋳ⱘ⍂⚍᭄䕀ᤶЎ᮴ヺোᭈൟⱘ㒧ᵰᰃ≵᳝ᅮНⱘDŽ ᔧᡞ⍂⚍㉏ൟⱘؐ䕀ᤶЎᭈൟᯊˈᇣ᭄䚼ߚᇚ㹿϶ᓗDŽབᵰ㒧ᵰؐϡ㛑⫼ᭈൟ㸼⼎ˈ߭ A.6.3 ᭈ᭄੠⍂⚍᭄ ৺߭ᅗⱘؐৠ݋ԧⱘᅲ⦄᳝݇DŽ ᇚӏԩᭈ᭄䕀ᤶЎᏺヺো㉏ൟᯊˈབᵰᅗৃҹ೼ᮄ㉏ൟЁ㸼⼎ߎᴹˈֱ߭݊ؐᣕϡবˈ DŽܙ฿ ؐ䖯㸠ヺোᠽሩ੠ᇍ᮴ヺোؐ䖯㸠 0 ⱘԡ῵ᓣ䕗じˈ䖭ህⳌᔧѢᎺ៾প˗བᵰ䆹᮴ヺো㉏ൟⱘԡ῵ᓣ䕗ᆑˈ䖭ህⳌᔧѢᇍᏺヺো ؐࡴ 1 Ў῵ˈᡒߎϢℸᭈ᭄ৠԭⱘ᳔ᇣⱘ䴲䋳ؐDŽ೼ᇍѠⱘ㸹ⷕ㸼⼎Ёˈབᵰ䆹᮴ヺো㉏ൟ ᇚӏԩᭈ᭄䕀ᤶЎᶤ⾡ᣛᅮⱘ᮴ヺো㉏ൟ᭄ⱘᮍ⊩ᰃ˖ҹ䆹᮴ヺো㉏ൟ㛑໳㸼⼎ⱘ᳔໻ A.6.2 ᭈൟ䕀ᤶ ಖ˄integral promotion˅DŽ ߭݊ؐᇚ㹿䕀ᤶЎ int ㉏ൟ˗৺߭ᇚ㹿䕀ᤶЎ unsigned int ㉏ൟDŽ䖭ϔ䖛⿟⿄Ўჼ໸ඔ ៪ᭈൟԡᄫ↉ˈ䖬ৃҹՓ⫼ᵮВ㉏ൟⱘᇍ䈵DŽབᵰॳྟ㉏ൟⱘ᠔᳝ؐ䛑ৃ⫼ int ㉏ൟ㸼⼎ˈ ೼ϔϾ㸼䖒ᓣЁˈ޵ᰃৃҹՓ⫼ᭈൟⱘഄᮍ䛑ৃҹՓ⫼ᏺヺো៪᮴ヺোⱘᄫヺǃⷁᭈൟ A.6.1 ᭈൟᦤछ DŽܙϔѯ㸹خϾ䖤ㅫヺᯊᇚ ᇍ唤䰤ࠊⱘᇍ䈵ⱘᣛ䩜ˈᑊৃҹֱ䆕ॳᇕϡࡼഄݡ䕀ᤶಲᴹDŽټϔϾᣛ৥݋᳝᳈ᇣ៪Ⳍৠᄬ ᇍ唤㽕∖ˈ߭㒧ᵰᣛ䩜ৃ㛑Ӯᇐ㟈ഄഔᓖᐌDŽᣛ৥ᶤᇍ䈵ⱘᣛ䩜ৃҹ䕀ᤶЎټ⒵䎇ϔᅮⱘᄬ ᣛ৥ᶤϔ㉏ൟⱘᣛ䩜ৃҹ䕀ᤶЎᣛ৥঺ϔ㉏ൟⱘᣛ䩜ˈԚᰃˈབᵰ䆹ᣛ䩜ᣛ৥ⱘᇍ䈵ϡ ձ䌪Ѣ݋ԧⱘᅲ⦄DŽމᤶЎৠϔϾᣛ䩜ˈ݊ᅗᚙ ᭈൟᇍ䈵ৃҹᰒᓣഄ䕀ᤶЎᣛ䩜DŽ䖭⾡᯴ᇘᘏᰃᇚϔϾ䎇໳ᆑⱘҢᣛ䩜䕀ᤶᴹⱘᭈ᭄䕀 ᭄гձ䌪Ѣ݋ԧⱘᅲ⦄DŽ ᣛ䩜ৃҹ䕀ᤶЎᭈൟˈԚℸᭈൟᖙ乏䎇໳໻˗᠔㽕∖ⱘ໻ᇣձ䌪Ѣ݋ԧⱘᅲ⦄DŽ᯴ᇘߑ Ͼᰒᓣⱘ㉏ൟ䕀ᤶ䖤ㅫヺ៪ᔎࠊ㉏ൟ䕀ᤶᴹᣛᅮ˄খ㾕 A.7.5 㡖੠ A.8.8 㡖 ˅DŽ 䆌䖯㸠ᣛ䩜Ⳍ݇ⱘ݊ᅗᶤѯ䕀ᤶˈԚ݊㒧ᵰձ䌪Ѣ݋ԧⱘᅲ⦄DŽ䖭ѯ䕀ᤶᖙ乏⬅ϔܕ䖬 ϔぎᣛ䩜ˈԚϡㄝѢӏԩᣛ৥ߑ᭄៪ᇍ䈵ⱘᣛ䩜DŽ 䕗᪡԰䕀ᤶЎӏᛣ㉏ൟⱘᣛ䩜DŽ݊㒧ᵰᇚѻ⫳ϔϾぎᣛ䩜ˈℸぎᣛ䩜ㄝѢᣛ৥ৠϔ㉏ൟⱘ঺ ؐЎ 0 ⱘᭈൟᐌ䞣㸼䖒ᓣ៪ᔎࠊ䕀ᤶЎ void * ㉏ൟⱘ㸼䖒ᓣৃ䗮䖛ᔎࠊ䕀ᤶǃ䌟ؐ៪↨ 䖤ㅫヺⱘᮍᓣ䖯㸠˄খ㾕 A.7.7 㡖 ˅DŽ⫣ޣ✻䕀ᤶᮍᓣᣝ 䖤ㅫˈ݊㒧ᵰᇚ㹿䕀ᤶЎᭈൟ˗⫣ޣϸϾᣛ৥ৠϔ᭄㒘Ёৠϔ㉏ൟⱘᇍ䈵ⱘᣛ䩜ৃҹ䖯㸠 ヺⱘᮍᓣ䖯㸠˄খ㾕 A.7.7 㡖 ˅DŽ ϟˈᭈൟ㸼䖒ᓣⱘ䕀ᤶᣝ✻ࡴ⊩䖤ㅫމএϔϾᭈൟ㸼䖒ᓣDŽ೼䖭⾡ᚙޣᣛ䩜ৃҹࡴϞ៪ A.6.6 ᣛ䩜੠ᭈ᭄ ಾํ֥၇ॸԅd ఏ௶ڴࣣຏԅৰְēӲྡྷّํ׸ۜζӒ಴ူྡྷّ࡮ပලྂЩէԅӛ׸ۜζӒ಴ອΑࠀನēࠒ ರဈெۦ႕౭ฑ؏ၶྡྷ໔ēӬ߈౲ॴํ׸ۜ೴ူӛ׸ۜ೴ݡڟۜफ໸ᆙಾ҉ဟᄆଅԙสd໭ फ໸Ģ֗ၽԛ 1 ͑ᄯēํ׸ڴЩԅӛ׸ۜफ໸ྡྷ୯ၮസನēϢߜํ׸ۜफ໸ԅ೫໿ҎԞٓࠒ Շēൎပԅ׼Ԥၮസ՛ಾഀ࠺էdԛ֝ēӲࠀիԅํ׸ۜफ໸ူࠀڟഀ࠺էĢ֗ၽԛ 1 ͑ᄯ dԛྡྷēճ float फ໸Ѐᆴ೴ԅസ೬ၮസࢶྻᄚဈӦ࠺է֗Ϣಾܤ䇈ᯢ˖უऺပ०ّέ ৺߭ˈᇚϸϾ᪡԰᭄䛑䕀ᤶЎ int ㉏ൟDŽ int ㉏ൟDŽ ৺߭ˈབᵰӏԩϔϾ᪡԰᭄Ў unsigned int ㉏ൟˈ߭ᇚ঺ϔϾ᪡԰᭄䕀ᤶЎ unsigned ৺߭ˈབᵰϔϾ᪡԰᭄Ў long int ㉏ൟˈ߭ᇚ঺ϔϾ᪡԰᭄䕀ᤶЎ long int ㉏ൟDŽ unsigned long int ㉏ൟDŽ ᇚ unsigned int ㉏ൟⱘ᪡԰᭄䕀ᤶЎ long int ˗བᵰϡৃҹˈ߭ᇚϸϾ᪡԰᭄䛑䕀ᤶЎ 㒧ᵰձ䌪Ѣ long int ㉏ൟᰃ৺ৃҹ㸼⼎᠔᳝ⱘ unsigned int ㉏ൟⱘؐDŽབᵰৃҹˈ߭ ৺߭ˈབᵰϔϾ᪡԰᭄Ў long int ㉏ൟϨ঺ϔϾ᪡԰᭄Ў unsigned int ㉏ൟˈ߭ int ㉏ൟˈ߭ᇚ঺ϔϾ᪡԰᭄䕀ᤶЎ unsigned long int ㉏ൟDŽ ৺߭ˈৠᯊᇍϸϾ᪡԰᭄䖯㸠ᭈൟᦤछ˗✊ৢˈབᵰӏԩϔϾ᪡԰᭄Ў unsigned long ৺߭ˈབᵰӏԩϔϾ᪡԰᭄Ў float ㉏ൟˈ߭ᇚ঺ϔϾ᪡԰᭄䕀ᤶЎ float ㉏ൟDŽ ৺߭ˈབᵰӏԩϔϾ᪡԰᭄Ў double ㉏ൟˈ߭ᇚ঺ϔϾ᪡԰᭄䕀ᤶЎ double ㉏ൟDŽ ໪ˈ㸼䖒ᓣⱘ∖ؐ⃵ᑣމ㑻੠㒧ড়ᗻ᳝ᯢ⹂ⱘ㾘ᅮˈԚᰃˈ䰸ᇥ᭄՟໪ᚙܜ䖤ㅫヺⱘӬ ড়ᗻDŽ 㑻੠㒧ܜ䅼䆎䆹㡖⍝ঞࠄⱘ䖤ㅫヺⱘᎺǃে㒧ড়ᗻDŽA.13 㡖Ё㒭ߎⱘ䇁⊩㓐ড়њ䖤ㅫヺⱘӬ 㑻ⳌৠDŽ↣Ͼᇣ㡖Ё䖬ᇚܜヺ+˄খ㾕 A.7.7 㡖˅ⱘ᪡԰᭄DŽ೼↣ϔᇣ㡖Ёˈ৘Ͼ䖤ㅫヺⱘӬ 㑻ҟ㒡DŽВϾ՟ᄤˈᣝ✻䖭⾡݇㋏ˈA.7.1 㟇 A.7.6 㡖ЁᅮНⱘ㸼䖒ᓣৃҹ⫼԰ࡴ⊩䖤ㅫܜӬ 㑻ˈ៥Ӏᇚձ⃵ᣝ✻Ң催ࠄԢⱘܜᴀ㡖Ё৘Џ㽕ᇣ㡖ⱘ乎ᑣህҷ㸼њ㸼䖒ᓣ䖤ㅫヺⱘӬ A.7 㸼䖒ᓣ ֗ճୣ൑फ໸ᄗწԅݡဈ႕ྑௐࠩ໻ມ಴ஜᄥफ໸ᅧܰd ຂζӒ಴ᄯݡဈēڑANSI γᅹඋιၭ༘ void *फ໸ԅᄗწူୣ൑ճເᄗწၽ؎ᄔζӒ಴ۤ 䇈ᯢ˖ճ void *ᄗწԅࠓೄಾ໭႙ަԅdྻ஍ēchar *ᄗწ͒སᆂඹဈᄗწԅ߸ౄd 䌟ؐ㒭 void *㉏ൟⱘᣛ䩜ˈᑊৃϢ void *㉏ൟⱘᣛ䩜䖯㸠↨䕗DŽ ϔ㠀䳔㽕ᰒᓣⱘᔎࠊ䕀ᤶˈ䖭䞠᠔ϡৠⱘᰃˈᣛ䩜ৃҹ㹿䌟ؐЎ void * ㉏ൟⱘᣛ䩜ˈгৃҹ ߱ྟᣛ䩜㉏ൟˈ߭ৃҹᘶ໡߱ྟᣛ䩜DŽ៥Ӏ೼ A.6.6 㡖Ё䅼䆎䖛ˈᠻ㸠ᣛ䩜ࠄᣛ䩜ⱘ䕀ᤶᯊˈ ᣛ৥ӏԩᇍ䈵ⱘᣛ䩜䛑ৃҹ䕀ᤶЎ void * ㉏ൟˈϨϡӮ϶༅ֵᙃDŽབᵰᇚ㒧ᵰݡ䕀ᤶЎ A.6.8 ᣛ৥ void ⱘᣛ䩜 ᆂd ರဈַڜ΄ē൑ྡྷᄐ܊䇈ᯢ˖void ਠပၽ·ೠԅԛ 1 ͑ᄯѻຣēӬಾၽ·ೠԛ 1 ͑ѻ͑ ࠊ㉏ൟ䕀ᤶᇚ϶ᥝߑ᭄䇗⫼ⱘ䖨ಲؐDŽ ৃҹ䗮䖛ᔎࠊ㉏ൟ䕀ᤶᇚ㸼䖒ᓣ䕀ᤶЎ void ㉏ൟDŽ՟བˈ೼㸼䖒ᓣ䇁হЁˈϔϾぎⱘᔎ 㡖 ˅DŽ ⱘഄᮍˈ՟བ԰ЎϔϾ㸼䖒ᓣ䇁হ˄খ㾕 A.9.2 㡖˅៪԰Ў䗫ো䖤ㅫヺⱘᎺ᪡԰᭄˄খ㾕 A.7.18 䴲ぎ㉏ൟDŽ಴Ўぎ˄void˅㸼䖒ᓣ㸼⼎ϔϾϡᄬ೼ⱘؐˈ䖭ḋⱘ㸼䖒ᓣাৃҹ⫼೼ϡ䳔㽕ؐ void ᇍ䈵ⱘ˄ϡᄬ೼ⱘ˅ؐϡ㛑໳ҹӏԩᮍᓣՓ⫼ˈгϡ㛑㹿ᰒᓣ៪䱤ᓣഄ䕀ᤶЎӏϔ A.6.7 void Ϣॳᴹⱘᣛ䩜ϔ㟈DŽ ߑ᭄ⱘ㒧ᵰձ䌪Ѣ݋ԧⱘᅲ⦄DŽԚᰃˈབᵰ䕀ᤶৢⱘᣛ䩜㹿䞡ᮄ䕀ᤶЎॳᴹⱘ㉏ൟˈ߭㒧ᵰ ᳔ৢˈᣛ৥ϔϾߑ᭄ⱘᣛ䩜ৃҹ䕀ᤶЎᣛ৥঺ϔϾߑ᭄ⱘᣛ䩜DŽ䇗⫼䕀ᤶৢᣛ䩜᠔ᣛⱘ ⱘ䰤ᅮヺⱘ䰤ࠊDŽ ৠⱘᰃ๲ࡴњ䰤ᅮヺᏺᴹⱘ䰤ࠊDŽབᵰߴ䰸њ䰤ᅮヺˈ߭ᇍᑩሖᇍ䈵ⱘ䖤ㅫҡফᅲ䰙ໄᯢЁ 䰸໪DŽབᵰ๲ࡴњ䰤ᅮヺˈ߭ᮄᣛ䩜Ϣॳᣛ䩜ㄝӋˈϡމヺ˄খ㾕 A.4.4 㡖੠ A.8.2 㡖˅ⱘᚙ ϔϾᣛ䩜ৃҹ䕀ᤶЎৠ㉏ൟⱘ঺ϔϾᣛ䩜ˈԚ๲ࡴ៪ߴ䰸њᣛ䩜᠔ᣛⱘᇍ䈵㉏ൟⱘ䰤ᅮ 㡖ⱘ䅼䆎Ёⳟࠄˈᣛ䩜гৃҹ䕀ᤶЎ void *㉏ൟˈᑊৃॳᇕϡࡼഄ䕀ᤶಲᴹDŽ Āᇍ唤āⱘὖᗉձ䌪Ѣ݋ԧⱘᅲ⦄ˈԚ char ㉏ൟⱘᇍ䈵݋᳔᳝ᇣⱘᇍ唤䰤ࠊDŽ៥Ӏᇚ೼ A.6.8 ᰃ৺ᰃᎺؐϡফᣀোⱘᕅડDŽ ⫼ᣀোᣀ䍋ᴹⱘ㸼䖒ᓣᰃ߱ㄝ㸼䖒ᓣˈᅗⱘ㉏ൟ੠ؐϢ᮴ᣀোⱘ㸼䖒ᓣⳌৠDŽℸ㸼䖒ᓣ ᶤѯ߱ྟ࣪⿟ᑣЁϡ䖯㸠䖭ḋⱘ䕀ᤶˈ䆺㒚ֵᙃˈখ㾕 A.8.7 㡖DŽ ৥ char ㉏ൟ˄៪ wchar_t ㉏ൟ˅ⱘᣛ䩜ā㉏ൟˈ݊㒧ᵰᰃᣛ৥ᄫヺІЁ㄀ϔϾᄫヺⱘᣛ䩜DŽ ヺІˈ߭Ў“wchar_t ㉏ൟⱘ᭄㒘ā㉏ൟ˅ˈԚ䙉ᕾ A.7.1 㡖Ёⱘ㾘߭DŽᅗ䗮ᐌ㹿ׂᬍЎĀᣛ ᄫヺІᄫ䴶ؐᰃ߱ㄝ㸼䖒ᓣDŽᅗⱘ߱ྟ㉏ൟЎ“char ㉏ൟⱘ᭄㒘ā㉏ൟ˄ᇍѢᆑᄫヺᄫ ᐌ䞣ᰃ߱ㄝ㸼䖒ᓣˈ݊㉏ൟৠ݊ᔶᓣ᳝݇DŽ᳈䆺㒚ⱘֵᙃˈখ㾕 A.2.5 㡖Ёⱘ䅼䆎DŽ ᣛ䩜ˈ䙷МᅗህᰃϔϾᎺؐDŽ ໄᯢᣛᅮDŽབᵰᷛ䆚ヺᓩ⫼ϔϾᇍ䈵˄খ㾕 A.5 㡖˅ˈᑊϨ݊㉏ൟᰃㅫᴃ㉏ൟǃ㒧ᵘǃ㘨ড়៪ བᵰᣝ✻ϟ䴶ⱘᮍᓣᇍᷛ䆚ヺ䖯㸠䗖ᔧⱘໄᯢˈ䆹ᷛ䆚ヺህᰃ߱ㄝ㸼䖒ᓣDŽ݊㉏ൟ⬅݊ ďζӒ಴Đ ᆓ׸ґ Ш२ γಭ׸ ѺԉζӒ಴ġ ߱ㄝ㸼䖒ᓣࣙᣀᷛ䆚ヺǃᐌ䞣ǃᄫヺІ៪ᏺᣀোⱘ㸼䖒ᓣDŽ A.7.2 ߱ㄝ㸼䖒ᓣ ⱘߑ᭄ⱘᣛ䩜ā㉏ൟDŽ ⱘ᪡԰᭄ˈ৺߭ˈ㉏ൟЎĀ䖨ಲ T ㉏ൟؐⱘߑ᭄āⱘ㸼䖒ᓣᇚ㹿䕀ᤶЎĀᣛ৥䖨ಲ T ㉏ൟؐ 䖤ㅫヺ&៪ sizeofˈ߭ϡӮ䖯㸠䕀ᤶDŽ㉏Ԑഄˈ䰸䴲㸼䖒ᓣ㹿⫼԰&䖤ㅫヺܗℸ㸼䖒ᓣᰃϔ ᣛ৥᭄㒘Ё㄀ϔϾᇍ䈵ⱘᣛ䩜ˈᑊϨℸ㸼䖒ᓣⱘ㉏ൟᇚ㹿䕀ᤶЎĀᣛ৥ T ㉏ൟⱘᣛ䩜āDŽབᵰ ᇍѢᶤ㉏ൟ Tˈབᵰᶤ㸼䖒ᓣ៪ᄤ㸼䖒ᓣⱘ㉏ൟЎ“T ㉏ൟⱘ᭄㒘āˈ߭ℸ㸼䖒ᓣⱘؐᰃ A.7.1 ᣛ䩜⫳៤ ᑧߑ᭄䖯㸠䇗ᭈDŽޚ䴲ᷛ DŽᇍ䰸᭄Ў 0 ੠᠔᳝⍂⚍ᓖᐌⱘ໘⧚ˈϡৠⱘᅲ⦄䞛⫼ϡৠⱘᮍᓣˈ᳝ᯊ׭ৃҹ⫼خ䛑䖭М 䇁㿔ⱘᅲ⦄೼䖯㸠ᏺヺোᭈൟ㸼䖒ᓣⱘ∖ؐҹঞ䌟ؐᯊᗑ⬹⑶ߎᓖᐌˈԚᑊϡᰃ᠔᳝ⱘᅲ⦄ C 䇁㿔≵᳝ᅮН㸼䖒ᓣ∖ؐ䖛⿟Ёⱘ⑶ߎǃ䰸⊩Ẕᶹ੠݊ᅗᓖᐌⱘ໘⧚DŽ໻໮᭄⦄᳝ C ࢶટ֟ಓ࿎ѻԅெࣣdރസྻޙ׼Ԥ೴ၽࠄ࠭ୣ࠺էຫᄥನԅ έࠧ࿵ູ؟৐dუّۦസನࢶટݖϢਁᆠࠒޙ৐ನēࢶྻճζӒ಴ᄷ໭૦༝ēӬಾēၽۦࠒ ġӲζӒ಴ᄯԅၮസ׸ၽ೴༰౨ਁᆠ߬ܰ৐ۤއ႕ēڟ႕׌҂ॴၐຕԅྡྷّڟ䇈ᯢ˖؞ ᓣᰃݐᆍⱘDŽ ᤶ∖ؐ⃵ᑣDŽԚᰃˈ↣Ͼ䖤ㅫヺᇚ݊᪡԰᭄⫳៤ⱘؐ㒧ড়䍋ᴹⱘᮍᓣϢ㸼䖒ᓣⱘ䇁⊩ߚᵤᮍ ݊᪡԰᭄ᣝᶤϔ⡍ᅮ乎ᑣ∖ؐˈ৺߭ˈ݋ԧⱘᅲ⦄ৃҹ㞾⬅䗝ᢽӏϔ∖ؐ⃵ᑣˈ⫮㟇ৃҹѸ ᳝ᅮНˈ⫮㟇ᶤѯ᳝ࡃ԰⫼ⱘᄤ㸼䖒ᓣг≵᳝ᅮНDŽгህᰃ䇈ˈ䰸䴲䖤ㅫヺⱘᅮНֱ䆕њ≴ ߑ᭄ໄᯢⱘ䅼䆎ˈখ㾕 A.8.6 㡖੠ A.10.1 㡖DŽ ᰃߑ᭄㉏ൟⱘϔ䚼ߚˈ䖭⾡ໄᯢ⿄Ўߑ᭄ॳൟDŽ೼ᮻⱘᮍᓣЁˈϡᣛᅮᔶᓣখ᭄㉏ൟDŽ᳝݇ ৃҹ䗮䖛ϸ⾡ᮍᓣໄᯢߑ᭄DŽ೼ᮄⱘໄᯢᮍᓣЁˈᔶᓣখ᭄ⱘ㉏ൟᰃᰒᓣໄᯢⱘˈᑊϨ ⱘؐDŽԚᰃˈৃҹᇚᣛ䩜԰Ўᅲ䰙খ᭄Ӵ䗦ˈ䖭ḋˈߑ᭄֓ৃҹׂᬍᣛ䩜ᣛ৥ⱘᇍ䈵ⱘؐDŽ ᭄ৃ㛑Ӯׂᬍᔶᓣখ᭄ᇍ䈵ⱘؐ˄ेᅲ䰙খ᭄㸼䖒ᓣⱘࡃᴀ˅ˈԚ䖭ϾׂᬍϡӮᕅડᅲ䰙খ᭄ ೼䇗⫼ߑ᭄Пࠡˈߑ᭄ⱘ↣Ͼᅲ䰙খ᭄ᇚ㹿໡ࠊˈ᠔᳝ⱘᅲ䰙খ᭄ϹḐഄᣝؐӴ䗦DŽߑ ⼎ߑ᭄ᅮН៪ߑ᭄ໄᯢЁⱘ䕧ܹᇍ䈵˄៪ᷛ䆚ヺ˅DŽ 䗮ᐌ⫼ᴃ䇁Āᅲ䰙খ᭄ā㸼⼎Ӵ䗦㒭ߑ᭄䇗⫼ⱘ㸼䖒ᓣˈ㗠ᴃ䇁Āᔶᓣখ᭄ā߭⫼ᴹ㸼 ೴Ըဈdࡘԅဴ֥ఏ௶ပ໒dۆ೴ԅᄗწࠩ໻ۆᄗ຿ڶဈۤඹ ೴Ըۆ೴ನΡ༖ပྡྷّມ಴ԅ*ၮസ׸dANSI γᅹၭ༘ຣပԅྡྷ໔Ω࿒୶ဈලྂԅဴ֥ࠩ໻ ۆ೴ԅᄗწԸဈۆᄗ຿ڶ೴rफ໸ēωயēඹۆ䇈ᯢ˖ၽԛ 1 ͑ᄯē؞फ໸΄ຫᄥนq T ㉏ൟⱘߑ᭄ⱘᣛ䩜āˈ݊Ё T Ўᶤ⾡㉏ൟˈϨߑ᭄䇗⫼ⱘؐⱘ㉏ൟЎ TDŽ 䆹ৢ㓔㸼䖒ᓣ˄೼ৃ㛑ⱘ䱤ᓣໄᯢ੠ᣛ䩜⫳៤Пৢˈখ㾕 A.7.1 㡖˅ⱘ㉏ൟᖙ乏ЎĀᣛ৥䖨ಲ extern int ᷛ䆚ヺ() 䱤ᓣഄໄᯢˈㄝৠѢ೼ᠻ㸠ℸߑ᭄䇗⫼ⱘ᳔ݙሖ⿟ᑣഫЁࣙ৿ϟ߫ໄᯢ˖ ህᰃߑ᭄ⱘখ᭄DŽབᵰৢ㓔㸼䖒ᓣࣙ৿ϔϾᔧࠡ԰⫼ඳЁϡᄬ೼ⱘᷛ䆚ヺˈ߭ℸᷛ䆚ヺᇚ㹿 ⱘ䌟ؐ㸼䖒ᓣ߫㸼㒘៤ˈ݊Ёⱘ䌟ؐ㸼䖒ᓣ߫㸼ৃ㛑Ўぎˈᑊ⬅䗫ো䖯㸠ߚ䱨ˈ䖭ѯ㸼䖒ᓣ ߑ᭄䇗⫼⬅ϔϾৢ㓔㸼䖒ᓣ˄⿄Ўߑ᭄ᷛᖫヺˈfunction designator˅ৢ䎳⬅೚ᣀোᣀ䍋ᴹ 2 ߑ᭄䇗⫼ *((E1) + (E2))DŽ᭄᳝݇㒘ᓩ⫼ⱘ᳈໮䅼䆎ˈখ㾕 A.8.6 㡖DŽ 䖒ᓣⱘ㉏ൟᖙ乏ЎᭈൟDŽ㒧ᵰᕫࠄϟᷛ㸼䖒ᓣⱘ㉏ൟЎ TDŽ㸼䖒ᓣ E1[E2]೼ᅮНϞㄝৠѢ ᮍᣀোࠡⱘৢ㓔㸼䖒ᓣⱘ㉏ൟᖙ乏ЎĀᣛ৥ T ㉏ൟⱘᣛ䩜āˈ݊Ё T Ўᶤ⾡㉏ൟ˗ᮍᣀোЁ㸼 ᏺϟᷛⱘ᭄㒘ᓩ⫼ৢ㓔㸼䖒ᓣ⬅ϔϾৢ㓔㸼䖒ᓣৢ䎳ϔϾᣀ೼ᮍᣀোЁⱘ㸼䖒ᓣ㒘៤DŽ 1 ᭄㒘ᓩ⫼ ϵ೴ζӒ಴ζ,؎ᄔζӒ಴ ؎ᄔζӒ಴ ϵ೴ζӒ಴ζġ կζӒ಴••܊ ++ᅷζӒ಴܊ ᅷζӒ಴•>γಭ׸܊ ᅷζӒ಴.γಭ׸܊ (ϵ೴ζӒ಴ζ opt ᅷζӒ಴܊ [ᅷζӒ಴>ζӒ಴܊ ѺԉζӒ಴ ᅷζӒ಴ġ܊ ৢ㓔㸼䖒ᓣЁⱘ䖤ㅫヺ䙉ᕾҢᎺࠄেⱘ㒧ড়㾘߭DŽ A.7.3 ৢ㓔㸼䖒ᓣ 䖤ㅫヺⱘ㸼䖒ᓣ䙉ᕾҢেࠄᎺⱘ㒧ড়ᗻDŽܗᏺϔ 䖤ㅫヺܗA.7.4 ϔ 㡖˅Ёⱘ䅼䆎DŽ݊㒧ᵰϡᰃᎺؐDŽ ԰᭄ⱘ䰤ࠊ੠䖤ㅫ㒚㡖ⱘ䆺㒚ֵᙃˈখ㾕ࡴ⊩㉏䖤ㅫヺ˄A.7.7 㡖˅੠䌟ؐ㉏䖤ㅫヺ˄A.7.17 DŽ᪡԰᭄ᖙ乏ᰃϔϾᎺؐDŽ᳝݇᪡˅••˄1 ޣᠻ㸠ᅠ䆹㸼䖒ᓣৢˈ᪡԰᭄ⱘؐᇚࡴ 1˄++˅៪ ৢ㓔㸼䖒ᓣৢ䎳ϔϾ++៪••䖤ㅫヺҡᰃϔϾৢ㓔㸼䖒ᓣDŽ㸼䖒ᓣⱘؐህᰃ᪡԰᭄ⱘؐDŽ 䖤ㅫヺޣ4 ৢ㓔㞾๲䖤ㅫヺϢৢ㓔㞾 ႕dڟ႕ωਠပஜᄥᄓ໻dᆫ໭ԅΩ࿒୶ۤ ANSI γᅹஜᄥᄓ໻ॴუྡྷڟēӬಾē؞ۦݧ॓ٲࠒ ᅷζӒ಴ᄗՇԅ܊ՇॴუᄵζӒ಴ᄯюၔԅ੠ᆓΡ༖೫ဟڟ䇈ᯢ˖ၽ·ೠԅԛ 1 ͑ᄯē ಴ℸˈ㸼䖒ᓣ E1•>MOS Ϣ(*E1).MOS ㄝӋDŽ㒧ᵘ੠㘨ড়ᇚ೼ A.8.3 㡖Ё䅼䆎DŽ ᰃ᭄㒘㉏ൟˈ߭㒧ᵰᰃϔϾᎺؐDŽ ৥ᣛ䩜㸼䖒ᓣᣛ৥ⱘ㒧ᵘ៪㘨ড়Ёੑৡⱘ៤ਬˈ㒧ᵰ㉏ൟᰃᇍᑨ៤ਬⱘ㉏ൟDŽབᵰ䆹㉏ൟϡ 㸼䖒ᓣᖙ乏ᰃϔϾᣛ৥㒧ᵘ៪㘨ড়ⱘᣛ䩜ˈᷛ䆚ヺᖙ乏ᰃ㒧ᵘ៪㘨ড়ⱘ៤ਬⱘৡᄫDŽ㒧ᵰᣛ ৢ㓔㸼䖒ᓣৢ䎳ϔϾㆁ༈˄⬅•੠>㒘៤˅੠ϔϾᷛ䆚ヺҡᰃৢ㓔㸼䖒ᓣDŽ㄀ϔϾ᪡԰᭄ ᰃ᭄㒘㉏ൟˈ߭ᭈϾ㸼䖒ᓣᰃϔϾᎺؐDŽ ਬˈ݊㉏ൟᰃᇍᑨ៤ਬⱘ㉏ൟDŽབᵰ㄀ϔϾ㸼䖒ᓣᰃϔϾᎺؐˈᑊϨ㄀ѠϾ㸼䖒ᓣⱘ㉏ൟϡ 乏ᰃ㒧ᵘ៪㘨ড়ˈᷛ䆚ヺᖙ乏ᰃ㒧ᵘ៪㘨ড়ⱘ៤ਬⱘৡᄫDŽ㒧ᵰؐᰃ㒧ᵘ៪㘨ড়Ёੑৡⱘ៤ ৢ㓔㸼䖒ᓣৢ䎳ϔϾ೚⚍੠ϔϾᷛ䆚ヺҡᰃৢ㓔㸼䖒ᓣDŽ㄀ϔϾ᪡԰᭄㸼䖒ᓣⱘ㉏ൟᖙ 3 㒧ᵘᓩ⫼ 䆌䖯㸠䗦ᔦ䇗⫼DŽܕᅲ䰙খ᭄੠ߑ᭄ᷛᖫヺᰃᅠܼ∖ؐⱘˈࣙᣀ᠔᳝ⱘࡃ԰⫼DŽᇍӏԩߑ᭄䛑 ᅲ䰙খ᭄ⱘ∖ؐ⃵ᑣ≵᳝ᣛᅮDŽϡৠ㓪䆥఼ⱘᅲ⦄ᮍᓣ৘ϡⳌৠDŽԚᰃˈ೼䖯ܹߑ᭄ࠡˈ ರဈdۦ೴ಒੜݡۆ಴ ರဈd࿫࠰ࢶટΥੇ໭ࡘۦ೴ԅݡۆ႕ׄШ؏ၶē࿙นΡ༖ྑࢩৎ໭ࡘ಴ڟ䇈ᯢ˖უ໔ ᔶᓣখ᭄㉏ൟ㒣䖛খ᭄ᦤछৢ˅DŽ ߑ᭄ॳൟЁ↣Ͼᔶᓣখ᭄ⱘ㉏ൟᖙ乏Ϣߑ᭄ᅮНЁⳌᑨⱘᔶᓣখ᭄㉏ൟϔ㟈˄ߑ᭄ᅮНЁⱘ 䰙খ᭄㽕䖯㸠咬䅸ⱘখ᭄ᦤछˈᦤछᮍ⊩ৠࠡ䴶᠔䗄DŽབᵰߑ᭄ᰃҹᮻᓣᮍᓣᅮНⱘˈ䙷Мˈ ⱘ᭄Ⳃᖙ乏ㄝѢ៪䍙䖛ᔶᓣখ᭄ⱘ᭄Ⳃ˗ᇍѢሒ䚼≵᳝ᰒᓣᣛᅮ㉏ൟⱘᔶᓣখ᭄ˈⳌᑨⱘᅲ ϟˈᅲ䰙খ᭄މ᭄ⳂⳌৠˈ䰸䴲ߑ᭄ໄᯢⱘᔶᓣখ᭄㸼ҹⳕ⬹ো˄, ...˅㒧ሒDŽ೼䖭⾡ᚙ ൟЁⱘⳌᑨᔶᓣখ᭄㉏ൟˈ䖭Ͼ䖛⿟㉏ԐѢ䌟ؐDŽᅲ䰙খ᭄᭄Ⳃᖙ乏Ϣᰒᓣໄᯢⱘᔶᓣখ᭄ ೼ߑ᭄䇗⫼ⱘ԰⫼ඳЁˈབᵰߑ᭄ᰃҹᮄᓣᮍᓣໄᯢⱘˈ߭ᅲ䰙খ᭄ᇚ㹿䕀ᤶЎߑ᭄ॳ ᦤछৢⱘᅲ䰙খ᭄㉏ൟᖙ乏Ϣ≵᳝ᦤछⱘᔶᓣখ᭄㞾䑿ⱘ㉏ൟֱᣕϔ㟈DŽ ↨䕗㒣ᦤछৢߑ᭄䇗⫼Ёⱘᅲ䰙খ᭄㉏ൟ੠ᦤछৢⱘᔶᓣখ᭄㉏ൟ˗བᵰᰃᮄᓣⱘᅮНˈ߭ ㉏ൟϔ㟈ᗻձ䌪Ѣߑ᭄ᰃҹᮄᓣᮍᓣᅮНⱘ䖬ᰃҹᮻᓣᮍᓣᅮНⱘDŽབᵰᰃᮻᓣⱘᅮНˈ߭ 㗙ᶤϾᅲ䰙খ᭄ⱘ㉏ൟᦤछৢϢⳌᑨⱘᔶᓣখ᭄㉏ൟϡϔ㟈ˈ߭ߑ᭄䇗⫼ⱘ㒧ᵰᰃ᳾ᅮНⱘDŽ ᭄䕀ᤶЎ double ㉏ൟDŽབᵰ䇗⫼ᯊᅲ䰙খ᭄ⱘ᭄ⳂϢߑ᭄ᅮНЁᔶᓣখ᭄ⱘ᭄Ⳃϡㄝˈ៪ 䖯㸠咬䅸খ᭄ᦤछ˖ᇍ↣Ͼᭈൟখ᭄䖯㸠ᭈൟᦤछ˄খ㾕 A.6.l 㡖˅˗ᇚ↣Ͼ float ㉏ൟⱘখ ೼ߑ᭄䇗⫼ⱘ԰⫼ඳЁˈབᵰߑ᭄ᰃҹᮻᓣᮍᓣໄᯢⱘˈ߭ᣝҹϟᮍᓣᇍ↣Ͼᅲ䰙খ᭄ ᵰⱘ㉏ൟЎᦤछৢⱘ᪡԰᭄ⱘ㉏ൟDŽ ⱘ᪡԰᭄䕀ᤶЎⳌᑨⱘ᮴ヺো㉏ൟˈՓ⫼䖤ㅫヺ~䅵ㅫডⷕˈݡᇚ㒧ᵰ䕀ᤶЎᏺヺো㉏ൟDŽ㒧 এ᪡԰᭄ⱘؐ㗠ᕫࠄⱘ㒧ᵰؐDŽབᵰ᪡԰᭄Ўᏺヺো㉏ൟˈ߭㒧ᵰⱘ䅵ㅫᮍᓣЎ˖ᇚᦤछৢ ޣ԰᭄䖯㸠ᭈൟᦤछDŽབᵰ᪡԰᭄Ў᮴ヺো㉏ൟˈ߭㒧ᵰЎᦤछৢⱘ㉏ൟ㛑໳㸼⼎ⱘ᳔໻ؐ 䖤ㅫヺ~ⱘ᪡԰᭄ᖙ乏ᰃᭈൟˈ㒧ᵰЎ᪡԰᭄ⱘѠ䖯ࠊডⷕDŽ೼䖤ㅫ䖛⿟Ё䳔㽕ᇍ᪡ܗϔ 6 Ѡ䖯ࠊডⷕ䖤ㅫヺ ᦤछৢⱘ᪡԰᭄ⱘؐˈ✊ৢࡴ 1˗Ԛ 0 ⱘ䋳ؐҡЎ 0DŽ㒧ᵰ㉏ൟЎᦤछৢⱘ᪡԰᭄ⱘ㉏ൟDŽ এޣ䖯㸠ᭈൟᦤछDŽᏺヺো᭄ⱘ䋳ؐⱘ䅵ㅫᮍ⊩Ў˖ᇚᦤछৢᕫࠄⱘ㉏ൟ㛑໳㸼⼎ⱘ᳔໻ؐ 䖤ㅫヺ•ⱘ᪡԰᭄ᖙ乏ᰃㅫᴃ㉏ൟˈ㒧ᵰЎ᪡԰᭄ⱘ䋳ؐDŽབᵰ᪡԰᭄ᰃᭈൟˈ߭ᇚܗϔ 䖤ㅫヺޣܗ5 ϔ 䇈ᯢ˖ྡྷ၍ၮസ׸+ಾ ANSI γᅹ໭႙ަԅē႙ަ؞ၮസ׸ಾนॴူྡྷ၍ၮസ׸•ճ࿫d 䖯㸠ᭈൟᦤछˈ㒧ᵰ㉏ൟᰃ㒣䖛ᦤछৢⱘ᪡԰᭄ⱘ㉏ൟDŽ 䖤ㅫヺ+ⱘ᪡԰᭄ᖙ乏ᰃㅫᴃ㉏ൟˈ݊㒧ᵰᰃ᪡԰᭄ⱘؐDŽབᵰ᪡԰᭄ᰃᭈൟˈ߭ᇚܗϔ ࡴ䖤ㅫヺܗ4 ϔ Āᣛ৥ T ㉏ൟⱘᣛ䩜āˈ߭㒧ᵰ㉏ൟЎ TDŽ ᣛ䩜Ϩᣛ৥ⱘᇍ䈵ᰃㅫᴃǃ㒧ᵘǃ㘨ড়៪ᣛ䩜㉏ൟˈ߭ᅗᰃϔϾᎺؐDŽབᵰ㸼䖒ᓣⱘ㉏ൟЎ 䖤ㅫヺ*㸼⼎䯈᥹ᇏഔˈᅗ䖨ಲ݊᪡԰᭄ᣛ৥ⱘᇍ䈵៪ߑ᭄DŽབᵰᅗⱘ᪡԰᭄ᰃϔϾܗϔ 3 䯈᥹ᇏഔ䖤ㅫヺ ߑ᭄DŽབᵰ᪡԰᭄ⱘ㉏ൟЎ Tˈ߭㒧ᵰⱘ㉏ൟЎᣛ৥ T ㉏ൟⱘᣛ䩜DŽ ᯢЎ register ㉏ൟⱘᇍ䈵˅ˈ៪㗙Ўߑ᭄㉏ൟDŽ㒧ᵰؐᰃϔϾᣛ䩜ˈᣛ৥Ꮊؐᣛ৥ⱘᇍ䈵៪ 䖤ㅫヺ&⫼Ѣপ᪡԰᭄ⱘഄഔDŽ䆹᪡԰᭄ᖙ乏ᰃϔϾᎺؐ˄ϡᣛ৥ԡᄫ↉ǃϡᣛ৥ໄܗϔ 2 ഄഔ䖤ㅫヺ ヺ˄খ㾕 A.7.17 㡖˅DŽ݊㒧ᵰϡᰃᎺؐDŽ ᳝݇᪡԰᭄ⱘ䰤ࠊ੠䖤ㅫ㒚㡖ⱘ䆺㒚ֵᙃˈখ㾕ࡴ⊩㉏䖤ㅫヺ˄খ㾕 A.7.7 㡖˅੠䌟ؐ㉏䖤ㅫ ҹৢⱘؐDŽ᪡԰᭄ᖙ乏ᰃϔϾᎺؐDŽ 1 ޣ㸼䖒ᓣⱘؐᰃ㒣䖛ࡴ 1ǃˈ˅••˄1 ޣࡴ 1˄++˅៪ 㸼䖒ᓣDŽ᪡԰᭄ᇚ㹿ܗ㸼䖒ᓣⱘࠡ䴶⏏ࡴ䖤ㅫヺ++៪••ৢᕫࠄⱘ㸼䖒ᓣᰃϔϾϔܗ೼ϔ 䖤ㅫヺޣ1 ࠡ㓔㞾๲䖤ㅫヺϢࠡ㓔㞾 & * + • ~ ! ྡྷ၍ၮസ׸ġone of sizeof(फ໸੠) sizeoff ྡྷ၍ζӒ಴ ྡྷ၍ၮസ׸ ஜᄥफ໸ᅧܰζӒ಴ ••ྡྷ၍ζӒ಴ ++ྡྷ၍ζӒ಴ ᅷζӒ಴܊ ၍ζӒ಴ġྡྷ ᭄ⱘ㒱ᇍؐᇣѢ䰸᭄ⱘ㒱ᇍؐDŽ ㄝѢ a ∌䖰៤ゟDŽབᵰϸϾ᪡԰᭄ഛЎ䴲䋳ˈ߭ԭ᭄Ў䴲䋳ؐϨᇣѢ䰸᭄ˈ৺߭ˈҙֱ䆕ԭ ϸϾ᪡԰᭄Ⳍ䰸ৢ᠔ᕫⱘԭ᭄DŽབᵰ㄀ѠϾ᪡԰᭄Ў 0ˈ߭㒧ᵰ≵᳝ᅮНDŽᑊϨˈ(a/b)*b+a%b 䖤ㅫヺ/⫼Ѣ䅵ㅫ㄀ϔϾ᪡԰᭄ৠ㄀ѠϾ᪡԰᭄Ⳍ䰸᠔ᕫⱘଚˈ㗠䖤ㅫヺ%⫼Ѣ䅵ㅫܗѠ 䖤ㅫヺ*㸼⼎Ь⊩DŽܗѠ ᅮDŽއ㸠᱂䗮ⱘㅫᴃ㉏ൟ䕀ᤶˈ㒧ᵰ㉏ൟ⬅ᠻ㸠ⱘ䕀ᤶ 䖤ㅫヺ*੠/ⱘ᪡԰᭄ᖙ乏Ўㅫᴃ㉏ൟˈ䖤ㅫヺ&ⱘ᪡԰᭄ᖙ乏ЎᭈൟDŽ䖭ѯ᪡԰᭄䳔㽕䖯 ѐ֥फζӒ಴%ஜᄥफ໸ᅧܰζӒ಴ ѐ֥फζӒ಴/ஜᄥफ໸ᅧܰζӒ಴ ѐ֥फζӒ಴*ஜᄥफ໸ᅧܰζӒ಴ ஜᄥफ໸ᅧܰζӒ಴ ѐ֥फζӒ಴ġ Ь⊩㉏䖤ㅫヺ&ǃ/੠%䙉ᕾҢᎺࠄেⱘ㒧ড়ᗻDŽ A.7.6 Ь⊩㉏䖤ㅫヺ ᔎࠊ㉏ൟ䕀ᤶⱘ㸼䖒ᓣϡᰃᎺؐDŽ 䖭⾡㒧ᵘ⿄Ўᔎࠊ㉏ൟ䕀ᤶDŽ㉏ൟৡᇚ೼ A.8.8 㡖ᦣ䗄DŽ䕀ᤶⱘ㒧ᵰᏆ೼ A.6 㡖䅼䆎䖛DŽࣙ৿ फ໸੠ ஜᄥफ໸ᅧܰζӒ಴ ྡྷ၍ζӒ಴ ஜᄥफ໸ᅧܰζӒ಴ġ 㸼䖒ᓣᇚᇐ㟈㸼䖒ᓣⱘؐ㹿䕀ᤶЎᣛᅮⱘ㉏ൟDŽܗҹᣀোᣀ䍋ᴹⱘ㉏ൟৡᓔ༈ⱘϔ A.7.5 ᔎࠊ㉏ൟ䕀ᤶ 㾕䰘ᔩ B˅Ёˈ䖭ϔ㉏ൟ㹿ᅮНЎ size_t ㉏ൟDŽ ༈᭛ӊ˄খޚ↉DŽ㒧ᵰᰃϔϾ᮴ヺোᭈൟᐌ䞣ˈ݋ԧⱘ㉏ൟ⬅ᅲ⦄ᅮНDŽ೼ᷛ DŽℸ䖤ㅫヺϡ㛑⫼Ѣߑ᭄㉏ൟ੠ϡᅠᭈ㉏ൟⱘ᪡԰᭄ˈгϡ㛑⫼Ѣԡᄫס ㋴䭓ᑺⱘ nܗ᭄㒘 ㋴ⱘ᭄㒘ⱘ䭓ᑺᰃϔϾܗぎ䯈˖᳝ n Ͼܙ฿ⱘᄫ㡖᭄ˈࣙᣀᇍ䈵Ёࣙ৿ⱘ᭄㒘᠔䳔㽕ⱘӏԩ ᵰؐЎ 1˗ᇚᅗᑨ⫼Ѣ᭄㒘ᯊˈ݊ؐЎ᭄㒘Ёᄫ㡖ⱘᘏ᭄DŽᑨ⫼Ѣ㒧ᵘ៪㘨ড়ᯊˈ㒧ᵰЎᇍ䈵 ؐⱘ㸼䖒ᓣˈгৃҹЎϔϾ⫼ᣀোᣀ䍋ᴹⱘ㉏ൟৡDŽᇚ sizeof ᑨ⫼Ѣ char ㉏ൟᯊˈ݊㒧 Ϣ݊᪡԰᭄ৠ㉏ൟⱘᇍ䈵᠔䳔ⱘᄫ㡖᭄DŽ᪡԰᭄ৃҹЎϔϾ᳾∖ټsizeof 䖤ㅫヺ䅵ㅫᄬ 8 sizeof 䖤ㅫヺ Ў 0DŽ㒧ᵰ㉏ൟЎ intDŽ 䖤ㅫヺ!ⱘ᪡԰᭄ᖙ乏ᰃㅫᴃ㉏ൟ៪㗙ᣛ䩜DŽབᵰ᪡԰᭄ㄝѢ 0ˈ߭㒧ᵰЎ 1ˈ৺߭㒧ᵰ 䘏䕥䴲䖤ㅫヺ 7 ݇㋏䖤ㅫヺ䙉ᕾҢᎺࠄেⱘ㒧ড়ᗻˈԚ䖭Ͼ㾘߭≵᳝ҔМ԰⫼DŽa>E2 ⱘؐЎ E1 ৥ে⿏ E2 ԡᕫࠄⱘ㒧ᵰDŽབᵰ E1 Ў᮴ヺো E1<>ަ֥फζӒ಴ ྭสζӒ಴<<ަ֥फζӒ಴ ަ֥फζӒ಴ ྭสζӒ಴ġ Ѣ៪ㄝѢᎺ᪡԰᭄㉏ൟⱘԡ᭄ˈ߭㒧ᵰ≵᳝ᅮНDŽ 䙉ᕾᭈൟᦤछॳ߭DŽ㒧ᵰⱘ㉏ൟᰃᦤछৢⱘᎺ᪡԰᭄ⱘ㉏ൟDŽབᵰে᪡԰᭄Ў䋳ؐˈ៪㗙໻ ⿏ԡ䖤ㅫヺ<<੠>>䙉ᕾҢᎺࠄেⱘ㒧ড়ᗻDŽ↣Ͼ䖤ㅫヺⱘ৘᪡԰᭄䛑ᖙ乏ЎᭈൟˈᑊϨ A.7.8 ⿏ԡ䖤ㅫヺ ㋴ˈ߭(P+1)•P ⱘؐЎ 1DŽܗԚᰃˈབᵰ P ᣛ৥᭄㒘ⱘ᳔ৢϔϾ ЁᅮНЎ ptrdiff_tDŽা᳝ᔧᣛ䩜ᣛ৥ⱘᇍ䈵ሲѢৠϔ᭄㒘ᯊˈᏂؐᠡ᳝ᛣНDŽ ༈᭛ӊޚ䞣Ў 1DŽ㒧ᵰⱘ㉏ൟৠ݋ԧⱘᅲ⦄᳝݇ˈԚ೼ᷛ⿏أ䞣DŽⳌ䚏ᇍ䈵䯈ⱘ⿏أП䯈ⱘ ߭㒧ᵰᰃϔϾᏺヺোᭈൟ᭄ˈ㸼⼎ᅗӀᣛ৥ⱘᇍ䈵ˈޣབᵰᣛ৥ৠϔ㉏ൟⱘϸϾᣛ䩜Ⳍ ㅫⱘ䕀ᤶ㾘߭੠ᴵӊϢࡴ⊩ⱘⳌৠDŽ 䖤⫣ޣˈএϔϾӏԩᭈൟⱘؐޣ䖤ㅫヺ•⫼Ѣ䅵ㅫϸϾ᪡԰᭄ⱘᏂؐDŽৃҹҢᶤϾᣛ䩜Ϟ ೴ᆦ၍ഭdู׀ԙܬ༶ژྻ̟ზඹШԅ๽ 䇈ᯢ˖ၭ༘ᄗწᄗ຿೴ᆦ੬ม၍ഭԅຏྡྷّ၍ഭಾ ANSI ᄯ໭႙ަԅඋჸē൑ರԄแਬࢶ ㋴ৢⱘ㄀ϔϾԡ㕂ˈ߭㒧ᵰ≵᳝ᅮНDŽ ܗЁϟϔϾᇍ䈵ⱘᣛ䩜DŽབᵰⳌࡴ᠔ᕫⱘ੠ᇍᑨⱘᣛ䩜ϡ೼᭄㒘ⱘ㣗ೈݙˈϨϡᰃ᭄㒘᳿ሒ 䞣DŽ಴ℸˈབᵰ P ᰃϔϾᣛ৥᭄㒘ЁᶤϾᇍ䈵ⱘᣛ䩜ˈ߭㸼䖒ᓣ P+1 ᰃᣛ৥᭄㒘⿏أϔᅮⱘ Ϣ߱ྟᣛ䩜݋᳝Ⳍৠⱘ㉏ൟˈᑊᣛ৥ৠϔ᭄㒘Ёⱘ঺ϔϾᇍ䈵ˈℸᇍ䈵Ϣ߱ྟᇍ䈵П䯈݋᳝ 䞣DŽⳌࡴᕫࠄⱘ੠ᰃϔϾᣛ䩜ˈᅗ⿏أⳌࡴˈৢ㗙ᇚ䗮䖛Ьҹ᠔ᣛᇍ䈵ⱘ䭓ᑺ㹿䕀ᤶЎഄഔ 䖤ㅫヺ+⫼Ѣ䅵ㅫϸϾ᪡԰᭄ⱘ੠DŽᣛ৥᭄㒘ЁᶤϾᇍ䈵ⱘᣛ䩜ৃҹ੠ϔϾӏԩᭈൟⱘؐ ަ֥फζӒ಴•ѐ֥फζӒ಴ ަ֥फζӒ಴+ѐ֥फζӒ಴ ѐ֥फζӒ಴ ަ֥फζӒ಴ġ 䖯㸠᱂䗮ⱘㅫᴃ㉏ൟ䕀ᤶDŽ↣Ͼ䖤ㅫヺৃ㛑Ў໮⾡㉏ൟDŽ ࡴ⊩㉏䖤ㅫヺ+੠•䙉ᕾҢᎺࠄেⱘ㒧ড়ᗻDŽབᵰ᪡԰᭄Ё᳝ㅫᴃ㉏ൟⱘ᪡԰᭄ˈ߭䳔㽕 A.7.7 ࡴ⊩㉏䖤ㅫヺ 䖤ㅫヺҙ䗖⫼Ѣᭈൟ᪡԰᭄DŽ ᠻ㸠ᣝԡϢ䖤ㅫᯊ㽕䖯㸠᱂䗮ⱘㅫᴃ㉏ൟ䕀ᤶDŽ㒧ᵰЎ᪡԰᭄㒣ᣝԡϢ䖤ㅫৢᕫࠄⱘؐDŽ䆹 ̟สူζӒ಴&ອԉफζӒ಴ ອԉफζӒ಴ ̟สူζӒ಴ġ A.7.11 ᣝԡϢ䖤ㅫヺ ৃҹϢؐЎ 0 ⱘᐌ䞣ᭈൟ㸼䖒ᓣ៪ᣛ৥ void ⱘᣛ䩜䖯㸠↨䕗DŽখ㾕 A.6.6 㡖DŽ 䆌ᠻ㸠ϟ߫↨䕗˖ᣛ䩜ܕⳌㄝ㉏䖤ㅫヺϢ݇㋏䖤ㅫヺ݋᳝Ⳍৠⱘ㾘߭ˈԚ䖭㉏䖤ㅫヺ䖬 Ϣ c˄໻Ѣ˅ǃ<=˄ᇣѢㄝѢ˅੠>=˄໻ѢㄝѢ˅؛ᔧ݇㋏㸼䖒ᓣⱘ㒧ᵰЎ ຂζӒ಴>=ྭสζӒ಴ڑ ຂζӒ಴<=ྭสζӒ಴ڑ ຂζӒ಴>ྭสζӒ಴ڑ ຂζӒ಴<ྭสζӒ಴ڑ ྭสζӒ಴ ຂζӒ಴ġڑ 㹿㾷䞞Ў(a>= &= ^= != ؎ᄔၮസ׸ġone of ྡྷ၍ζӒ಴ ؎ᄔၮസ׸ ؎ᄔζӒ಴ ඨߑζӒ಴ ؎ᄔζӒ಴ġ 䌟ؐ䖤ㅫヺ᳝໮ϾˈᅗӀ䛑ᰃҢᎺࠄে㒧ড়DŽ A.7.17 䌟ؐ㸼䖒ᓣ ⬹ˈԚ㒧ᵰ㉏ൟӮ㒻ᡓᴵӊⱘ৘ߚᬃⱘ䰤ᅮヺDŽ ೼↨䕗ᣛ䩜ⱘ㉏ൟᯊˈᣛ䩜᠔ᣛᇍ䈵ⱘ㉏ൟⱘӏԩ㉏ൟ䰤ᅮヺ˄খ㾕 A.8.2 㡖˅䛑ᇚ㹿ᗑ ൟⱘᣛ䩜ᇚ㹿䕀ᤶЎᣛ৥ void ⱘᣛ䩜ˈ䖭гᰃ㒧ᵰⱘ㉏ൟDŽ བᵰϔϾ᪡԰᭄Ўᣛ৥ void ⱘᣛ䩜ˈ㗠঺ϔϾ᪡԰᭄Ўᣛ৥݊ᅗ㉏ൟⱘᣛ䩜ˈ߭ᣛ৥݊ᅗ㉏ ݊ЁϔϾ᪡԰᭄ᰃᣛ䩜ˈ㗠঺ϔϾᰃᐌ䞣 0ˈ߭ 0 ᇚ㹿䕀ᤶЎᣛ䩜㉏ൟˈϨ㒧ᵰЎᣛ䩜㉏ൟDŽ 㘨ড়ˈ៪㗙ᰃᣛ৥ৠϔ㉏ൟⱘᇍ䈵ⱘᣛ䩜ˈ߭㒧ᵰⱘ㉏ൟϢ䖭ϸϾ᪡԰᭄ⱘ㉏ൟⳌৠDŽབᵰ Ӏⱘ㉏ൟⳌৠˈ䆹㉏ൟгᰃ㒧ᵰⱘ㉏ൟDŽབᵰᅗӀ䛑ᰃ void ㉏ൟˈ៪㗙ᰃৠϔ㉏ൟⱘ㒧ᵘ៪ Ӯ㹿䅵ㅫDŽབᵰ㄀ѠϾ੠㄀ϝϾ᪡԰᭄Ўㅫᴃ㉏ൟˈ߭㽕䖯㸠᱂䗮ⱘㅫᴃ㉏ൟ䕀ᤶˈҹՓᅗ ѠϾ㸼䖒ᓣⱘؐˈ৺߭㒧ᵰЎ㄀ϝϾ㸼䖒ᓣⱘؐDŽ㄀ѠϾ੠㄀ϝϾ᪡԰᭄Ёҙ᳝ϔϾ᪡԰᭄ 䅵ㅫ㄀ϔϾ㸼䖒ᓣ˄ࣙᣀ᠔᳝ৃ㛑ⱘࡃ԰⫼˅ˈབᵰ䆹㸼䖒ᓣⱘؐϡㄝѢ 0ˈ߭㒧ᵰЎ㄀ܜ佪 ݧζӒ಴?ζӒ಴:ඨߑζӒ಴ހ০ ݧζӒ಴ހ০ ඨߑζӒ಴ġ A.7.16 ᴵӊ䖤ㅫヺ ㉏ൟDŽ ໄᯢ ぎ䯈ⱘໄᯢ⿄ЎᅮН˄definition˅DŽໄᯢⱘᔶᓣབϟ˖ټ乘⬭ᄬ ぎ䯈DŽټໄ ᯢ˄ declaration˅⫼Ѣ䇈ᯢ↣Ͼᷛ䆚ヺⱘ৿Нˈ㗠ᑊϡ䳔㽕Ў↣Ͼᷛ䆚ヺ乘⬭ᄬ A.8 ໄᯢ 䞣੠ᔎࠊ㉏ൟ䕀ᤶDŽ䆺㒚ֵᙃখ㾕 A.12.5 㡖DŽ 䆌 sizeof 㸼䖒ᓣǃᵮВᐌܕ䆌ߎ⦄೼#if ৢ䴶ⱘᭈൟᐌ䞣㸼䖒ᓣⱘ㣗ೈ䕗ᇣˈᅗϡܕ এϔϾᐌ䞣DŽޣࠡ䴶ໄᯢⱘ໪䚼៪䴭ᗕᇍ䈵ⱘഄഔࡴϞ៪ 䖤ㅫヺ&ᇚ㹿䱤ᓣഄᑨ⫼DŽ߱ؐ䅵ㅫⱘ㒧ᵰؐᖙ乏Ўϟ߫Ѡ㗙Пϔ˖ϔϾᐌ䞣˗ܗϔˈމ᭄ⱘᚙ ԰⫼Ѣ໪䚼ǃ䴭ᗕᇍ䈵ҹঞҹᐌ䞣㸼䖒ᓣЎϟᷛⱘ໪䚼៪䴭ᗕ᭄㒘DŽᇍѢ᮴ϟᷛⱘ᭄㒘៪ߑ 䖤ㅫヺ&ৃҹܗ䆌᳈໻ⱘ㣗ೈ˖᪡԰᭄ৃҹᰃӏᛣ㉏ൟⱘᐌ䞣ˈϔܕ߱ؐЁⱘᐌ䞣㸼䖒ᓣ ԩ㉏ൟⱘ᪡԰᭄DŽ˅ ℸ㾘߭ᇍ᭄㒘ǃ䯈᥹䆓䯂ǃপഄഔ䖤ㅫヺ੠㒧ᵘ៤ਬ᪡԰ϡ䗖⫼DŽ˄Ԛᰃˈsizeof ৃҹᏺӏ ヺ੠⍂⚍ᐌ䞣㒘៤˗ᔎࠊ㉏ൟ䕀ᤶᖙ乏ᣛᅮЎᭈൟˈӏԩ⍂⚍ᐌ䞣䛑ᇚ㹿ᔎࠊ䕀ᤶЎᭈൟDŽ ߑ᭄䇗⫼៪䗫ো䖤ㅫヺDŽབᵰ㽕∖ᐌ䞣㸼䖒ᓣЎᭈൟˈ߭ᅗⱘ᪡԰᭄ᖙ乏⬅ᭈൟǃᵮВǃᄫ 䖤ㅫヺǃޣ䰸њ԰Ў sizeof ⱘ᪡԰᭄П໪ˈᐌ䞣㸼䖒ᓣЁৃҹϡࣙ৿䌟ؐǃ㞾๲៪㞾 ᄫ↉ⱘ䭓ᑺǃᵮВᐌ䞣ⱘؐǃ߱ؐҹঞᶤѯ乘໘⧚఼㸼䖒ᓣDŽ ᶤѯϞϟ᭛㽕∖㸼䖒ᓣⱘؐЎᐌ䞣ˈ՟བˈswitch 䇁হЁ case ৢ䴶ⱘ᭄ؐǃ᭄㒘䖍⬠੠ԡ ඨߑζӒ಴ Ш२ζӒ಴ġ Ң䇁⊩Ϟⳟˈᐌ䞣㸼䖒ᓣᰃ䰤ᅮѢ䖤ㅫヺⱘᶤϔϾᄤ䲚ⱘ㸼䖒ᓣ˖ A.7.19 ᐌ䞣㸼䖒ᓣ ࣙ৿ 3 Ͼখ᭄ˈ݊Ё㄀ѠϾখ᭄ⱘؐЎ 5DŽ f(a, (t=3, t+2), c) ϟ߫ߑ᭄䇗⫼˖ 䖭ḋˈ䗫ো䖤ㅫヺҙߎ⦄೼೚ᣀোЁDŽ՟བˈˈܗ㡖˅Ёˈ䳔㽕Փ⫼䌟ؐ㸼䖒ᓣ԰Ў䇁⊩ऩ 䅵ㅫDŽ೼䗫ো᳝⡍߿৿НⱘϞϟ᭛Ёˈབ೼ߑ᭄খ᭄㸼˄খ㾕 A.7.3 㡖˅੠߱ؐ߫㸼˄A.8.7 ൟ੠ؐህᰃ㒧ᵰⱘ㉏ൟ੠ؐDŽ೼ᓔྟ䅵ㅫে᪡԰᭄ҹࠡˈᇚᅠ៤Ꮊ᪡԰᭄⍝ঞࠄⱘࡃ԰⫼ⱘ ⬅䗫োߚ䱨ⱘϸϾ㸼䖒ᓣⱘ∖ؐ⃵ᑣЎҢᎺࠄেˈᑊϨᎺ㸼䖒ᓣⱘؐ㹿϶ᓗDŽে᪡԰᭄ⱘ㉏ ζӒ಴, ؎ᄔζӒ಴ ؎ᄔζӒ಴ ζӒ಴ġ A.7.18 䗫ো䖤ㅫヺ 䆌˅DŽܕϡ 䖤ㅫヺ&˄ᰒᓣᑨ⫼៪䱤ᓣᑨ⫼䛑ܗབᵰϔϾᇍ䈵㹿ໄᯢЎ registerˈ߭ᇚϡ㛑ᇍᅗᑨ⫼ϔ ⱘᇍ䈵㹿ⳳℷᄬᬒ೼ᆘᄬ఼ЁˈᑊϨা᳝⡍ᅮ㉏ൟᠡৃҹDŽ䆹䰤ࠊৠ݋ԧⱘᅲ⦄᳝݇DŽԚᰃˈ Ѣᏺ᳝ auto 䇈ᯢヺⱘໄᯢˈ᠔ϡৠⱘᰃˈࠡ㗙ᱫ⼎њໄᯢⱘᇍ䈵ᇚ㹿乥㐕ഄ䆓䯂DŽা᳝ᕜᇥ ぎ䯈DŽᏺ᳝ register 䇈ᯢヺⱘໄᯢㄝӋټ᭄ЁDŽ䖭⾡ໄᯢг݋᳝ᅮНⱘ԰⫼ˈᑊᇚ乘⬭ᄬ ㉏ᇍ䈵ˈ䖭ѯᇍ䈵ҙৃ⫼೼ߑټ䇈ᯢヺ auto ੠ register ᇚໄᯢⱘᇍ䈵䇈ᯢЎ㞾ࡼᄬ ㉏ⱘᛣНˈ៥ӀᏆ೼ A.4 㡖Ё䅼䆎䖛DŽټ᳝݇ᄬ typedef extern static register auto ㉏䇈ᯢヺ˖ټᄬ ㉏䇈ᯢヺབϟ᠔⼎˖ټᄬ ㉏䇈ᯢヺټA.8.1 ᄬ 䆌ぎໄᯢDŽܕ៤ਬDŽϡ 乏㟇ᇥࣙ৿ϔϾໄᯢヺˈ៪㗙݊㉏ൟ䇈ᯢヺᖙ乏ໄᯢϔϾ㒧ᵘᷛ䆄ǃϔϾ㘨ড়ᷛ䆄៪ᵮВⱘ ໄᯢヺᇚ೼⿡ৢ䚼ߚ䅼䆎˄খ㾕 A.8.5 㡖˅DŽໄᯢヺࣙ৿њ㹿ໄᯢⱘৡᄫDŽϔϾໄᯢЁᖙ ໄᯢヺ = ߱ؐ ໄᯢヺ ߱ྟ࣪ໄᯢヺ˖ ߱ྥ࣪ໄᯢヺ㹕ˈ߱ྟ࣪ໄᯢヺ ߱ྟ࣪ໄᯢヺ ߱ྥ࣪ໄᯢヺ㸼˖ ㉏ൟ䰤ᅮヺ ໄᯢ䇈ᯢヺ opt ㉏ൟ䇈ᯢヺ ໄᯢ䇈ᯢヺ opt ㉏䇈ᯢヺ ໄᯢ䇈ᯢヺ optټᄬ ໄᯢ䇈ᯢヺ˖ 䇈ᯢヺ㒘៤DŽ ㉏ټ߱ྟ࣪ໄᯢヺ㸼Ёⱘໄᯢヺࣙ৿㹿ໄᯢⱘᷛ䆚ヺ˗ໄᯢ䇈ᯢヺ⬅ϔ㋏߫ⱘ㉏ൟ੠ᄬ ໄᯢ䇈ᯢヺ ߱ྟ࣪ໄᯢヺ㸼 opt; 䆌ᏺ signed ໄᯢˈԚ䖭ᰃܕᯢヺᇍѢᔎࠊ char ᇍ䈵ᏺヺোԡᰃ䴲ᐌ᳝⫼ⱘ˗݊ᅗᭈൟг ϟ咬䅸Ў int ㉏ൟDŽsigned 䇈މϔ䍋Փ⫼DŽsigned ੠ unsigned ৃҹऩ⣀Փ⫼ˈ䖭⾡ᚙ unsigned 䖭ϸϾ㉏ൟ䇈ᯢヺЁ᳔໮᳝ϔϾৃৠᯊϢ intǃint ⱘ short ៪ long ᔶᓣǃchar ϟⳕ⬹݇䬂ᄫ int ⱘ৿НгᰃϔḋⱘDŽlong ৃϢ double ϔ䍋Փ⫼DŽsigned ੠މ⾡ᚙ ݊Ёˈlong ੠ short 䖭ϸϾ㉏ൟ䇈ᯢヺЁ᳔໮᳝ϔϾৃৠᯊϢ int ϔ䍋Փ⫼ˈᑊϨˈ೼䖭 ㉏ൟᅮНৡ ᵮВ䇈ᯢヺ 㒧ᵘ៪㘨ড়䇈ᯢヺ unsigned signed double float long int short char void ㉏ൟ䇈ᯢヺ˖ ㉏ൟ䇈ᯢヺⱘᅮНབϟ˖ A.8.2 ㉏ൟ䇈ᯢヺ 㒚ֵᙃখ㾕 A.10 㡖੠ A.11 㡖DŽ extern ㉏ൟ˗೼ߑ᭄໪䚼ໄᯢⱘᇍ䈵Ϣߑ᭄ᇚ㹿䅸Ўᰃ static ㉏ൟˈϨ݋᳝໪䚼䖲᥹DŽ䆺 㾘߭䖯㸠˖೼ߑ᭄ݙ䚼ໄᯢⱘᇍ䈵㹿䅸Ўᰃ auto ㉏ൟ˗೼ߑ᭄ݙ䚼ໄᯢⱘߑ᭄㹿䅸Ўᰃ ㉏䇈ᯢヺˈ߭ᇚᣝ✻ϟ߫ټ㉏䇈ᯢヺDŽབᵰ≵᳝ᣛᅮᄬټϔϾໄᯢЁ᳔໮া㛑᳝ϔϾᄬ ⊩ᦣ䗄Ϟⱘᮍ֓DŽ៥Ӏᇚ೼ A.8.9 㡖Ё䅼䆎ᅗDŽ ㉏䇈ᯢヺˈᰃЎњ䇁ټぎ䯈DŽП᠔ҹᇚᅗ⿄Ўᄬټtypedef 䇈ᯢヺᑊϡӮЎᇍ䈵乘⬭ᄬ ヺ೼ߑ᭄໪䚼ⱘ԰⫼খ㾕 A.11.2 㡖DŽ ぎ䯈ᅮН೼݊ᅗഄᮍDŽ᳝݇䆹䇈ᯢټߑ᭄ݙ䚼ⱘ extern ໄᯢ㸼ᯢˈ㹿ໄᯢⱘᇍ䈵ⱘᄬ ໪䚼ⱘ԰⫼খ㾕 A.11.2 㡖DŽ ぎ䯈ⱘߚ䜡ˈ݋᳝ᅮНⱘ԰⫼DŽ᳝݇䆹䇈ᯢヺ೼ߑ᭄ټ䚼DŽ೼ߑ᭄ݙ䚼ˈ䆹䇈ᯢヺᇚᓩ䍋ᄬ ㉏DŽ䖭⾡ᇍ䈵ৃҹ⫼೼ߑ᭄ݙ䚼៪ߑ᭄໪ټ䇈ᯢヺ static ᇚໄᯢⱘᇍ䈵䇈ᯢЎ䴭ᗕᄬ 䖭ᰃϔϾᮄ๲ࡴⱘ㾘߭DŽ 䇈ᯢ˖ᇍໄᯢЎ register Ԛᅲ䰙ᣝ✻ auto ㉏ൟ໘⧚ⱘᇍ䈵ⱘഄഔ䖯㸠䅵ㅫᰃ䴲⊩ⱘDŽ ˖䇈ᯢヺ䰤ᅮヺ㸼 䇈ᯢヺ䰤ᅮヺ㸼 㒧ᵘໄᯢヺ㸼 㒧ᵘໄᯢ˖ 㒧ᵘໄᯢ㸼 㒧ᵘໄᯢ 㒧ᵘໄᯢ 㒧ᵘໄᯢ㸼 㒧ᵘໄᯢ㸼ᰃᇍ㒧ᵘ៪㘨ড়ⱘ៤ਬ䖯㸠ໄᯢⱘໄᯢᑣ߫˖ union struct 㒧ᵘ៪㘨ড়˖ 㒧ᵘ៪㘨ড় ᷛ䆚ヺ 㒧ᵘ៪㘨ড়ᷛ䆚ヺ opt {㒧ᵘໄᯢ㸼} 㒧ᵘ៪㘨ড়䇈ᯢヺ˖ Ͼϡৠ㉏ൟ៤ਬЁⱘӏᛣϔϾ៤ਬDŽ㒧ᵘ੠㘨ড়䇈ᯢヺ݋᳝ⳌৠⱘᔶᓣDŽ 㒧ᵘᰃ⬅ϡৠ㉏ൟⱘੑৡ៤ਬᑣ߫㒘៤ⱘᇍ䈵DŽ㘨ড়гᰃᇍ䈵ˈ೼ϡৠᯊࠏˈᅗࣙ৿໮ A.8.3 㒧ᵘ੠㘨ড়ໄᯢ ໪ˈ㓪䆥఼ৃ㛑Ӯᗑ⬹䖭ѯ䰤ᅮヺDŽމׂᬍ const ᇍ䈵ⱘᚙ ৥ volatile ⱘᣛ䩜ˈⳂⱘᰃ䰆ℶ㓪䆥఼䗮䖛ᣛ䩜ߴ䰸ᯢᰒ໮ԭⱘᓩ⫼DŽ䰸њ䆞ᮁᰒᓣᇱ䆩 䕧ܹˋ䕧ߎⱘᴎ఼ˈᣛ৥䆒໛ᆘᄬ఼ⱘᣛ䩜ৃҹໄᯢЎᣛڣབˈᇍѢ݋᳝ݙᄬ᯴צⱘӬ࣪DŽ ఼Ёⱘᇍ䈵ˈᑊৃ㛑ᦤ催Ӭ࣪ⱘৃ㛑ᗻDŽvolatile ⫼ѢᔎࠊᶤϾᅲ⦄ሣ㬑ৃ㛑ټ೼া䇏ᄬ ᮄ๲ࡴⱘ⡍ᗻDŽconst ⫼Ѣໄᯢৃҹᄬᬒޚ䇈ᯢ˖const ੠ volatile ሲᗻᰃ ANSI ᷛ ҹৢϡ㛑䖯㸠䌟ؐDŽvolatile ᇍ䈵≵᳝Ϣᅲ⦄᮴݇ⱘ䇁НDŽ ㉏ൟ䰤ᅮヺৃϢӏԩ㉏ൟ䇈ᯢヺϔ䍋Փ⫼DŽৃҹᇍ const ᇍ䈵䖯㸠߱ྟ࣪ˈԚ೼߱ྟ࣪ volatile const ㉏ൟ䰤ᅮヺ˖ ㉏ൟгৃҹ⫼䰤ᅮヺ䰤ᅮˈҹᣛᅮ㹿ໄᯢᇍ䈵ⱘ⡍⅞ሲᗻDŽ ㉏ൟ䇈ᯢヺˈ߭咬䅸Ў int ㉏ൟDŽ П໪ˈ೼ϔϾໄᯢЁ᳔໮া㛑Փ⫼ϔϾ㉏ൟ䇈ᯢヺDŽབᵰໄᯢЁ≵᳝މ䰸њϞ䴶䖭ѯᚙ ໮ԭⱘDŽ さDŽϔϾ៤ਬৡᄫϡ㛑೼ৠϔ㒧ކさˈгϡӮϢ᱂䗮ব䞣ކ៤ਬ੠ᷛ䆄ⱘৡᄫϡӮⳌѦ 㹿ᅗ᠔೼ⱘໄᯢⳈ᥹ᓩ⫼DŽ ݋᳝㒧ᵘໄᯢ㸼㗠᮴ᷛ䆄ⱘ㒧ᵘ䇈ᯢヺ៪㘨ড়䇈ᯢヺ⫼Ѣ߯ᓎϔϾᚳϔⱘ㉏ൟˈᅗা㛑 ᔦ䇗⫼ⱘ㒧ᵘˈԚ䖭ѯ㒧ᵘⱘᷛ䆄ৃ㛑Ꮖ೼໪ሖ԰⫼ඳЁໄᯢDŽ 䇈ᯢ˖䖭ᰃ ANSI ЁϔϾᮄⱘ↨䕗䲒⧚㾷ⱘ㾘߭DŽᅗᮼ೼໘⧚ݙሖ԰⫼ඳЁໄᯢⱘⳌѦ䗦 ᔧࠡ԰⫼ඳݙϔϾᮄⱘϡᅠᭈ㉏ൟⱘ㒧ᵘᷛ䆄៪㘨ড়ⱘᷛ䆄DŽ ሖ԰⫼ඳЁᏆໄᯢ䖛ⱘ㒧ᵘᷛ䆄៪㘨ড়ⱘᷛ䆄˄খ㾕 A.11.1 㡖˅ˈ䆹ໄᯢҡᇚՓ䆹ᷛ䆚ヺ៤Ў 䖭⾡ᔶᓣⱘໄᯢໄᯢњϔϾ㒧ᵘ៪㘨ড়ˈԚᅗ≵᳝ໄᯢ㸼੠ໄᯢヺDŽेՓ䆹ᷛ䆚ヺᰃ໪ 㒧ᵘ៪㘨ড় ᷛ䆚ヺˈ ϟ߫ᔶᓣⱘໄᯢ䗖⫼ϔϾ䴲ᐌ⡍⅞ⱘ㾘߭˖ ᭈ㉏ൟⱘᣛ䩜ˈ᠔ҹˈ㒧ᵘ៪㘨ড়ৃࣙ৿ᣛ৥㞾䑿ᅲ՟ⱘᣛ䩜DŽ 䰸њৃҹੑৡ㒧ᵘ៪㘨ড়㉏ൟ໪ˈᷛ䆄䖬ৃҹ⫼ᴹᅮН㞾ᓩ⫼㒧ᵘDŽ⬅Ѣৃҹໄᯢᣛ৥ϡᅠ 㒧ᵘЁϡ㛑ࣙ৿ϡᅠᭈ㉏ൟⱘ៤ਬDŽ಴ℸˈϡ㛑ໄᯢࣙ৿㞾䑿ᅲ՟ⱘ㒧ᵘ៪㘨ড়DŽԚᰃˈ Ⳉࠄ㢅ᣀো“}ā㒜ℶ䆹䇈ᯢヺᯊˈໄᯢⱘ㉏ൟᠡ៤Ўᅠᭈ㉏ൟDŽ ೼ࣙ৿㒧ᵘໄᯢ㸼ⱘ䇈ᯢヺЁˈ೼䆹㒧ᵘໄᯢ㸼ݙໄᯢⱘ㒧ᵘ៪㘨ড়㉏ൟгᰃϡᅠᭈⱘˈϔ ৢˈབᵰ݋᳝䆹ᷛ䆄ⱘ䇈ᯢヺݡ⃵ߎ⦄ᑊࣙ৿ϔϾໄᯢ㸼ˈ߭䆹㉏ൟ៤Ўᅠᭈ㉏ൟDŽेՓᰃ 䆌DŽ೼ᓩ⫼Пܕ߭ϡމᅮНЁ˅ˈᅗৃ⫼Ѣ䇈ᯢϔϾᣛ䩜៪߯ᓎϔϾ typedef ㉏ൟˈ݊ԭᚙ ϡᅠᭈ㒧ᵘ៪㘨ড়㉏ൟⱘᇍ䈵ৃ೼ϡ䳔㽕ᇍ䈵໻ᇣⱘϞϟ᭛Ёᓩ⫼ˈ↨བˈ೼ໄᯢЁ˄ϡᰃ བᵰ䇈ᯢヺЁা᳝ᷛ䆄㗠᮴㒧ᵘໄᯢ㸼ˈᑊϨᷛ䆄≵᳝ໄᯢˈ߭䅸Ў㧕Ўϡᅠᭈ㉏ൟDŽ݋᳝ 㒧ᵘ៪㘨ড় ᷛ䆚ヺ 㸼˅ᴹᓩ⫼ৠϔ㉏ൟˈབϟ᠔⼎˖ ೼ৠϔ԰⫼ඳ៪ݙሖ԰⫼ඳЁⱘৢ㓁ໄᯢЁˈৃҹ೼䇈ᯢヺЁՓ⫼ᷛ䆄˄㗠ϡՓ⫼㒧ᵘໄᯢ 㒧ᵘ៪㘨ড়ᷛ䆚ヺ {㒧ᵘໄᯢ㸼} ϟ߫ᔶᓣⱘ㉏ൟ䇈ᯢヺᇚ݊Ёⱘᷛ䆚ヺໄᯢЎ㒧ᵘໄᯢ㸼ᣛᅮⱘ㒧ᵘ៪㘨ড়ⱘᷛ䆄˖ ໄᯢヺ opt: ᐌ䞣㸼䖒ᓣ ໄᯢヺ 㒧ᵘໄᯢヺ˖ 䖭⾡៤ਬ⿄Ўԡᄫ↉ˈ៪ҙ⿄Ўᄫ↉ˈ݊䭓ᑺ⬅䎳೼ໄᯢヺݦোПৢⱘᐌ䞣㸼䖒ᓣᣛᅮDŽ 䗮ᐌˈ㒧ᵘໄᯢヺህᰃ㒧ᵘ៪㘨ড়៤ਬⱘໄᯢヺDŽ㒧ᵘ៤ਬгৃ㛑⬅ᣛᅮ᭄Ⳃⱘ↨⡍ԡ㒘៤ˈ 㒧ᵘໄᯢヺ㸼, 㒧ᵘໄᯢヺ 㒧ᵘໄᯢヺ 㒧ᵘໄᯢヺ㸼˖ ㉏ൟ䰤ᅮヺ 䇈ᯢヺ䰤ᅮヺ㸼 opt ㉏ൟ䇈ᯢヺ 䇈ᯢヺ䰤ᅮヺ㸼 opt ᓩ⫼ sp ᣛ৥ⱘ㒧ᵘⱘ count ᄫ↉ˈ㗠 sp•>count Ϟˈ㸼䖒ᓣ ᇚᡞ s ໄᯢЎ㒭ᅮ㉏ൟⱘ㒧ᵘˈᡞ sp ໄᯢЎᣛ৥㒭ᅮ㉏ൟⱘ㒧ᵘⱘᣛ䩜DŽ೼䖭ѯໄᯢⱘ෎⸔ struct tnode s, *sp; 䖭ḋⱘໄᯢৢˈϟ߫䇈ᯢ˖ 䆹㒧ᵘࣙ৿ϔϾ݋᳝ 20 Ͼᄫヺⱘ᭄㒘ǃϔϾᭈ᭄ҹঞϸϾᣛ৥㉏Ԑ㒧ᵘⱘᣛ䩜DŽ೼㒭ߎ } struct tnode *right; struct tnode *left; int count; char tword[20]; struct tnode { བϟ᠔⼎ᰃ㒧ᵘໄᯢⱘϔϾㅔऩ՟ᄤ˖ ៤ਬⱘᣛ䩜㉏ൟˈ߭㒧ᵰᇚᣛ৥䆹៤ਬDŽ ЁⱘϔϾ៤ਬDŽབᵰᣛ৥ᶤϔ㘨ড়ⱘᣛ䩜㹿ᔎࠊ䕀ᤶЎᣛ৥ϔϾ݊ټӏϔᯊࠏᅗ᳔໮া㛑ᄬ 䞣䛑Ў 0ˈᑊϨ݊໻ᇣ䎇ҹᆍ㒇ӏԩ៤ਬDŽ⿏أ㘨ড়ৃҹ㹿ⳟ԰Ў㒧ᵘˈ݊᠔᳝៤ਬ䍋ྟ 㒧ᵘ㄀ϔϾ៤ਬⱘᣛ䩜㉏ൟˈ߭㒧ᵰᇚᣛ৥䆹㒧ᵘⱘ㄀ϔϾ៤ਬDŽ ⬠Ϟᇍ唤ˈ಴ℸˈ೼㒧ᵘЁৃ㛑ᄬ೼᮴ৡぎえDŽ㢹ᣛ৥ᶤϔ㒧ᵘⱘᣛ䩜㹿ᔎࠊ䕀ᤶЎᣛ৥䆹 㒧ᵘ៤ਬⱘഄഔؐᣝᅗӀໄᯢⱘ乎ᑣ䗦๲DŽ䴲ᄫ↉㉏ൟⱘ㒧ᵘ៤ਬḍ᥂݊㉏ൟ೼ഄഔ䖍 ϟˈᖙ乏њ㾷ᴀഄᅲ⦄ⱘϔѯ㾘߭DŽމᏗሔˈԚ䆹ᮍ⊩ϡৃ⿏ỡˈ೼䖭⾡ᚙټϞᦣ䗄ᄬ ぎ䯈˄ҷӋᰃ๲ࡴњᣛҸぎ䯈੠䆓䯂ᄫ↉ⱘᯊ䯈˅ˈৠᯊˈᅗ䖬ৃҹ⫼ᴹ೼ԡሖ⃵ټᴹ㡖ⳕᄬ ᄫ↉ˈᓎ䆂䯙䇏ϔϟ䆹䇁㿔㾘߭DŽ԰Ўϔ⾡ৃ⿏ỡⱘᮍ⊩ˈᏺᄫ↉ⱘ㒧ᵘৃ⫼ټ݇ⱘᮍᓣᄬ ㄀ 1 ⠜᳈ձ䌪Ѣ݋ԧⱘᅲ⦄DŽབᵰ㽕ᣝ✻Ϣᅲ⦄Ⳍ↨ޚ䇈ᯢ˖೼ᄫ↉໘⧚ᮍ䴶ˈANSI ᷛ DŽټⱘ䖍⬠ᓔྟᄬܗऩ Ң㗠Փᕫϟϔᄫ↉Ңϟϔߚ䜡ˈܙ฿⾡DŽ៥Ӏৃҹ⫼ᆑᑺЎ 0 ⱘ᮴ৡᄫ↉ᴹᔎࠊ䖯㸠䖭ܙ฿ Ёⱘ࠽ԭ䚼ߚгৃ㛑㹿ܗऩټЁˈ៪㗙ᰃˈᄬܗऩټЁˈ߭ᅗৃ㛑Ӯ㹿ߚࡆᄬᬒࠄ໮Ͼᄬܗ ऩټԧⱘᅲ⦄᳝݇˅DŽབᵰᶤᄫ↉Пৢⱘ঺ϔᄫ↉᮴⊩ܼ䚼ᄬܹᏆ㹿ࠡ䴶ⱘᄫ↉䚼ߚऴ⫼ⱘᄬ Ё˄ৠ݋ܗऩټ᳝݇DŽ㒧ᵘⱘⳌ䚏ᄫ↉៤ਬҹᶤ⾡࡯ᓣ˄ৠ݋ԧⱘᅲ⦄᳝݇˅ᄬᬒ೼ᶤѯᄬ ᅮ䭓ᑺ˄⫼Ѡ䖯ࠊԡ㸼⼎˅ⱘᭈൟᇍ䈵ˈint ㉏ൟⱘᄫ↉ᰃ৺ⳟ԰Ў᳝ヺো᭄ৠ݋ԧⱘᅲ⦄ ໄᯢヺˈ಴ℸৃҹϡੑৡ˅ⱘ㉏ൟЎ intǃunsigned int ៪ signed intˈᑊ㹿㾷䞞Ўᣛ 䰸ᄫ↉㉏ൟⱘ៤ਬ໪ˈ㒧ᵘ៤ਬ៪㘨ড়៤ਬৃҹЎӏᛣᇍ䈵㉏ൟDŽᄫ↉៤ਬ˄ᅗϡ䳔㽕 ࠊᅮࠡˈ䖭⾡݇㘨೼㓪䆥఼Ё᱂䘡ᄬ೼DŽ ޚ䇈ᯢ˖೼ᴀкⱘ㄀ 1 ⠜Ёˈ㒧ᵘ៪㘨ড়ⱘ៤ਬৡϢ݊⠊䕜᮴݇㘨DŽԚᰃˈ೼ ANSI ᷛ ᵘ៪㘨ড়Ёߎ⦄ϸ⃵ˈԚⳌৠⱘ៤ਬৡᄫৃ⫼೼ϡৠⱘ㒧ᵘ៪㘨ড়ЁDŽ 䡈њ㒧ᵘ䇈ᯢヺ੠㘨ড়䇈ᯢヺⱘᔶᓣDŽ׳ヺDŽᵮВ䇈ᯢヺⱘᔶᓣ ᵮВ㉏ൟᰃϔ⾡⡍⅞ⱘ㉏ൟˈᅗⱘؐࣙ৿೼ϔϾੑৡⱘᐌ䞣䲚ড়ЁDŽ䖭ѯᐌ䞣⿄ЎᵮВ A.8.4 ᵮВ ... sin(u.nf.floatnode) ... if (u.n.type == FLOAT) ... u.nf.floatnode = 3.14; u.nf.type = FLOAT; ... } u; } nf; float floatnode; int type; struct { } ni; int intnode; int type; struct { } n; int type; struct { union { ՟བˈϟ䴶䖭↉⿟ᑣᰃড়⊩ⱘ˖ 䆌ᓩ⫼䖭ѯ㒧ᵘЁӏϔ㒧ᵘⱘ݀݅߱ྟ䚼ߚDŽܕᑊϨ䆹㘨ড়ᔧࠡࣙ৿䖭ѯ㒧ᵘЁⱘᶤϔϾˈ߭ ҹㅔ࣪㘨ড়ⱘՓ⫼˖བᵰϔϾ㘨ড়ࣙ৿݅ѿϔϾ݀݅߱ྟᑣ߫ⱘ໮Ͼ㒧ᵘˈৃމϾ⡍⅞ⱘᚙ ϟˈ៥Ӏ᮴⊩Ẕᶹ㘨ড়ⱘᶤϔ៤ਬˈ䰸䴲Ꮖ⫼䆹៤ਬ㒭㘨ড়䌟ؐDŽԚᰃˈ᳝ϔމ䗮ᐌᚙ ᓩ⫼ s েᄤᷥЁ tword ៤ਬⱘ㄀ϔϾᄫヺDŽ s.right•>tword[0] ߭ᓩ⫼㒧ᵘⱘᎺᄤᷥᣛ䩜ˈ㸼䖒ᓣ s.left (Ⳉ᥹ໄᯢヺ(ᷛ䆚㸼 opt Ⳉ᥹ໄᯢヺ(ᔶᓣখ᭄㉏ൟ㸼) Ⳉ᥹ໄᯢヺ [ᐌ䞣㸼䖒ᓣ opt] (ໄᯢヺ) ᷛ䆚ヺ Ⳉ᥹ໄᯢヺ ᣛ䩜 opt Ⳉ᥹ໄᯢヺ ໄᯢヺ˖ ໄᯢヺⱘ䇁⊩བϟ᠔⼎ A.8.5 ໄᯢヺ ᑈњDŽ 䇈ᯢ˖ⳌᇍѢᴀк㄀ 1 ⠜ˈᵮВ㉏ൟᰃϔϾᮄὖᗉˈԚᅗ԰Ў C 䇁㿔ⱘϔ䚼ߚᏆ᳝ད໮ Ё঺ϔϾ݋᳝ᵮВヺ㸼ⱘ䇈ᯢヺDŽ ϟⱘ㾘߭Ϣ㒧ᵘ៪㘨ড়ЁⳌᑨⱘ㾘߭ⳌৠDŽ᮴ᵮВヺ㸼ⱘᵮВ䇈ᯢヺⱘᷛ䆄ᖙ乏ᣛ৥԰⫼ඳ މᵮВ㉏ൟDŽ䰸њϡᄬ೼ϡᅠᭈᵮВ㉏ൟП໪ˈᵮВ䇈ᯢヺ೼᳝᮴ᷛ䆄ǃ᳝᮴ᵮВヺ㸼ⱘᚙ ᵮВ䇈ᯢヺЁᷛ䆚ヺⱘ԰⫼Ϣ㒧ᵘ䇈ᯢヺЁ㒧ᵘᷛ䆄ⱘ԰⫼㉏ԐˈᅗੑৡњϔϾ⡍ᅮⱘ ⳌৠDŽ ৠϔ԰⫼ඳЁⱘ৘ᵮВヺⱘৡᄫᖙ乏ѦϡⳌৠˈгϡ㛑Ϣ᱂䗮ব䞣ৡⳌৠˈԚ݊ؐৃҹ ྟձ⃵䗦๲DŽ བᵰ݊Ёࣙᣀᏺ᳝=ⱘᵮВヺˈ߭䆹ᵮВヺⱘؐ⬅䆹㸼䖒ᓣᣛᅮˈ݊ৢⱘᷛ䆚ヺⱘؐҢ䆹ؐᓔ བᵰ݊Ёϡࣙᣀᏺ᳝=ⱘᵮВヺˈ߭Ⳍᑨᐌ䞣ؐҢ 0 ᓔྟˈϨᵮВᐌ䞣ؐҢᎺ㟇েձ⃵䗦๲ 1DŽ ᵮВヺ㸼Ёⱘᷛ䆚ヺໄᯢЎ int ㉏ൟⱘᐌ䞣ˈᅗӀৃҹ⫼೼ᐌ䞣ৃҹߎ⦄ⱘӏԩഄᮍDŽ ᷛ䆚ヺ = ᐌ䞣㸼䖒ᓣ ᷛ䆚ヺ ᵮВヺ˖ ᵮВヺ㸼, ᵮВヺ ᵮВヺ ᵮВヺ㸼˖ enum ᷛ䆚ヺ enum ᷛ䆚ヺ opt {ᵮВヺ㸼} ᵮВ䇈ᯢヺ˖ ; int i, *pi, *const cpi = &i ៥Ӏᴹⳟ঺໪ϔϾ՟ᄤDŽϟ߫ໄᯢ˖ ৥ int ㉏ൟⱘᣛ䩜᭄㒘ā㉏ൟDŽ ㉏ൟ䰤ᅮヺ㸼ЎぎˈϨ㉏ൟׂ佄ヺЎ“……ⱘ᭄㒘āDŽ಴ℸˈ䆹ໄᯢᅲ䰙Ϟᇚᡞ ap ໄᯢЎĀᣛ ݊Ёˈap[]ⱘ԰⫼ㄝӋѢ D1ˈໄ ᯢ“int ap[] āᇚᡞ ap ⱘ㉏ൟໄᯢЎ“int ㉏ൟⱘ᭄㒘āˈ int *ap[]; ՟བDŽ㗗㰥ϟ߫ໄᯢ˖ 䈵DŽ ൟ䰤ᅮヺ㸼ᣛ৥ T ⱘᣛ䩜āDŽ᯳ো*ৢⱘ䰤ᅮヺ԰⫼Ѣᣛ䩜ᴀ䑿ˈ㗠ϡᰃ԰⫼Ѣᣛ䩜ᣛ৥ⱘᇍ Ϩໄᯢ T D1 Ёⱘᷛ䆚ヺⱘ㉏ൟЎĀ㉏ൟׂ佄ヺ Tāˈ߭ D Ёᷛ䆚ヺⱘ㉏ൟЎĀ㉏ൟׂ佄ヺ ㉏ * ㉏ൟ䰤ᅮヺ㸼 D1 ೼ໄᯢ T D Ёˈབᵰ D ݋᳝ϟ߫ᔶᓣ˖ 1ˈᣛ䩜ໄᯢヺ ߭ D1 Ёᷛ䆚ヺⱘ㉏ൟϢ D ⱘ㉏ൟⳌৠDŽ೚ᣀোϡᬍব㉏ൟˈԚৃᬍব໡ᴖໄᯢヺП䯈ⱘ㒧ড়DŽ (D1) ೼ໄᯢ T D Ёˈབᵰ D ⱘᔶᓣЎ˖ ೼ໄᯢ T D Ёˈབᵰ D ᰃϔϾϡࡴӏԩ䰤ᅮⱘᷛ䆚ヺˈ߭䆹ᷛ䆚ヺⱘ㉏ൟЎ TDŽ 䖭⾡ᔶᓣᴹ㸼䗄DŽ “T D āⱘᔶᓣˈ݊Ё T ҷ㸼㉏ൟˈD ҷ㸼ໄᯢヺDŽ೼ϡৠᔶᓣⱘໄᯢЁˈᷛ䆚ヺⱘ㉏ൟৃ⫼ བᵰা㗗㰥ໄᯢ䇈ᯢヺ˄খ㾕 A.8.2 㡖˅ⱘ㉏ൟ䚼ߚঞ⡍ᅮⱘໄᯢヺˈ߭ໄᯢৃҹ㸼⼎Ў 䆹ໄᯢヺᇚ㹿԰ЎϔϾᮁ㿔ˈ݊㒧ᵰᇚѻ⫳ϔϾᣛᅮ㉏ൟⱘᇍ䈵DŽ ᅮDŽᔧໄᯢヺⱘᷛ䆚ヺߎ⦄೼Ϣ䆹ໄᯢヺᔶᓣⳌৠⱘ㸼䖒ᓣЁᯊˈއԚ݊㉏ൟ⬅ໄᯢヺⱘᔶᓣ ㉏䇈ᯢヺৃⳈ᥹԰⫼Ѣ䆹ᷛ䆚ヺˈټ䆚ヺˈ䆹ᷛ䆚ヺᰃⳈ᥹ໄᯢヺѻ⫳ᓣⱘ㄀ϔϾ׭䗝ᓣDŽᄬ ㉏䇈ᯢヺᑣ߫ПৢDŽ↣ϾໄᯢヺໄᯢϔϾⴶϔⱘЏᷛټໄᯢヺ㸼ߎ⦄೼㉏ൟ䇈ᯢヺ੠ᄬ A.8.6 ໄᯢヺⱘ৿Н ໄᯢヺⱘ㒧ᵘϢ䯈᥹ᣛ䩜ǃߑ᭄ঞ᭄㒘㸼䖒ᓣⱘ㒧ᵘ㉏Ԑˈ㒧ড়ᗻгⳌৠDŽ ㉏ൟ䰤ᅮヺ㸼 ㉏ൟ䰤ᅮヺ ㉏ൟ䰤ᅮヺ ㉏ൟ䰤ᅮヺ㸼 * ㉏ൟ䰤ᅮヺ㸼 opt ᣛ䩜 * ㉏ൟ䰤ᅮヺ㸼 opt ᣛ䩜˖ (D1(ᔶᓣখ᭄㉏ൟ㸼 ೼ᮄᓣⱘߑ᭄ໄᯢ T D Ёˈབᵰ D ݋᳝ϟ߫ᔶᓣ˖ 3 ߑ᭄ໄᯢヺ ԰⫼DŽ ऎ໻ᇣˈԚ㄀ϔ㓈ϟᷛ೼ϟᷛ䅵ㅫᯊ᮴݊ᅗټᅮ᭄㒘᠔䳔ⱘᄬއᖿ˅ˈϨໄᯢЁⱘ㄀ϔ㓈ϟᷛ ᳔ৢϔ㓈ϟᷛবࡼ᳔˄ټࡴ⊩䖤ㅫ䳔㽕Ьҹᭈൟ㉏ൟⱘ䭓ᑺDŽᅗ䙉ᕾϟ߫㾘߭˖᭄㒘ᣝ㸠ᄬ ᣝ✻ A.7.1 㡖Ёⱘ㾘߭䕀ᤶЎ“ᣛ৥ᭈൟ᭄㒘ⱘᣛ䩜”㉏ൟˈ㗠ḍ᥂ A.7.7 㡖Ёⱘ㾘߭ˈ䖭䞠ⱘ ೼ᴀ՟Ёˈx3d[i][j][k]ㄝӋѢ*(x3d[i][j]+k)DŽ㄀ϔϾᄤ㸼䖒ᓣ x3d[i][j]ᇚ A.7.1 㡖Ϣ A.7.7 㡖˅ˈ㢹 E1 ᰃ᭄㒘Ϩ E2 ᰃᭈ᭄ˈ߭ E1[E2]ҷ㸼 E1 ⱘ㄀ E2 Ͼ៤ਬDŽ ϡᇍ⿄ˈԚϟᷛ䖤ㅫᰃৃѸᤶⱘ䖤ㅫDŽḍ᥂䗖⫼Ѣ䖤ㅫヺ+੠᭄㒘ⱘ䕀ᤶ㾘߭˄খ㾕 A.6.6 㡖ǃ ḍ᥂᭄㒘ϟᷛ䖤ㅫⱘᅮНˈE1[E2]ㄝӋѢ*(E1+E2)DŽ಴ℸˈሑㅵ㸼䖒ᓣⱘᔶᓣⳟϞএ ⱘ᭄㒘DŽ ㋴ܗ㋴জᰃϔϾ݋᳝ 7 Ͼᭈൟܗ㋴ⱘ᭄㒘ˈ㗠݊Ёⱘ↣Ͼܗ㋴ⱘ᭄㒘˗x3d[i]߭ᰃ᳝ 5 Ͼܗ ⹂ഄ䇈ˈx3d[i][j]ᰃϔϾ᳝ 7 ϾᭈൟޚЁDŽࠡϝ㗙ᰃ᭄㒘㉏ൟˈ᳔ৢϔϾᰃ int ㉏ൟDŽ᳈ ៤ⱘ᭄㒘DŽx3dǃx3d[i]ǃx3d[i][j]Ϣ x3d[i][j][k]䛑ৃҹড়⊩ഄߎ⦄೼ϔϾ㸼䖒ᓣ ᭄㒘ˈ↣Ͼ乍䛑ᰃ⬅ 5 Ͼ᭄㒘㒘៤ⱘϔϾ᭄㒘ˈ5 Ͼ᭄㒘Ёⱘ↣Ͼ᭄㒘জ䛑ᰃ⬅ 7 Ͼᭈൟ᭄㒘 ߭ໄᯢњϔϾ䴭ᗕⱘϝ㓈ᭈൟ᭄㒘ˈ݊໻ᇣЎ 3×5×7DŽ݋ԧᴹ䇈ˈx3d ᰃϔϾ⬅ 3 Ͼ乍㒘៤ⱘ static int x3d[3][5][7]; ໄᯢњϔϾ⍂⚍᭄᭄㒘੠ϔϾᣛ৥⍂⚍᭄ⱘᣛ䩜᭄㒘ˈ㗠 float fa[17], *afp[17]; 㡖˅ᴹՓ݊ᅠᭈDŽ՟བ˖ ᴹ䇈ˈ݊㉏ൟৃҹ䗮䖛ᇍ䆹ᇍ䈵䖯㸠঺ϔϾᅠᭈໄᯢ˄খ㾕 A.10.2 㡖˅៪߱ྟ࣪˄খ㾕 A.8.7 ៪㒧ᵘDŽгህᰃ䇈ˈᇍѢ໮㓈᭄㒘ᴹ䇈ˈা᳝㄀ϔ㓈ৃҹ㔎ⳕDŽᇍѢϡᅠᭈ᭄㒘㉏ൟⱘᇍ䈵 ᵘ䗴㗠៤˄⫳៤໮㓈᭄㒘˅DŽᵘ䗴᭄㒘ⱘ㉏ൟᖙ乏ᰃᅠᭈ㉏ൟˈ㒱ᇍϡ㛑ᰃϡᅠᭈ㉏ൟⱘ᭄㒘 ᭄㒘ৃҹ⬅ㅫᴃ㉏ൟǃᣛ䩜㉏ൟǃ㒧ᵘ㉏ൟ៪㘨ড়㉏ൟᵘ䗴㗠៤ˈгৃҹ⬅঺ϔϾ᭄㒘 㒘Ϟ⬠ⱘᐌ䞣㸼䖒ᓣˈ߭䆹᭄㒘㉏ൟᰃϡᅠᭈ㉏ൟDŽ ㉏ൟⱘ᭄㒘āDŽབᵰᄬ೼ᐌ䞣㸼䖒ᓣˈ߭䆹ᐌ䞣㸼䖒ᓣᖙ乏ЎᭈൟϨؐ໻Ѣ 0DŽ㢹㔎ᇥᣛᅮ᭄ Ϩໄᯢ T D1 Ёᷛ䆚ヺⱘ㉏ൟᰃĀ㉏ൟׂ佄ヺ Tāˈ߭ D ⱘᷛ䆚ヺ㉏ൟЎĀ㉏ൟׂ佄ヺ T D1[ᐌ䞣㸼䖒ᓣ opt] ೼ໄᯢ T D Ёˈབᵰ D ݋᳝ϟ߫ᔶᓣ˖ 2 ᭄㒘ໄᯢヺ ϔϾഄᮍˈԚᅗ᠔ᣛП໘ⱘؐϡ㛑䗮䖛 pci 䌟ؐᴹᬍবˈ བᴀ՟Ё᠔⼎˅DŽpci ⱘ㉏ൟᰃĀᣛ৥ const int ⱘᣛ䩜āˈpci ᴀ䑿ৃҹ㹿ׂᬍҹᣛ৥঺ ᣛ৥ৠϔԡ㕂ˈԚᅗ᠔ᣛП໘ⱘؐৃҹᬍবDŽᭈൟ ci ᰃᐌ䞣ˈгϡ㛑ׂᬍ˄ৃҹ䖯㸠߱ྟ࣪ˈ ໄᯢњϔϾᭈൟ i ੠ϔϾᣛ৥ᭈൟⱘᣛ䩜 piDŽϡ㛑ׂᬍᐌ䞣ᣛ䩜 cpi ⱘؐˈ䆹ᣛ䩜ᘏᰃ const int ci = 3, *pci; ໄᯢњϔϾ䖨ಲᭈൟؐⱘߑ᭄ fǃϔϾ䖨ಲᣛ৥ᭈൟⱘᣛ䩜ⱘߑ᭄ fpi ҹঞϔϾᣛ৥䖨ಲᭈ int f(), *fpi(), (*pfi)(); ՟བˈϟ߫ໄᯢ˖ A.10.1 㡖˅DŽໄᯢϡᦤկ᳝݇ᔶᓣখ᭄㉏ൟⱘֵᙃDŽ ೼ᮻᓣⱘໄᯢヺЁˈ䰸䴲೼ߑ᭄ᅮНⱘࠡ䴶Փ⫼њໄᯢヺˈ৺߭ˈᷛ䆚ヺ㸼ᖙ乏ぎ㔎˄খ㾕 ᷛ䆚ヺ㸼, ᷛ䆚ヺ ᷛ䆚ヺ ᷛ䆚ヺ㸼˖ ؐϨ᳾ᣛᅮখ᭄ⱘþ㉏ൟׂ佄ヺÿ㉏ൟⱘߑ᭄āDŽᔶᓣখ᭄˄བᵰ᳝ⱘ䆱˅ⱘᔶᓣབϟ˖ ᑊϨໄᯢ D1 Ёⱘᷛ䆚ヺⱘ㉏ൟᰃĀ㉏ൟׂ佄ヺ Tāˈ߭ D ⱘᷛ䆚ヺ㉏ൟЎĀ䖨ಲ T ㉏ൟ D1(ᷛ䆚ヺ㸼 opt) ೼ᮻᓣⱘߑ᭄ໄᯢ T D Ёˈབᵰ D ݋᳝ϟ߫ᔶᓣ˖ ⱘᢑ䈵ໄᯢヺᇚ೼ A.8.8 㡖Ё䅼䆎DŽ ࣙ৿ᷛ䆚ヺˈϨߑ᭄ᅮНⱘᓔ༈≵᳝ߑ᭄ໄᯢヺˈ߭䆹ᷛ䆚ヺ䍙ߎњ԰⫼ඳDŽϡ⍝ঞᷛ䆚ヺ ㉏䇈ᯢヺᇚ㹿ᗑ⬹DŽ㉏Ԑഄˈབᵰᔶᓣখ᭄ໄᯢЁⱘໄᯢヺټ༈ࣙᣀߑ᭄ໄᯢヺˈ৺߭䆹ᄬ ㉏䇈ᯢヺᰃ registerˈᑊϨˈ䰸䴲ߑ᭄ᅮНⱘᓔټ䆌ⱘᄬܕᣛ䩜DŽᔶᓣখ᭄ⱘໄᯢЁᚳϔ བᵰᔶᓣখ᭄㉏ൟᰃ᭄㒘៪ߑ᭄ˈᣝ✻খ᭄䕀ᤶ㾘߭˄খ㾕 A.10.1 㡖˅ˈᅗӀᇚ㹿䕀ᤶЎ 㽕໮DŽ䆺㒚ֵᙃখ㾕 A.7.3 㡖DŽ ᭄㸼ҹⳕ⬹ো“, ...ā㒧ሒˈ߭䆹ߑ᭄ৃ᥹ফⱘᅲ䰙খ᭄Ͼ᭄↨ᰒᓣ䇈ᯢⱘᔶᓣখ᭄Ͼ᭄ ໄᯢⱘ᮴ᔶᓣখ᭄ߑ᭄ⱘໄᯢヺг᳝ϔϾᔶᓣখ᭄㸼ˈ䆹㸼ҙࣙ৿݇䬂ᄫ voidDŽབᵰᔶᓣখ ೼䖭⾡ᮄᓣⱘໄᯢЁˈᔶᓣখ᭄㸼ᣛᅮњᔶᓣখ᭄ⱘ㉏ൟDŽ䖭䞠᳝ϔϾ⡍՟ˈᣝ✻ᮄᓣᮍᓣ ໄᯢ䇈ᯢヺ ᢑ䈵ໄᯢヺ opt ໄᯢ䇈ᯢヺ ໄᯢヺ ᔶᓣখ᭄ໄᯢ˖ ᔶᓣখ᭄㸼, ᔶᓣখ᭄ໄᯢ ᔶᓣখ᭄ໄᯢ ᔶᓣখ᭄㸼˖ ᔶᓣখ᭄㸼, ... ᔶᓣখ᭄㸼 ᔶᓣখ᭄㉏ൟ㸼˖ ᔶᓣখ᭄ⱘ䇁⊩ᅮНЎ˖ Ϩ݋᳝þᔶᓣখ᭄㉏ൟ㸼ÿЁⱘখ᭄ⱘþ㉏ൟׂ佄ヺÿ㉏ൟⱘߑ᭄āDŽ ᑊϨˈໄᯢ T D1 Ёᷛ䆚ヺⱘ㉏ൟЎĀ㉏ൟׂ佄ヺ Tāˈ߭ D ⱘᷛ䆚ヺ㉏ൟᰃĀ䖨ಲ T ㉏ൟؐ ᳾ᰒᓣ߱ྟ࣪ⱘ㞾ࡼᇍ䈵ⱘ߱ྟؐ≵᳝ᅮНDŽ ᳾ᰒᓣ߱ྟ࣪ⱘ䴭ᗕᇍ䈵ᇚ㹿䱤ᓣ߱ྟ࣪ˈ݊ᬜᵰㄝৠѢᅗ˄៪ᅗⱘ៤ਬ˅㹿䌟ҹᐌ䞣 0DŽ 䗮䖛ᐌ䞣㒧ᵘ䖯㸠߱ྟ࣪ˈ䰸䴲߱ؐৃҹ䗮䖛ㅔऩ㸼䖒ᓣ㸼⼎ߎᴹDŽ 䆌ⱘˈԚা㛑ܕᰃޚ䇈ᯢ˖㄀ 1 ⠜ϡᬃᣕ㞾ࡼ㒧ᵘǃ㘨ড়៪᭄㒘ⱘ߱ྟ࣪DŽ㗠 ANSI ᷛ ߭ᅗϡᖙᰃᐌ䞣㸼䖒ᓣˈԚᖙ乏ヺড়ᇍ䈵䌟ؐⱘ㉏ൟ㽕∖DŽ ؐЁⱘ㸼䖒ᓣгৠḋᖙ乏ᰃᐌ䞣㸼䖒ᓣDŽԚᰃˈབᵰ㞾ࡼᇍ䈵ⱘ߱ؐᰃϔϾऩϾⱘ㸼䖒ᓣˈ བᵰ߱ؐᰃ⫼㢅ᣀোᣀ䍋ᴹⱘ߱ؐ㸼ˈ߭ᇍ auto ៪ register ㉏ൟⱘᇍ䈵៪᭄㒘ᴹ䇈ˈ߱ ᇍ䴭ᗕᇍ䈵៪᭄㒘㗠㿔ˈ߱ؐЁⱘ᠔᳝㸼䖒ᓣᖙ乏ᰃ A.7.19 㡖Ёᦣ䗄ⱘᐌ䞣㸼䖒ᓣDŽ ߱ؐ㸼, ߱ؐ ߱ؐ ߱ؐ㸼˖ {߱ؐ㸼,} {߱ؐ㸼} 䌟ؐ㸼䖒ᓣ ߱ؐ˖ ৃҹՓḐᓣㅔ⋕㕢㾖DŽ ৃҹᰃϔϾ㸼䖒ᓣˈгৃҹᰃጠ༫೼㢅ᣀোЁⱘ߱ؐᑣ߫DŽ߱ؐᑣ߫ৃҹҹ䗫ো㒧ᴳˈ䖭ḋ ໄᯢᇍ䈵ᯊˈᇍ䈵ⱘ߱ྟ࣪ໄᯢヺৃЎ݊ᣛᅮϔϾ߱ྟؐDŽ߱ؐ㋻䎳೼䖤ㅫヺ=Пৢˈᅗ A.8.7 ߱ྟ࣪ 䖭ѯ㸼⼎⊩䍋⑤Ѣ C++DŽ ᮍϞ⽕ℶⱘˈԚৃ䴲ℷᓣഄՓ⫼DŽ ༈᭛ӊЁⱘϔѯᅣˈ݅ৠᇚ䖭Ͼᴎࠊℷᓣ࣪њDŽ䆹ᴎࠊ೼㄀ 1 ⠜Ёᰃᅬޚড়ᷛ ЁᮄᓩܹⱘˈᑊϨˈ㒧ޚгᰃ ANSI ᷛ⫣خ䞛⫼ⳕ⬹ো“, ... ā㸼⼎ߑ᭄ব䭓খ᭄㸼ⱘ 䆄DŽ ᣕݐᆍˈህϡᕫϡ೼䇁⊩Ϟ䖯㸠ϔѯ໘⧚ˈे䞛⫼ void ԰Ўᮄᓣⱘ᮴ᔶᓣখ᭄ߑ᭄ⱘᰒᓣᷛ ᭄ᔎࠊ䕀ᤶˈԚᓩܹⱘৠᯊгᏺᴹњᕜ໮⏋х੠咏⚺ˈ㗠Ϩ䖬ᖙ乏ݐᅶ䖭ϸ⾡ᔶᓣDŽЎњֱ 㿔ব࣪DŽᅗӀӬѢ㄀ 1 ⠜ЁⱘĀᮻᓣāໄᯢヺˈ಴ЎᅗӀᦤկњߑ᭄䇗⫼ᯊⱘ䫭䇃Ẕᶹ੠খ Ёᓩܹⱘ᳔䞡㽕ⱘϔϾ䇁ޚ䇈ᯢ˖ࠄⳂࠡЎℶˈᏺᔶᓣখ᭄ॳൟⱘߑ᭄ໄᯢヺᰃ ANSI ᷛ ㄀ѠϾߑ᭄ rand ϡᏺখ᭄ˈϨ䖨ಲ㉏ൟЎ intDŽ ㄀ϔϾᅲ䰙খ᭄᯳ϔϾᣛ৥ᐌ䞣ᄫヺⱘᣛ䩜DŽ݊Ёⱘᔶᓣখ᭄ৡᄫৃҹ䍋ࠄ⊼䞞䇈ᯢⱘ԰⫼DŽ strcpy ᰃϔϾ䖨ಲ int ㉏ൟⱘߑ᭄ˈᅗ᳝ϸϾᅲ䰙খ᭄ˈ㄀ϔϾᅲ䰙খ᭄ᰃϔϾᄫヺᣛ䩜ˈ int strcpy(char *dest, const char *source), rand(void); ೼ϟ߫ᮄᓣⱘໄᯢЁ˖ ൟⱘߑ᭄ⱘᣛ䩜 pfiDŽᅗӀ䛑≵᳝䇈ᯢᔶᓣখ᭄㉏ൟˈ಴ℸ䛑ሲѢᮻᓣⱘໄᯢDŽ ˖㋴ᇚ㹿߱ྟ࣪Ў 0ˈᅠܼⳌৠⱘᬜᵰ䖬ৃҹ䗮䖛ϟ߫ໄᯢ㦋ᕫܗy[3]Ёⱘ y[0][1]੠ y[0][2]DŽ㉏Ԑഄˈ঺ϸ㸠ᇚ߱ྟ࣪ y[1]੠ y[2]DŽ಴Ў߱ؐⱘ᭄Ⳃϡ໳ˈ᠔ҹ ᰃϔϾᅠܼ⫼㢅ᣀোߚ䱨ⱘ߱ྟ࣪˖1ǃ3੠5䖭3Ͼ᭄߱ྟ᭄࣪㒘y[0]ⱘ㄀ϔ㸠ˈेy[0][0]ǃ }; { 3, 5, 7 }, { 2, 4, 6 }, { 1, 3, 5 }, float y[4][3] = { ؐDŽϟ䴶ⱘ՟ᄤ˖ ᇚ x ໄᯢᑊ߱ྟ࣪ЎϔϾ݋᳝ 3 Ͼ៤ਬⱘϔ㓈᭄㒘ˈ䖭ᰃ಴Ўˈ᭄㒘᳾ᣛᅮ໻ᇣϨ᳝ 3 Ͼ߱ int x[] = { 1, 3, 5 }; ՟བ˖ 㘮䲚ⱘϟϔϾ៤ਬDŽ ㋴԰ЎѢ㘮䲚ⱘ៤ਬˈ݊ᅗ࠽ԭⱘ៤ਬᇚ⫼ᴹ߱ྟ࣪䆹ᄤ㘮䲚᠔೼ⱘܗ㸼Ёপߎ䎇໳᭄Ⳃⱘ 䆌䍙䖛៤ਬⱘ᭄ⳂDŽԚᰃˈབᵰᄤ㘮䲚ⱘ߱ؐϡҹᎺ㢅ᣀোᓔ༈ˈ߭াҢ߱ؐܕؐⱘ᭄Ⳃϡ ⱘ߱ྟ࣪ヺҹᎺ㢅ᣀোᓔ༈ˈ߭ৢ㓁䚼ߚЁ⫼䗫ো䱨ᓔⱘ߱ؐ㸼ᇚ߱ྟ࣪ᄤ㘮䲚ⱘ៤ਬDŽ߱ ⱘ߱ྟ࣪Ёৃҹⳕ⬹ᣀো˖བᵰ㘮䲚ⱘ៤ਬг䎇ϔϾ㘮䲚ˈϨ䆹៤ਬމྟ࣪㾘߭DŽ೼ϟ߫ᚙ 㘮䲚ᰃϔϾ㒧ᵘ៪᭄㒘DŽབᵰϔϾ㘮䲚ࣙ৿㘮䲚㉏ൟⱘ៤ਬˈ߭߱ྟ࣪ᯊᇚ䗦ᔦՓ⫼߱ ANSI 㾘߭䖬㒭ߎњ䴲ᰒᓣ߱ྟ࣪ⱘ䴭ᗕ㘨ড়ⱘ㊒⹂䇁НDŽ 䆌ҹϔ⾡ㅔऩᮍᓣᇍ㘨ড়䖯㸠ᰒᓣ߱ྟ࣪໪ˈܕϟᕜ䲒ᇍᅗ䖯㸠ϔ㠀࣪DŽ䰸њ㟇ᇥމ⊩ⱘᚙ 䆌ᇍ㘨ড়䖯㸠੠ྟ࣪DŽĀ㄀ϔϾ៤ਬā㾘߭ᑊϡᕜᅠ㕢ˈԚ೼≵᳝ᮄ䇁ܕ䇈ᯢ˖㄀ 1 ⠜ϡ ⱘ߱ؐDŽ 㘨ড়ⱘ߱ؐৃҹᰃ㉏ൟⳌৠⱘऩϾ㸼䖒ᓣˈгৃҹᰃᣀ೼㢅ᣀোЁⱘ㘨ড়ⱘ㄀ϔϾ៤ਬ ᭄㒘໻ᇣ೎ᅮˈ߭ᄫヺІЁⱘᄫヺ᭄˄ϡ䅵ሒ䚼ⱘぎᄫヺ˅ϡ㛑䍙䖛᭄㒘ⱘ໻ᇣDŽ ᅮDŽ㢹އ˅㒘DŽ㢹᭄㒘໻ᇣ᳾ⶹˈ᭄߭㒘໻ᇣᇚ⬅ᄫヺІЁᄫヺⱘ᭄Ⳃ˄ࣙᣀሒ䚼ⱘぎᄫヺ ᭄㒘ЁⱘⳌᑨ៤ਬDŽ㉏Ԑഄˈᆑᄫヺᄫ䴶ؐ˄খ㾕 A.2.6 㡖˅ৃҹ߱ྟ࣪ wchar_t ㉏ൟⱘ᭄ 䖭䞠᳝ϔϾ⡍՟˖ᄫヺ᭄㒘ৃ⫼ᄫヺІᄫ䴶ؐ߱ྟ࣪DŽᄫヺІЁⱘ৘Ͼᄫヺձ⃵߱ྟ࣪ ៤ਬᇚ㹿߱ྟ࣪Ў 0DŽ ⱘ᭄Ⳃϡ㛑䍙䖛᭄㒘៤ਬⱘ᭄ⳂDŽབᵰ߱ؐⱘ᭄Ⳃ↨᭄㒘៤ਬⱘ᭄Ⳃᇥˈ߭ሒ䚼ԭϟⱘ᭄㒘 ᅮ᭄㒘ⱘ໻ᇣˈҢ㗠Փ᭄㒘㉏ൟ៤Ўᅠᭈ㉏ൟDŽ㢹᭄㒘໻ᇣ೎ᅮˈ߭߱ؐއ߭߱ؐⱘ᭄Ⳃᇚ ᭄㒘ⱘ߱ؐᰃϔϾᣀ೼㢅ᣀোЁⱘǃ⬅᭄㒘៤ਬⱘ߱ؐᵘ៤ⱘ㸼DŽབᵰ᭄㒘໻ᇣ᳾ⶹˈ ᇥˈ߭ৢ䴶ԭϟⱘ㒧ᵘ៤ਬᇚ㹿߱ྟ࣪Ў 0DŽ߱ؐⱘ᭄Ⳃϡ㛑↨៤ਬ᭄໮DŽ ؐ㸼DŽ᮴ৡⱘԡᄫ↉៤ਬᇚ㹿ᗑ⬹ˈ಴ℸϡ㹿߱ྟ࣪DŽབᵰ㸼Ё߱ؐⱘ᭄Ⳃ↨㒧ᵘⱘ៤ਬ᭄ 㒧ᵘⱘ߱ؐৃҹᰃ㉏ൟⳌৠⱘ㸼䖒ᓣˈгৃҹᰃᣀ೼㢅ᣀোЁⱘᣝ݊៤ਬ⃵ᑣᥦ߫ⱘ߱ ؐ㒭ᇍ䈵DŽ ᣛ䩜៪ㅫᴃ㉏ൟᇍ䈵ⱘ߱ؐᰃϔϾऩϾⱘ㸼䖒ᓣˈгৃ㛑ᣀ೼㢅ᣀোЁDŽ䆹㸼䖒ᓣᇚ䌟 * int int 䆒ᷛ䆚ヺⱘ㉏ൟⳌৠDŽ՟བ˖؛ⱘ㉏ൟᇚϢ བᵰ䆹㒧ᵘᰃໄᯢЁⱘϔϾໄᯢヺˈህ᳝ৃ㛑ᚳϔ⹂ᅮᷛ䆚ヺ೼ᢑ䈵ໄᯢヺЁⱘԡ㕂DŽੑৡ Ⳉ᥹ᢑ䈵ໄᯢヺ opt (ᔶᓣখ᭄㉏ൟ㸼 opt) Ⳉ᥹ᢑ䈵ໄᯢヺ opt [ᐌ䞣㸼䖒ᓣ opt] (ᢑ䈵ໄᯢヺ) Ⳉ᥹ᢑ䈵ໄᯢヺ ᣛ䩜 opt Ⳉ᥹ᢑ䈵ໄᯢヺ ᣛ䩜 ᢑ䈵ໄᯢヺ˖ 䇈ᯢヺ䰤ᅮヺ㸼 ᢑ䈵ໄᯢヺ opt ㉏ൟৡ˖ 䖭Ͼ䯂乬ˈҢ䇁⊩Ϟ䆆ˈгህᰃᇍᶤ⾡㉏ൟⱘᇍ䈵䖯㸠ໄᯢˈাᰃⳕ⬹њᇍ䈵ⱘৡᄫ㗠ᏆDŽ އ᭄㉏ൟǃ԰Ў sizeof ⱘᅲ䰙খ᭄ㄝ˅ˈ៥Ӏ䳔㽕ᦤկ᭄᥂㉏ൟⱘৡᄫDŽՓ⫼㉏ൟৡৃҹ㾷 ೼ᶤѯϞϟ᭛Ё˄՟བˈ䳔㽕ᰒᓣ䖯㸠ᔎࠊ㉏ൟ䕀ᤶǃ䳔㽕೼ߑ᭄ໄᯢヺЁໄᯢᔶᓣখ A.8.8 ㉏ൟৡ ሒ䚼ⱘぎᄫヺDŽ ㋴DŽ䆹᭄㒘ⱘ໻ᇣࣙᣀܗໄᯢњϔϾᄫヺ᭄㒘ˈᑊ⫼ϔϾᄫヺІᄫ䴶ؐ߱ྟ࣪䆹ᄫヺ᭄㒘ⱘ char msg[] = "Syntax error on line %s\n"; ᳔ৢ ㋴ᇚ咬䅸߱ྟ࣪Ў 0DŽܗᇚ߱ྟ࣪ y ⱘ㄀ϔ߫˄ᇚ y ⳟ៤ЎϔϾѠ㓈᭄㒘˅ˈ݊ԭⱘ }; { 1 }, { 2 }, { 3 }, { 4 } float y[4][3] = { ϟ߫ໄᯢ˖ ㋴䖯㸠߱ྟ࣪ˈy[2]ձℸ㉏᥼DŽ঺໪ˈܗ㋴DŽৠ⧚ˈy[1]ᇚՓ⫼ৢ㓁ⱘ 3 Ͼܗ⫼㸼Ёⱘ 3 Ͼ y ⱘ߱ؐҹᎺ㢅ᣀোᓔྟˈԚ y[0]ⱘ߱ؐ߭≵᳝ҹᎺ㢅ᣀোᓔྟˈ಴ℸ y[0]ⱘ߱ྟ࣪ᇚՓ }; 1, 3, 5, 2, 4, 6, 3, 5, 7 float y[4][3] = { ;extern int Blockno ᑊ≵᳝䞡ᮄໄᯢ BlocknoˈԚϟ߫ໄᯢ˖ extern Blockno; ϟ߫ໄᯢ˖ ㉏ൟᅮНৡৃ೼ݙሖ԰⫼ඳЁ䞡ᮄໄᯢˈԚᖙ乏㒭ߎϔϾ䴲ぎⱘ㉏ൟ䇈ᯢヺ䲚ড়DŽ՟བˈ ҹ䗮䖛঺ϔ⾡ᮍᓣ䖯㸠㉏ൟໄᯢDŽ೼ᴀջЁˈb Ϣ݊ᅗӏԩ long ㉏ൟᇍ䈵ⱘ㉏ൟⳌৠDŽ typedef ㉏ൟᅮНᑊ≵᳝ᓩܹᮄⱘ㉏ൟˈᅗাᰃᅮНњ᭄᥂㉏ൟⱘৠН䆡ˈ䖭ḋˈህৃ ᅮⱘ㒧ᵘ㉏ൟˈzp ⱘ㉏ൟЎᣛ৥䆹㒧ᵘⱘᣛ䩜DŽ 䛑ᰃড়⊩ⱘໄᯢDŽb ⱘ㉏ൟЎ longˈbp ⱘ㉏ൟЎĀᣛ৥ long ㉏ൟⱘᣛ䩜āDŽz ⱘ㉏ൟЎᣛ Complex z, *zp; extern Blockptr bp; Blockno b; Пৢˈϟ䗄ᔶᓣ˖ typedef struct { double r, theta; } Complex; typedef long Blockno, *Blockptr; ՟བˈ೼ᅮН ℸৢˈ㉏ൟᅮНৡ೼䇁⊩ϞህㄝӋѢⳌ݇㉏ൟⱘ㉏ൟ䇈ᯢヺ݇䬂ᄫDŽ typedef ໄᯢᣝ✻᱂䗮ⱘໄᯢᮍᓣᇚϾ㉏ൟᣛ⌒㒭݊ໄᯢヺЁⱘ↣Ͼৡᄫ˄খ㾕 A.8.6 㡖 ˅DŽ ᷛ䆚ヺ ㉏ൟᅮНৡ˖ ᷛ䆚ヺ⿄Ў㉏ൟᅮНৡDŽ ㉏䇈ᯢヺЎ typedef ⱘໄᯢϡ⫼Ѣໄᯢᇍ䈵ˈ㗠ᰃᅮНЎ㉏ൟੑৡⱘᷛ䆚ヺDŽ䖭ѯټᄬ A.8.9 typedef ಲϔϾᭈൟؐāDŽ ㋴Ўᣛ৥ߑ᭄ⱘᣛ䩜ˈ䆹ߑ᭄≵᳝খ᭄Ϩ䖨ܗ䩜ⱘߑ᭄”ǃ“ϔϾ᭄㒘ˈ݊䭓ᑺ᳾ᣛᅮˈ᭄㒘ⱘ ㋴Ͼ᭄ⱘᭈൟ᭄㒘ⱘᣛ䩜”ǃ“᳾ᣛᅮখ᭄ǃ䖨ಲᣛ৥ᭈൟⱘᣛܗⱘᣛ䩜ⱘ᭄㒘”ǃ“ᣛ৥᳾ᣛᅮ ݊Ёⱘ 6 Ͼໄᯢߚ߿ੑৡњϟ߫㉏ൟ˖“ᭈൟ”ǃ“ᣛ৥ᭈൟⱘᣛ䩜”ǃ“ࣙ৿ 3 Ͼᣛ৥ᭈൟ int (*[])(void) int *() int (*)[] int *[3] case ᷛো੠ default ᷛো⫼೼ switch 䇁হЁ˄খ㾕 A.9.4 㡖 ˅DŽ case ᷛোЁⱘᐌ䞣 ᑊϨϡ㛑㹿䞡ᮄໄᯢDŽ䆺㒚ֵᙃখ㾕 A.11.1 㡖DŽ ᷛDŽᷛ䆚ヺⱘ԰⫼ඳᰃᔧࠡߑ᭄DŽ಴Ўᷛো᳝㞾Ꮕⱘৡᄫぎ䯈ˈ಴ℸϡӮϢ݊ᅗᷛ䆚ヺ⏋⎚ˈ ⬅ᷛ䆚ヺᵘ៤ⱘᷛোໄᯢњ䆹ᷛ䆚ヺDŽᷛ䆚ヺᷛোⱘᚳϔ⫼䗨ህᰃ԰Ў goto 䇁হⱘ䏇䕀Ⳃ default: 䇁হ case ᐌ䞣㸼䖒ᓣ: 䇁হ ᷛ䆚ヺ: 䇁হ ᏺᷛো䇁হ˖ 䇁হৃᏺ᳝ᷛোࠡ㓔DŽ A.9.1 ᏺᷛো䇁হ 䏇䕀䇁হ ᕾ⦃䇁হ 䗝ᢽ䇁হ ໡ড়䇁হ 㸼䖒ᓣ䇁হ ᏺᷛো䇁হ 䇁হ˖ ߚЎ޴⾡㉏ൟDŽ བᵰϡ⡍߿ᣛᯢˈ䇁হ䛑ᰃ乎ᑣᠻ㸠ⱘDŽ䇁হᠻ㸠䛑᳝ϔᅮⱘ㒧ᵰˈԚ≵᳝ؐDŽ䇁হৃ A.9 䇁হ 䭓ᑺ੠ߑ᭄ᔶᓣখ᭄㉏ൟᰃ݊Ёᕜ䞡㽕ⱘ಴㋴DŽ 䈵ໄᯢヺ˄খ㾕 A.8.8 㡖˅ⳌৠˈϨᅗӀⱘ㉏ൟ䇈ᯢヺ㸼ㄝӋˈ߭䖭ϸϾ㉏ൟᰃⳌৠⱘDŽ᭄㒘 ೼ሩᓔ݊Ёⱘӏԩ typedef ㉏ൟᑊߴ䰸᠔᳝ߑ᭄ᔶᓣখ᭄ᷛ䆚ヺৢˈབᵰϸϾ㉏ൟⱘᢑ ᮴ᷛ䆄ⱘᵮВᣛᅮⱘ㉏ൟгᰃϡㄝӋⱘDŽ 䆄ⱘ㒧ᵘǃϡৠᷛ䆄ⱘ㘨ড়੠ϡৠᷛ䆄ⱘᵮВᰃϡㄝӋⱘˈ᮴ᷛ䆄ⱘ㘨ড়ǃ᮴ᷛ䆄ⱘ㒧ᵘ៪ ㋏ˈ՟བˈऩ⣀ⱘ long 㭈৿њ long int˅ˈ߭䖭ϸϾ㉏ൟ䇈ᯢヺ㸼ᰃㄝӋⱘDŽ݋᳝ϡৠᷛ བᵰϸϾ㉏ൟ䇈ᯢヺ㸼ࣙ৿Ⳍৠⱘ㉏ൟ䇈ᯢヺ䲚ড়˄䳔㽕㗗㰥㉏ൟ䇈ᯢヺП䯈ⱘ㭈⎉݇ A.8.10 ㉏ൟㄝӋ ߭䞡ᮄໄᯢњ BlocknoDŽ ˖䗝ᢽ䇁হ 䗝ᢽ䇁হࣙᣀϟ߫޴⾡᥻ࠊ⌕ᔶᓣ˖ A.9.4 䗝ᢽ䇁হ ྟ࣪ϔ⃵DŽ བᵰᠻ㸠䏇䕀䇁হ䖯ܹ⿟ᑣഫˈ߭ϡ䖯㸠߱ྟ࣪DŽStatic ㉏ൟⱘᇍ䈵ҙ೼⿟ᑣᓔྟᠻ㸠ࠡ߱ 㞾ࡼᇍ䈵ⱘ߱ྟ࣪೼↣⃵䖯ܹ⿟ᑣഫⱘ乊ッᯊᠻ㸠ˈᠻ㸠ⱘ乎ᑣᣝ✻ໄᯢⱘ乎ᑣ䖯㸠DŽ ߭г䗖⫼Ѣৠϔৡᄫぎ䯈ⱘᷛ䆚ヺ˄খ㾕 A.11 㡖˅ˈϡৠৡᄫぎ䯈ⱘᷛ䆚ヺ㹿䅸ЎᰃϡৠⱘDŽ A.11.1 㡖˅ˈ೼⿟ᑣഫПৢݡᘶ໡݊԰⫼DŽ೼ৠϔ⿟ᑣഫЁˈϔϾᷛ䆚ヺা㛑ໄᯢϔ⃵DŽℸ㾘 བᵰໄᯢ㸼Ёⱘᷛ䆚ヺԡѢ⿟ᑣഫ໪ⱘ԰⫼ඳЁˈ߭໪䚼ໄᯢ೼⿟ᑣഫݙᇚ㹿ᣖ䍋˄খ㾕 䇁হ㸼 䇁হ 䇁হ 䇁㞾㸼 ໄᯢ㸼 ໄᯢ ໄᯢ ໄᯢ㸼˖ {ໄᯢ㸼 opt 䇁হ㸼 opt} ໡ড়䇁হ˖ ᭄ᅮНЁⱘߑ᭄ԧህᰃϔϾ໡ড়䇁হDŽ ᔧ䳔㽕ᡞ㢹ᑆᴵ䇁হ԰Ўϔᴵ䇁হՓ⫼ᯊˈৃҹՓ⫼໡ড়䇁হ˄г⿄ЎĀ⿟ᑣഫā˅DŽߑ A.9.3 ໡ড়䇁হ 䆒㕂ᷛোDŽ ࠡ㒧ᴳDŽ≵᳝㸼䖒ᓣⱘ䇁হ⿄Ўぎ䇁হDŽぎ䇁হᐌᐌ⫼ᴹЎᕾ⦃䇁হᦤկϔϾぎⱘᕾ⦃ԧ៪ ໻໮᭄㸼䖒ᓣ䇁হЎ䌟ؐ䇁হ៪ߑ᭄䇗⫼䇁হDŽ㸼䖒ᓣᓩ䍋ⱘ᠔᳝ࡃ԰⫼೼ϟϔᴵ䇁হᠻ㸠 㸼䖒ᓣ opt; 㸼䖒ᓣ䇁হ˖ ໻䚼ߚ䇁হЎ㸼䖒ᓣ䇁হˈ݊ᔶᓣབϟ᠔⼎˖ A.9.2 㸼䖒ᓣ䇁হ ᷛোᴀ䑿ϡӮᬍব⿟ᑣⱘ᥻ࠊ⌕DŽ 㸼䖒ᓣᖙ乏ЎᭈൟDŽ ㄝӋѢ for (㸼䖒ᓣ 1; 㸼䖒ᓣ 2; 㸼䖒ᓣ 3) 䇁হ continue 䇁হˈ߭䇁হ ྟ࣪ˈ݊㉏ൟ≵᳝䰤ࠊDŽ᠔᳝㸼䖒ᓣⱘࡃ԰⫼೼䅵ㅫ݊ؐৢゟे㒧ᴳDŽབᵰᄤ䇁হЁ≵᳝ ⱘؐㄝѢ 0ˈ߭ for 䇁হ㒜ℶᠻ㸠DŽ㄀ϝϾ㸼䖒ᓣ೼↣⃵ᕾ⦃ৢ䅵ㅫˈҹ䞡ᮄᇍᕾ⦃䖯㸠߱ ࠊDŽ㄀ѠϾ㸼䖒ᓣᖙ乏Ўㅫᴃ㉏ൟ៪ᣛ䩜㉏ൟˈ೼↣⃵ᓔྟᕾ⦃ࠡ䅵ㅫ݊ؐDŽབᵰ䆹㸼䖒ᓣ ೼ for 䇁হЁˈ㄀ϔϾ㸼䖒ᓣা䅵ㅫϔ⃵ˈ⫼Ѣᇍᕾ⦃߱ྟ࣪DŽ䆹㸼䖒ᓣⱘ㉏ൟ≵᳝䰤 㗠 do 䇁হ೼↣⃵ᕾ⦃ৢ⌟䆩㸼䖒ᓣDŽ 䖒ᓣᖙ乏Ўㅫᴃ㉏ൟ៪ᣛ䩜㉏ൟDŽwhile 䇁হ೼䇁হᠻ㸠ࠡ⌟䆩㸼䖒ᓣˈᑊ䅵ㅫ݊ࡃ԰⫼ˈ ೼ while 䇁হ੠ do 䇁হЁˈা㽕㸼䖒ᓣⱘؐϡЎ 0ˈ݊Ёⱘᄤ䇁হᇚϔⳈ䞡໡ᠻ㸠DŽ㸼 for (㸼䖒ᓣ opt; 㸼䖒ᓣ opt; 㸼䖒ᓣ opt) 䇁হ do 䇁হ while (㸼䖒ᓣ); while (㸼䖒ᓣ) 䇁হ ᕾ⦃䇁হ ᕾ⦃䇁হ⫼Ѣᣛᅮ⿟ᑣ↉ⱘᕾ⦃ᠻ㸠DŽ A.9.5 ᕾ⦃䇁হ 䇈ᯢ˖೼ᴀк㄀ 1 ⠜Ёˈswitch 䇁হⱘ᥻ࠊ㸼䖒ᓣϢ case ᐌ䞣䛑ᖙ乏Ў int ㉏ൟDŽ ᵰ≵᳝ case ᐌ䞣ऍ䜡ˈϨ≵᳝ default ᷛোˈ߭ switch 䇁হⱘ᠔᳝ᄤ䇁হ䛑ϡᠻ㸠DŽ case ᐌ䞣Ϣ㸼䖒ᓣऍ䜡ˈᑊϨ᳝ default ᷛোˈ߭ᇚ᥻ࠊ䕀৥ default ᷛোⱘ䇁হDŽབ བᵰᶤϾ case ᐌ䞣Ϣ㸼䖒ᓣⱘؐⳌৠˈ߭ᇚ᥻ࠊ䕀৥Ϣ䆹 case ᷛোऍ䜡ⱘ䇁হDŽབᵰ≵᳝ 䅵ㅫ㸼䖒ᓣⱘؐঞ݊ࡃ԰⫼ˈᑊᇚ݊ؐϢ↣Ͼ case ᐌ䞣↨䕗ˈܜswitch 䇁হᠻ㸠ᯊˈ佪 ༫ˈcase ៪ default ᷛোϢࣙ৿ᅗⱘ᳔䖥ⱘ switch Ⳍ݇㘨DŽ ৢϡ㛑᳝ⳌৠⱘؐDŽϔϾ switch 䇁হ᳔໮ৃҹ᳝ϔϾ default ᷛোDŽswltch 䇁হৃҹጠ case ᐌ䞣ᇚ㹿䕀ᤶЎᭈൟᦤछৢⱘ㉏ൟDŽৠϔ switch 䇁হЁⱘӏԩϸϾ case ᐌ䞣೼䕀ᤶ ৃᏺϔϾ៪໮Ͼ case ᷛো˄খ㾕 A.9.1 㡖˅DŽ᥻ࠊ㸼䖒ᓣ䳔㽕䖯㸠ᭈൟᦤछ˄খ㾕 A.6.1 㡖 ˅ˈ োᣀ䍋ᴹⱘ㸼䖒ᓣᖙ乏Ўᭈൟˈℸ䇁হ᥻ࠊⱘᄤ䇁হϔ㠀ᰃ໡ড়䇁হDŽᄤ䇁হЁⱘӏԩ䇁হ switch 䇁হḍ᥂㸼䖒ᓣⱘϡৠপؐᇚ᥻ࠊ䕀৥ⳌᑨⱘߚᬃDŽ݇䬂ᄫ switch Пৢ⫼೚ᣀ else ⱘ℻Нᗻ䯂乬DŽ އҹ㾷 㸠㄀ѠϾᄤ䇁হˈ䗮䖛ᇚ else Ϣৠϔጠ༫ሖЁ⺄ࠄⱘ᳔䖥ⱘ᳾ऍ䜡 else ⱘ if Ⳍ䖲᥹ˈৃ ⱘࡃ԰⫼˅ˈབᵰϡㄝѢ 0ˈ߭ᠻ㸠㄀ϔϾᄤ䇁হDŽ೼㄀Ѡ⾡ᔶᓣЁˈབᵰ㸼䖒ᓣЎ 0ˈ߭ᠻ 㹿∖ؐ˄ࣙᣀ᠔᳝ܜ೼ϸ⾡ᔶᓣⱘ if 䇁হЁˈ㸼䖒ᓣ˄ᖙ乏Ўㅫᴃ㉏ൟ៪ᣛ䩜㉏ൟ˅佪 switch (㸼䖒ᓣ) 䇁হ if (㸼䖒ᓣ) 䇁হ else 䇁হ if (㸼䗝ᓣ) 䇁হ 䛑ᰃ≵᳝ᅮНⱘDŽ ϟˈ䖨ಲؐމ᥻ࠊࠄ䖒ߑ᭄ⱘ㒧ሒㄝӋѢϔϾϡᏺ㸼䖒ᓣⱘ return 䇁হDŽ೼䖭ϸ⾡ᚙ ߑ᭄ⱘ䖨ಲؐ㉏ൟDŽ 䗮䖛䌟ؐ᪡԰䕀ᤶ㉏ൟ䙷ḋˈ䆹㸼䖒ᓣᇚ㹿䕀ᤶЎᅗ᠔೼ⱘڣᓣⱘؐᇚ䖨ಲ㒭ߑ᭄䇗⫼㗙DŽ return 䇁হ⫼Ѣᇚ᥻ࠊҢߑ᭄䖨ಲ㒭䇗⫼㗙DŽᔧ return 䇁হৢ䎳ϔϾ㸼䖒ᓣᯊˈ㸼䖒 হⱘᠻ㸠ˈᑊᇚ᥻ࠊ䕀৥㹿㒜ℶ䇁হⱘϟϔᴵ䇁হDŽ break 䇁হা㛑⫼೼ᕾ⦃䇁হ៪ switch 䇁হЁˈᅗᇚ㒜ℶࣙ৿䆹䇁হⱘ᳔ݙሖᕾ⦃䇁 བᵰ continuet 䇁হϡࣙ৿೼᳈ᇣⱘᕾ⦃䇁হЁˈ߭݊԰⫼Ϣ goto contin 䇁হㄝӋDŽ } } while (...); } contin: ; contin: ; contin: ; ... ... ... while (...) { do { for (...) { ⹂ഄ䇈ˈ೼ϟ߫ӏϔ䇁হЁ˖ޚ᳈ continue 䇁হা㛑ߎ⦄೼ᕾ⦃䇁হݙˈᅗᇚ᥻ࠊ䕀৥ࣙ৿ℸ䇁হⱘ᳔ݙሖᕾ⦃䚼ߚDŽ ᅮⱘ䇁হDŽ ೼ goto 䇁হЁˈᷛ䆚ヺᖙ乏ᰃԡѢᔧࠡߑ᭄Ёⱘᷛো(খ㾕 A.9.1 㡖)DŽ᥻ࠊᇚ䕀⿏ࠄᷛোᣛ return 㸼䖒ᓣ opt; break; continue; goto ᷛ䆚ヺ; 䏇䕀䇁হ˖ 䏇䕀䇁হ⫼Ѣ᮴ᴵӊഄ䕀⿏᥻ࠊDŽ A.9.6 䏇䕀䇁হ for 䇁হЁⱘ 3 Ͼ㸼䖒ᓣЁ䛑ৃҹⳕ⬹DŽ㄀ѠϾ㸼䖒ᓣⳕ⬹ᯊㄝӋѢ⌟䆩ϔϾ䴲 0 ᐌ䞣DŽ } 㸼䖒ᓣ 3; 䇁হ while (㸼䖒ᓣ 2) { 㸼䖒ᓣ 1˗ ㄀Ѡ⾡ᔶᓣᰃϔ⾡ᮻᓣⱘߑ᭄ᅮН˖ᷛ䆚ヺ㸼߫ߎˈᔶᓣখ᭄ⱘৡᄫˈ䖭ѯᔶᓣখ᭄ⱘ ೼䰘ᔩ B Ёҟ㒡DŽᏺ᳝ৃবᔶᓣখ᭄ⱘߑ᭄ᖙ乏㟇ᇥ᳝ϔϾੑৡⱘᔶᓣখ᭄DŽ ༈᭛ӊ c) ? m : c; m = (a > b) ? a : b; int m; { int max(int a, int b, int c) ϟ䴶ᰃϔϾᮄᓣߑ᭄ᅮНⱘᅠᭈ՟ᄤ˖ ϔϾᣛ৥ᔶᓣখ᭄ⱘᣛ䩜ᯊˈᅗӀП䯈ⱘऎ߿ህᰒ㗠ᯧ㾕њDŽ ࣪DŽ㄀ 1 ⠜ᣛᅮˈfloat ㉏ൟⱘᔶᓣখ᭄ໄᯢᇚ㹿䇗ᭈЎ double ㉏ൟDŽᔧ೼ߑ᭄ݙ䚼⫳៤ ᮄᓩܹⱘϔϾ⡍ᕕDŽ᳝݇ᦤछⱘϔѯ㒚㡖г᳝㒚ᖂⱘবޚ䇈ᯢ˖ᮄᓣߑ᭄ᅮНᰃ ANSI ᷛ ⫼ߑ᭄ᯊˈᖙ㽕ᯊ㽕ᇍᅲ䰙খ᭄䖯㸠㉏ൟ䕀ᤶˈ✊ৢ䌟ؐ㒭ᔶᓣখ᭄ˈ䆺㒚ֵᙃখ㾕 A.7.3 㡖DŽ ಲ type ㉏ൟؐⱘߑ᭄āˈ߭䆹ໄᯢᇚӮ㹿䇗ᭈЎĀᣛ৥䖨ಲ type ㉏ൟؐⱘߑ᭄ⱘᣛ䩜āDŽ䇗 ߭䆹ໄᯢᇚӮ㹿㞾ࡼ䇗ᭈЎĀᣛ৥ type ㉏ൟⱘᣛ䩜āDŽ㉏Ԑഄˈབᵰᶤϔᔶᓣখ᭄ໄᯢЎĀ䖨 䆹໡ড়䇁হⱘݙሖ⿟ᑣഫЁ䞡ᮄໄᯢ˅DŽབᵰᶤϔᔶᓣখ᭄ໄᯢⱘ㉏ൟЎ“type ㉏ൟⱘ᭄㒘āˈ ᅗᷛ䆚ヺϔḋ೼݊ڣ໘䖯㸠ໄᯢˈᑊϨ೼䆹໡ড়䇁হЁϡ㛑䞡໡ໄᯢⳌৠⱘᷛ䆚ヺ˄Ԛৃҹ ೼䖭ϸ⾡ᮍᓣⱘߑ᭄ᅮНЁˈৃҹ䖭ḋ⧚㾷ᔶᓣখ᭄˖೼ᵘ៤ߑ᭄ԧⱘ໡ড়䇁হⱘᓔྟ ㉏䇈ᯢヺ registerDŽټ䆌䖯㸠߱ྟ࣪ˈᑊϨҙৃՓ⫼ᄬܕ䆚ヺ㸼Ёᣛᅮⱘᔶᓣখ᭄ˈϡ ㉏ൟ⬅ໄᯢ㸼ᣛᅮDŽᇍѢ᳾ໄᯢⱘᔶᓣখ᭄ˈ݊㉏ൟ咬䅸Ў int ㉏ൟDŽໄᯢ㸼ᖙ乏াໄᯢᷛ ˖䯈˗ᡞϡৠ⾡㉏ⱘᷛ䆄ᬒ೼ৠϔৡᄫぎ䯈Ёᰃᮄ๲ࡴⱘ䰤ࠊDŽϢ㄀ 1 ⠜П䯈᳔໻ⱘϡৠ೼Ѣ 䯈˗㒧ᵘᷛ䆄੠㘨ড়ᷛ䆄ߚ߿᳝৘㞾ⱘৡᄫぎ䯈ˈ೼ᶤѯᅲ⦄ЁᴪВᷛ䆄г᳝㞾Ꮕⱘৡᄫぎ 䇈ᯢ˖䖭ѯ㾘߭Ϣᴀ᠟ݠ㄀ 1 ⠜Ё᠔䗄ⱘݙᆍ᳝޴⚍ϡৠDŽҹࠡᷛো≵᳝㞾Ꮕⱘৡᄫぎ ㉏ൟᅮНৡ੠ᵮВᐌ䞣˗ᷛো˗㒧ᵘᷛ䆄ǃ㘨ড়ᷛ䆄੠ᵮВᷛ䆄˗৘㒧ᵘ៪㘨ড়㞾䑿ⱘ៤ਬDŽ ೼ৠϔ԰⫼ඳݙˈⳌৠⱘᷛ䆚ヺгৃ⫼ѢϡৠⱘⳂⱘDŽৡᄫぎ䯈ⱘ㉏ൟࣙᣀ˖ᇍ䈵ǃߑ᭄ǃ ᷛ䆚ヺৃҹ೼㢹ᑆϾৡᄫぎ䯈ЁՓ⫼㗠ѦϡᕅડDŽབᵰԡѢϡৠⱘৡᄫぎ䯈ЁˈेՓᰃ A.11.1 䆡⊩԰⫼ඳ Ёᷛ䆚ヺП䯈ⱘ䖲᥹DŽܗऩ⣀㓪䆥ⱘ㗏䆥ऩ ᅮ৘Ͼއヺ⡍ᗻⱘ⿟ᑣ᭛ᴀऎඳ˗㄀Ѡ⾡ᰃϢ݋᳝໪䚼䖲᥹ⱘᇍ䈵੠ߑ᭄Ⳍ݇ⱘ԰⫼ඳˈᅗ ಴ℸˈ៥Ӏ䳔㽕㗗㰥ϸ⾡㉏ൟⱘ԰⫼ඳ˖㄀ϔ⾡ᰃᷛ䆚ヺⱘ䆡⊩԰⫼ඳˈᅗᰃԧ⦄ᷛ䆚 ҹ䗮䖛䇗⫼੠᪡԰໪䚼᭄᥂ᴹᅲ⦄DŽ 㓪䆥䖛ⱘ՟⿟ৃҹҢᑧЁ䖯㸠ࡴ䕑ˈ⿟ᑣЁߑ᭄䯈ⱘ䗮ֵৃܜ乘ˈܗЁৃҹࣙ৿໮Ͼ㗏䆥ऩ ϡᖙৠᯊ䖯㸠㓪䆥DŽ⑤᭛ӊ᭛ᴀৃֱᄬ೼㢹ᑆϾ᭛ӊЁˈ↣Ͼ᭛ӊܗϔϾ⿟ᑣⱘ᠔᳝ऩ A.11 ԰⫼ඳϢ䖲᥹ ᠔᳝ЈᯊᅮНᇚ㹿䕀বЎ߱ؐЎ 0 ⱘᅮНDŽ བᵰᅮН೼⿟ᑣЁⱘᶤϾഄᮍߎ⦄ˈ߭ЈᯊᅮНҙ㹿䅸ЎᰃໄᯢˈԚབᵰ≵᳝ᅮНߎ⦄ˈ߭ ⱘϔ㠀ᠽሩDŽޚЁߚ߿㗗㰥ˈUNIX ㋏㒳䗮ᐌህ䞛⫼䖭⾡ᮍ⊩ˈᑊϨ㹿䅸Ўᰃ䆹ᷛܗ೼৘㗏䆥ऩ ⱘ໪䚼䖲᥹ᇍ䈵ⱘ᠔᳝ЈᯊᅮНᇚ䲚Ё䖯㸠㗗㰥ˈ㗠ϡᰃܗᔶᓣЁˈϔϾ⿟ᑣЁ᠔᳝㗏䆥ऩ Ԛ೼ᬜᵰϞᰃㄝӋⱘDŽᶤѯᅲ⦄䗮䖛ᇚЈᯊᅮНⱘὖᗉϔ㠀࣪㗠ᬒᆑњ䖭Ͼ䰤ࠊDŽ೼঺ϔ⾡ 䇈ᯢ˖㱑✊ऩϔᅮН㾘߭˄0ne•definition rule ˅೼㸼䖒ϞϢᴀк㄀ 1 ⠜᳝᠔ϡৠˈ 䆹㾘߭䗖⫼ѢᭈϾ⿟ᑣDŽ ᰃᚳϔⱘDŽᇍѢ݋᳝໪䚼䖲᥹ⱘᇍ䈵ˈܗ䖭ᰃ಴Ўˈݙ䚼䖲᥹ⱘᇍ䈵ᇍ↣Ͼ㗏䆥ऩˈܗ㗏䆥ऩ ↣Ͼᇍ䈵䛑ᖙ乏᳝Ϩҙ᳝ϔϾᅮНDŽᇍѢ݋᳝ݙ䚼䖲᥹ⱘᇍ䈵ˈ䆹㾘߭ߚ߿䗖⫼Ѣ↣Ͼ ᅮНˈ߭䆹ЈᯊᅮНᇚ䕀বЎϔϾ߱ؐЎ 0 ⱘᅮНDŽ Ёϡᄬ೼䆹ᇍ䈵ⱘܗЁDŽ߭᠔᳝ЈᯊᅮН䛑ᇚҙҾ㹿䅸Ўᰃ໮ԭⱘໄᯢ˗བᵰ䆹㗏䆥ऩܗऩ ᏺ᳝߱ؐˈᑊϨϡࣙ৿ extern 䇈ᯢヺˈ߭ᅗᰃϔϾЈᯊᅮНDŽབᵰᇍ䈵ⱘᅮНߎ⦄೼㗏䆥 བᵰϔϾᇍ䈵ⱘ໪䚼ໄᯢᏺ᳝߱ؐˈ߭䆹ໄᯢህᰃϔϾᅮНDŽབᵰϔϾ໪䚼ᇍ䈵ໄᯢϡ ৺߭݋᳝໪䚼䖲᥹DŽ᳝݇䖲᥹ⱘ䆺㒚ֵᙃˈখ㾕 A.11.2 㡖Ёⱘ䅼䆎DŽ བᵰϔϾᇍ䈵៪ߑ᭄ⱘ㄀ϔϾ໪䚼ໄᯢࣙ৿ static 䇈ᯢヺˈ߭䆹ᷛ䆚ヺ݋᳝ݙ䚼䖲᥹ˈ Ӏⱘ㉏ൟгᰃϔ㟈ⱘDŽ ᭄ˈ㗠঺ϔϾ㉏ൟᣛᅮњᏺᔶᓣখ᭄ໄᯢⱘᮄᓣߑ᭄ˈѠ㗙П䯈݊ᅗᮍ䴶䛑Ⳍৠˈ߭䅸Ўᅗ ൟˈ݊ᅗሲᗻ䛑Ⳍৠˈ߭䅸Ў䖭ϸϾ㉏ൟᰃϔ㟈ⱘDŽ᳔ৢˈབᵰϔϾ㉏ൟᣛᅮњϔϾᮻᓣߑ 㟈ⱘDŽℸ໪ˈབᵰϔϾ㉏ൟЎϡᅠᭈ᭄㒘㉏ൟ˄খ㾕 A.8.6 㡖˅ˈ㗠঺ϔϾ㉏ൟЎᅠᭈ᭄㒘㉏ В㉏ൟ˄খ㾕 A.8.3 㡖˅ˈ㗠঺ϔϾᰃᇍᑨⱘᏺৠϔᷛ䆄ⱘᅠᭈ㉏ൟˈ߭䅸Ў䖭ϸϾ㉏ൟᰃϔ 㟈ⱘDŽᑊϨˈབᵰϸϾໄᯢП䯈ⱘऎ߿ҙҙ೼Ѣ˖݊ЁϔϾⱘ㉏ൟЎϡᅠᭈ㒧ᵘǃ㘨ড়៪ᵮ བᵰϔϾᇍ䈵៪ߑ᭄ⱘϸϾໄᯢ䙉ᕾ A.8.10 㡖Ё᠔䗄ⱘ㾘߭ˈ߭䅸ЎᅗӀⱘ㉏ൟᰃϔ ᑊ䖯㸠ᅣᠽሩ˄খ㾕 A.12.3 㡖̚A.12.10 㡖 ˅DŽ 3 ˅ᇚ⿟ᑣߚ៤⫼ぎⱑヺߚ䱨ⱘ䆄োDŽ⊼䞞ᇚ㹿᳓ᤶЎϔϾぎⱑヺDŽ᥹ⴔᠻ㸠乘໘⧚ᣛҸˈ 㡖 ˅DŽ 2˅ᇚᣛҸ㸠ЁԡѢᤶ㸠ヺࠡⱘড᭰ᴴヺ\ߴ䰸ᥝˈҹᡞ৘ᣛҸ㸠䖲᥹䍋ᴹ˄খ㾕 A.12.2 㽕೼⑤᭛ӊⱘ৘㸠П䯈ᦦܹᤶ㸠ヺDŽ ᇚ A.12.1 㡖᠔䗄ⱘϝᄫヺᑣ߫᳓ᤶЎㄝӋᄫヺDŽབᵰ᪡԰㋏㒳⦃๗䳔㽕ˈ䖬ˈܜ1˅佪 DŽ˅ޣ乘໘⧚䖛⿟೼䘏䕥ϞৃҹߦߚЎ޴Ͼ䖲㓁ⱘ䰊↉˄೼ᶤѯ⡍⅞ⱘᅲ⦄Ёৃҹ㓽 㸠Ёˈ䰸ぎḐǃ῾৥ࠊ㸼ヺ໪ⱘ݊ᅗぎⱑヺⱘ԰⫼ᰃ≵᳝ᅮНⱘDŽ ⱘᄫヺᑣ߫ˈℸ໪ˈ᠔᳝᳾䖯㸠݊ᅗᅮНⱘᄫヺ䛑ᇚ㹿䅸Ўᰃ䆄োDŽԚᰃˈ೼乘໘⧚఼ᣛҸ 㿔ˈ䆄োৃҹᰃӏԩ䇁㿔䆄োˈгৃҹᰃ㉏ԐѢ#include ᣛҸ˄খ㾕 A.12.4 㡖˅Ё㸼⼎᭛ӊৡ ↣ϔ㸠䛑ᇚऩ⣀䖯㸠ߚᵤ˄᳝݇བԩᇚ㸠䖲㒧䍋ᴹⱘ䆺㒚ֵᙃখ㾕 A.12.4 㡖˅DŽᇍ乘໘⧚఼㗠 ⱘ᳿ሒ˄Ϣ԰⫼ඳ᮴݇˅DŽ㸠䖍⬠ᰃ᳝ᅲ䰙ᛣНⱘ˗ܗ೼ӏԩഄᮍˈ݊԰⫼ৃᓊ㓁ࠄ᠔೼㗏䆥ऩ ぎḐ˅ህᰃ乘໘⧚఼໘⧚ⱘᇍ䈵DŽ䖭ѯੑҸ㸠ⱘ䇁⊩⣀ゟѢ䇁㿔ⱘ݊ᅗ䚼ߚˈᅗӀৃҹߎ⦄ 乘໘⧚఼ᠻ㸠ᅣ᳓ᤶǃᴵӊ㓪䆥ҹঞࣙ৿ᣛᅮⱘ᭛ӊˈҹ#ᓔ༈ⱘੑҸ㸠˄“#āࠡৃҹ᳝ A.12 乘໘⧚ ߭䆹䖲᥹ᰃ໪䚼ⱘDŽ Ϣ䆹໪䚼ໄᯢ݋᳝Ⳍৠⱘ䖲᥹ˈᑊᓩ⫼ৠϔᇍ䈵៪ߑ᭄DŽԚᰃˈབᵰ≵᳝ৃ㾕ⱘ໪䚼ໄᯢˈ extern 䇈ᯢヺˈᑊϨˈ೼ࣙ৿䆹⿟ᑣഫⱘ԰⫼ඳЁ᳝ϔϾ䆹ᷛ䆚ヺⱘ໪䚼ໄᯢˈ߭䆹ᷛ䆚ヺ ࣙ৿ extern 䇈ᯢヺˈ߭䆹ᷛ䆚ヺ≵᳝䖲᥹ˈᑊϨ೼ߑ᭄ЁᰃᚳϔⱘDŽབᵰ䖭⾡ໄᯢЁࣙ৿ 䆚ヺ݋᳝ݙ䚼䖲᥹ˈ৺߭ˈ䆹ᷛ䆚ヺᇚ݋᳝໪䚼䖲᥹DŽབᵰ⿟ᑣഫЁᇍϔϾᷛ䆚ヺⱘໄᯢϡ བ A.10.2 㡖᠔䗄ˈབᵰՓ⫼њ static 䇈ᯢヺˈ߭ᷛ䆚ヺⱘ㄀ϔϾ໪䚼ໄᯢᇚՓᕫ䆹ᷛ ᠔᳝ໄᯢгᓩ⫼ৠϔᅲԧˈᑊϨ䆹ᇍ䈵៪ߑ᭄ᰃ㹿ᭈϾ⿟ᑣЁ݅ѿⱘDŽ ᴹ䇈ᰃᚳϔⱘDŽ݋᳝໪䚼䖲᥹ⱘৠϔᇍ䈵៪ߑ᭄ᷛ䆚ヺⱘܗϨˈ䆹ᇍ䈵៪ߑ᭄ᇍ䖭Ͼ㗏䆥ऩ Ёˈ݋᳝ݙ䚼䖲᥹ⱘৠϔᇍ䈵៪ߑ᭄ᷛ䆚ヺⱘ᠔᳝ໄᯢ䛑ᓩ⫼ৠϔᅲԧˈᑊܗ೼㗏䆥ऩ A.11.2 䖲᥹ 䚼Ёℸᷛ䆚ヺⱘӏԩໄᯢ䛑ᇚ㹿ᣖ䍋ˈⳈࠄ⿟ᑣഫ㒧ᴳݡᘶ໡݊԰⫼DŽ བᵰᶤϔᷛ䆚ヺᰒᓣഄ೼⿟ᑣഫ˄ࣙᣀᵘ៤ߑ᭄ⱘ⿟ᑣഫ˅༈䚼Ёໄᯢˈ߭䆹⿟ᑣഫ໪ 㗠㿔˅៪ࠄ⿟ᑣഫ㒧ᴳЎℶ˄ᇍߑ᭄ݙ䚼ໄᯢ㗠㿔˅DŽ 㒧ᴳЎℶ˄ᇍ໪䚼ໄᯢܗᷛ䆄៪ᵮВᐌ䞣ⱘ԰⫼ඳҢ݊ߎ⦄೼㉏ൟ䇈ᯢヺЁᓔྟˈࠄ㗏䆥ऩ ԰⫼ඳᰃ݊᠔೼ⱘᭈϾ⿟ᑣഫDŽᷛোⱘ԰⫼ඳᰃ݊᠔೼ⱘߑ᭄DŽ㒧ᵘᷛ䆄ǃ㘨ড়ᷛ䆄ǃᵮВ ߑ᭄˗ߑ᭄ໄᯢЁᔶᓣখ᭄ⱘ԰⫼ඳࠄໄᯢヺⱘ᳿ሒ໘㒧ᴳDŽ⿟ᑣഫ༈䚼Ёໄᯢⱘᷛ䆚ヺⱘ 㒧ᴳЎℶDŽߑ᭄ᅮНЁᔶᓣখ᭄ⱘ԰⫼ඳҢᅮНߑ᭄ⱘ⿟ᑣഫᓔྟ໘ᓔྟˈᑊ䌃こᭈϾܗऩ ೼໪䚼ໄᯢЁˈᇍ䈵៪ߑ᭄ᷛ䆚ヺⱘ䆡⊩԰⫼ඳҢ݊ໄᯢ㒧ᴳⱘԡ㕂ᓔྟˈࠄ᠔೼㗏䆥 䖭ϔ㾘߭೼᳔䖥޴ᑈՓ⫼ᕫᕜ໮DŽ Ͼ㒧ᵘ੠㘨ড়䛑Ў݊៤ਬᓎゟϡৠⱘৡᄫぎ䯈ˈ಴ℸৠϔৡᄫৃߎ⦄೼໮Ͼϡৠⱘ㒧ᵘЁDŽ↣ #undef ᷛ䆚ヺ ㉏ԐѢϟ߫ᔶᓣⱘ᥻ࠊᣛҸ˖ ߭ᖙ乏ֱ䆕݊ᔶᓣখ᭄Ͼ᭄ǃᣐݭঞ䆄োᑣ߫䛑ᖙ乏Ϣࠡ䴶ⱘᅮНⳌৠDŽ ぎḐDŽৠ㄀ϔ⾡ᔶᓣϔḋˈ䆄োᑣ߫ࠡৢⱘぎⱑヺ䛑ᇚ㹿϶ᓗᥝDŽབᵰ㽕ᇍᅣ䖯㸠䞡ᅮНˈ ᰃϔϾᏺ᳝ᔶᓣখ᭄˄⬅ᷛ䆚ヺ㸼ᣛᅮ˅ⱘᅣᅮНˈ݊Ё㄀ϔϾᷛ䆚ヺϢ೚ᣀো˄П䯈≵᳝ #define ᷛ䆚ヺ(ᷛ䆚ヺ㸼 opt) 䆄োᑣ߫ ㉏ԐѢϟ߫ᔶᓣⱘᣛҸ㸠˖ ᷛ䆄ᑣ߫Ϣ㄀ϔ⃵Ⳍৠ˄᠔᳝ⱘぎⱑߚ䱨ヺ㹿䅸ЎᰃⳌৠⱘ˅DŽ ⱑヺ䛑ᇚ㹿϶ᓗᥝDŽ㄀Ѡ⃵⫼#define ᣛҸᅮНৠϔᷛ䆚ヺᰃ䫭䇃ⱘˈ䰸䴲㄀Ѡ⃵ᅮНЁⱘ ᇚՓᕫ乘໘⧚఼ᡞ䆹ᷛ䆚ヺৢ㓁ߎ⦄ⱘ৘Ͼᅲ՟⫼㒭ᅮⱘ䆄োᑣ߫᳓ᤶDŽ䆄োᑣ߫ࠡৢⱘぎ #define ᷛ䆚ヺ 䆄োᑣ߫ ㉏ԐѢϟ߫ᔶᓣⱘ᥻ࠊᣛҸ˖ A.12.3 ᅣᅮН੠ᠽሩ ড়ᑊ៤ϔ㸠DŽ䖭⾡໘⧚㽕೼ߚ䱨䆄োПࠡ䖯㸠DŽ 䗮䖛ᇚҹড᭰ᴴ\㒧ᴳⱘᣛҸ㸠᳿ሒⱘড᭰ᴴ੠݊ৢⱘᤶ㸠ヺߴ䰸ᥝDŽৃҹᇚ㢹ᑆᣛҸ㸠 A.12.2 㸠䖲᥹ ᮄᓩܹⱘ⡍ᕕDŽޚ䇈ᯢ˖ϝᄫヺᑣ߫ᰃ ANSI ᷛ 䰸ℸП໪ϡ䖯㸠݊ᅗ᳓ᤶDŽ ??' ^ ??! | ??• ~ ??/ \ ??) ] ??> } ??= # ??( [ ??< { ᄫヺ᳓ᤶˈ䖭⾡᳓ᤶ೼䖯㸠᠔᳝ᅗҪ໘⧚Пࠡ䖯㸠DŽ ⱘᄫヺ䲚㸼⼎ߎᴹˈϟ߫᠔⼎ⱘ᠔᳝ϝᄫヺᑣ߫䛑㽕⫼ⳌᑨⱘऩϾޣЎњᇚ⿟ᑣ䗮䖛䖭⾡㓽 C 䇁㚆⑤⿟ᑣⱘᄫヺ䲚ᰃ 7 ԡ ASCII ⷕⱘᄤ䲚ˈԚᅗᰃ ISO 646•1983 ϡবҷⷕ䲚ⱘ䍙䲚DŽ A.12.1 ϝᄫヺᑣ߫ ໘⧚ᕫࠄⱘ㒧ᵰˈ✊ৢϢ݊ᅗ⿟ᑣ੠ᑧ䖲᥹䍋ᴹDŽ 5˅ᬊ䲚ᖙ㽕ⱘ⿟ᑣ੠᭄᥂ˈᑊᇚ໪䚼ߑ᭄੠ᇍ䈵ⱘᓩ⫼Ϣ݊ᅮНⳌ䖲᥹ˈ㗏䆥㒣䖛ҹϞ ㄝӋᄫヺˈ✊ৢᡞⳌ䚏ⱘᄫヺІᄫ䴶ؐ䖲᥹䍋ᴹDŽ ᇚᄫヺᐌ䞣੠ᄫヺІᄫ䴶ؐЁⱘ䕀Нᄫヺᑣ߫˄খ㾕 A.2.5 㡖Ϣ A.2.6 㡖˅᳓ᤶЎ˅4 ᅣ䇗⫼ tempfile(/usr/tmp)ᇚ⫳៤ #define tempfile(dir) #dir "%s" ᅮ᳝ϟ߫ᅮН˖؛ ⃵ˈϔ⃵䖯㸠⌟䆩ˈ঺ϔ⃵߭⫳៤ؐDŽ 䖨ಲؐৃҹᰃӏᛣㅫᴃ㉏ൟˈ⫮㟇ৃҹᰃᣛ䩜DŽৠᯊˈখ᭄ৃ㛑᳝ࡃ԰⫼ˈ㗠Ϩ䳔㽕䅵ㅫϸ ᅮНњϔϾᅣˈᅗ䖨ಲϸϾখ᭄ПᏂⱘ㒱ᇍؐDŽϢᠻ㸠ৠḋࡳ㛑ⱘߑ᭄᠔ϡৠⱘᰃˈখ᭄Ϣ #define ABSDIFF(a, b) ((a)>(b) ? (a)•(b) : (b)•(a)) ᅮН int table[TABSIZE]; #define TABSIZE 100 ՟བˈ䖭⾡ࡳ㛑ৃ⫼ᴹᅮНĀ㸼⼎ᐌ䞣āˈབϟ՟᠔⼎˖ ↨䕗⣀⡍˄খ㾕ϟ䴶ⱘ՟ᄤ˅DŽ ࡴܹњ#੠##䖤ㅫヺˈ䖭ህՓᕫᓩ⫼੠䖲᥹៤Ўৃ㛑DŽᶤѯᮄ㾘߭˄⡍߿ᰃϢ䖲᥹᳝݇ⱘ㾘߭˅ ㄀ 1 ⠜ᦣ䗄ᕫ᳈䆺㒚DŽ᳔䞡㽕ⱘব࣪ᰃ↨ޚ䇈ᯢ˖᳝݇ᅣᠽሩ໘⧚ⱘ㒚㡖ֵᙃˈANSI ᷛ ेՓᠻ㸠ᅣᠽሩৢᕫࠄⱘ᳔㒜㒧ᵰҹ#ᠧ༈ˈгϡ䅸Ўᅗᰃ乘໘⧚ᣛҸDŽ ᰃֱᣕϡবDŽ ᶤϾᷛ䆚ヺ೼ᶤϾᠽሩЁ㹿᳓ᤶৢˈݡ⃵ᠿᦣᑊݡ⃵䘛ࠄℸᷛ䆚ヺᯊϡݡᇍ݊ᠻ㸠᳓ᤶˈ㗠 ᇍ䖭ϸ⾡㉏ൟⱘᅣˈ䛑㽕䞡໡ᠿᦣ᳓ᤶ䆄োᑣ߫ҹᶹᡒ᳈໮ⱘᏆᅮНᷛ䆚ヺDŽԚᰃDŽᔧ ᤶ䆄োᑣ߫ⱘᓔ༈៪㒧ሒDŽ ᮴ᬜˈ៪㗙㒧ᵰձ䌪Ѣ##䖤ㅫヺⱘ໘⧚乎ᑣˈ߭㒧ᵰ≵᳝ᅮНDŽৠᯊˈ##гৃҹϡߎ⦄೼᳓ ݊ࠡৢⱘぎⱑヺ䛑ߴ䰸ᥝˈҹ֓ᇚⳌ䚏䆄ো䖲᥹䍋ᴹᔶ៤ϔϾᮄ䆄োDŽབᵰ䖭ḋѻ⫳ⱘ䆄ো ݊⃵ˈ᮴䆎ા⾡ᅣⱘᅮН䆄োᑣ߫Ёࣙ৿ϔϾ##䖤ㅫヺˈ೼ᔶᓣখ᭄᳓ᤶৢ䛑㽕ᡞ##ঞ 䞣ϸ䖍៪ݙ䚼ⱘ↣Ͼঠᓩো˄"˅៪ড᭰ᴴ˄\˅ࠡ䴶䛑㽕ᦦܹϔϾড᭰ᴴ˄\˅DŽ ੠ᔶᓣখ᭄ᷛ䆚ヺᇚ㹿⫼ᓩোᓩ䍋ᴹⱘᅲ䰙খ᭄᳓ᤶDŽᅲ䰙খ᭄ЁⱘᄫヺІᄫ䴶ؐǃᄫヺᐌ ᥹ᰃϔϾ#ヺো˄ᅗӀП䯈≵᳝ぎⱑヺ˅ˈⳌᑨᔶᓣখ᭄ⱘϸ䖍ᇚ㹿ࡴϞঠᓩো˄"˅ˈ䱣ৢˈ# བᵰ᳓ᤶ䆄োᑣ߫ЁⱘᶤϾᔶᓣখ᭄ࠡ䴶ⳈˈܜϸϾ⡍⅞ⱘ䖤ㅫヺӮᕅડ᳓ᤶ䖛⿟DŽ佪 㽕ᇍᅣ䇗⫼ⱘᅲ䰙খ᭄䆄ো䖯㸠Ẕᶹˈᑊ೼ᖙ㽕ᯊ䖯㸠ᠽሩDŽ ᤶᑣ߫Ёⱘᔶᓣখ᭄ⱘࠡ䴶᳝ϔϾ#ヺোˈ៪㗙݊ࠡ䴶៪ৢ䴶᳝ϔϾ##ヺোˈ৺߭ˈ೼ᦦܹࠡ োᑣ߫ᇚ᳓ᤶ᳾⫼ᓩোᓩ䍋ᴹⱘⳌᑨᔶᓣখ᭄ⱘᷛ䆚ヺ˄ԡѢᅣⱘ᳓ᤶ䆄োᑣ߫Ё˅DŽ䰸䴲᳓ Ⳃऍ䜡DŽᅲ䰙খ᭄㹿ߚ⾏ৢˈࠡᇐ੠ሒ䚼ⱘぎⱑヺᇚ㹿ߴ䰸DŽ䱣ৢˈ⬅৘ᅲ䰙খ᭄ѻ⫳ⱘ䆄 ⧚䖛⿟Ёˈᅲ䰙খ᭄ϡ䖯㸠ᅣᠽሩDŽᅣ䇗⫼ᯊˈᅲ䰙খ᭄ⱘ᭄Ⳃᖙ乏ϢᅮНЁᔶᓣখ᭄ⱘ᭄ ᰃ⫼䗫োߚ䱨ⱘ䆄োᑣ߫ˈ⫼ᓩো៪ጠ༫ⱘᣀোᣀ䍋ᴹⱘ䗫োϡ㛑⫼Ѣߚ䱨ᅲ䰙খ᭄DŽ೼໘ ৢ⫼ϔᇍ೚ᣀোᣀ䍋ᴹⱘǃ⬅䗫োߚ䱨ⱘ䆄োᑣ߫ህᵘ៤њϔϾᅣ䇗⫼DŽᅣ䇗⫼ⱘᅲ䰙খ᭄ ᣝ✻㄀Ѡ⾡ᔶᓣᅮНᅣᯊˈᅣᷛ䆚ヺ˄ৢ䴶ৃҹ䎳ϔϾぎⱑヺˈぎⱑヺᰃৃ䗝ⱘ˅ঞ݊ ⱘᷛ䆚ヺ˅ᑊϡӮᇐ㟈䫭䇃DŽ Ѣপ⍜ᷛ䆚ヺⱘ乘໘⧚఼ᅮНDŽᇚ#undef ᑨ⫼Ѣ᳾ⶹᷛ䆚ヺ˄े᳾⫼#define ᣛҸᅮН⫼ ˖ᇍϔϾ⿟ᑣⱘᶤѯ䚼ߚৃҹ䖯㸠ᴵӊ㓪䆥ˈᴵӊ㓪䆥ⱘ䇁⊩ᔶᓣབϟ A.12.5 ᴵӊ㓪䆥 #include ᭛ӊৃҹጠ༫DŽ 乏㹿㾷䞞Ў<...>៪"..."ϸ⾡ᔶᓣПϔˈ✊ৢݡᣝ✻Ϟ䗄ᮍᓣ䖯㸠Ⳍᑨⱘ໘⧚DŽ 䛑ϡৠˈᅗᇚᣝ✻ᠽሩ᱂䗮᭛ᴀⱘᮍᓣᠽሩ䆄োᑣ߫䖯㸠㾷䞞DŽ䆄োᑣ߫ᖙމৠϞ䗄ϸ⾡ᚙ #include 䆄োᑣ߫ ᳔ৢˈϟ߫ᔶᓣⱘᣛҸ㸠˖ ᰃ≵᳝ᅮНⱘˈԚৃҹՓ⫼ᄫヺ>DŽ ᅮⱘ᭛ӊˈ߭ᣝ✻㄀ϔ⾡ᅮНⱘᮍᓣ໘⧚DŽབᵰ᭛ӊৡЁࣙ৿ᄫヺ'ǃ\ǃ៪/*ˈ݊㒧ᵰҡ✊ Ң⑤᭛ӊⱘԡ㕂ᓔྟ᧰㋶ᣛᅮ᭛ӊ˄᧰㋶䖛⿟Ϣ݋ԧⱘᅲ⦄Ⳍ݇˅ˈབᵰ≵᳝ᡒࠄᣛܜ佪 #include "᭛ӊৡ" ㉏Ԑഄˈϟ߫ᔶᓣⱘ᥻ࠊᣛҸ˖ ᡒⱘԡ㕂Ϣ݋ԧⱘᅲ⦄Ⳍ݇DŽ ヺ"ǃ'ǃ\ǃ៪/*ˈ߭݊㸠Ў≵᳝ᅮНDŽ乘໘⧚఼ᇚ೼ᶤѯ⡍ᅮⱘԡ㕂ᶹᡒᣛᅮⱘ᭛ӊˈᶹ ᇚᡞ䆹㸠᳓ᤶЎ᭛ӊৡᣛᅮⱘ᭛ӊⱘݙᆍDŽ᭛ӊৡϡ㛑ࣙ৿>៪ᤶ㸠ヺDŽབᵰ᭛ӊৡЁࣙ৿ᄫ #include <᭛ӊৡ> ϟ߫ᔶᓣⱘ᥻ࠊᣛҸ˖ A.12.4 ᭛ӊࣙ৿ ㉏ԐഄˈABSDIFF(ABSDIFF(a,b),c)ᇚ⫳៤᠔ᳳᳯⱘ㒣ᅠܼᠽሩৢⱘ㒧ᵰDŽ ϡࣙ৿##䖤ㅫヺDŽ ៥Ӏህৃҹᕫࠄℷ⹂ⱘ㒧ᵰDŽxcat(xcat(1, 2), 3)ᇚ⫳៤ 123ˈ಴Ў xcat 㞾䑿ⱘᠽሩ #define xcat(x, y) cat(x,y) ো䖲᥹㗠៤DŽབᵰݡᓩܹ㄀ѠሖⱘᅣᅮНˈབϟ᠔⼎˖ ᑊϨˈ)3 (ϡᰃϔϾড়⊩ⱘ䆄োˈᅗ⬅㄀ϔϾখ᭄ⱘ᳔ৢϔϾ䆄োϢ㄀ѠϾখ᭄ⱘ㄀ϔϾ䆄 cat ( 1 , 2 )3 Н˖##䰏ℶњ໪ሖ䇗⫼ⱘখ᭄ⱘᠽሩDŽ಴ℸˈᅗᇚ⫳៤ϟ߫䆄োІ˖ 䙷Мˈᅣ䇗⫼ cat(var, 123)ᇚ⫳៤ var123DŽԚᰃˈᅣ䇗⫼ cat(cat(1,2),3)≵᳝ᅮ #define cat(x, y) x ## y 䱣ৢˈ䆹㒧ᵰᇚ㹿䖲᥹ЎϔϾऩϾⱘᄫヺІDŽ㒭ᅮϟ߫ᅮН˖ usr/tmp" "%s"/" ᑊϨ݊Ёϡࣙ৿ sizeofǃᔎࠊ㉏ൟ䕀ᤶ䖤ㅫヺ៪ᵮВᐌ䞣DŽ 䖯㸠Ϟ䗄໘⧚Пৢⱘᐌ䞣㸼䖒ᓣ˄খ㾕 A.7.19 㡖˅⒵䎇ϟ߫䰤ࠊᴵӊ˖ᅗᖙ乏ᰃᭈൟˈ ᮴ヺো䭓ᭈൟⱘ᪡԰᭄П䯈䖯㸠ⱘ䖤ㅫDŽ Ͼᭈൟᐌ䞣䛑㹿乘໘⧚఼䅸Ў݊ৢ䴶䎳᳝ৢ㓔 Lˈҹ֓ᡞ᠔᳝ⱘㅫᴃ䖤ㅫ䛑ᔧ԰ᰃ೼䭓ᭈൟ៪ ߭ˈ⫼ 0 ᳓ᤶDŽ乘໘⧚఼䖯㸠ᅣᠽሩПৢҡ✊ᄬ೼ⱘӏԩᷛ䆚ヺ䛑ᇚ⫼ 0 ᴹ᳓ᤶDŽ᳔ৢˈ↣ 䛑ᇚ೼ᠻ㸠ᅣᠿᦣПࠡ䖯㸠᳓ᤶˈབᵰ䆹ᷛ䆚ヺ೼乘໘⧚఼ЁᏆ㒣ᅮНˈ߭⫼ 1 ᳓ᤶᅗˈ৺ Defined(ᷛ䆚ヺ) ៪ defined ᷛ䆚ヺ #if ੠#elif Ёⱘᐌ䞣㸼䖒ᓣᇚᠻ㸠䗮ᐌⱘᅣ᳓ᤶDŽᑊϨˈӏԩϟ߫ᔶᓣⱘ㸼䖒ᓣ˖ ⱘߚᬃ˅᥻ࠊⱘ᭛ᴀ䛑ᇚ㹿ᗑ⬹DŽ؛ӊؐЎ ҸПৢⱘ᭛ᴀDŽ䰸њᇍᴵӊ㓪䆥ᣛҸⱘጠ༫䖯㸠ẔᶹП໪ˈᴵӊ㓪䆥ᣛҸⱘ᮴ᬜߚᬃ˄ेᴵ ᐌ䞣㸼䖒ᓣⱘؐ䛑Ў 0ˈᑊϨ䆹ᴵӊ㓪䆥ᣛҸ䫒Ёࣙ৿ϔᴵ#else ᣛҸDŽ߭ᇚ䗝ᢽ#else ᣛ ⱘ㓪䆥䰊↉Փ⫼ᯊˈৢ㓁ⱘ#elif ੠#else ᴵӊ㓪䆥ᣛҸঞⳌᑨⱘ᭛ᴀᇚ㹿ᬒᓗDŽབᵰ᠔᳝ থ⦄ᶤϾ#if ៪#elif ᴵӊ㓪䆥ᣛҸЁⱘᐌ䞣㸼䖒ᓣⱘؐϡЎ 0ˈᑊ䗝ᢽ݊ৢⱘ᭛ᴀկҹৢ ϡሲѢᴵӊ㓪䆥ᣛҸ㒧ᵘⱘ⿟ᑣҷⷕˈᅗৃҹࣙ৿乘໘⧚ᣛҸˈгৃҹЎぎDŽϔᮺ乘໘⧚఼ ੠#elif ᣛҸПৢⱘ᭛ᴀᇚᣝ✻݊ᅗ᱂䗮⿟ᑣҷⷕϔḋ䖯㸠㓪䆥DŽ೼䖭䞠ˈĀ᭛ᴀāᰃᣛӏԩ 䞣㸼䖒ᓣЎ䴲 0 ؐЎℶˈ䖭ᯊᇚᬒᓗؐЎ 0 ⱘᣛҸ㸠ৢ䴶ⱘ᭛ᴀDŽᐌ䞣㸼䖒ᓣϡЎ 0 ⱘ#if 乘໘⧚఼ձ⃵ᇍ#if ҹঞৢ㓁ⱘ#elif 㸠Ёⱘᐌ䞣㸼䖒ᓣ䖯㸠䅵ㅫˈⳈࠄথ⦄ᶤϾᣛҸⱘᐌ ݊Ёˈ↣Ͼᴵӊ㓪䆥ᣛҸ˄if 㸠ǃelif 㸠ǃelse 㸠ҹঞ#endif˅೼⿟ᑣЁഛऩ⣀ऴϔ㸠DŽ #else else 㸠˖ else 㸠 ᭛ᴀ else 䚼ߚ˖ #elif ᐌ䞣㸼䖒ᓣ elif 㸠˖ elif 㸠 ᭛ᴀ elif 䚼ߚ opt elif 䚼ߚ˖ #ifndef ᷛ䆚ヺ #ifdef ᷛ䆚ヺ #if ᐌ䞣㸼䖒ᓣ if 㸠˖ if 㸠᭛ᴀ elif 䚼ߚ opt else 䚼ߚ opt #endif 乘໘⧚఼ᴵӊ˖ # ˖ϟ߫ᔶᓣⱘ乘໘⧚఼㸠ϡᠻ㸠ӏԩ᪡԰ A.12.9 ぎᣛҸ ᇚՓ乘໘⧚఼ᠻ㸠ϔϾϢ݋ԧᅲ⦄Ⳍ݇ⱘ᪡԰DŽ᮴⊩䆚߿ⱘ pragma˄㓪䆥ᣛ⼎˅ᇚ㹿ᗑ⬹ᥝDŽ #pragma 䆄োᑣ߫ opt ϟ߫ᔶᓣⱘ᥻ࠊᣛҸ˖ A.12.8 pragma ᇚՓ乘໘⧚఼ᠧॄࣙ৿䆹䆄োᑣ߫ⱘ䆞ᮁֵᙃDŽ #error 䆄োᑣ߫ opt ϟ߫ᔶᓣⱘ乘໘⧚఼᥻ࠊᣛҸ˖ A.12.7 䫭䇃ֵᙃ⫳៤ 䖯㸠ᠽሩˈ✊ৢݡ䖯㸠㾷䞞DŽܜϡᬍবᔧࠡ㓪䆥ⱘ⑤᭛ӊⱘৡᄫDŽ㸠Ёⱘᅣᇚ ߎⱘˈᑊϨˈᔧࠡⱘ䕧ܹ᭛ӊᰃ⬅䆹ᷛ䆚ヺੑৡⱘDŽབᵰ㔎ᇥᏺঠᓩোⱘ᭛ӊৡ䚼ߚˈ߭ᇚ ᇚՓ㓪䆥఼䅸Ў˄ߎѢ䫭䇃䆞ᮁⱘⳂⱘ˅˖ϟϔ㸠⑤ҷⷕⱘ㸠োᰃҹक䖯ࠊᭈൟᐌ䞣ⱘᔶᓣ㒭 #line ᐌ䞣 #line ᐌ䞣 "᭛ӊৡ" Ўњ֓Ѣ݊ᅗ乘໘⧚఼⫳៤ C 䇁㿔⿟ᑣˈϟ߫ᔶᓣⱘᣛҸ㸠˖ A.12.6 㸠᥻ࠊ њDŽdefined 乘໘⧚఼䖤ㅫヺгᰃ ANSI Ёᮄᓩܹⱘ⡍ᕕDŽ 䇈ᯢ˖#elif ᰃ ANSI Ёᮄᓩܹⱘᴵӊ㓪䆥ᣛҸˈԚℸࠡᅗᏆ㒣೼ᶤѯ乘໘⧚఼Ёᅲ⦄ #if !defined ᷛ䆚ヺ #if defined ᷛ䆚ヺ ߚ߿ㄝӋѢ˖ #ifndef ᷛ䆚ヺ #ifdef ᷛ䆚㩟 ϟ߫᥻ࠊᣛҸ˖ ໄᯢ ໄᯢ㸼˖ ໄᯢ䇈ᯢヺ ߱ྟ࣪ໄᯢヺ㸼 opt; ໄᯢ˖ ໄᯢ䇈ᯢヺ opt ໄᯢヺໄᯢ㸼 opt ໡ড়䇁হ ߑ᭄ᅮН˖ ໄᯢ ߑ᭄ᅮН ໪䚼ໄᯢ˖ ໪䚼ໄᯢ ܗ㗏䆥ऩ ໪䚼ໄᯢ ܗ㗏䆥ऩ さDŽކ఼᥹ফˈԚ⬅Ѣ if•else ⱘ℻Нᗻ䯂乬ˈ䖬ᄬ೼ϔ໘ ᷛ䆚ヺāˈ䖭ḋህՓᕫ݊Ёⱘ㉏ൟᅮНৡ៤ЎϾ㒜㒧ヺDŽ䆹䇁⊩ৃ㹿 YACC 䇁⊩ߚᵤ⿟ᑣ⫳៤ Ͼᏺ᳝ opt ヺোˈϔϾ≵᳝ opt ヺোDŽ䖭䞠䖬᳝ϔϾব࣪ˈेߴ䰸њѻ⫳ᓣĀ㉏ൟᅮНৡ: “one of”㒧ᵘˈᑊ˄ḍ᥂䇁⊩ߚᵤ⿟ᑣ⫳៤఼ⱘ㾘߭˅໡ࠊ↣Ͼᏺ᳝ opt ヺোⱘѻ⫳ᓣ˖ϔ ᑣ⫳៤఼ৃҹ᥹ফⱘ䕧ܹDŽ䰸њ๲ࡴ䇁⊩䆄ো䇈ᯢѻ⫳ᓣЁⱘ׭䗝乍໪ˈ䖬䳔㽕ᠽሩ݊Ёⱘ ᐌ䞣DŽҹᠧᄫᄫԧᔶᓣ㸼⼎ⱘऩ䆡੠ヺোᰃ㒜㒧ヺDŽᴀ䇁⊩ৃҹⳈ᥹䕀ᤶЎ㞾ࡼ䇁⊩ߚᵤ⿟ ᴀ䇁⊩≵᳝ᅮНϟ߫㒜㒧ヺ˖ᭈൟᐌ䞣ǃᄫヺᐌ䞣ǃ⍂⚍ᐌ䞣ǃᷛ䆚ヺǃᄫヺІ੠ᵮВ ᑣ᳝ϔѯ䇗ᭈDŽ 䖭ϔ䚼ߚⱘݙᆍᇚㅔ㽕ὖ䗄ᴀ䰘ᔩࠡ䴶䚼ߚЁ䆆䗄ⱘ䇁⊩DŽᅗӀⱘݙᆍᅠܼⳌৠˈԚ乎 A.13 䇁⊩ ࠡᏆ㒣೼ᶤѯ㓪䆥఼Ёᅲ⦄DŽܜᰃᮄᓩܹⱘˈ݊Ёⱘϔѯᅣ Ёᮄᓩܹⱘ⡍ᕕDŽ䖭ѯ乘ᅮঝⱘ乘໘⧚఼ᅣгޚ䇈ᯢ˖#error Ϣ#pragma ᰃ ANSI ᷛ ⱘᅲ⦄Ё䆹ᷛ䆚ヺᠡ㹿ᅮНЎ 1DŽޚ__STDC__ ᭈൟᐌ䞣 1DŽা᳝೼䙉ᕾᷛ __TIME__ ࣙ৿㓪䆥ᯊ䯈ⱘᄫヺІᄫ䴶ؐˈ݊ᔶᓣЎ“hh:mm:ssāDŽ __DATE__ ࣙ৿㓪䆥᮹ᳳⱘᄫヺІᄫ䴶ؐˈ݊ᔶᓣЎ“Mmm dd yyyyāDŽ __FILE__ ࣙ৿ℷ೼㹿㓪䆥ⱘ⑤᭛ӊৡᄫⱘᄫヺІᄫ䴶ؐDŽ __LINE__ ࣙ৿ᔧࠡ⑤᭛ӊ㸠᭄ⱘक䖯ࠊᐌ䞣DŽ defined ϔḋˈϡ㛑প⍜ᅮН៪䞡ᮄ䖯㸠ᅮНDŽ ᶤѯᷛ䆚ヺᰃ乘ᅮНⱘˈᠽሩৢᇚ⫳៤⡍ᅮⱘֵᙃDŽᅗӀৠ乘໘⧚఼㸼䖒ᓣ䖤ㅫヺ A.12.10乘ᅮНৡᄫ ㉏ൟ䇈ᯢヺ 䇈ᯢヺ䰤ᅮヺ㸼 opt 䇈ᯢヺ䰤ᅮヺ㸼˖ 䇈ᯢヺ䰤ᅮヺ㸼 㒧ᵘໄᯢヺ㸼; 㒧ᵘໄᯢ˖ ໄᯢヺ=߱ྟ࣪ヺ ໄᯢヺ ߱ྟ࣪ໄᯢヺ˖ ߱ྟ࣪ໄᯢヺ㸼, ߱ྟ࣪ໄᯢヺ ߱ྟ࣪ໄᯢヺ ߱ྟ࣪ໄᯢヺ㸼 㒧ᵘໄᯢ㸼 㒧ᵘໄᯢ 㒧ᵘໄᯢ 㒧ᵘໄᯢ㸼˖ struct union 㒧ᵘ៪㘨ড়˖one of 㒧ᵘ៪㘨ড় ᷛ䆚ヺ 㒧ᵘ៪㘨ড় ᷛ䆚ヺ opt {㒧ᵘໄᯢ㸼} 㒧ᵘ៪㘨ড়䇈ᯢヺ˖ const volatile ㉏ൟ䰤ᅮヺ˖one of unsigned 㒧ᵘ៪㘨ড়䇈ᯢヺ ᵮВ䇈ᯢヺ ㉏ൟᅮНৡ void char short int long float double signed ㉏ൟ䇈ᯢヺ˖one of auto register static extern tyedef ㉏䇈ᯢヺ˖one ofټᄬ ㉏ൟ䰤ᅮヺ ໄᯢ䇈ᯢヺ opt ㉏ൟ䇈ᯢヺ ໄᯢ䇈ᯢヺ opt ㉏䇈ᯢヺ ໄᯢ䇈ᯢヺ optټᄬ ໄᯢ䇈ᯢヺ˖ ໄᯢ㸼 ໄᯢ ㉏ൟ䰤ᅮヺ 䇈ᯢヺ䰤ᅮヺ㸼 opt 㒧ᵘໄᯢヺ㸼˖ 㒧ᵘໄᯢヺ 㒧ᵘໄᯢヺ㸼, 㒧ᵘໄᯢヺ 㒧ᵘໄᯢヺ˖ ໄᯢヺ ໄᯢヺ opt: ᐌ䞣㸼䖒ᓣ ᬝВ䇈ᯢヺ enum ᷛ䆚ヺ opt {ᵮВヺ㸼} enum ᷛ䆚ヺ ᵮВヺ㸼˖ ᵮВヺ ᵮВヺ㸼, ᵮВヺ ᵮВヺ ᷛ䆚ヺ ᷛ䆚ヺ=ᐌ䞣㸼䖒ᓣ ໄᯢヺ ᣛ䩜 opt Ⳉ᥹ໄᯢヺ Ⳉ᥹ໄᯢヺ˖ ᷛ䆚ヺ (ໄᯢヺ) Ⳉ᥹ໄᯢヺ[ᐌ䞣㸼䖒ᓣ] Ⳉ᥹ໄᯢヺ(ᔶᓣখ᭄㉏ൟ㸼) Ⳉ᥹ໄᯢヺ(ᷛ䆚ヺ㸼 opt) ᣛ䩜˖ * ㉏ൟ䰤ᅮヺ㸼 opt * ㉏ൟ䰤ᅮヺ㸼 opt ᣛ䩜 ㉏ൟ䰤ᅮヺ㸼˖ ㉏ൟ䰤ᅮヺ ㉏ൟ䰤ᅮヺ㸼 ㉏ൟ䰤ᅮヺ ᷛ䆚ヺ ㉏ൟᅮНৡ˖ Ⳉ᥹ᢑ䈵ໄᯢヺ opt (ᔶᓣখ᭄㉏ൟ㸼 opt) Ⳉ᥹ᢑ䈵ໄᯢヺ opt [ᐌ䞣㸼䖒ᓣ] (ᢑ䈵ໄᯢヺ) Ⳉ᥹ᢑ䈵ໄᯢヺ˖ ᣛ䩜 opt Ⳉ᥹ᢑ䈵ໄᯢヺ ᣛ䩜 ᢑ䈵ໄᯢヺ˖ 䇈ᯢヺ䰤ᅮヺ㸼 ᢑ䈵ໄᯢヺ opt ㉏ൟৡ˖ ߱ؐ㸼, ߱ؐ ߱ؐ ߱ؐ㸼˖ {߱ؐ㸼, } {߱ؐ㸼} 䌟ؐ㸼䖒ᓣ ߱ؐ˖ ᷛ䆚ヺ㸼, ᷛ䆚ヺ ᷛ䆚ヺ ᷛ䆚ヺ㸼˖ ໄᯢ䇈ᯢヺ ᢑ䈵ໄᯢヺ opt ໄᯢ䇈ᯢヺ ໄᯢヺ ᔶᓣখ᭄ໄᯢ˖ ᔶᓣখ᭄㸼, ᔶᓣখ᭄ໄᯢ ᔶᓣখ᭄㸼ໄᯢ ᔶᓣখ᭄㸼˖ ᔶᓣখ᭄㸼, ... ᔶᓣখ᭄㸼 ᔶᓣখ᭄㉏ൟ㸼˖ 䇁হ˖ ᏺᷛো䇁হ 㸼䖒ᓣ䇁হ ໡ড়䇁হ 䗝ᢽ䇁হ ᕾ⦃䇁হ 䏇䕀䇁হ ᏺᷛো䇁হ˖ ᷛ䆚ヺ: 䇁হ case ᐌ䞣㸼䖒ᓣ䇁হ dafault: 䇁হ 㸼䖒ᓣ䇁হ˗ 㸼䖒ᓣ opt; ໡ড়䇁হ˖ {ໄᯢ㸼 opt 䇁হ㸼 opt} 䇁হ㸼˖ 䇁হ 䇁হ㸼 䇁হ 䗝ᢽ䇁হ˖ if (㸼䖒ᓣ) 䇁হ if (㸼䖒ᓣ) 䇁হ else 䇁হ switch (㸼䖒ᓣ) 䇁হ ᕾ⦃䇁হ while (㸼䖒ᓣ) 䇁হ do 䇁হ while (㸼䖒ᓣ); for (㸼䖒ᓣ opt; 㸼䖒ᓣ opt; 㸼䖒ᓣ opt) 䇁হ 䏇䕀䇁হ˖ goto ᷛ䆚ヺ; continue; break; ˖Ⳍㄝ㉏㸼䖒ᓣ ᣝԡϢ㸼䖒ᓣ&Ⳍㄝ㉏㸼䖒ᓣ Ⳍㄝ㉏㸼䖒ᓣ ᣝԡϢ㸼䖒ᓣ˖ ᥹ԡᓖ៪㸼䖒ᓣ^ᣝԡϢ㸼䖒ᓣ ᣝԡϢ㸼䖒ᓣ ᥹ԡᓖ៪㸼䖒ᓣ˖ ᣝԡ៪㸼䖒ᓣ|ᣝԡᓖ៪㸼䖒ᓣ ᣝԡᓖ៪㸼䖒ᓣ ᣝԡ៪㸼䖒ᓣ˖ 䘏䕥Ϣ㸼䖒ᓣ&&ᣝԡ៪㸼䖒ᓣ ᥹ԡ៪㸼䖒ᓣ 䘏䕥Ϣ㸼䖒ᓣ 䘏䕥៪㸼䖒ᓣ||䘏䕥Ϣ㸼䖒ᓣ 䘏䕥Ϣ㸼䖒ᓣ 䘏䕥៪㸼䖒ᓣ˖ ᴵӊ㸼䖒ᓣ ᐌ䞣㸼䖒ᓣ˖ ៪㸼䖒ᓣ?㸼䖒ᓣ:ᴵӊ㸼䖒ᓣ 䘏䕥៪㸼䖒ᓣ ᴵӊ㸼䖒ᓣ˖ = *= /= %= += •= <<= >>= &= ^= |= 䌟ؐ䖤ㅫヺ˖one of 㸼䖒ᓣ 䌟ؐ䖤ㅫヺ 䌟ؐ㸼䖒ᓣܗϔ ᴵӊ㸼䖒ᓣ 䌟ؐ㸼䖒ᓣ˖ 㸼䖒ᓣ, 䌟ؐ㸼䗝ᓣ 䌟ؐ㸼䖒ᓣ 㸼䖒ᓣ˖ return 㸼䖒ᓣ opt; ݇㋏㸼䖒ᓣ Ⳍㄝ㉏㸼䖒ᓣ==݇㋏㸼䖒ᓣ Ⳍㄝ㉏㸼䖒ᓣ!=݇㋏㸼䖒ᓣ ݇㋏㸼䖒ᓣ˖ ⿏ԡ㸼䖒ᓣ ݇㋏㸼䖒ᓣ<⿏ԡ㸼䖒ᓣ ݇㋏㸼䖒ᓣ>⿏ԡ㸼䖒ᓣ ݇㋏㸼䖒ᓣ<=⿏ԡ㸼䖒ᓣ ݇㋏㸼䖒ᓣ>=⿏ԡ㸼䖒ᓣ ⿏ԡ㸼䖒ᓣ ࡴ⊩㉏㸼䖒ᓣ ⿏ԡ㸼䖒ᓣ<<ࡴ⊩㉏㸼䖒ᓣ ⿏ԡ㸼䖒ᓣ>>ࡴ⊩㉏㸼䖒ᓣ ࡴ⊩㉏㸼䖒ᓣ˖ Ь⊩㉏㸼䖒ᓣ ࡴ⊩㉏㸼䖒ᓣ+Ь⊩㉏㸼䖒ᓣ ࡴ⊩㉏㸼䖒ᓣ•Ь⊩㉏㸼䖒ᓣ Ь⊩㉏㸼䖒ᓣ˖ ᔎࠊ㉏ൟ䕀ᤶ㸼䖒ᓣ ㉏㸼䖒ᓣ*ᔎࠊ㉏ൟ䕀ᤶ㸼䖒ᓣ Ь⊩㉏㸼䖒⚍/ᔎࠊ㉏ൟ䕀ᤶ㸼䖒ᓣ Ь⊩㉏㸼䖒ᓣ%ᔎࠊ㉏ൟ䕀ᤶ㸼䖒ᓣ ᔎࠊ㉏ൟ䕀ᤶ㸼䖒ᓣ˖ ϔܗ㸼䖒ᓣ (㉏ൟৡ)ᔎࠊ㉏ൟ䕀ᤶ㸼䖒ᓣ ϔܗ㸼䖒ᓣ˖ ৢ㓔ϔܗᓣ ++ϔܗ㸼䖒ᓣ ••ϔܗ㸼䖒ᓣ ϔܗ䖤ㅫヺᔎࠊ㉏ൟ䕀ᤶ㸼䖒ᓣ =define ᷛ䆚ヺ(ᷛ䆚ヺ㸼 opt) 䆄োᑣ߫ =define ᷛ䆚ヺ 䆄োᑣ߫ ᥻ࠊᣛҸ˖ োĀ᭛ᴀā˄े䗮ᐌⱘ⿟ᑣ᭛ᴀ˅ǃ䴲ᴵӊ乘໘⧚఼᥻ࠊᣛҸ៪ᅠᭈⱘ乘໘⧚఼ᴵӊ㒧ᵘDŽ ϟ߫乘໘⧚఼䇁⊩ᘏ㒧њ᥻ࠊᣛҸⱘ㒧ᵘˈԚϡ䗖ড়ѢᴎẄ࣪ⱘ䇁⊩ߚᵤDŽ݊Ёࣙ৿ヺ ᵮВᐌ䞣 ⍂⚍ᐌ䞣 ᄫヺᐌ䞣 ᭈൟᐌ䞣 ᐌ䞣˖ খ᭄㸼䖒ᓣ㸼, 䌟ؐ㸼䖒ᓣ 䌟ؐ㸼䖒ᓣ খ᭄㸼䖒ᓣ㸼˖ (㸼䖒ᓣ) ᄫヺІ ᐌ䞣 ᷛ䆚ヺ ߱ㄝ㸼䖒ᓣ˖ ৢ㓔㸼䖒ᓣ•• ৢ㓔㸼䖒ᓣ++ ৢ㓔㸼䖒ᓣ•>ᷛ䆚ヺ ৢ㓔㸼䖒ᓣ.ᷛ䆚ヺ ৢ㓔㸼䖒ᓣ(খ᭄㸼䖒ᓣ㸼 opt) ৢ㓔㸼䖒ᓣ[㸼䖒ᓣ] ߱ㄝ㸼䖒ᓣ ৢ㓔㸼䖒ᓣ˖ & * + • ~ ! 䖤ㅫヺ˖one ofܗϔ sizeof(㉏ൟৡ) 㸼䖒ᓣܗsizeof ϔ #undef ᷛ䆚ヺ #include <᭛ӊৡ> #include "᭛ӊৡ" #include 䆄োᑣ߫ #line ᐌ䞣 "᭛ӊৡ" #line ᐌ䞣 #error 䆄োᑣ߫DŽ #pragma 䆄োᑣ߫DŽ # 乘໘⧚఼ᴵӊᣛҸ 乘໘⧚఼ᴵӊᣛҸ˖ if 㸠 ᭛ᴀ elif 䚼ߚ opt else 䚼ߚ opt #endif if 㸠˖ #if ᐌ䞣㸼䖒ᓣ #ifdef ᷛ䆚ヺ #ifndef ᷛ䆚ヺ elif 䚼ߚ˖ elif 㸠 ᭛ᴀ elif 䚼ߚ opt elif 㸠˖ #elif ᐌ䞣㸼䖒ᓣ else 䚼ߚ˖ else 㸠 ᭛ᴀ else 㸠˖ #else ⿟ᑣᓔྟᠻ㸠ᯊˈstdinǃstdout ੠ stderr 䖭 3 Ͼ⌕Ꮖ㒣໘Ѣᠧᓔ⢊ᗕDŽ ϟˈ៥Ӏ೼ϟ᭛ЁᇚϡݡऎߚĀ᭛ӊᣛ䩜ā੠Ā⌕āDŽމ䍋℻Нⱘᚙ ᭛ӊᇚ䖨ಲϔϾᣛ৥ FILE ㉏ൟᇍ䈵ⱘᣛ䩜ˈ䆹ᣛ䩜䆄ᔩњ᥻ࠊ䆹⌕ⱘ᠔᳝ᖙ㽕ֵᙃˈ೼ϡᓩ ᠧᓔϔϾ⌕ˈᇚᡞ䆹⌕ϢϔϾ᭛ӊ៪䆒໛䖲᥹䍋ᴹˈ݇䯁⌕ᇚᮁᓔ䖭⾡䖲᥹ˈᠧᓔϔϾ ⱘݙᆍᅠܼⳌৠDŽ ᑊ݋᳝ϟ߫ᗻ䋼˖བᵰ೼ৠϔ㋏㒳ЁݭܹѠ䖯ࠊ⌕ˈ✊ৢݡ䇏প䆹Ѡ䖯ࠊ⌕ˈ߭䇏ߎ੠ݭܹ ᔶᓣ䕀ᤶЎ᭛ᴀ⌕DŽѠ䖯ࠊ⌕ᰃ⬅᳾㒣໘⧚ⱘᄫ㡖ᵘ៤ⱘᑣ߫ˈ䖭ѯᄫ㡖䆄ᔩⴔݙ䚼᭄᥂ˈ ৃ㛑䳔㽕ᇚ᭛ᴀ⌕䕀ᤶЎ݊ᅗ㸼⼎ᔶᓣ˄՟བᡞ'\n'᯴ᇘ៤ಲ䔺ヺ੠ᤶ㸠ヺ˅ˈ៪Ң݊ᅗ㸼⼎ ᭛ᴀ⌕ᰃ⬅᭛ᴀ㸠㒘៤ⱘᑣ߫ˈ↣ϔ㸠ࣙ৿ 0 Ͼ៪໮Ͼᄫヺˈᑊҹ'\n' 㒧ሒDŽ೼ᶤѯ⦃๗Ёˈ ᑧҡ✊ᦤկњ䖭ϸ⾡㉏ൟⱘ⌕DŽޚ೼㨫ৡⱘ UNIX ㋏㒳Ё˅ˈ᭛ᴀ⌕੠Ѡ䖯ࠊ⌕ᰃⳌৠⱘˈԚᷛ ⌕˄stream˅ᰃϢ⺕Ⲭ៪݊ᅗ໪ೈ䆒໛݇㘨ⱘ᭄᥂ⱘ⑤៪ⳂⱘഄDŽሑㅵ೼ᶤѯ㋏㒳Ё˄བ ߚПϔDŽ ᑧⱘϝޚ༈᭛ӊЁᅮНⱘ䕧ܹ੠䕧ߎߑ᭄ǃ㉏ൟҹঞᅣⱘ᭄Ⳃ޴ТऴᭈϾᷛ B.1 䕧ܹϢ䕧ߎ˖ ᑧՓ⫼DŽޚݭᄫ↡ᓔ༈ⱘᷛ䆚ヺҹঞҹϸϾϟߦ㒓ᓔ༈ⱘᷛ䆚ヺг䛑ֱ⬭㒭ᷛ ᑧՓ⫼ˈৠᯊˈ݊ᅗ᠔᳝ҹϔϾϟߦ㒓੠ϔϾ໻ޚҹϟߦ㒓ᓔ༈ⱘ໪䚼ᷛ䆚ヺֱ⬭㒭ᷛ ᭛ӊDŽ ᅮНП໪ˈᑊϨˈᖙ乏೼Փ⫼༈᭛ӊЁⱘӏԩໄᯢПࠡࣙ৿༈᭛ӊDŽ༈᭛ӊϡϔᅮᰃϔϾ⑤ ༈᭛ӊⱘࣙ৿乎ᑣᰃӏᛣⱘˈᑊৃࣙ৿ӏᛣ໮⃵DŽ༈᭛ӊᖙ乏㹿ࣙ৿೼ӏԩ໪䚼ໄᯢ៪ #include <༈᭛ӊ> ৃҹ䗮䖛ϟ߫ᮍᓣ䆓䯂༈᭛ӊ˖ ༈᭛ӊЁᅮН˖ޚᑧЁⱘߑ᭄ˈ㉏ൟҹঞᅣߚ߿೼ϟ䴶ⱘᷛޚᷛ ݇ⱘሲᗻDŽ ໛䅼䆎ϢऎඳⳌ݇ⱘϔѯሲᗻˈгህ᯳Ϣᴀഄ䇁㿔ǃ೑㈡៪᭛࣪Ⳍޚヺⱘݙᆍˈৠᯊˈгϡ њϔѯՓ⫼↨䕗ফ䰤ⱘߑ᭄ҹঞϔѯৃҹ䗮䖛݊ᅗߑ᭄ㅔऩড়៤ⱘߑ᭄ˈгⳕ⬹њ໮ᄫ㡖ᄫ C ⱘᅲ⦄Ӯᦤկ䆹ߑ᭄ᑧЁⱘߑ᭄ໄᯢǃ㉏ൟҹঞᅣᅮНDŽ೼䖭䚼ߚݙᆍЁˈ៥Ӏⳕ⬹ ޚᷛ ᑧϡᰃ C 䇁㿔ᴀ䑿ⱘᵘ៤䚼ߚˈԚᰃᬃᣕޚᅮНⱘߑ᭄ᑧDŽᷛޚᴀ䰘ᔩᘏ㒧њ ANSI ᷛ ᑧޚ䰘ᔩBᷛ ([char *tmpnam(char s[L_tmpnam 㞾ࡼߴ䰸DŽབᵰ߯ᓎ᪡԰៤ࡳˈ䆹ߑ᭄䖨ಲϔϾ⌕˗བᵰ߯ᓎ᭛ӊ༅䋹ˈ߭䖨ಲ NULLDŽ tmpfile ߑ᭄ҹ῵ᓣ"wb+"߯ᓎϔϾЈᯊ᭛ӊˈ䆹᭛ӊ೼㹿݇䯁៪⿟ᑣℷᐌ㒧ᴳᯊᇚ㹿 FILE *tmpfile(void) rename ߑ᭄ׂᬍ᭛ӊⱘৡᄫDŽབᵰ᪡԰༅䋹ˈ߭䖨ಲϔϾ䴲 0 ؐDŽ int rename(const char *oldname, const char *newname) བᵰߴ䰸᪡԰༅䋹ˈ߭䖨ಲϔϾ䴲 0 ؐDŽ remove ߑ᭄ߴ䰸 filename ᣛᅮⱘ᭛ӊˈ䖭ḋˈৢ㓁䆩೒ᠧᓔ䆹᭛ӊⱘ᪡԰ᇚ༅䋹DŽ int remove(const char *filename) ऎˈ᳔ৢ݇䯁⌕DŽ㢹ߎ䫭߭䖨ಲ EOFˈ৺߭䖨ಲ 0DŽކᑊ䞞ᬒ㞾ࡼߚ䜡ⱘܼ䚼㓧 ऎЁⱘ᠔᳝᳾䇏䕧᭄ܹ᥂ˈކfclose ߑ᭄ᇚ᠔᳝᳾ݭܹⱘ᭄᥂ݭܹ stream Ёˈ϶ᓗ㓧 int fclose(FILE *stream) fflush(NULL)ᇚ⏙⋫᠔᳝ⱘ䕧ߎ⌕DŽ 䕧ܹ⌕ᴹ䇈ˈ݊㒧ᵰᰃ᳾ᅮНⱘDŽབᵰ೼ݭⱘ䖛⿟Ёথ⫳䫭䇃ˈ߭䖨ಲ EOFˈ৺߭䖨ಲ 0DŽ ऎԚᇮ᳾ݭܹ᭛ӊⱘ᠔᭄᳝᥂ݭࠄ᭛ӊЁDŽᇍކᇍ䕧ߎ⌕ᴹ䇈ˈfflush ߑ᭄ᇚᏆݭࠄ㓧 int fflush(FILE *stream) stdinǃstdout ੠ stderr Ⳍ݇㘨ⱘ᭛ӊDŽ stream ᣛᅮⱘ⌕DŽᅗ䖨ಲ stream˗㢹ߎ䫭߭䖨ಲ NULLDŽFreopen ߑ᭄ϔ㠀⫼ѢᬍবϢ freopen ߑ᭄ҹ mode ᣛᅮⱘ῵ᓣᠧᓔ filename ᣛᅮⱘ᭛ӊˈᑊᇚ䆹᭛ӊ݇㘨ࠄ FILE *freopen(const char *filename, const char *mode, FILE *stream) ⃵᳔໮ৃᠧᓔ FOPEN_MAX Ͼ᭛ӊDŽ ߭㸼⼎ᇍѠ䖯ࠊ᭛ӊ䖯㸠᪡԰DŽ᭛ӊৡ filename 䰤ᅮ᳔໮Ў FILENAME_MAX ϾᄫヺDŽϔ ⫼ fflush ߑ᭄៪᭛ӊᅮԡߑ᭄DŽབᵰ೼Ϟ䗄䆓䯂῵ᓣПৢݡࡴϞ bˈབ“ rbā៪“ w+bāㄝˈ 䆌ᇍৠϔ᭛ӊ䖯㸠䇏੠ݭDŽ೼䇏੠ݭⱘѸঝ䖛⿟Ёˈᖙ乏䇗ܕ˅ৢ 3 ⾡ᮍᓣ˄᳈ᮄᮍᓣ "a+" 䗑ࡴ˗ᠧᓔ៪߯ᓎ᭛ᴀ᭛ӊ⫼Ѣ᳈ᮄˈݭ᭛ӊᯊ䗑ࡴࠄ᭛ӊ᳿ሒ "w+" ߯ᓎ᭛ᴀ᭛ӊ⫼Ѣ᳈ᮄˈᑊߴ䰸Ꮖᄬ೼ⱘݙᆍ˄བᵰ᳝ⱘ䆱˅ "r+" ᠧᓔ᭛ᴀ᭛ӊ⫼Ѣ᳈ᮄ˄े䇏੠ݭ˅ "a" 䗑ࡴ˗ᠧᓔ៪߯ᓎ᭛ᴀ᭛ӊˈᑊ৥᭛ӊ᳿ሒ䗑ࡴݙᆍ "w" ߯ᓎ᭛ᴀ᭛ӊ⫼Ѣݭˈᑊߴ䰸Ꮖᄬ೼ⱘݙᆍ˄བᵰ᳝ⱘ䆱˅ "r" ᠧᓔ᭛ᴀ᭛ӊ⫼Ѣ䇏 䆓䯂῵ᓣ mode ৃҹЎϟ߫ড়⊩ؐПϔ˖ 䋹ˈ߭䖨ಲ NULLDŽ fopen ߑ᭄ᠧᓔ filename ᣛᅮⱘ᭛ӊˈᑊ䖨ಲϔϾϢПⳌ݇㘨ⱘ⌕DŽབᵰᠧᓔ᪡԰༅ FILE *fopen(const char *filename, const char *mode) ᮴ヺোᭈൟDŽ ϟ߫ߑ᭄⫼Ѣ໘⧚Ϣ᭛ӊ᳝݇ⱘ᪡԰DŽ݊Ёˈ㉏ൟ size_t ᰃ⬅䖤ㅫヺ sizeof ⫳៤ⱘ B.1.1 ᭛ӊ᪡԰ · 䭓ᑺׂ佄ヺ hǃl ៪ LDŽh 㸼⼎ᇚⳌᑨⱘখ᭄ᣝ short ៪ unsigned short ㉏ൟ䕧 ԡ 0 ҹ䖒ࠄ㽕∖ⱘᆑᑺ˅DŽܙ฿ᇍѢᭈൟ᭄ˈᅗᣛᅮᠧॄⱘ᭄ᄫԡ᭄˄ᖙ㽕ᯊৃࡴ ᅗᣛᅮᠧॄⱘᇣ᭄⚍ৢⱘ᭄ᄫԡ᭄˗ᇍѢ g ៪ G 䕀ᤶˈᅗᣛᅮᠧॄⱘ᳝ᬜ᭄ᄫԡ᭄˗ · 㸼⼎㊒ᑺⱘ᭄DŽᇍѢᄫヺІˈᅗᣛᅮᠧॄⱘᄫヺⱘ᳔໻Ͼ᭄˗ᇍѢ eǃE ៪ f 䕀ᤶˈ · ⚍োˈ⫼Ѣߚ䱨ᄫ↉ᆑᑺ੠㊒ᑺDŽ ᄫヺЎ 0DŽܙ฿ᷛᖫˈ߭ܙ฿ ᄫヺ䗮ᐌЎぎḐˈԚᰃˈབᵰ䆒㕂њ 0ܙ฿ϔѯᄫヺDŽܙ ฿˅བᵰখ᭄ⱘᄫヺ᭄ᇣѢℸ᭄ؐˈ߭೼খ᭄ᆑ䖍˄བᵰ㽕∖Ꮊᇍ唤ⱘ䆱߭Ўে䖍 · ϔϾ᭄ؐˈ⫼Ѣᣛᅮ᳔ᇣᄫ↉ᆑᑺDŽ䕀ᤶৢⱘখ᭄䕧ߎᆑᑺ㟇ᇥ㽕䖒ࠄ䖭Ͼ᭄ؐDŽ ᘏࣙᣀϔϾᇣ᭄⚍˗ᇍѢ g ៪ G 䕀ᤶˈᣛᅮ䕧ߎؐሒ䚼᮴ᛣНⱘ 0 ᇚ㹿ֱ⬭ ߭ᣛᅮ೼䕧ߎⱘ䴲 0 ؐࠡࡴ 0x ៪ 0X˗ᇍѢ eǃEǃfǃg ៪ G 䕀ᤶˈᣛᅮ䕧ߎ ᣛᅮ঺ϔ⾡䕧ߎᔶᓣDŽབᵰЎ o 䕀ᤶˈ߭㄀ϔϾ᭄ᄫЎ䳊˗བᵰЎ x ៪ X 䕀ᤶˈ # ܙ฿0 ᇍѢ᭄ؐ䕀ᤶˈᔧ䕧ߎ䭓ᑺᇣѢᄫ↉ᆑᑺᯊˈ⏏ࡴࠡᇐ 0 䖯㸠 ぎḐ བᵰ㄀ϔϾᄫヺϡᰃℷ䋳োˈ߭೼݊ࠡ䴶ࡴϞϔϾぎḐ + ᣛᅮ೼䕧ߎⱘ᭄ࠡ䴶ࡴϞℷ䋳ো • ᣛᅮ㹿䕀ᤶⱘখ᭄೼݊ᄫ↉ݙᎺᇍ唤 · ᷛᖫ˄ৃҹҹӏᛣ乎ᑣߎ⦄˅ˈ⫼Ѣׂᬍ䕀ᤶ䇈ᯢ ᄫヺП䯈ৃҹձ⃵ࣙᣀϟ߫ݙᆍ˖ ᅮϟϔৢ㓁খ᭄ⱘ䕀ᤶ੠ᠧॄ˅DŽ↣Ͼ䕀ᤶ䇈ᯢഛҹᄫヺ%ᓔ༈ˈҹ䕀ᤶᄫヺ㒧ᴳDŽ೼%Ϣ䕀ᤶ އḐᓣІ⬅ϸ⾡㉏ൟⱘᇍ䈵㒘៤˖᱂䗮ᄫヺ˄ᇚ㹿໡ࠊࠄ䕧ߎ⌕Ё˅Ϣ䕀ᤶ䇈ᯢ˄ߚ߿ ᰃᅲ䰙ݭܹⱘᄫヺ᭄DŽ㢹ߎ䫭߭䖨ಲϔϾ䋳ؐDŽ fprintf ߑ᭄ᣝ✻ format 䇈ᯢⱘḐᓣᇍ䕧ߎ䖯㸠䕀ᤶˈᑊݭࠄ stream ⌕ЁDŽ䖨ಲؐ int fprintf(FILE *stream, const char *format, ...) printf ߑ᭄ᦤկḐᓣ࣪䕧ߎ䕀ᤶDŽ B.1.2 Ḑᓣ࣪䕧ߎ (void)setvbuf(stream, buf, _IOFBF, BUFSIZ)DŽ ৺߭ setbuf ߑ᭄ㄝӋѢ˗ކབᵰ buf ⱘؐЎ NULLˈ߭݇䯁⌕ stream ⱘ㓧 void setbuf(FILE *stream, char *buf) ऎⱘ䭓ᑺDŽབᵰ setvbuf ߑ᭄ߎ䫭ˈ߭䖨ಲϾϔ䴲 0 ؐDŽކ ᅮ㓧އ ऎDŽsizeކऎˈ৺߭ᇚߚ䜡ϔϾ㓧ކ߭ setvbuf ߑ᭄ᇚ buf ᣛ৥ⱘऎඳ԰Ў⌕ⱘ㓧 DŽབᵰ buf ⱘؐϡᰃ NULLˈކᔧ mode ⱘؐЎ_IONBF ᯊˈ㸼⼎ϡ䆒㕂㓧ˈކ᭛ӊ䖯㸠㸠㓧 DŽᔧ mode ⱘؐЎ_IOLBF ᯊˈᇚᇍ᭛ᴀކߑ᭄DŽᔧ mode ⱘؐЎ_IOFBF ᯊˈᇚ䖯㸠ᅠܼ㓧 DŽ೼ᠻ㸠䇏ǃݭҹঞ݊ᅗӏԩ᪡԰Пࠡᖙ乏䇗⫼ℸކsetvbuf ߑ᭄᥻ࠊ⌕ stream ⱘ㓧 int setvbuf(FILE *stream, char *buf, int mode, size_t size) Ѣ߯ᓎϔϾৡᄫˈ㗠ϡᰃ߯ᓎϔϾ᭛ӊDŽ ⿟ᑣᠻ㸠ⱘ䖛⿟Ёˈ᳔໮া㛑⹂ֱ⫳៤ TMP_MAX ϾϡৠⱘৡᄫDŽ⊼ᛣˈtmpnam ߑ᭄াᰃ⫼ Ё㟇ᇥ㽕᳝ L_tmpnam Ͼᄫヺⱘぎ䯈DŽTmpnam ߑ᭄೼↣⃵㹿䇗⫼ᯊഛ⫳៤ϡৠⱘৡᄫDŽ೼ ᭄㒘ⱘᣛ䩜DŽtmpnam(S)ߑ᭄ᡞ߯ᓎⱘᄫヺІֱᄬࠄ᭄㒘 s Ёˈᑊᇚᅗ԰Ўߑ᭄ؐ䖨ಲDŽs tmpnam(NULL)ߑ᭄߯ᓎϔϾϢ⦄᳝᭛ӊৡϡৠⱘᄫヺІˈᑊ䖨ಲϔϾᣛ৥ϔݙ䚼䴭ᗕ 䆺㒚ֵᙃখ㾕 B.7 㡖Ёᇍ༈᭛ӊⱘ䅼䆎DŽ Ӏ⫼ arg ҷ᳓њৃবখ᭄㸼DŽarg ⬅ᅣ va_start ߱ྟ࣪ˈгৃ㛑⬅ va_arg 䇗⫼߱ྟ࣪DŽ vprintfǃvfprintfǃvsprintf 䖭 3 Ͼߑ᭄ߚ߿Ϣᇍᑨⱘ printf ߑ᭄ㄝӋˈԚᅗ int vsprintf(char *s, const char *format, va_list arg) int vfprintf(FILE *stream, const char *format, va_list arg) int vprintf(const char *format, va_list arg) 㒧ᴳDŽs ᖙ乏䎇໳໻ˈҹ䎇໳ᆍ㒇ϟ䕧ߎ㒧ᵰDŽ䆹ߑ᭄䖨ಲᅲ䰙䕧ߎⱘᄫヺ᭄ˈϡࣙᣀ'\0'DŽ sprintf ߑ᭄Ϣ printf ߑ᭄෎ᴀⳌৠˈԚ݊䕧ߎᇚ㹿ݭܹࠄᄫヺІ s Ёˈᑊҹ'\0' int sprintf(char *s, const char *format, ...) printf(...)ߑ᭄ㄝӋѢ fprintf(stdout, …)DŽ int printf(const char *format, ...) % ϡ䖯㸠খ᭄䕀ᤶ˗ᠧॄϔϾヺো% n int *˗ࠄⳂࠡЎℶˈℸ printf 䇗⫼䕧ߎⱘᄫヺⱘ᭄Ⳃᇚ㹿ݭܹࠄⳌᑨখ᭄ЁDŽϡ䖯㸠খ᭄䕀ᤶ p void *˗ᠧॄᣛ䩜ؐ˄݋ԧ㸼⼎ᮍᓣϢᅲ⦄᳝݇˅ ϡᠧॄ g, G double˗ᔧᣛ᭄ᇣѢ•4 ៪໻ѢㄝѢ㊒ᑺᯊˈ䞛⫼%e ៪%E ⱘḐᓣˈ৺߭䞛⫼%f ⱘḐᓣDŽሒ䚼ⱘ 0 ੠ᇣ᭄⚍ 䕧ߎᇣ᭄⚍ e, E double˗ᔶᓣЎ[•]m.dddddd e ±xx ៪[•]m.dddddd E ±xxDŽd ⱘ᭄Ⳃ⬅㊒ᑺ⹂ᅮˈ咬䅸㊒ᑺЎ 6DŽ㊒ᑺЎ 0 ᯊϡ ߎᇣ᭄⚍ f double˗ᔶᓣЎ[•]mmm.ddd ⱘक䖯ࠊ㸼⼎ˈ݊Ёˈd ⱘ᭄Ⳃ⬅㊒ᑺ⹂⹂ᅮˈ咬䅸㊒ᑺЎ 6DŽ㊒ᑺЎ 0 ᯊϡ䕧 s char *˗ᠧॄᄫヺІЁⱘᄫヺˈⳈࠄ䘛ࠄ'\0'៪Ꮖᠧॄњ⬅㊒ᑺᣛᅮⱘᄫヺ᭄ c int˗䕀ᤶЎ unsigned char ㉏ൟৢЎϔϾᄫヺ u int˗᮴ヺোक䖯ࠊ㸼⼎ ⫼ ABCDEF x, X unsigned int ˗᮴ヺোक݁䖯ࠊ㸼⼎˄≵᳝ࠡᇐ 0x ៪ 0X˅ˈབᵰᰃ 0xˈ߭Փ⫼ abcdefˈབᵰᰃ 0Xˈ߭Փ o unsigned int˗᮴ヺোܿ䖯ࠊ㸼⼎ d, i int˗᳝ヺোक䖯ࠊ㸼⼎ 䕀ᤶᄫヺ খ᭄㉏ൟ˗䕀ᤶ㒧ᵰ 㸼 B•1 printf ߑ᭄ⱘ䕀ᤶᄫヺ ᅮНDŽ 㸼 B•1 Ё߫ߎњ䖭ѯ䕀ᤶᄫヺঞ݊ᛣНDŽབᵰ%ৢ䴶ⱘᄫヺϡᰃ䕀ᤶᄫヺˈ߭݊㸠Ў≵᳝ ᭄䅵ㅫᕫࠄ˄ϟϔϾখ᭄ᖙ乏Ў int ㉏ൟ˅DŽ ϟˈ䆹ؐᇚ䗮䖛䕀ᤶϟϔϾখމᆑᑺ੠㊒ᑺЁⱘӏԩϔϾ៪ϸ㗙䛑ৃҹ⫼*ᣛᅮˈ䖭⾡ᚙ ᭄ᣝ long double ㉏ൟ䕧ߎDŽ ߎDŽl 㸼⼎ᇚⳌᑨⱘখ᭄ᣝ long ៪ unsigned long ㉏ൟ䕧ߎ˗L 㸼⼎ᇚⳌᑨⱘখ ˈe, f, g ⍂⚍᭄ˈfloat *DŽFloat ㉏ൟ⍂⚍᭄ⱘ䕧ܹḐᓣЎ˖ϔϾৃ䗝ⱘℷ䋳োǃϔϾৃ㛑ࣙ৿ᇣ᭄⚍ⱘ᭄ᄫІ ֱᄬ䆹ᄫヺІҹঞ೼ሒ䚼⏏ࡴⱘ'\0'ᄫヺ s ⬅䴲ぎⱑヺ㒘៤ⱘᄫヺІ˄ϡࣙ৿ᓩো˅˗char *ˈᅗᣛ৥ϔϾᄫヺ᭄㒘ˈ䆹ᄫヺ᭄㒘ᖙ乏᳝䎇໳ぎ䯈ˈҹ ϟˈ䇏প䕧ܹᯊᇚϡ䏇䖛ぎⱑヺDŽབᵰ䳔㽕䇏ܹϟϔϾ䴲ぎⱑヺˈৃҹՓ⫼%1sމ೼䖭⾡ᚙ c ᄫヺ˗char * ˈᣝ✻ᄫ↉ᆑᑺⱘ໻ᇣᡞ䇏পⱘᄫヺֱᄬࠄࠊᅮⱘ᭄㒘Ёˈϡ๲ࡴ'\0'ᄫ↉ᆑᑺⱘ咬䅸ؐЎ 1DŽ x क݁䖯ࠊᭈൟ᭄˄ৃҹᏺ៪ϡᏺࠡᇐ 0x ៪ 0X˅˗ int * u ᮴ヺোक䖯ࠊᭈൟ᭄˗unsigned int * o ܿ䖯ࠊᭈൟ᭄˄ৃҹᏺ៪ϡᏺࠡᇐ 0˅˗ int * i ᭈൟ᭄˗int *DŽ䆹ᭈൟ᭄ৃҹᰃܿ䖯ࠊ˄ҹ 0 ᓔ༈˅៪क݁䖯ࠊ˄ҹ 0x ៪ 0X ᓔ༈˅ d क䖯ࠊᭈ᭄˗int * 䕀ᤶᄫヺ 䕧᭄ܹ᥂˗খ᭄㉏ൟ 㸼 B•2 scanf ߑ᭄ⱘ䕀ᤶᅛヺ ࡴϞᄫ↡ LDŽ ҹࡴϞᄫ↡ lDŽབᵰখ᭄ᰃᣛ৥ long double ㉏ൟⱘᣛ䩜ˈ߭೼䕀ᤶᄫヺ eǃf ੠ g ࠡৃҹ ↡ lDŽབᵰখ᭄ᰃᣛ৥ double ㉏ൟ㗠䴲 float ㉏ൟⱘᣛ䩜ˈ߭೼䕀ᤶᄫヺ eǃf ੠ g ࠡৃ ПࠡৃҹࡴϞࠡ㓔 hDŽབᵰখ᭄ᰃᣛ৥ long ㉏ൟⱘᣛ䩜ˈ߭೼䖭޴Ͼ䕀ᤶᄫヺࠡৃҹࡴϞᄫ བᵰখ᭄ᰃᣛ৥ short ㉏ൟ㗠䴲 int ㉏ൟⱘᣛ䩜ˈ߭೼䕀ᤶᄫヺ dǃiǃnǃoǃu ੠ x B•2 ᠔⼎DŽ 䕀ᤶᄫヺ䇈ᯢњᇍ䕧ܹᄫ↉ⱘ㾷䞞ᮍᓣDŽᇍᑨⱘখ᭄ᖙ乏ᰃᣛ䩜DŽড়⊩ⱘ䕀ᤶᄫヺབ㸼 ᤶ㸠ヺǃಲ䔺ヺ੠ᤶ义ヺ˅DŽ 䍞㸠ⱘ䖍⬠䇏প䕧ܹˈ಴Ўᤶ㸠ヺгᰃぎⱑヺ˄ぎⱑヺࣙᣀぎḐǃ῾৥ࠊ㸼ヺǃ㒉৥ࠊ㸼ヺǃ ໻ᄫ↉ᆑᑺ˄བᵰ᳝ⱘ䆱˅ᯊˈᇍᔧࠡ䕧ܹᄫ↉ⱘ䇏প㒧ᴳDŽ䖭ᛣੇⴔˈscanf ߑ᭄ৃҹ䎼 ϡ䖯㸠䌟ؐDŽ䕧ܹᄫ↉ᯊϔϾ⬅䴲ぎⱑヺᄫヺ㒘៤ⱘᄫヺІˈᔧ䘛ࠄϟϔϾぎⱑヺ៪䖒ࠄ᳔ 䞣ЁDŽԚᰃˈབᵰ䕀ᤶ䇈ᯢЁࣙ৿䌟ؐሣ㬑ᄫヺ*ˈ՟བ%*sˈ߭ᇚ䏇䖛ᇍᑨⱘ䕧ܹᄫ↉ˈᑊ ᅮњϟϔϾ䕧ܹᄫ↉ⱘ䕀ᤶᮍᓣDŽ䗮ᐌ㒧ᵰᇚ㹿ֱᄬ೼⬅ᇍᑨখ᭄ᣛ৥ⱘবއ䕀ᤶ䇈ᯢ 䗝˅ǃϔϾᣛᅮⳂᷛᄫ↉ᆑᑺⱘᄫヺ˄hǃl ៪ L˅˄ৃ䗝˅ҹঞϔϾ䕀ᤶᄫヺ㒘៤DŽ · 䕀ᤶ䇈ᯢˈ⬅ϔϾ%ǃϔϾ䌟ؐሣ㬑ᄫヺ*˄ৃ䗝˅ǃϔϾᣛᅮ᳔໻ᄫ↉ᆑᑺⱘ᭄˄ৃ · ᱂䗮ᄫヺ˄%䰸໪˅ˈᅗᇚϢ䕧ܹ⌕ЁϟϔϾ䴲ぎⱑᄫヺ䖯㸠ऍ䜡 · ぎḐ៪ࠊ㸼ヺ ৿ϟ߫乍Ⳃ˖ ḐᓣІ format 䗮ᐌࣙᣀ䕀ᤶ䇈ᯢˈᅗ⫼Ѣᣛᇐᇍ䕧ܹ䖯㸠㾷䞞DŽḐᓣᄫヺІЁৃҹࣙ 䕧ܹ乍ⱘ᭄ⳂDŽ ᵰࠄ䖒᭛ӊⱘ᳿ሒ៪೼䕀ᤶ䕧ܹࠡߎ䫭ˈ䆹ߑ᭄䖨ಲ EOF˗৺߭ˈ䖨ಲᅲ䰙㹿䕀ᤶᑊ䌟ؐⱘ 㓁৘Ͼখ᭄ˈ݊Ёⱘ↣Ͼখ᭄䛑ᖙ乏ᰃϔϾᣛ䩜DŽᔧḐᓣІ format ⫼ᅠᯊˈߑ᭄䖨ಲDŽབ fscanf ߑ᭄ḍ᥂ḐᓣІ format Ң⌕ stream Ё䇏প䕧ܹˈᑊᡞ䕀ᤶৢⱘؐ䌟ؐ㒭ৢ int fscanf(FILE *stream, const char *format, ...) scanf ߑ᭄໘⧚Ḑᓣ࣪䕧ܹ䕀ᤶDŽ B.1.3 Ḑᓣ࣪䕧ܹ getchar ߑ᭄ㄝӋѢ getc(stdin)DŽ int getchar(void) stream ⱘؐDŽ getc ߑ᭄ㄝӋѢ fgetcˈ᠔ϡৠⱘᰃˈᔧ getc ߑ᭄ᅮНЎᅣᯊˈᅗৃ㛑໮⃵䅵ㅫ int getc(FILE *stream) 㢹ߎ䫭߭䖨ಲ EOFDŽ fputs ߑ᭄ᡞᄫヺІ s˄ϡࣙ৿ᄫヺ'\n'˅䕧ߎࠄ⌕ Btream Ё˗ᅗ䖨ಲϔϾ䴲䋳ؐˈ int fputs(const char *s, FILE *stream) ܹⱘᄫヺˈ㢹ߎ䫭߭䖨ಲ EOFDŽ fputc ߑ᭄ᡞᄫヺ c˄䕀ᤶЎ unsigned char ㉏ൟ˅䕧ߎࠄ⌕ stream ЁDŽᅗ䖨ಲݭ int fputc(int c, FILE *stream) ៪থ⫳䫭䇃ˈ߭䖨ಲ NULLDŽ 㒘 s Ёˈ䇏প䖛⿟㒜ℶDŽ᭄㒘 s ҹ'\0'㒧ሒDŽfgets ߑ᭄䖨ಲ᭄㒘 sDŽབᵰࠄ䖒᭛ӊⱘ᳿ሒ fgets ߑ᳔᭄໮ᇚϟ n•1 Ͼᄫヺ䇏ܹࠄ᭄㒘 s ЁDŽᔧ䘛ࠄᤶ㸠ヺᯊˈᡞᤶ㸠ヺ䇏ܹࠄ᭄ char *fgets(char *s, int n, FILE *stream) ㉏ൟ˅DŽབᵰࠄ䖒᭛ӊ᳿ሒ៪থ⫳䫭䇃ˈ߭䖨ಲ EOFDŽ fqetc ߑ᭄䖨ಲ stream ⌕ⱘϟϔϾᄫヺˈ䖨ಲ㉏ൟЎ unsigned char˄㹿䕀ᤶЎ int int fgetc(FILE *stream) B.1.4 ᄫヺ䕧ܹˋ䕧ߎߑ᭄ І sDŽ sscanf(s, ...)ߑ᭄Ϣ scanf(...)ㄝӋˈ᠔ϡৠⱘᰃˈࠡ㗙ⱘ䕧ܹᄫヺᴹ⑤Ѣᄫヺ int sscanf(const char *s, const char *format, ...) scanf(...)ߑ᭄Ϣ fscanf(stdin, ...)ⳌৠDŽ int scanf(const char *format, ...) % 㸼⼎“%āˈϡ䖯㸠䌟ؐ 䲚ড়Ёϡࣙ৿ᄫヺ“]” [^...] ϢᮍᣀোЁⱘᄫヺ䲚ড়ϡऍ䜡ⱘ䕧ܹᄫヺЁ᳔䭓ⱘ䴲ぎᄫヺІ˗char *DŽ᳿ሒᇚ⏏ࡴ'\0'DŽ[^]...]㸼⼎ Ёࣙ৿ᄫヺ“]” [...] ϢᮍᣀোЁⱘᄫヺ䲚ড়ऍ䜡ⱘ䕧ܹᄫヺЁ᳔䭓ⱘ䴲ぎᄫヺІ˗char * DŽ᳿ሒᇚ⏏ࡴ'\0'DŽ[]...]㸼⼎䲚ড় 䅵᭄ n ᇚࠄⳂࠡЎℶ䆹ߑ᭄䇗⫼䇏পⱘᄫヺ᭄ݭܹᇍᑨⱘখ᭄Ё˗int *DŽ䚼䇏প䕧ܹᄫヺDŽϡ๲ࡴᏆ䕀ᤶⱘ乍Ⳃ p printf("%p")ߑ᭄䇗⫼ᠧॄⱘᣛ䩜ؐ˗void * ϔϾৃ䗝ⱘᣛ᭄ᄫ↉˄ᄫ↡ e ៪ E ৢ䎳ϔϾৃ㛑ᏺℷ䋳োⱘᭈൟ᭄˅ (long ftell(FILE *stream ߑ᭄೼ߎ䫭ᯊ䖨ಲϔϾ䴲 0 ؐDŽ ᖙ乏䆒㕂Ў 0ˈ៪㗙ᰃ⬅ߑ᭄ ftell 䖨ಲⱘؐ˄ℸᯊ origin ⱘؐᖙ乏ᰃ SEEK_SET˅DŽfseek ˄᭛ӊᓔྟ໘˅ǃSEEK_CUR˄ᔧࠡԡ㕂˅៪ SEEK_END˄᭛ӊ㒧ᴳ໘˅DŽᇍѢ᭛ᴀ⌕ˈoffset ᭛ӊˈℸԡ㕂㹿䆒㕂ЎҢoriginᓔྟⱘ㄀offsetϾᄫヺ໘DŽOriginⱘؐৃҹЎSEEK_SET fseek ߑ᭄䆒㕂⌕ stream ⱘ᭛ӊԡ㕂ˈৢ㓁ⱘ䇏ݭ᪡԰ᇚҢᮄԡ㕂ᓔྟDŽᇍѢѠ䖯ࠊ int fseek(FILE *stream, long offset, int origin) B.1.6 ᭛ӊᅮԡߑ᭄ ЁDŽᅗ䖨ಲ䕧ߎⱘᇍ䈵᭄ⳂDŽབᵰথ⫳䫭䇃ˈ䖨ಲؐӮᇣѢ nobj ⱘؐDŽ fwriteߑ᭄Ңptrᣛ৥ⱘ᭄㒘Ё䇏পnobjϾ䭓ᑺЎsizeⱘᇍ䈵ˈᑊ䕧ߎࠄ⌕stream size_t fwrite(const void *ptr, size_t size, size_t nobj, FILE *stream) 㦋ᕫ㒧ᵰᠻ㸠⢊ᗕDŽ ⱘ᭄㒘ЁDŽᅗ䖨ಲ䇏পⱘᇍ䈵᭄Ⳃˈℸ䖨ಲؐৃ㛑ᇣѢ nobjDŽᖙ乏䗮䖛ߑ᭄ feof ੠ ferror fread ߑ᭄Ң⌕ stream Ё䇏প᳔໮ nobj Ͼ䭓ᑺЎ size ⱘᇍ䈵ˈᑊֱᄬࠄ ptr ᣛ৥ size_t fread(void *ptr, size_t size, size_t nobj, FILE *stream) B.1.5 Ⳉ᥹䕧ܹˋ䕧ߎߑ᭄ ߑ᭄䖨ಲ㹿ݭಲⱘᄫヺˈབᵰথ⫳䫭䇃ˈ߭䖨ಲ EOFDŽ 䖯㸠䇏᪡԰ᯊˈᇚ䖨ಲ䆹ᄫヺDŽᇍ↣Ͼ⌕া㛑ݭಲϔϾᄫヺˈϨℸᄫヺϡ㛑ᰃ EOFDŽungetc ungetc ߑ᭄ᡞ c˄䕀ᤶЎ unsigned char ㉏ൟ˅ݭಲࠄ⌕ stream Ёˈϟ⃵ᇍ䆹⌕ int ungetc(int c, FILE *stream) ߭䖨ಲϔϾ䴲䋳ؐDŽ puts ߑ᭄ᡞᄫヺІ s ੠ϔϾᤶ㸠ヺ䕧ߎࠄ stdout ЁDŽབᵰথ⫳䫭䇃ˈ߭䖨ಲ EOF˗৺ int puts(const char *s) putchar(c)ߑ᭄ㄝӋѢ putc(c, stdout)DŽ int putchar(int c) stream ⱘؐDŽ putc ߑ᭄ㄝӋѢ fputcˈ᠔ϡৠⱘᰃˈᔧ putc ߑ᭄ᅮНЎᅣᯊˈᅗৃ㛑໮⃵䅵ㅫ int putc(int c, FILE *stream) ಲ᭄㒘 sˈབᵰࠄ䖒᭛ӊⱘ᳿ሒ៪থ⫳䫭䇃ˈ߭䖨ಲ NULLDŽ gets ߑ᭄ᡞϟϔϾ䕧ܹ㸠䇏ܹࠄ᭄㒘 s Ёˈᑊᡞ᳿ሒⱘᤶ㸠ヺ᳓ᤶЎᄫヺ'\0'DŽᅗ䖨 char *gets(char *s) isalnum(c) ߑ᭄ isalpha(c)៪ isdigit(c)Ўⳳ DŽ䖭ѯߑ᭄ࣙᣀ˖˅؛⼎খ᭄ c ⒵䎇ᣛᅮⱘᴵӊˈ߭ߑ᭄䖨ಲ䴲 0 ؐ˄㸼⼎ⳳ˅ˈ৺߭䖨ಲ 0˄㸼 ⱘؐᖙ乏ᰃ EOF ៪ৃ⫼ unsigned char ㉏ൟ㸼⼎ⱘᄫヺˈߑ᭄ⱘ䖨ಲؐЎ int ㉏ൟDŽབᵰ ༈᭛ӊЁໄᯢњϔѯ⌟䆩ᄫヺⱘߑ᭄DŽ↣Ͼߑ᭄ⱘখ᭄ഛЎ int ㉏ൟˈখ᭄ B.2 ᄫヺ㉏߿⌟䆩˖ ᳝݇ߑ᭄ strerror ⱘֵᙃˈখ㾕 B.3 㡖Ёⱘҟ㒡DŽ fprintf(stderr, "%s: %s\n", s, "error message"); ԧݙᆍϢ݋ԧⱘᅲ⦄᳝݇DŽ䆹ߑ᭄ⱘࡳ㛑㉏ԐѢᠻ㸠ϟ߫䇁হ˖ perror(s)ߑ᭄ᠧॄᄫヺІ s ҹঞϢ errno ЁᭈൟؐⳌᑨⱘ䫭䇃ֵᙃˈ䫭䇃ֵᙃⱘ݋ void perror(const char *s) བᵰ䆒㕂њϢ stream ⌕Ⳍ݇ⱘ䫭䇃ᣛ⼎ヺˈferror ߑ᭄ᇚ䖨ಲϔϾ䴲 0 ؐDŽ int ferror(FILE *stream) བᵰ䆒㕂њϢ stream ⌕Ⳍ݇ⱘ᭛ӊ㒧ᴳᣛ⼎ヺˈfeof ߑ᭄ᇚ䖨ಲϔϾ䴲 0 ؐˈ int feof(FILE *stream) clearerr ߑ᭄⏙䰸Ϣ⌕ stream Ⳍ݇ⱘ᭛ӊ㒧ᴳヺ੠䫭䇃ᣛ⼎ヺDŽ void clearerr(FILE *stream) Ͼ䫭䇃㓪োˈ᥂ℸৃҹ䖯ϔℹњ㾷᳔䖥ϔ⃵ߎ䫭ⱘֵᙃDŽ ⼎ヺৃ㹿ᰒᓣഄ䆒㕂੠⌟䆩DŽ঺໪ˈᭈൟ㸼䖒ᓣ errno˄೼Ёໄᯢ˅ৃҹࣙ৿ϔ ᑧЁⱘ䆌໮ߑ᭄䛑Ӯ䆒㕂⢊ᗕᣛ⼎ヺDŽ䖭ѯ⢊ᗕᣛޚᔧথ⫳䫭䇃៪ࠄ䖒᭛ӊ᳿ሒᯊˈᷛ B.1.7 䫭䇃໘⧚ߑ᭄ ߭䖨ಲϔϾ䴲 0 ؐDŽ fsetpos ߑ᭄ᇚ⌕ stream ⱘᔧࠡԡ㕂䆒㕂Ў fgetpos 䆄ᔩ೼*ptr Ёⱘԡ㕂DŽ㢹ߎ䫭 int fsetpos(FILE *stream, const fpos_t *ptr) ⫼DŽ㢹ߎ䫭߭䖨ಲϔϾ䴲 0 ؐDŽ fgetpos ߑ᭄ᡞ stream ⌕ⱘᔧࠡԡ㕂䆄ᔩ೼*ptr Ёˈկ䱣ৢⱘ fsetpos ߑ᭄䇗⫼Փ int fgetpos(FILE *stream, fpos_t *ptr) 㒧ᵰDŽ rewind(fp)ߑ᭄ㄝӋѢ䇁হ fseek(fp, 0L, SEEK_SET)˗clearerr(fp)ⱘᠻ㸠 void rewind(FILE *stream) ftell ߑ᭄䖨ಲ stream ⌕ⱘᔧࠡ᭛ӊԡ㕂ˈߎ䫭ᯊ䆹ߑ᭄䖨ಲ•lLDŽ ᣛ䩜˗བᵰ cs Ёϡࣙ৿ cˈ߭䆹ߑ᭄䖨ಲ NULL char *strchr(cs,c) 䖨ಲᣛ৥ᄫヺ c ೼ᄫヺІ cs Ё㄀ϔ⃵ߎ⦄ⱘԡ㕂ⱘ 0˗ᔧ cs>ct ᯊˈ䖨ಲϔϾℷ᭄ ᔧ csct ᯊˈ䖨ಲϔϾℷ int strcmp(cs,ct) ↨䕗ᄫヺІ cs ੠ ct˗ᔧ csЁᅮНњϸ㒘ᄫヺІߑ᭄DŽ㄀ϔ㒘ߑ᭄ⱘৡᄫҹ str ᓔ༈˗㄀Ѡ㒘 B.3 ᄫヺІߑ᭄˖ ߭ toupper(c)䖨ಲⳌᑨⱘ໻ݭᄫ↡ˈ৺߭䖨ಲ cDŽ བᵰ c ᰃ໻ݭᄫ↡ˈ߭ tolower(c)䖨ಲⳌᑨⱘᇣݭᄫ↡ˈ৺߭䖨ಲ cDŽབᵰ c ᰃᇣݭᄫ↡ˈ int toupper(int c) ᇚ c 䕀ᤶЎ໻ݭᄫ↡ int tolower(int c) ᇚ c 䕀ᤶЎᇣݭᄫ↡ ঺໪ˈϟ䴶ϸϾߑ᭄ৃ⫼Ѣᄫ↡ⱘ໻ᇣݭ䕀ᤶ˖ ᄫヺᰃҢ 0˄NUL˅ࠄ 0xlF˄US˅П䯈ⱘᄫヺҹঞᄫヺ 0x7F˄DEL˅DŽ ೼ 7 ԡ ASCII ᄫヺ䲚ЁˈৃᠧॄᄫヺᰃҢ 0x20˄' '˅ࠄ 0x7E˄'~'˅П䯈ⱘᄫヺ˗᥻ࠊ isxdigit(c) cᰃक݁䖯ࠊ᭄ᄫ isupper(c) cᰃ໻ݭᄫ↡ isspace(c) cᰃぎḐǃᤶ义ヺǃᤶ㸠ヺǃಲ䔺ヺǃ῾৥ࠊ㸼ヺ៪㒉৥ࠊ㸼ヺ ispunct(c) cᰃ䰸ぎḐǃᄫ↡੠᭄ᄫ໪ⱘৃᠧॄᄫヺ isprint(c) cᰃࣙᣀぎḐⱘৃᠧॄᄫヺ islower(c) cᰃᇣݭᄫ↡ isgraph(c) cᰃ䰸ぎḐ໪ⱘৃᠧॄᄫヺ isdigit(c) cЎक䖯ࠊ᭄ᄫ iscntrl(c) cЎ᥻ࠊᄫヺ isalpha(c) ߑ᭄ isupper(c)៪ islower(c)Ўⳳ ᇚ㹿䆒㕂Ў ERANGEDŽᔧ㒧ᵰϟ⑶ᯊˈߑ᭄䖨ಲ 0ˈ㗠 errno ᰃ৺䆒㕂Ў ERANGE 㽕㾚݋ԧ Ӯথ⫳ؐඳ䫭䇃DŽᔧ㒧ᵰϞ⑶ᯊˈߑ᭄䖨ಲ HUGE_VALˈᑊᏺ᳝ℷ⹂ⱘℷ䋳োˈerrpo ⱘؐ 㕂Ў EDOMˈߑ᭄ⱘ䖨ಲؐϢ݋ԧⱘᅲ⦄Ⳍ݇DŽབᵰߑ᭄ⱘ㒧ᵰϡ㛑⫼ double ㉏ൟ㸼⼎ˈ߭ ⱘ԰⫼ඳП໪ᯊˈህӮߎ⦄ᅮНඳ䫭䇃DŽ೼থ⫳ᅮНඳ䫭䇃ᯊˈܼሔব䞣 errno ⱘؐᇚ㹿䆒 ᭄ⱘᅮНඳ䫭䇃੠ؐඳ䫭䇃˗HUGE_VAL ᰃϔϾ double ㉏ൟⱘℷ᭄DŽᔧখ᭄ԡѢߑ᭄ᅮН ᅣ EDOM ੠ ERANGE˄೼༈᭛ӊЁໄᯢ˅ᰃϸϾ䴲 0 ᭈൟᐌ䞣ˈ⫼Ѣᣛ⼎ߑ ༈᭛ӊЁໄᯢњϔѯ᭄ᄺߑ᭄੠ᅣDŽ B.4 ᭄ᄺߑ᭄˖ void *memset(s,c,n) ᇚ s Ёⱘࠡ n Ͼᄫヺ᳓ᤶЎ cˈᑊ䖨ಲ s cs ⱘࠡ n ϾᄫヺЁᡒϡࠄऍ䜡ˈ߭䖨ಲ NULL void *memchr(cs,c,n) 䖨ಲϔϾᣛ䩜ˈᅗᣛ৥ c ೼ cs Ё㄀ϔ⃵ߎ⦄ⱘԡ㕂DŽབᵰ ⱘ䖨ಲؐⳌৠ int memcmp(cs,ct,n) ᇚ cs ⱘࠡ n ϾᄫヺϢ ct 䖯㸠↨䕗ˈ݊䖨ಲؐϢ strcmp ᯊˈ䆹ߑ᭄ҡ㛑ℷ⹂ᠻ㸠 void *memmove(s,ct,n) 䆹ߑ᭄ⱘࡳ㛑Ϣ memcpy ⳌԐˈ᠔ϡৠⱘᰃˈᔧᇍ䈵䞡঴ void *memcpy(s,ct,n) ᇚᄫヺІ ct Ёⱘ n Ͼᄫヺᣋ䋱ࠄ s Ёˈᑊ䖨ಲ s n ⱘ㉏ൟЎ size_tˈc ⱘ㉏ൟЎ int˄ᇚ㹿䕀ᤶЎ unsigned char ㉏ൟ˅DŽ ষDŽ೼ϟ㸼߫ߎⱘߑ᭄Ёˈs ੠ t ⱘ㉏ൟഛЎ void * ˈcs ੠ ct ⱘ㉏ൟഛЎ const void * ˈ ҹ mem ᓔ༈ⱘߑ᭄ᣝ✻ᄫヺ᭄㒘ⱘᮍᓣ᪡԰ᇍ䈵ˈ݊Џ㽕ⳂⱘᰃᦤկϔϾ催ᬜⱘߑ᭄᥹ 䆄োᯊˈ䖨ಲ NULLDŽ↣⃵䇗⫼ᯊᄫヺІ ct ৃҹϡৠDŽ ˄⬅ s ⱘؐᰃ৺Ў NULL ᣛ⼎˅ˈഛ䖨ಲϟϔϾϡࣙ৿ ct Ёᄫヺⱘ䆄োDŽᔧ s Ё≵᳝䖭ḋⱘ ᇚ s ЁⱘϟϔϾᄫヺ᳓ᤶЎ'\0'ˈᑊ䖨ಲᣛ৥䆄োⱘᣛ䩜DŽ䱣ৢˈ↣⃵䇗⫼ strtok ߑ᭄ᯊ ⱘᄫヺЎߚ⬠ヺDŽ㄀ϔ⃵䇗⫼ᯊˈs Ў䴲ぎDŽᅗ᧰㋶ sˈᡒࠄϡࣙ৿ ct Ёᄫヺⱘ㄀ϔϾ䆄োˈ ᇍ strtok(s, ct) 䖯㸠ϔ㋏߫䇗⫼ˈৃҹᡞᄫヺІ s ߚ៤䆌໮䆄োˈ䖭ѯ䆄োҹ ct Ё 䆺㒚ֵᙃখ㾕ϟ䴶ⱘ䅼䆎 char *strtok(s,ct) strtok ߑ᭄೼ s Ё᧰㋶⬅ ct Ёⱘᄫヺ⬠ᅮⱘ䆄োDŽ ᄫヺІ˄䫭䇃ֵᙃⱘ݋ԧݙᆍϢ݋ԧᅲ⦄Ⳍ݇˅ char *strerror(n) 䗔ಲϔϾᣛ䩜ˈᅗᣛ৥Ϣ䫭䇃㓪ো n ᇍᑨⱘ䫭䇃ֵᙃ size_t strlen(cs) 䖨ಲᄫヺІ cs ⱘ䭓ᑺ ಲ NULL І cs Ёⱘԡ㕂˗བᵰ cs Ёϡࣙ৿ᄫヺІ ctˈ߭䖨 char *strstr(cs,ct) 䖨ಲϔϾᣛ䩜ˈᅗᣛ৥ᄫヺІ ct ㄀ϔ⃵ߎ⦄೼ᄫヺ Ⳍৠⱘᄫヺˈ߭䖨ಲ NULL ⃵ߎ⦄೼ᄫヺІ cs Ёⱘԡ㕂˗བᵰ cs Ё≵᳝Ϣ ct char *strpbrk(cs,ct) 䖨ಲϔϾᣛ䩜ˈᅗᣛ৥ᄫヺІ ct Ёⱘӏᛣᄫヺ㄀ϔ size_t strcspn(cs,ct) 䖨ಲᄫヺІ cs Ёϡࣙ৿ ct Ёⱘᄫヺⱘࠡ㓔ⱘ䭓ᑺ size_t strspn(cs,ct) 䖨ಲᄫヺІ cs Ёࣙ৿ ct Ёⱘᄫヺⱘࠡ㓔ⱘ䭓ᑺ ⱘᣛ䩜˗བᵰ cs Ёϡࣙ৿ cˈ߭䆹ߑ᭄䖨ಲ NULL char *strrchr(cs,c) 䖨ಲᣛ৥ᄫヺ c ೼ᄫヺІ cs Ё᳔ৢϔ⃵ߎ⦄ⱘԡ㕂 (char**)NULL, 10)DŽ atoi ߑ᭄ᇚᄫヺІ s 䕀ᤶЎ int ㉏ൟDŽ䆹ߑ᭄ㄝӋѢ(int)strtol(s, int atoi(const char *s) (char**)NULL˅DŽ atof ߑ᭄ᇚᄫヺІ s 䕀ᤶЎ double ㉏ൟDŽ䆹ߑ᭄ㄝӋѢ strtod ˄ s, double atof(const char *s) ༈᭛ӊЁໄᯢњϔѯᠻ㸠᭄ؐ䕀ᤶǃݙᄬߚ䜡ҹঞ݊ᅗ㉏ԐᎹ԰ⱘߑ᭄DŽ B.5 ᅲ⫼ߑ᭄˖ ⱘᅲ⦄Ⳍ݇ fmod(x,y) ∖ JJy ⱘ⍂⚍ԭ᭄ˈヺোϢ j ⳌৠDŽབᵰ y Ў 0ˈ߭㒧ᵰϢ݋ԧ ᭄䖨ಲᇣ᭄䚼ߚˈᭈ᭄䚼ߚֱᄬ೼‘ip Ё modf(x, double *ip) ᡞ J ߚ៤ᭈ᭄੠ᇣ᭄ϸ䚼ߚˈϸ䚼ߚⱘℷ䋳োഛϢ x ⳌৠDŽ䆹ߑ ߭䖭ϸ䚼ߚഛЎ 0 ᵰᇚ䖨ಲⳳߚ᭄䚼ߚˈᑊᇚᐖ᭄ֱᄬ೼*exp ЁDŽབᵰ x Ў 0ˈ frexp(x, int *ip) ᡞ x ߚ៤ϔϾ೼[1/2, 1]ऎ䯈ݙⱘⳳߚ᭄੠ϔϾ 2 ⱘᐖ᭄DŽ㒧 ldexp(x,n) 䅵ㅫ x.2n ⱘؐ fabs(x) x ⱘ㒱ᇍؐ|x| floor(x) ϡ໻Ѣ x ⱘ᳔໻ᭈൟ᭄ˈ݊Ё x ⱘ㉏ൟЎ double ceil(x) ϡᇣѢ x ⱘ᳔ᇣᭈൟ᭄ˈ݊Ё x ⱘ㉏ൟЎ double sqrt(x) x ⱘᑇᮍḍˈ݊Ё xı0 ඳ䫭䇃 pow(x,y) xyDŽབᵰ x=0 Ϩ yİ0ˈ៪㗙 x<0 Ϩ y ϡᰃᭈൟ᭄ˈᇚѻ⫳ᅮН log10(x) ҹ 10 Ўᑩⱘᇍ᭄ log10(x)ˈ݊Ё x>0 log(x) 㞾✊ᇍ᭄ ln(x)ˈ݊Ё x>0 exp(x) ᐖߑ᭄ ex tanh(x) x ⱘঠ᳆ℷߛؐ cosh(x) x ⱘঠ᳆ԭᓺؐ sinh(x) x ⱘঠ᳆ℷᓺؐ atan2(y,x) tan•1(y/x)ˈؐඳЎ[•±, ±] atan(x) tan•1(x)ˈؐඳЎ[±/2, ±/2] acos(x) cos•1(x)ˈؐඳЎ[0, ±]ˈ݊Ё [ę[•1, 1] asin(x) sin•1(x)ˈؐඳЎ[±/2, ±/2]ˈ݊Ё [ę[•1, 1] tan(x) x ⱘℷߛؐ cos(x) x ⱘԭᓺؐ sin(x) x ⱘℷᓺؐ doubleDŽϝ㾦ߑ᭄ⱘ㾦ᑺ⫼ᓻᑺ㸼⼎DŽ ೼ϟ㸼Ёˈx ੠ y ⱘ㉏ൟЎ doubleˈn ⱘ㉏ൟЎ intˈ᠔᳝ߑ᭄ⱘ䖨ಲؐⱘ㉏ൟഛЎ ⱘᅲ⦄㗠ᅮDŽ ࠡՓ⫼ࡼᗕߚ䜡ߑ᭄ malloeǃrealloc ៪ calloc ߚ䜡ⱘぎ䯈DŽܜ乏ᣛ৥ free ߑ᭄䞞ᬒ p ᣛ৥ⱘݙᄬぎ䯈DŽᔧ p ⱘؐЎ NULL ᯊˈ䆹ߑ᭄ϡᠻ㸠ӏԩ᪡԰DŽP ᖙ void free(void *p) ݙᆍֱᣕϡবDŽܗϟˈॳᣛ䩜 p ᣛ৥ⱘऩމ߭䖨ಲ NULLDŽ೼䖭⾡ᚙ ϡ㹿߱ྟ࣪˖realloc ߑ᭄䖨ಲᣛ৥ᮄߚ䜡ぎ䯈ⱘᣛ䩜˗㢹᮴⊩⒵䎇㽕∖ˈܗ߭ᮄߚ䜡ݙᄬऩ ໻ˈ߭ॳݙᄬⱘݙᆍֱᣕϡবˈ๲ࡴⱘぎ䯈ϡ䖯㸠߱ྟ࣪DŽབᵰᮄߚ䜡ⱘݙᄬ↨ॳݙᄬᇣˈ realloc ߑ᭄ᇚ p ᣛ৥ⱘᇍ䈵ⱘ䭓ᑺׂᬍЎ size Ͼᄫ㡖DŽབᵰᮄߚ䜡ⱘݙᄬ↨ॳݙᄬ void *realloc(void *p, size_t size) 㽕∖ˈ߭䖨ಲ NULLDŽ䆹ߑ᭄ϡᇍߚ䜡ⱘݙᄬऎඳ䖯㸠߱ྟ࣪DŽ malloc ߑ᭄Ў䭓ᑺЎ size ⱘᇍ䈵ߚ䜡ݙᄬˈᑊ䖨ಲᣛ৥ߚ䜡ऎඳⱘᣛ䩜˗㢹᮴⊩⒵䎇 void *malloc(size_t size) ඳⱘᣛ䩜˗㢹᮴⊩⒵䎇㽕∖ˈ߭䖨ಲ NULLDŽ䆹ぎ䯈ⱘ߱ྟ䭓ᑺЎ 0 ᄫ㡖DŽ calloc ߑ᭄Ў⬅ nobj Ͼ䭓ᑺЎ size ⱘᇍ䈵㒘៤ⱘ᭄㒘ߚ䜡ݙᄬˈᑊ䖨ಲᣛ৥ߚ䜡ऎ void *calloc(size_t nobj, size_t size) srand ߑ᭄ᇚ seed ԰Ў⫳៤ᮄⱘӾ䱣ᴎ᭄ᑣ߫ⱘ⾡ᄤ᭄DŽ⾡ᄤ᭄ seed ⱘ߱ؐЎ 1DŽ void srand(unsigned int seed) rand ߑ᭄ѻ⫳ϔϾ 0̚RAND_MAX П䯈ⱘӾ䱣ᴎᭈ᭄DŽRAND_MAX ⱘপؐ㟇ᇥЎ 32767DŽ int rand(void) Ў ULONG_MAXDŽ strtoul ߑ᭄ⱘࡳ㛑Ϣ strtol ߑ᭄ⳌৠˈԚ݊㒧ᵰЎ unsigned long ㉏ൟˈ䫭䇃ؐ unsigned long strtoul(const char *s, char **endp, int base) ৠᯊᇚ errno ⱘؐ䆒㕂Ў ERANGEDŽ ҹࡴϞࠡᇐ 0x ៪ 0XDŽབᵰ㒧ᵰϞ⑶ˈ߭ߑ᭄ḍ᥂㒧ᵰⱘヺো䖨ಲ LONG_MAX ៪ LONG_MINˈ ϟDŽᄫ↡ഛ㸼⼎ 10̚base•1 П䯈ⱘ᭄ᄫDŽབᵰ base ؐᰃ 16ˈ߭ৃމ䖯ࠊDŽ᮴䆎೼ા⾡ᚙ ߭෎ᑩЎܿ䖯ࠊǃक䖯ࠊ៪क݁䖯ࠊDŽҹ 0 Ўࠡ㓔ⱘᰃܿ䖯ࠊˈҹ 0x ៪ 0X Ўࠡ㓔ⱘᰃक݁ ᅮ䕧ܹᰃҹ䆹᭄Ў෎ᑩⱘ˗བᵰ base ⱘপؐЎ 0ˈ؛ЁDŽབᵰ base ⱘপؐ೼ 2̚36 П䯈ˈ߭ 䴲 endp Ў NULLˈ৺߭䆹ߑ᭄ᇚᡞᣛ৥ s Ё᳿䕀ᤶ䚼ߚ˄s ⱘৢ䕡䚼ߚ˅ⱘᣛ䩜ֱᄬ೼*endp strtol ߑ᭄ᇚᄫヺІ s ⱘࠡ㓔䕀ᤶЎ long ㉏ൟˈᑊ೼䕀ᤶᯊ䏇䖛 s ⱘࠡᇐぎⱑヺDŽ䰸 long strtol(const char *s, char **endp, int base) ϟˈerrno 䛑ᇚ㹿䆒㕂Ў ERANGEDŽމ⾡ᚙ ЁDŽབᵰ㒧ᵰϞ⑶ˈ߭ߑ᭄䖨ಲᏺ᳝䗖ᔧヺো HUGE_VAL˗བᵰ㒧ᵰϟ⑶ˈ߭䖨ಲ 0DŽ೼䖭ϸ 䰸њ endp Ў NULLˈ৺߭䆹ߑ᭄ᇚᡞᣛ৥ sЁ᳾䕀ᤶ䚼ߚ˄sⱘৢ㓔䚼ߚ˅ⱘᣛ䩜ֱᄬ೼*endp strtod ߑ᭄ᇚᄫヺІ s ⱘࠡ㓔䕀ᤶЎ double ㉏ൟˈᑊ೼䕀ᤶᯊ䏇䖛 s ⱘࠡᇐぎⱑヺDŽ double strtod(const char *s, char **endp) 10)DŽ atol ߑ᭄ᇚᄫヺІ s 䕀ᤶЎ long ㉏ൟDŽ䆹ߑ᭄ㄝӋѢ strtol(s, (char**)NULL, long atol(const char *s) idiv ߑ᭄䅵ㅫ num/denom ⱘଚ੠ԭ᭄ˈᑊᡞ㒧ᵰߚ߿ֱᄬ೼㒧ᵘ㉏ൟ idiv_t ⱘϸϾ ldiv_t ldiv(long num, long denom) ㉏ൟⱘ៤ਬ quot ੠ rem ЁDŽ div ߑ᭄䅵ㅫ num/denom ⱘଚ੠ԭ᭄ˈᑊᡞ㒧ᵰߚ߿ֱᄬ೼㒧ᵘ㉏ൟ div_t ⱘϸϾ int div_t div(int num, int denom) labs ߑ᭄䖨ಲ long ㉏ൟখ᭄ n ⱘ㒱ᇍؐDŽ long labs(long n) abs ߑ᭄䖨ಲ int ㉏ൟখ᭄ n ⱘ㒱ᇍؐDŽ int abs(int n) 䭓ᑺЎ sizeDŽ↨䕗ߑ᭄ cmp Ϣ bsearch ߑ᭄Ёⱘᦣ䗄ⳌৠDŽ qsort ߑ᭄ᇍ base[0]...base[n•1]᭄㒘Ёⱘᇍ䈵䖯㸠छᑣᥦᑣˈ᭄㒘Ё↣Ͼᇍ䈵ⱘ int (*cmp)(const void *, const void *)) void qsort(void *base, size_t n, size_t size, བᵰϡᄬ೼ऍ䜡乍ˈ߭䖨ಲ NULLDŽ ؐDŽ᭄㒘 base Ёⱘ乍ᖙ乏ᣝछᑣᥦ߫DŽbsearch ߑ᭄䖨ಲϔϾᣛ䩜ˈᅗᣛ৥ϔϾऍ䜡乍ˈ খ᭄ㄝѢ㄀ѠϾখ᭄ˈᅗᖙ乏䖨ಲ䳊˗བᵰ㄀ϔϾখ᭄໻Ѣ㄀ѠϾখ᭄ˈᅗᖙ乏䖨ಲϔϾℷ བᵰ㄀ϔϾখ᭄˄ᶹᡒ݇䬂ᄫ˅ᇣѢ㄀ѠϾখ᭄˄㸼乍˅ˈᅗᖙ乏䖨ಲϔϾ䋳ؐ˗བᵰ㄀ϔϾ bsearch ߑ᭄೼ base[0]...base[n•1]П䯈ᶹᡒϢ*key ऍ䜡ⱘ乍DŽ೼ߑ᭄ cmp Ёˈ int (*cmp)(const void *keyval, const void *datum)) size_t n, size_t size, void *bsearch(const void *key, const void *base, 㒚㡖Ϣ݋ԧⱘᅲ⦄᳝݇DŽ getenv ߑ᭄䖨ಲϢ name ᳝݇ⱘ⦃๗ᄫヺІDŽབᵰ䆹ᄫヺІϡᄬ೼ˈ߭䖨ಲ NULLDŽ݊ char *getenv(const char *name) ߭䆹ߑ᭄䖨ಲ䴲 0 ؐDŽབᵰ s ⱘؐϡᰃ NULLˈ߭䖨ಲؐϢ݋ԧⱘᅲ⦄᳝݇DŽ system ߑ᭄ᇚᄫヺІ s Ӵ䗦㒭ᠻ㸠⦃๗DŽབᵰ s ⱘؐЎ NULLˈᑊϨ᳝ੑҸ໘⧚⿟ᑣˈ int system(const char *s) 䴲 0 ؐDŽ atexit ߑ᭄ⱏ䆄ߑ᭄ fcnˈ䆹ߑ᭄ᇚ೼⿟ᑣℷᐌ㒜ℶᯊ㹿䇗⫼DŽབᵰⱏ䆄༅䋹ˈ߭䖨ಲ int atexit(void (*fcn)(void)) ੠ EXIT_FAILURE ԰Ў䖨ಲؐDŽ ⱘؐབԩ䖨ಲ㒭⦃๗㽕㾚݋ԧⱘᅲ⦄㗠ᅮˈԚ 0 ؐ㸼⼎㒜ℶ៤ࡳDŽгৃՓ⫼ؐ EXIT_SUCCESS ऎᇚ㹿⏙⋫ˈ᠔᳝Ꮖᠧᓔⱘ⌕ᇚ㹿݇䯁ˈ᥻ࠊгᇚ䖨ಲ㒭⦃๗DŽstatusކ᠔᳝Ꮖᠧᓔⱘ᭛ӊ㓧 ϟˈމexit ߑ᭄Փ⿟ᑣℷᐌ㒜ℶDŽatexit ߑ᭄ⱘ䇗⫼乎ᑣϢⱏ䆄ⱘ乎ᑣⳌডˈ䖭⾡ᚙ void exit(int status) abort ߑ᭄Փ⿟ᑣ䴲ℷᐌ㒜ℶDŽ݊ࡳ㛑Ϣ raise(SIGABRT)㉏ԐDŽ void abort(void) setjmp ᅣᇚ⢊ᗕֵᙃֱᄬࠄ env Ёˈկ longjmp Փ⫼DŽབᵰⳈ᥹䇗⫼ setjmpˈ߭䖨 int setjmp(jmp_buf env) 䆌ゟेҢϔϾ⏅ሖጠ༫ⱘߑ᭄䇗⫼Ё䖨ಲDŽܕ߿ᰃˈᅗ ༈᭛ӊЁⱘໄᯢᦤկњϔ⾡ϡৠѢ䗮ᐌⱘߑ᭄䇗⫼੠䖨ಲ乎ᑣⱘᮍᓣˈ⡍ B.8 䴲ሔ䚼䏇䕀˖ void va_end(va_list ap); ೼᠔᳝ⱘখ᭄໘⧚ᅠ↩ПৢˈϨ೼䗔ߎߑ᭄ f Пࠡˈᖙ乏䇗⫼ᅣ va_end ϔ⃵ˈབϟ᠔⼎˖ type va_arg(va_list ap, type); ᅗৠᯊ䖬ׂᬍ apˈҹՓᕫϟϔ⃵ᠻ㸠 va_arg ᯊ䖨ಲϟϔϾখ᭄˖ ℸৢˈ↣⃵ᠻ㸠ᅣ va_arg 䛑ᇚѻ⫳ϔϾϢϟϔϾ᳾ੑৡⱘখ᭄݋᳝Ⳍৠ㉏ൟ੠᭄ؐⱘؐˈ va_start(va_list ap, lastarg); ೼䆓䯂ӏԩ᳾ੑৡⱘখ᭄ࠡˈᖙ乏⫼ va—start ᅣ߱ྟ࣪ ap ϔ⃵˖ va_list ap; ೼ߑ᭄ f ݙໄᯢϔϾ㉏ൟЎ va_list ⱘব䞣 apˈᅗᇚձ⃵ᣛ৥↣Ͼᅲ䰙খ᭄˖ ᅮߑ᭄ f ᏺ᳝ৃব᭄Ⳃⱘᅲ䰙খ᭄ˈlastarg ᰃᅗⱘ᳔ৢϔϾੑৡⱘᔶᓣখ᭄DŽ䙷Мˈ؛ ༈᭛ӊᦤկњ䘡ग़᳾ⶹ᭄Ⳃ੠㉏ൟⱘߑ᭄খ᭄㸼ⱘࡳ㛑DŽ B.7 ৃবখ᭄㸼˖ བᵰᅮНњᅣ NDEBUGˈৠᯊজࣙ৿њ༈᭛ӊˈ߭ assert ᅣᇚ㹿ᗑ⬹DŽ __FILE__ঞ__LINE__DŽ ᠧॄ⍜ᙃৢˈ䆹ᅣᇚ䇗⫼ abort 㒜ℶ⿟ᑣⱘᠻ㸠DŽ݊Ёⱘ⑤᭛ӊৡ੠㸠োᴹ㞾Ѣ乘໘⧚఼ᅣ Assertion failed: 㸼䖲ᓣ, file ⑤᭛ӊৡ, line 㸠ো ᯊˈ㸼䖒ᓣⱘؐЎ 0ˈ߭ assert ᅣᇚ೼ stderr Ёᠧॄϔᴵ⍜ᙃˈ↨བ˖ assert(expression) བᵰᠻ㸠䇁হ void assert(int expression) assert ᅣ⫼ѢЎ⿟ᑣ๲ࡴ䆞ᮁࡳ㛑DŽ݊ᔶᓣབϟ˖ B.6 䆞ᮁ˖ long ㉏ൟⱘ៤ਬ quot ੠ rem ЁDŽ ᔧഄᯊ䯈ˈ಴Ўᯊऎㄝॳৠˈᔧഄᯊ䯈Ϣ᮹ग़ᯊ䯈ৃ㛑ϡⳌৠDŽclock_t ੠ time_t ᰃϸϾ ༈᭛ӊЁໄᯢњϔѯ໘⧚᮹ᳳϢᯊ䯈ⱘ㉏ൟ੠ߑ᭄DŽ݊Ёⱘϔѯߑ᭄⫼Ѣ໘⧚ B.10 ᮹ᳳϢᯊ䯈ߑ᭄˖ raise ৥⿟ᑣথ䗕ֵো sigDŽབᵰথ䗕ϡ៤ࡳˈ߭䖨ಲϔϾ䴲 0 ؐDŽ int raise(int sig) ֵোⱘ߱ྟ⢊ᗕ⬅݋ԧⱘᅲ⦄ᅮНDŽ ᠻ㸠DŽ handler)(sig)䇗⫼ⱘϔḋDŽֵো໘⧚⿟ᑣ䖨ಲৢˈ⿟ᑣᇚҢֵোথ⫳ⱘԡ㕂䞡ᮄᓔྟ*) ⬅ڣᔧ䱣ৢ⺄ࠄֵো sig ᯊˈ䆹ֵোᇚᘶ໡Ў咬䅸㸠Ўˈ䱣ৢ䇗⫼ֵো໘⧚⿟ᑣˈህད ᇍѢ⡍ᅮⱘֵোˈsignal ᇚ䖨ಲ handler ⱘࠡϔϾؐ˗བᵰߎ⦄䫭䇃ˈ߭䖨ಲؐ SIG_ERRDŽ SIGTERM থ䗕㒭⿟ᑣⱘ㒜ℶ䇋∖ ܗ఼䆓䯂ˈབ䆓䯂ϡᄬ೼ⱘݙᄬऩټSIGSEGV 䴲⊩ᄬ SIGINT ⫼ѢѸѦᓣⳂⱘֵোˈབЁᮁ བ䴲⊩ᣛҸˈڣSIGILL 䴲⊩ߑ᭄᯴ SIGFPE ㅫᴃ䖤ㅫߎ䫭ˈབ䰸᭄Ў 0 ៪⑶ߎ SIGABRT ᓖᐌ㒜ℶˈ՟བ⬅ abort ᓩ䍋ⱘ㒜ℶ ৥ⱘߑ᭄(ҹֵো԰Ўখ᭄)DŽ᳝ᬜⱘֵোࣙᣀ˖ ᅮНⱘ咬䅸㸠Ў˗བᵰ handler ⱘؐᰃ SIG_IGNˈ߭ᗑ⬹䆹ֵো˗৺߭ˈ䇗⫼ handler ᣛ ᅮњབԩ໘⧚ৢ㓁ⱘֵোDŽབᵰ handler ⱘؐᰃ SIG_DFLˈ߭䞛⫼⬅ᅲ⦄އ signal void (*signal(int sig, void (*handler)(int)))(int) ⑤Ѣ໪䚼ⱘЁᮁֵো៪⿟ᑣᠻ㸠䫭䇃ᓩ䍋ⱘЁᮁֵোDŽ ༈᭛ӊᦤկњϔѯ໘⧚⿟ᑣ䖤㸠ᳳ䯈ᓩথⱘ৘⾡ᓖᐌᴵӊⱘࡳ㛑ˈ↨བᴹ B.9 ֵো˖ ߭ᅗӀᇚব៤᳾ᅮН⢊ᗕDŽ ৠ˖೼䇗⫼ setjmp ᅣৢˈབᵰ䇗⫼ setjmp ᅣⱘߑ᭄Ёⱘ䴲 volatile 㞾ࡼব䞣ᬍবњˈ ໪ˈৃ䆓䯂ᇍ䈵ⱘؐৠ䇗⫼ longjmp ᯊⱘؐⳌމ⫼ⱘߑ᭄ⱘᠻ㸠ᖙ乏䖬≵᳝㒜ℶDŽ䰸ϟ߫ᚙ ᘶ໡ᠻ㸠ˈ݊⢊ᗕㄝৠѢ setjmp ᅣ䇗⫼߮߮ᠻ㸠ᅠᑊ䖨ಲ䴲 0 ؐ valDŽࣙ৿ setjmp ᅣ䇗 longjmp 䗮䖛᳔䖥ϔ⃵䇗⫼ setjmp ᯊֱᄬࠄ env Ёⱘֵᙃᘶ໡⢊ᗕˈৠᯊˈ⿟ᑣ䞡ᮄ void longjmp(jmp_buf env, int val) /* get here by calling longjmp */ else /* get here on direct call */ if (setjmp(env) == 0) ՟བ˖ ᭛Ёˈབ⫼Ѣ if 䇁হǃswitch 䇁হǃᕾ⦃䇁হⱘᴵӊ⌟䆩Ёҹঞϔѯㅔऩⱘ݇㋏㸼䖒ᓣЁDŽ ಲؐЎ 0˗བᵰᰃ೼ longjmp Ё䇗⫼ setjmpˈ߭䖨ಲؐЎ䴲 0DŽSetjmp া㛑⫼ѢᶤѯϞϟ localtime ߑ᭄ᇚ㒧ᵘ*tp Ёⱘ᮹ग़ᯊ䯈䕀ᤶЎᔧഄᯊ䯈DŽ struct tm *localtime(const time_t *tp) ߑ᭄䖨ಲ NULLDŽߑ᭄ৡᄫ gmtime ᳝ϔᅮⱘग़৆ᛣНDŽ gmtime ߑ᭄ᇚ*tp Ёⱘ᮹ग़ᯊ䯈䕀ᤶЎण䇗Ϫ⬠ᯊ˄UTC˅DŽབᵰ᮴⊩㦋প UTCˈ߭䆹 struct tm *gmtime(const time_t *tp) asctime(localtime(tp)) ctime ߑ᭄ᇚ㒧ᵘ*tp Ёⱘ᮹ग़ᯊ䯈䕀ᤶЎᔧഄᯊ䯈DŽᅗㄝӋѢϟ߫ߑ᭄䇗⫼˖ char *ctime(const time_t *tp) Sun Jan 3 15:14:13 1988\n\0 asctime ߑ᭄ᇚ㒧ᵘ*tp Ёⱘᯊ䯈䕀ᤶЎϟ߫᠔⼎ⱘᄫヺІᔶᓣ˖ char *asctime(const struct tm *tp) ϟ䴶 4 Ͼߑ᭄䖨ಲᣛ৥ৃ㹿݊ᅗ䇗⫼㽚Ⲫⱘ䴭ᗕᇍ䈵ⱘᣛ䩜DŽ 㛑㸼⼎ˈ߭䖨ಲ•1DŽ ৘៤ਬⱘؐԡѢϞ䴶᠔⼎㣗ೈПݙDŽmktime ߑ᭄䖨ಲ䕀ᤶৢᕫࠄⱘ᮹ग़ᯊ䯈˗བᵰ䆹ᯊ䯈ϡ mktime ߑ᭄ᇚ㒧ᵘ*tp Ёⱘᔧഄᯊ䯈䕀ᤶЎϢ time 㸼⼎ᮍᓣⳌৠⱘ᮹ग़ᯊ䯈ˈ㒧ᵘЁ time_t mktime(struct tm *tp) difftime ߑ᭄䖨ಲ time2•time1 ⱘؐ˄ҹ⾦Ўऩԡ˅DŽ double difftime(time_t time2, time_t time1) ߭ৠᯊᇚ䖨ಲؐ䌟㒭*tpDŽ time ߑ᭄䖨ಲᔧࠡ᮹ग़ᯊ䯈DŽབᵰ᮴⊩㦋প᮹ग़ᯊ䯈ˈ߭䖨ಲؐЎ•1DŽབᵰ tp ϡᰃ NULLˈ time_t time(time_t *tp) ؐЎ•1DŽclock()/CLOCKS_PER_SEC ᰃҹ⾦Ўऩԡ㸼⼎ⱘᯊ䯈DŽ clock ߑ᭄䖨ಲ⿟ᑣᓔྟᠻ㸠ৢऴ⫼ⱘ໘⧚఼ᯊ䯈DŽབᵰ᮴⊩㦋প໘⧚఼ᯊ䯈ˈ߭䖨ಲ clock_t clock(void) Փ⫼Ҹᯊˈtm_isdst ⱘؐЎℷˈ৺߭Ў 0DŽབᵰ䆹ֵᙃ᮴ᬜˈ߭݊ؐЎ䋳DŽ int tm_isdst; ໣Ҹᯊᷛ䆄 int tm_yday; Ң 1 ᳜ 1 ᮹䍋㒣䖛ⱘ໽᭄(0, 365) int tm_wday; Ң᯳ᳳ໽䍋㒣䖛ⱘ໽᭄(0, 6) int tm_year; Ң 1900 ᑈ䍋㒣䖛ⱘᑈ᭄ int tm_mon; Ң 1 ᳜䍋㒣䖛ⱘ᭄᳜(0, 11) int tm_mday; ᔧ᳜ⱘ໽᭄(1, 31) int tm_hour; Ңज໰ᓔྟ㒣䖛ⱘᇣᯊ᭄(0, 23) int tm_min; Ңᔧࠡᇣᯊᓔྟ㒣䖛ⱘߚ䩳᭄(0, 59) int tm_sec; Ңᔧࠡߚ䩳ᓔྟ㒣䖛ⱘ⾦᭄(0, 61) ⫼䗨ঞপؐ㣗ೈབϟ᠔⼎˖ 㸼⼎ᯊ䯈ⱘㅫᴃ㉏ൟˈstruct tm ⫼Ѣֱᄬ᮹ग़ᯊ䯈ⱘ৘Ͼᵘ៤䚼ߚDŽ㒧ᵘ tm Ё৘៤ਬⱘ LONG_MIN •2147483647 long ㉏ൟⱘ᳔ᇣؐ LONG_MAX 2147483647 long ㉏ൟⱘ᳔໻ؐ INT_MIN •32767 int ㉏ൟⱘ᳔ᇣؐ INT_MAX 32767 int ㉏ൟⱘ᳔໻ؐ CHAR_MIN 0 ៪ SCHAR_MIN char ㉏ൟⱘ᳔ᇣؐ CHAR_MAX UCHAR_MAX ៪ SCHAR_MAX char ㉏ൟⱘ᳔໻ؐ CHAR_BIT 8 char ㉏ൟⱘԡ᭄ ೼ᅲ䰙㋏㒳ЁৃҹՓ⫼᳈໻ⱘؐDŽ ༈᭛ӊᅮНњϔѯ㸼⼎ᭈൟ໻ᇣⱘᐌ䞣DŽҹϟ᠔߫ⱘؐᰃৃ᥹ফⱘ᳔ᇣؐˈ B.11 Ϣ݋ԧᅲ⦄Ⳍ݇ⱘ䰤ࠊ˖ %% %ᴀ䑿 %Z ᯊऎৡ˄བᵰ᳝ⱘ䆱˅ %Y ᏺϪ㑾᭄Ⳃⱘᑈӑ %y ϡᏺϪ㑾᭄Ⳃⱘᑈӑ˄00•99˅ %X ᔧഄᯊ䯈㸼⼎ %x ᔧഄ᮹ᳳ㸼⼎ %W ϔᑈЁⱘ᯳ᳳᑣো˄00•53ˈᇚ᯳ᳳϔⳟ԰ᰃ↣਼ⱘ㄀ϔ໽˅ %w ϔ਼Ёⱘ৘໽˄0•6ˈ᯳ᳳ᮹Ў 0˅ %U ϔᑈЁⱘ᯳ᳳᑣো˄00•53ˈᇚ᯳ᳳ᮹ⳟ԰ᰃ↣਼ⱘ㄀ϔ໽˅ %S ⾦˄00•61˅ %p Ϣ AM Ϣ PM Ⳍᑨⱘᔧഄᯊ䯈ㄝӋ㸼⼎ᮍ⊩ %M ߚ䩳˄00•59˅ %m ᳜ӑ˄01•12˅ %j ϔᑈЁⱘ৘໽˄001—366˅ %I ᇣᯊ˄12 ᇣᯊ㸼⼎˅˄01•12˅ %H ᇣᯊ˄24 ᇣᯊ㸼⼎˅˄00•23˅ %d ϔϾ᳜Ёⱘᶤϔ໽˄01•31˅ %c ᔧഄᯊ䯈੠᮹ᳳ㸼⼎ %B ᳜ӑܼৡ %b 㓽ݭⱘ᳜ӑৡ %A ϔ᯳ᳳЁ৘໽ⱘܼৡ %a ϔ᯳ᳳЁ৘໽ⱘ㓽ݭৡ fmt ⱘ䕀ᤶ䇈ᯢঞ݊৿Нབϟ᠔⼎˖ ᭄໮Ѣ smaxˈ䆹ߑ᭄ᇚ䖨ಲؐ 0DŽ ᄫヺݭࠄ s ЁDŽstrftime ߑ᭄䖨ಲᅲ䰙ݭࠄ s Ёⱘᄫヺ᭄˄ϡࣙᣀᄫヺ'\0'˅˗བᵰᄫヺ ᇚ໡ࠊࠄ s ЁDŽ↣Ͼ%c ᇚᣝ✻ϟ䴶ᦣ䗄ⱘḐᓣ᳓ᤶЎϢᴀഄ⦃๗Ⳍ䗖ᑨⱘؐDŽ᳔໮ smax Ͼ ࠄ s Ёˈ݊Ё fmt ㉏ԐѢ printf ߑ᭄ЁⱘḐᓣ䇈ᯢDŽ᱂䗮ᄫヺ˄ࣙᣀ㒜㒧ヺ'\0'˅ټᑊᄬ strftime ߑ᭄ḍ᥂ fmt ЁⱘḐᓣᡞ㒧ᵘ*tp Ёⱘ᮹ᳳϢᯊ䯈ֵᙃ䕀ᤶЎᣛᅮⱘḐᓣˈ *tp) size_t strftime(char *s, size_t smax, const char *fmt, const struct tm DBL_MIN_EXP ᳔ᇣⱘ᭄ nˈn ⒵䎇˖10n ᰃϔϾ㾘Ḑ᭄࣪ DBL_MIN 1E•37 ᳔ᇣⱘ㾘Ḑ࣪ঠ㊒ᑺ⍂⚍᭄ DBL_MAX_EXP ᳔໻ⱘ᭄ nˈn ⒵䎇 FLT_RADIXn•1 ҡᰃৃ㸼⼎ⱘ DBL_MAX 1E+37 ᳔໻ⱘঠ㊒ᑺ⍂⚍᭄ DBL_MANT_DIG ሒ᭄Ёⱘ᭄˄ҹ FLT_RADIX Ў෎᭄˅ DBL_EPSILON 1E•9 ᳔ᇣⱘ᭄ xˈx ⒵䎇˖1.0 + x  1.0 DBL_DIG 10 㸼⼎㊒ᑺⱘक䖯ࠊ᭄ᄫ FLT_MIN_EXP ᳔ᇣⱘ᭄ nˈn ⒵䎇˖10n ᰃϔϾ㾘Ḑ᭄࣪ FLT_MIN 1E•37 ᳔ᇣⱘ㾘Ḑ࣪⍂⚍᭄ FLT_MAX_EXP ᳔໻ⱘ᭄ nˈn ⒵䎇 FLT_RADIXn•1 ҡᰃৃ㸼⼎ⱘ FLT_MAX 1E+37 ᳔໻ⱘ⍂⚍᭄ FLT_MANT_DIG ሒ᭄Ёⱘ᭄˄ҹ FLT_RADIX Ў෎᭄˅ FLT_EPSILON 1E•5 ᳔ᇣⱘ᭄ xˈx ⒵䎇˖1.0 + x  1.0 FLT_DIG 6 㸼⼎㊒ᑺⱘक䖯ࠊ᭄ᄫ FLT_ROUNDS ࡴ⊩ⱘ⍂⚍㟡ܹ῵ᓣ FLT_RADIX 2 ᣛ᭄㸼⼎ⱘ෎᭄ˈ՟བ 2ǃ16 ߎⱘ↣Ͼؐҷ㸼Ⳍᑨ䞣ⱘ᳔ᇣপؐDŽ৘Ͼᅲ⦄ৃҹᅮН䗖ᔧⱘؐDŽ ϟ㸼߫ߎⱘৡᄫᰃⱘϔϾᄤ䲚ˈᅗӀᰃϢ⍂⚍ㅫᴃ䖤ㅫⳌ݇ⱘϔѯᐌ䞣DŽ㒭 USHRT_MAX 65535 unsigned short ㉏ൟⱘ᳔໻ؐ ULONG_MAX 4294967295 unsigned long ㉏ൟⱘ᳔໻ؐ UINT_MAX 65535 unsigend int ㉏ൟⱘ᳔໻ؐ UCHAR_MAX 255 unsigned char ㉏ൟⱘ᳔໻ؐ SHRT_MIN •32767 short ㉏ൟⱘ᳔ᇣؐ SHRT_MAX +32767 short ㉏ൟⱘ᳔໻ؐ SCHAR_MIN •127 signed char ㉏ൟⱘ᳔ᇣؐ SCHAR_MAX +127 signed char ㉏ൟⱘ᳔໻ؐ ϟˈᣛ䩜ϢᭈൟП䯈ҹঞϡৠ㉏ൟⱘᣛ䩜П䯈䖤ㅫⱘ㾘߭DŽމࠊ㉏ൟ䕀ᤶⱘᚙ 䗮⫼ᣛ䩜㉏ൟ˗೼ℸПࠡ char * ᡂⓨⴔ䖭ϔ㾦㡆DŽৠᯊˈᯢ⹂ഄ㾘ᅮњ೼ϡ䖯㸠ᔎ ᓩܹњ void * ㉏ൟˈᑊ԰Ўϔ⾡ޚ· ᕜ໮㓪䆥఼೼޴ᑈࠡህᅲ⦄њ void ㉏ൟDŽᮄᷛ ⫼ᴹᰒᓣ㸼⼎ᄫヺ੠݊ᅗᭈൟᇍ䈵ⱘヺোDŽ ᓩܹњ݇䬂ᄫ signedˈޚ· ᳝↉ᯊ䯈ˈC 䇁㿔ЁৃҹՓ⫼ unsigned char ㉏ൟDŽᮄᷛ ⱘ⫼⊩ˈԚৃҹ⫼ long double ໄᯢ᳈催㊒ᑺⱘ⍂⚍᭄DŽ ᏺヺো㉏ൟ៪᮴ヺো㉏ൟDŽᬒᓗњᇚ long float ԰Ў double ⱘৠН䆡䖭⾡⣀⡍ · Ϣ݊ᅗ㉏ൟϔḋˈᇍᄫヺ㉏ൟгৃҹՓ⫼݇䬂ᄫ signed ៪ unsigned ᰒᓣໄᯢЎ · ᦤկњᆑᄫヺᄫヺІᄫ䴶ؐ੠ᄫヺᐌ䞣ⱘ㸼⼎ᮍ⊩ˈখ㾕 A.2.6 㡖DŽ · Ⳍ䚏ⱘᄫヺІᇚ㹿䖲᥹೼ϔ䍋DŽ ⫼Ѣ⍂⚍᭄DŽᅗৠᯊг㒚࣪њ᮴ৢ㓔ᐌ䞣㉏ൟⱘⳌ݇㾘߭˄খ㾕 A.2.5 㡖 ˅DŽ ᓩܹњ᳈໻ⱘৢ㓔䲚ড়ˈՓᕫᐌ䞣ⱘ㉏ൟ᳈ࡴᯢ⹂˖U ៪ L ⫼ѢᭈൟˈF ៪ Lޚ· ᮄᷛ · ᠔᳝Ҏ䛑୰⃶ⱘϔϾᇣব࣪˖8 ੠ 9 ϡ⫼԰ܿ䖯ࠊ᭄ᄫDŽ ϡᰃ䕀Нᑣ߫ˈ߭݊㒧ᵰᰃ᳾ᅮНⱘDŽখ㾕 A.2.5 㡖DŽ · ᅮНњᄫヺᐌ䞣੠ᄫヺІᄫ䴶ؐЁՓ⫼ⱘᮄ䕀Нᄫヺᑣ߫DŽབᵰ\ঞ݊ৢᄫヺᵘ៤ⱘ ᇚϡݡՓ⫼DŽ · ᓩܹњϔѯᮄ݇䬂ᄫ˄voidǃconstǃvolatileǃsigned ੠ enum˅DŽ݇䬂ᄫ entry ৃ㛑Ӯᬍবࣙ৿“??āⱘᄫヺІⱘ৿НDŽ \ǃ^ǃ[ǃ]ǃ{ǃ}ǃ|ǃ~ㄝ䕀Нᄫヺˈখ㾕 A.12.1 㡖DŽ⊼ᛣˈϝᄫヺᑣ߫ⱘᓩܹ · 䗮䖛ঠ䯂ো”??āᓩܹⱘϝᄫヺᑣ߫ৃҹ㸼⼎ᶤѯᄫヺ䲚Ё㔎ᇥⱘᄫヺDŽᅮНњ#ǃ 䆌᳈䭓ⱘᷛ䆚ヺ˅DŽܕᬜ䭓ᑺҡ✊Ў 6 Ͼᄫヺ˄ᕜ໮ᅲ⦄Ё · ᠔᳝ݙ䚼ᷛ䆚ヺⱘ᳔ᇣ᳝ᬜ䭓ᑺ๲ࡴЎ 31 Ͼᄫヺ˗݋᳝໪䚼䖲᥹ⱘᷛ䆚ヺⱘ᳔ᇣ᳝ ҙҙ䰤Ѣ೼ᄫヺІ੠ᅣᅮНЁDŽ䆺㒚ֵᙃখ㾕 A.12 㡖DŽ 䆌೼ӏԩഄᮍՓ⫼ড᭰ᴴᄫ“\ā䖯㸠㸠ⱘ䖲᥹ˈ㗠ϡܕЁⱘᔶᓣখ᭄ϡݡ㹿᳓ᤶDŽ 䆌Փ⫼Ⳍৠ䆄োᑣ߫䞡ᮄໄᯢᅣ˗ᄫヺІܕ᥻ࠊᣛҸ˄བ#elif ੠#pragma˅˗ᯢ⹂ Ў෎⸔˗๲ࡴњ䖲᥹䆄োⱘ䖤ㅫヺ˄##˅੠⫳៤ᄫヺІⱘ䖤ㅫヺ˄#˅˗๲ࡴњᮄⱘ ᯢ⹂ҹ䆄ো˖ܙC Ё݇Ѣ乘໘⧚ⱘᅮН᳈ࡴ㒚㟈ˈᑊ䖯㸠њᠽ ޚ· Ϣ㄀ 1 ⠜Ⳍ↨ˈᷛ ᮴ৃ↨П໘ˈ಴Ў㄀ 1 ⠜ᑊ≵᳝䆩೒㾘ᅮϔϾ⦃๗៪ᑧDŽ ⱘ䞡㽕㒘៤䚼ߚˈԚᅗӀϢ㄀ 1 ⠜޴Тޚ䇁㿔ᴀ䑿ˈϡ⍝ঞ⦃๗੠ᑧDŽሑㅵ⦃๗੠ᑧгᰃᷛ П䯈ⱘᏂ߿DŽ៥Ӏ೼䖭䞠ҙ䅼䆎ޚᴀ䰘ᔩᘏ㒧њᴀк㄀ 1 ⠜ᅮНⱘ C 䇁㿔Ϣ ANSI ᮄᷛ њDŽ ᳳ䞛⫼ܜথᏗПࠡˈANSI ⱘ᡹ਞህᏆ㒣㹿ϔѯ㓪䆥఼ᦤկଚ䚼ߚഄޚℷDŽ⫮㟇೼ℷᓣⱘ C ᷛ ࣪ᯊ䞛㒇њ݊Ё㒱໻䚼ߚⱘׂᬍˈᑊ䖯㸠њ݊ᅗϔѯ䞡㽕ׂޚ࣪णӮ೼ᇍ C 䇁㿔䖯㸠ᷛޚᷛ AT&T ᦤկⱘ㓪䆥఼ⱘ᭛ḷϔৠথᏗⱘˈᑊ㹿ℸৢⱘ݊ᅗ C 㓪䆥఼կᑨଚ䞛㒇DŽࠡϡЙˈANSI ѯׂᬍׂℷњॳ⠜ᴀЁⱘ℻Нᗻᦣ䗄˗ᶤѯׂᬍᰃᇍᏆ᳝⠜ᴀⱘব᳈DŽ䆌໮ᮄ๲ࡳ㛑䛑ᰃ䱣 䛑ᰃ㒣䖛㊒ᖗ䆒䅵ⱘˈᑊֱᣕњϢ⦄᳝⠜ᴀⱘݐᆍᗻ˗݊Ёⱘϔܙৠᯊ↣⃵ᠽˈܙⱘϔ⃵ᠽ 㞾ᴀк㄀ 1 ⠜ߎ⠜ҹᴹˈC 䇁㿔ⱘᅮНᏆ㒣থ⫳њϔѯব࣪DŽ޴Т↣⃵ব࣪䛑ᰃᇍॳ䇁㿔 䰘ᔩCব᳈ᇣ㒧 · switch 䇁হⱘ᥻ࠊ㸼䖒ᓣ੠ case ᷛোৃҹᰃӏᛣᭈൟDŽ \0˅DŽ · ᰒᓣᣛᅮ䭓ᑺⱘᄫヺ᭄㒘ৃҹ⫼Ϣℸ䭓ᑺⳌৠⱘᄫヺІᄫ䴶ؐ߱ྟ࣪˄ϡࣙᣀᄫヺ · 㞾ࡼ㒧ᵘǃ㘨ড়੠᭄㒘ৃҹ䖯㸠߱ྟ࣪ˈԚ᳝ϔѯ䰤ࠊDŽ · 㘨ড়ৃҹ䖯㸠߱ྟ࣪ˈ߱ؐᓩ⫼݊㄀ϔϾ៤ਬDŽ њ˅DŽ⫣خ݊᠔ሲⱘ㒧ᵘ៪㘨ড়Ⳍ݇㘨˄䖭Ꮖ㒣ᰃ䆌໮ᅲ⦄ⱘ݅ৠ ৠᯊгЎᷛোᓩܹњϔϾऩ⣀ⱘৡᄫぎ䯂ˈখ㾕 A.1.1 㡖DŽ㒧ᵘ៪㘨ড়ⱘ៤ਬৡᇚϢ ᇚ᠔᳝ⱘᷛোᬒ೼Ͼऩ⣀ⱘৡᄫぎ䯈Ёˈޚ· ᷛ䆚ヺⱘৡᄫぎ䯈᳝ϔѯব࣪DŽANSI ᷛ さDŽކϢᔶᓣখ᭄ · ᔶᓣখ᭄ⱘ԰⫼ඳᠽሩࠄߑ᭄ⱘ໡ড়䇁হЁˈ಴ℸˈߑ᭄Ё᳔乊ሖⱘব䞣ໄᯢϡ㛑 ᯢ⹂㾘ᅮˈ䖭⾡ໄᯢⱘ԰⫼ඳҙЎ䆹⿟ᑣഫDŽޚᅗ䚼ߚৃ㾕DŽANSI ᷛ · ೼ᶤѯᅲ⦄Ёˈབᵰݙሖ⿟ᑣഫЁࣙ৿ϔϾ extern ໄᯢˈ߭䆹ໄᯢᇍ䆹᭛ӊⱘ݊ · ⽕ℶ≵᳝ӏԩ䇈ᯢヺ៪䰤ᅮヺ˄াᰃϔϾぎⱘໄᯢヺ˅ⱘ໪䚼᭄᥂䇈ᯢDŽ 䆄ໄᯢ೼໪ሖ԰⫼ඳЁгᰃ䖭ḋDŽ ঺ϔᮍ䴶ˈҙҙাᏺ㒧ᵘᷛ䆄៪㘨ড়ᷛ䆄ⱘໄᯢᰃᇍ䆹ᷛ䆄ⱘ䞡ᮄໄᯢˈेՓ䆹ᷛ ℶぎໄᯢˈे≵᳝ໄᯢヺˈϨ≵᳝㟇ᇥໄᯢϔϾ㒧ᵘˈ㘨ড়៪ᵮВⱘໄᯢDŽ⽕ޚ· ᷛ ߑ᭄ҡ✊ৃҹՓ⫼ˈԚ᳝ϔᅮ䰤ࠊDŽ 䅸ৃⱘ໘⧚ৃবᔶᓣখ᭄㸼ⱘᮍ⊩DŽখ㾕 A.7.3 㡖ǃA.8.6 㡖੠ B.7 㡖DŽᮻᓣໄᯢⱘ 㸼ⱘߑ᭄ⱘᮍ⊩ˈᑊᦤկњϔ⾡㹿ܗЁ䖬㾘ᅮњᰒᓣໄᯢᏺৃববޚൟDŽৠᯊˈᷛ ⱘ㉏ܗ䡈Ѣ C++˅ᓩܹњߑ᭄ॳൟໄᯢⱘ㸼⼎⊩ˈߑ᭄ॳൟЁৃҹໄᯢব׳˄ޚ· ᷛ ᴃ੠݇㋏䖤ㅫDŽখ㾕 A.7.7 㡖DŽ 䆌ᇍ݊䖯㸠ㅫܕ㋴ⱘϟϔϾԡ㕂ⱘᣛ䩜ˈᑊܗ䆌߯ᓎϔϾᣛ৥᭄㒘᳔ৢϔϾܕޚ· ᷛ · ⿏ԡ㸼䖒ᓣⱘ㉏ൟᰃ݊Ꮊ᪡԰᭄ⱘ㉏ൟˈে᪡԰᭄ϡ㛑ᦤछ㒧ᵰ㉏ൟDŽখ㾕 A.7.8 㡖DŽ 䆌Փ⫼ഄഔ䖤ㅫヺDŽܕᬒ೼ᆘᄬ఼Ёгϡ · ഄഔ䖤ㅫヺ&ϡৃᑨ⫼ѢໄᯢЎ register ⱘᇍ䈵ˈेՓ݋ԧⱘᅲ⦄᳾ᇚ䖭⾡ᇍ䈵ᄬ ൟ˄ptrdiff_t˅г᳝㉏Ԑⱘব࣪DŽখ㾕 A.7.4 㡖Ϣ A.7.7 㡖DŽ ༈᭛ӊЁᅮНDŽ݇ѢϸϾᣛ䩜ⱘᏂⱘ㒧ᵰ㉏ޚᇚ݊㉏ൟ size_t ೼ᷛ ᯢ⹂њ䆹䖤ㅫヺⱘ㒧ᵰ㉏ൟϢ݋ԧⱘᅲ⦄᳝݇ˈԚ㽕∖ޚ԰Ў unsigned ㉏ൟDŽᷛ · ೼㄀ 1 ⠜Ёˈsizeof 䖤ㅫヺⱘ㒧ᵰ㉏ൟЎ intˈԚ䱣ৢᕜ໮㓪䆥఼ⱘᅲ⦄ᇚℸ㒧ᵰ 䆌ᇍ᭄㒘ᑨ⫼ഄഔ䖤ㅫヺˈ݊㒧ᵰЎᣛ৥᭄㒘ⱘᣛ䩜DŽܕ · · 㒧ᵘৃҹ㹿䌟ؐǃӴ䗦㒭ߑ᭄ҹঞ㹿ߑ᭄䖨ಲDŽ · ᣛ৥ߑ᭄ⱘᣛ䩜ৃҹ԰Ўߑ᭄ⱘᷛᖫヺˈ㗠ϡ䳔㽕ᰒᓣⱘ*䖤ㅫヺDŽখ㾕 A.7.3 㡖DŽ 䖤ㅫヺ+DŽܗ䖤ㅫヺ•ⱘᇍ⿄ˈᓩܹњϔܗ· ЎњֱᣕϢϔ 䅵ㅫϞгᰃৃ㒧ড়ⱘDŽخ· ೼㓪䆥఼Ёˈϡݡᇚ᭄ᄺϞৃ㒧ড়ⱘ䖤ㅫヺᔧ ೼㄀ 1 ⠜ЁˈᅗӀᰃϸϾ䆄োˈЁ䯈ৃҹ⫼ぎⱑヺߚᓔDŽ · ᮻⱘ䌟ؐ㉏䖤ㅫヺ˄བ=+˅ᏆϡݡՓ⫼DŽৠᯊˈ䌟ؐ㉏䖤ㅫヺ⦄೼ᰃऩϾ䆄ো˗㗠 ᘏᰃ䕀ᤶЎ double ㉏ൟāᏆ᳈ᬍЎĀᦤछࠄ᳔ᇣⱘ䎇໳໻ⱘ㉏ൟāDŽখ㾕 A.6.5 㡖DŽ · ׂᬍњĀ᱂䗮ㅫᴃ㉏ൟ䕀ᤶāˈ⡍߿ഄˈĀᭈൟᘏᰃ䕀ᤶЎ unsigned ㉏ൟˈ⍂⚍᭄ · ᄫヺІϡݡᰃৃҹׂᬍⱘˈ಴ℸৃҹᬒ೼া䇏ݙᄬऎЁDŽ 䞛⫼њ C++Ёⱘ㉏ൟ䰤ᅮヺⱘὖᗉˈབ const˄খ㾕 A.8.2 㡖 ˅DŽޚ· ᷛ · ᮄ๲ࡴⱘᵮВ㉏ൟᰃ㄀ 1 ⠜Ё᠔≵᳝ⱘDŽ ˅Ё㒭ߎњ৘⾡⡍ᅮᅲ⦄ⱘ⡍ᗻDŽ ᯢ⹂ᣛᅮњㅫᴃ㉏ൟপؐ㣗ೈⱘ᳔ᇣؐˈᑊ೼ϸϾ༈᭛ӊ˄੠ޚᮄᷛ ·
还剩224页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 10 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

eeechoer

贡献于2017-04-09

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf