C-Primer-Plus6
[toc]
第一章复习初识C语言
本章介绍以下内容:
• C的历史和特性
• 编写程序的步骤
• 编译器和链接器的一些知识
• C标准
1 | 欢迎来到C语言的世界。C是一门功能强大的专业化编程语言,深受业余编程爱好者和专业程序员的喜爱。本章为读者学习这一强大而流行的语言打好基础,并介绍几种开发C程序最可能使用的环境。 |
- 信任程序员
- 不要妨碍程序员做需要做的事
- 保持语言精炼简单
- 只提供一种方法执行一项操作
- 让程序运行更快,即使不能保证其可移植性。
本章小结
C是强大而简洁的编程语言。它之所以流行,在于自身提供大量的实用编程工具,能很好地控制硬件。而且,与大多数其他程序相比,C程序更容易从一个系统移植到另一个系统。
C是编译型语言。C编译器和链接器是把C语言源代码转换成可执行代码的程序。
用C语言编程可能费力、困难,让你感到沮丧,但是它也可以激发你的兴趣,让你兴奋、满意。我们希望你在愉快的学习过程中爱上C。
复习题
1.对编程而言,可移植性意味着什么?
完美的可移植程序是,其源代码无需修改就能在不同计算机系统中成功编译的程序。
2.解释源代码文件、目标代码文件和可执行文件有什么区别?
源代码文件包含程序员使用的任何编程语言编写的代码。目标文件包含机器语言代码,它不必是完整的程序代码。可执行文件包含组成可执行程序的完整机器语言代码。
3.编程七个主要步骤是什么?
(1)定义程序目标;(2)设计程序;(3)编写程序;(4)编译程序;(5)运行程序;(6)测试和调试程序;(7)维护和修改程序。
4.编译器的任务是什么?
把源代码翻译成等价的机器语言代码
5.链接器的任务是什么?
把编译器翻译好的源代码以及库代码和启动代码组合起来,生成一个可执行程序。
第二章C语言概述
本章介绍以下内容:
• 运算符:=
• 函数:main() 、printf()
• 编写一个简单的C程序
• 创建整型变量,为其赋值并在屏幕上显示其值
• 换行字符
• 如何在程序中写注释,创建包含多个函数的程序,发现程序的错误
• 什么是关键字
1 | C程序是什么样子的?浏览本书,能看到许多示例。初见C程序会觉得有些古怪,程序中有许多{、cp->tort 和*ptr++ 这样的符号。然而,在学习C的过程中,对这些符号和C语言特有的其他符号会越来越熟悉,甚至会喜欢上它们. |
关键概念
编程是一件富有挑战性的事情。程序员要具备抽象和逻辑的思维,并谨慎地处理细节问题(编译器会强迫你注意细节问题)。平时和朋友交流时,可能用错几个单词,犯一两个语法错误,或者说几句不完整的句子,但是对方能明白你想说什么。而编译器不允许这样,对它而言,几乎正确仍然是错误.
编译器不会在下面讲到的概念性问题上帮助你。因此,本书这一章中介绍一些关键概念帮助读者弥补这部分的内容。
在本章中,读者的目标应该是理解什么是C程序。可以把程序看作是你希望计算机如何完成任务的描述。编译器负责处理一些细节工作,例如把你要计算机完成的任务转换成底层的机器语言(如果从量化方面来解释编译器所做的工作,它可以把1KB的源文件创建成60KB的可执行文件;即使是一个很简单的C程序也要用大量的机器语言来表示)。由于编译器不具有真正的智能,所以你必须用编译器能理解的术语表达你的意图,这些术语就是C语言标准规定的形式规则(尽管有些约束,但总比直接用机器语言方便得多)。
编译器希望接收到特定格式的指令,我们在本章已经介绍过。作为程序员的任务是,在符合C标准的编译器框架中,表达你希望程序应该如何完成任务的想法。
本章小结
C程序由一个或多个C函数组成。每个C程序必须包含一个main() 函数,这是C程序要调用的第1个函数。简单的函数由函数头和后面的一对花括号组成,花括号中是由声明、语句组成的函数体。
在C语言中,大部分语句都以分号结尾。声明语句为变量指定变量名,并标识该变量中储存的数据类型。变量名是一种标识符。赋值表达式语句把值赋给变量,或者更一般地说,把值赋给存储空间。函数表达式语句用于调用指定的已命名函数。调用函数执行完毕后,程序会返回到函数调用后面的语句继续执行。
printf() 函数用于输出想要表达的内容和变量的值。
一门语言的语法 是一套规则,用于管理语言中各有效语句组合在一起的方式。语句的语义 是语句要表达的意思。编译器可以检测出语法错误,但是程序里的语义错误只有在编译完之后才能从程序的行为中表现出来。检查程序是否有语义错误要跟踪程序的状态,即检查程序每执行一步后所有变量的值。
最后,关键字是C语言的词汇。
| auto | extern | short | while |
|---|---|---|---|
| break | float | signed | _Alignas |
| case | for | sizeof | _Alignof |
| char | goto | static | _Atomic |
| const | if | struct | _Bool |
| continue | inline | switch | _Complex |
| default | int | typedef | _Generic |
| do | long | union | _Imaginary |
| double | register | unsigned | _Noreturn |
| else | restrict | void | _Static_assert |
| enum | return | volatile | _Thread_local |
复习题
1.C语言的基本模块是什么?
函数
2.什么是语法错误?写出一个英语例子和C语言例子。
违反了组成语句或程序的规则。这是一个有语法错误的英文例子:My speak English good. 。 这是一个有语法错误的C语言例子:printf “Where are the patent?”;。
3.什么是语义错误?写出一个英语例子和C语言例子。
含义错误。这是一个有语义错误的英文例子:This sentence is eccellent Czech. 这是一个有语义错误的C语言例子:thrice_n = 3 + ;
第三章数据和C
本章介绍以下内容:
• 关键字:int 、short 、long 、unsigned 、char 、float 、double 、_Bool 、
_Complex 、_Imaginary
• 运算符:sizeof()
• 函数:scanf()
• 整数类型和浮点数类型的区别
• 如何书写整型和浮点型常数,如何声明这些类型的变量
• 如何使用printf() 和scanf() 函数读写不同类型的值
1 | 程序离不开数据。把数字、字母和文字输入计算机,就是希望它利用这些数据完成某些任务。例如,需要计算一份利息或显示一份葡萄酒商的排序列表。本章除了介绍如何读取数据外,还将教会读者如何操控数据。 |
关键概念
C语言提供了大量的数值类型,目的是为程序员提供方便。那以整数类型为例,C认为一种整型不够,提供了有符号、无符号,以及大小不同的整型,以满足不同程序的需求。
计算机中的浮点数和整数在本质上不同,其存储方式和运算过程有很大区别。即使两个32位存储单元储存的位组合完全相同,但是一个解释为float 类型,另一个解释为long 类型,这两个相同的位组合表示的值也完全不同。例如,在PC中,假设一个位组合表示float 类型的数256.0,如果将其解释为long 类型,得到的值是113246208。C语言允许编写混合数据类型的表达式,但是会进行自动类型转换,以便在实际运算时统一使用一种类型。
计算机在内存中用数值编码来表示字符。美国最常用的是ASCII码,除此之外C也支持其他编码。字符常量是计算机系统使用的数值编码的符号表示,它表示为单引号括起来的字符,如’A’ 。
本章小结
C有多种的数据类型。基本数据类型分为两大类:整数类型和浮点数类型。通过为类型分配的储存量以及是有符号还是无符号,区分不同的整数类型。最小的整数类型是char ,因实现不同,可以是有符号的char或无符号的char ,即unsigned char 或signed char 。但是,通常用char 类型表示小整数时才这样显示说明。其他整数类型有short 、int 、long 和long long 类型。C规定,后面的类型不能小于前面的类型。上述都是有符号类型,但也可以使用unsigned 关键字创建相应的无符号类型:unsigned short 、unsigned int 、unsigned long和unsigned long long 。或者,在类型名前加上signed 修饰符显式表明该类型是有符号类型。最后,_Bool 类型是一种无符号类型,可储存0 或1 ,分别代表false 和true 。
浮点类型有3种:float 、double 和C90新增的long double 。后面的类型应大于或等于前面的类型。有些实现可选择支持复数类型和虚数类型,通过关键字_Complex 和_Imaginary 与浮点类型的关键字组合(如double_Complex 类型和float _Imaginary 类型)来表示这些类型。
整数可以表示为十进制、八进制或十六进制。0前缀表示八进制数,0x或0X前缀表示十六进制数。例如,32、040、0x20分别以十进制、八进制、十六进制表示同一个值。l 或L 后缀表明该值是long 类型,ll 或LL 后缀表明该值是long long 类型。
在C语言中,直接表示一个字符常量的方法是:把该字符用单引号括起来,如’Q’ 、’8’ 和’$’ 。C语言的转义序列(如,’\n’ )表示某些非打印字符。另外,还可以在八进制或十六进制数前加上一个反斜杠(如,’\007’ ),表示ASCII码中的一个字符。
浮点数可写成固定小数点的形式(如,9393.912 )或指数形式(如,7.38E10 )。C99和C11提供了第3种指数表示法,即用十六进制数和2的幂来表示(如,0xa.1fp10 )。
printf() 函数根据转换说明打印各种类型的值。转换说明最简单的形式由一个百分号(% )和一个转换字符组成,如%d 或%f 。
复习题
1.指出下面各种数据使用的合适数据类型(可使用多种
a.int 类型,也可以是short 类型或unsigned short 类型。
人口数是一个整数。
b.float 类型,价格通常不是一个整数(也可以使用double
类型,但实际上不需要那么高的精度)。
c.char 类型。
d.int 类型,也可以是unsigned 类型。
2.在什么情况下要用long类型的变量代替int类型变量?
原因之一:在系统中要表示的数超过了int 可表示的范围,这时
要使用long 类型。原因之二:如果要处理更大的值,那么使用一种在所
有系统上都保证至少是32位的类型,可提高程序的可移植性。
3.使用哪些可移植的数据类型可以获得32位有符号整数?理由
如果要正好获得32位的整数,可以使用int32_t 类型。要获得
可储存至少32位整数的最小类型,可以使用int_least32_t 类型。如
果要为32 位整数提供最快的计算速度,可以选择int_fast32_t 类型
(假设你的系统已定义了上述类型)。
4.写出下列常量在声明中使用的数据类型和在printf()中对应的转换说明:
| 常量 | 类型 | 转换说明 |
|---|---|---|
| 12 | int | %d |
| 0x3 | unsigned int | %#x |
| ‘c’ | char | %c |
| 2.34E07 | double | %e |
| ‘\040’ | char | %c |
| 7.0 | double | %f |
| 6L | long | %ld |
| 6.0f | float | %f |
| 0x5.b6p12 | float | %a |
| 012 | unsigned int | %#o |
| 2.9e05L | long double | %Le |
| ‘s’ | char | %c |
| 100000 | long | %ld |
| ‘\n’ | char | %c |
| 20.0f | float | %f |
| 0x44 | unsigned int | %#x |
| -40 | int | %d |
第四章 字符串和格式化输入/输出
本章介绍以下内容:
• 函数:strlen()
• 关键字:const
• 字符串
• 如何创建、存储字符串
• 如何使用strlen() 函数获取字符串的长度
• 用C预处理器指令#define 和ANSIC的const 修饰符创建符号常量
1 | 本章重点介绍输入和输出。与程序交互和使用字符串可以编写个性化的程序,本章将详细介绍C语言的两个输入/输出函数:printf()和scanf()。学会使用这两个函数,不仅能与用户交互,还可根据个人喜好和任务要求格式化输出。最后,简要介绍一个重要的工具——C预处理器指令,并学习如何定义、使用符号常量。 |
关键概念
C语言用char 类型表示单个字符,用字符串表示字符序列。字符常
量是一种字符串形式,即用双引号把字符括起来:”Good luck, my
friend” 。可以把字符串储存在字符数组(由内存中相邻的字节组成)
中。字符串,无论是表示成字符常量还是储存在字符数组中,都以一个叫做空字符 的隐藏字符结尾。在程序中,最好用#define 定义数值常量,用const 关键字声明的
变量为只读变量。在程序中使用符号常量(明示常量),提高了程序的可读性和可维护性。C语言的标准输入函数(scanf() )和标准输出函数(printf())都使用一种系统。在该系统中,第1个参数中的转换说明必须与后续参数中的值相匹配。例如,int 转换说明%d 与一个浮点值匹配会产生奇怪的结果。必须格外小心,确保转换说明的数量和类型与函数的其余参数相
匹配。对于scanf() ,一定要记得在变量名前加上地址运算符(& )。空白字符(制表符、空格和换行符)在scanf() 处理输入时起着至关重要的作用。除了%c 模式(读取下一个字符),scanf() 在读取输入时会跳过非空白字符前的所有空白字符,然后一直读取字符,直至遇到空白字符或与正在读取字符不匹配的字符。考虑一下,如果scanf() 根据不同的转换说明读取相同的输入行,会发生什么情况。假设有如下输入行:
-13.45e12# 0
如果其对应的转换说明是%d ,scanf() 会读取3个字符(-13 )并停在小数点处,小数点将被留在输入中作为下一次输入的首字符。如果其对应的转换说明是%f ,scanf() 会读取-13.45e12 ,并停在# 符号处,而# 将被留在输入中作为下一次输入的首字符;然后,scanf() 把读取的字符序列-13.45e12 转换成相应的浮点值,并储存在float 类型的目标变量中。如果其对应的转换说明是%s ,scanf() 会读取-13.45e12# ,并停在空格处,空格将被留在输入中作为下一次输入的首字符;然后,scanf() 把这10个字符的字符码储存在目标字符数组中,并在末尾加上一个空字符。如果其对应的转换说明是%c ,scanf()只会读取并储存第1个字符,该例中是一个空格 .
本章小结
字符串是一系列被视为一个处理单元的字符。在C语言中,字符串是以空字符(ASCII码是0)结尾的一系列字符。可以把字符串储存在字符数组中。数组是一系列同类型的项或元素。下面声明了一个名为name 、有30个char 类型元素的数组:
char name[30];要确保有足够多的元素来储存整个字符串(包括空字符)。
字符串常量是用双引号括起来的字符序列,如:”This is an example of a string” 。strlen() 函数(声明在string.h 头文件中)可用于获得字符串
的长度(末尾的空字符不计算在内)。scanf() 函数中的转换说明是%s
时,可读取一个单词。C预处理器为预处理器指令(以# 符号开始)查找源代码程序,并在开始编译程序之前处理它们。处理器根据#include 指令把另一个文件中的内容添加到该指令所在的位置。#define 指令可以创建明示常量(符号常量),即代表常量的符号。limits.h 和float.h 头文件用
#define 定义了一组表示整型和浮点型不同属性的符号常量。另外,还
可以使用const 限定符创建定义后就不能修改的变量。printf() 和scanf() 函数对输入和输出提供多种支持。两个函数
都使用格式字符串,其中包含的转换说明表明待读取或待打印数据项的数量和类型。另外,可以使用转换说明控制输出的外观:字段宽度、小数位和字段内的布局.
复习题
第五章 运算符、表达式和语句
本章介绍以下内容:
• 关键字:while 、typedef
• 运算符:= 、- 、*、/ 、% 、++ 、– 、(类型名)
• C语言的各种运算符,包括用于普通数学运算的运算符
• 运算符优先级以及语句、表达式的含义
• while 循环
• 复合语句、自动类型转换和强制类型转换
• 如何编写带有参数的函数
1 | 现在,读者已经熟悉了如何表示数据,接下来我们学习如何处理数 |
关键概念
C通过运算符提供多种操作。每个运算符的特性包括运算对象的数
量、优先级和结合律。当两个运算符共享一个运算对象时,优先级和结合律决定了先进行哪项运算。每个C表达式都有一个值。如果不了解运算符的优先级和结合律,写出的表达式可能不合法或者表达式的值与预期不符。这会影响你成为一名优秀的程序员。虽然C允许编写混合数值类型的表达式,但是算术运算要求运算对象都是相同的类型。因此,C会进行自动类型转换。尽管如此,不要养成依赖自动类型转换的习惯,应该显式选择合适的类型或使用强制类型转换。这样,就不用担心出现不必要的自动类型转换。
本章小结
C语言有许多运算符,如本章讨论的赋值运算符和算术运算符。一般而言,运算符需要一个或多个运算对象才能完成运算生成一个值。只需要一个运算对象的运算符(如负号和sizeof )称为一元运算符 ,需要两个运算对象的运算符(如加法运算符和乘法运算符)称为二元运算符 。
表达式 由运算符和运算对象组成。在C语言中,每个表达式都有一个值,包括赋值表达式和比较表达式。运算符优先级规则决定了表达式中各项的求值顺序。当两个运算符共享一个运算对象时,先进行优先级高的运算。如果运算符的优先级相等,由结合律(从左往右或从右往左)决定求值顺序。大部分语句 都以分号结尾。最常用的语句是表达式语句 。用花括号括起来的一条或多条语句构成了复合语句 (或称为块 )。while 语句是一种迭代语句 ,只要测试条件为真,就重复执行循环体中的语句。
在C语言中,许多类型转换 都是自动进行的。当char 和short 类型出现在表达式里或作为函数的参数(函数原型除外)时,都会被升级为
int 类型;float 类型在函数参数中时,会被升级为double 类型。在
K&R C(不是ANSI C)下,表达式中的float 也会被升级为double 类
型。当把一种类型的值赋给另一种类型的变量时,值将被转换成与变量的类型相同。当把较大类型转换成较小类型时(如,long 转换成short ,或double 转换成float ),可能会丢失数据。根据本章介绍的规则,在混合类型的运算中,较小类型会被转换成较大类型。定义带一个参数的函数时,便在函数定义中声明了一个变量, 或称为形式参数 。然后,在函数调用中传入的值会被赋给这个变量。这样,在函数中就可以使用该值了。
第六章 C控制语句:循环
本章介绍以下内容:
• 关键字:for 、while 、do while
• 运算符:< 、> 、>= 、<= 、!= 、== 、+= 、*= 、-= 、/= 、%=
• 函数:fabs()
• C语言有3种循环:for 、while 、do while
• 使用关系运算符构建控制循环的表达式
• 其他运算符
• 循环常用的数组
• 编写有返回值的函数
1 | 大多数人都希望自己是体格强健、天资聪颖、多才多艺的能人。虽然 |
关键概念
循环是一个强大的编程工具。在创建循环时,要特别注意以下3个方面:
• 注意循环的测试条件要能使循环结束;
• 确保循环测试中的值在首次使用之前已初始化;
• 确保循环在每次迭代都更新测试的值。C通过求值来处理测试条件,结果为0表示假,非0表示真。带关系运算符的表达式常用于循环测试,它们有些特殊。如果关系表达式为真,其值为1;如果为假,其值为0。这与新类型_Bool 的值保持一致。
数组由相邻的内存位置组成,只储存相同类型的数据。记住,数组元素的编号从0开始,所有数组最后一个元素的下标一定比元素数目少1。C编译器不会检查数组下标值是否有效,自己要多留心。
使用函数涉及3个步骤:
• 通过函数原型声明函数;
• 在程序中通过函数调用使用函数;
• 定义函数。函数原型是为了方便编译器查看程序中使用的函数是否正确,函数定义描述了函数如何工作。现代的编程习惯是把程序要素分为接口部分和实现部分,例如函数原型和函数定义。接口部分描述了如何使用一个特性,也就是函数原型所做的;实现部分描述了具体的行为,这正是函数定义所做的。
本章小结
本章的主题是程序控制。C语言为实现结构化的程序提供了许多工
具。while 语句和for 语句提供了入口条件循环。for 语句特别适用于
需要初始化和更新的循环。使用逗号运算符可以在for 循环中初始化和更新多个变量。有些场合也需要使用出口条件循环,C为此提供了do
while 语句。这些循环都使用测试条件来判断是否继续执行下一次迭代。一般而
言,如果对测试表达式求值为非0,则继续执行循环;否则,结束循环。通常,测试条件都是关系表达式(由关系运算符和表达式构成)。表达式的关系为真,则表达式的值为1;如果关系为假,则表达式的值为0。C99新增了_Bool 类型,该类型的变量只能储存1或0,分别表示真或假。除了关系运算符,本章还介绍了其他的组合赋值运算符,如+= 或*=. 这些运算符通过对其左侧运算对象执行算术运算来修改它的值。接下来还简单地介绍了数组。声明数组时,方括号中的值指明了该数
组的元素个数。数组的第1个元素编号为0 ,第2个元素编号为1 ,以此类推。最后,本章演示了如何编写和使用带返回值的函数。
第七章 C控制语句:分支和跳转
本章介绍以下内容:
• 关键字:if 、else 、switch 、continue 、break 、case 、default 、goto
• 运算符:&& 、|| 、?:
• 函数:getchar() 、putchar() 、ctype.h 系列
• 如何使用if 和if else 语句,如何嵌套它们
• 在更复杂的测试表达式中用逻辑运算符组合关系表达式
• C的条件运算符
• switch 语句
• break 、continue 和goto 语句
• 使用C的字符I/O函数:getchar() 和putchar()
• ctype.h 头文件提供的字符分析函数系列
1 | 随着越来越熟悉C,可以尝试用C程序解决一些更复杂的问题。这时候,需要一些方法来控制和组织程序,为此C提供了一些工具。前面已经学过如何在程序中用循环重复执行任务。本章将介绍分支结构(如,if和switch),让程序根据测试条件执行相应的行为。另外,还将介绍C语言的逻辑运算符,使用逻辑运算符能在while或if的条件中测试更多关系。此外,本章还将介绍跳转语句,它将程序流转换到程序的其他部分。学完本章后,读者就可以设计按自己期望方式运行的程序。 |
关键概念
智能的一个方面是,根据情况做出相应的响应。所以,选择语句是开发具有智能行为程序的基础。C语言通过if 、if else 和switch 语句,以及条件运算符(?: )可以实现智能选择。
if 和if else 语句使用测试条件来判断执行哪些语句。所有非零
值都被视为true ,零被视为false 。测试通常涉及关系表达式(比较两
个值)、逻辑表达式(用逻辑运算符组合或更改其他表达式)。要记住一个通用原则,如果要测试两个条件,应该使用逻辑运算符把两个完整的测试表达式组合起来。
本章小结
本章介绍了很多内容,我们来总结一下。if 语句使用测试条件控制
程序是否执行测试条件后面的一条简单语句或复合语句。如果测试表达式的值是非零值,则执行语句;如果测试表达式的值是零,则不执行语句。if else 语句可用于二选一的情况。如果测试条件是非零,则执行
else 前面的语句;如果测试表达式的值是零,则执行else 后面的语句。在else 后面使用另一个if 语句形成else if ,可构造多选一的结构。测试条件通常都是关系表达式 ,即用一个关系运算符(如,<或
==)的表达式。使用C的逻辑运算符,可以把关系表达式组合成更复杂
的测试条件。在多数情况下,用条件运算符 (?: )写成的表达式比if else 语句更简洁。ctype.h 系列的字符函数(如,issapce() 和isalpha() )为创
建以分类字符为基础的测试表达式提供了便捷的工具。switch 语句可以在一系列以整数作为标签的语句中进行选择。如果紧跟在switch 关键字后的测试条件的整数值与某标签匹配,程序就转至执行匹配的标签语句,然后在遇到break 之前,继续执行标签语句后面的语句。break 、continue 和goto 语句都是跳转语句,使程序流跳转至程
序的另一处。break 语句使程序跳转至紧跟在包含break 语句的循环或
switch 末尾的下一条语句。continue 语句使程序跳出当前循环的剩余
部分,并开始下一轮迭代。
第八章 字符输入/输出和输入验证
本章介绍以下内容:
• 更详细地介绍输入、输出以及缓冲输入和无缓冲输入的区别
• 如何通过键盘模拟文件结尾条件
• 如何使用重定向把程序和文件相连接
• 创建更友好的用户界面
1 | 在涉及计算机的话题时,我们经常会提到输入 (input )和输出(output )。我们谈论输入和输出设备(如键盘、U盘、扫描仪和激光打印机),讲解如何处理输入数据和输出数据,讨论执行输入和输出任务的函数。本章主要介绍用于输入和输出的函数(简称I/O函数)。 |
关键概念
C程序把输入作为传入的字节流。getchar() 函数把每个字符解释
成一个字符编码。scanf() 函数以同样的方式看待输入,但是根据转换
说明,它可以把字符输入转换成数值。许多操作系统都提供重定向,允许
用文件代替键盘输入,用文件代替显示器输出。程序通常接受特殊形式的输入。可以在设计程序时考虑用户在输入时可能犯的错误,在输入验证部分处理这些错误情况,让程序更强健更友好。对于一个小型程序,输入验证可能是代码中最复杂的部分。处理这类
问题有多种方案。例如,如果用户输入错误类型的信息,可以终止程序,
也可以给用户提供有限次或无限次机会重新输入。
本章小结
许多程序使用getchar() 逐字符读取输入。通常,系统使用行缓冲
输入 ,即当用户按下Enter 键后输入才被传送给程序。按下Enter 键也传
送了一个换行符,编程时要注意处理这个换行符。ANSI C把缓冲输入作为
标准。通过标准I/O包中的一系列函数,以统一的方式处理不同系统中的不
同文件形式,是C语言的特性之一。getchar() 和scanf() 函数也属于
这一系列。当检测到文件结尾时,这两个函数都返回EOF (被定义在
stdio.h 头文件中)。在不同系统中模拟文件结尾条件的方式稍有不同。在UNIX系统中,在一行开始处按下Ctrl+D 可以模拟文件结尾条件;而在DOS系统中则使用Ctrl+Z 。许多操作系统(包括UNIX和DOS)都有重定向 的特性,因此可以用
文件代替键盘和屏幕进行输入和输出。读到EOF即停止读取的程序可用于
键盘输入和模拟文件结尾信号,或者用于重定向文件。混合使用getchar() 和scanf() 时,如果在调用getchar() 之前,scanf() 在输入行留下一个换行符,会导致一些问题。不过,意识
到这个问题就可以在程序中妥善处理。编写程序时,要认真设计用户界面。事先预料一些用户可能会犯的错
误,然后设计程序妥善处理这些错误情况。
复习题
1.putchar(getchar()) 是一个有效表达式,它实现什么功能?getchar(putchar()) 是否也是有效表达式?
1 | 表达式putchar(getchar()) 使程序读取下一个输入字符并打印出来。getchar()的返回值是putchar()的参数。但getchar(putchar())是无效表达式,因为getchar() 不需要参数而putchar()需要一个参数。 |
2.EOF是什么?
1 | EOF是由getchar() 和 scanf() 返回的信号,表明函数检测到文件结尾。 |
3.C如何处理不同计算机系统中不同文件和换行约定?
1 | C的标准I/O库把不同文件映射为统一的流来统一处理。 |
4.在使用缓冲输入系统中,把数值和字符混合输入会遇到什么潜在问题?
1 | 数值输入会跳过换行符,但是字符输入不会。 |
第九章 函数
本章介绍以下内容:
• 关键字:return
• 运算符:*(一元)、& (一元)
• 函数及其定义方式
• 如何使用参数和返回值
• 如何把指针变量用作函数参数
• 函数类型
• ANSI C原型
• 递归
1 | 如何组织程序?C的设计思想是,把函数用作构件块。我们已经用过C标准库的函数,如printf() 、scanf() 、getchar() 、putchar() 和strlen() 。现在要进一步学习如何创建自己的函数。前面章节中已大致介绍了相关过程,本章将巩固以前学过的知识并做进一步的拓展。 |
关键概念
如果想用C编出高效灵活的程序,必须理解函数。把大型程序组织成
若干函数非常有用,甚至很关键。如果让一个函数处理一个任务,程序会
更好理解,更方便调试。要理解函数是如何把信息从一个函数传递到另一
函数,也就是说,要理解函数参数和返回值的工作原理。另外,要明白函
数形参和其他局部变量都属于函数私有,因此,声明在不同函数中的同名
变量是完全不同的变量。而且,函数无法直接访问其他函数中的变量。这
种限制访问保护了数据的完整性。但是,当确实需要在函数中访问另一个
函数的数据时,可以把指针作为函数的参数。
本章小结
函数可以作为组成大型程序的构件块。每个函数都应该有一个单独且
定义好的功能。使用参数把值传给函数,使用关键字return 把值返回函
数。如果函数返回的值不是int 类型,则必须在函数定义和函数原型中指
定函数的类型。如果需要在被调函数中修改主调函数的变量,使用地址或
指针作为参数。ANSI C提供了一个强大的工具——函数原型 ,允许编译器验证函数
调用中使用的参数个数和类型是否正确。C函数可以调用本身,这种调用方式被称为递归 。一些编程问题要用
递归来解决,但是递归不仅消耗内存多,效率不高,而且费时。
复习题
实际参数和形式参数的区别是什么?
形式参数是定义在被调函数中的变量。实际参数是出现在函数调用中的值,该值被赋给形式参数。可以把实际参数视为在函数调用时初始化形式参数的值。
第十章 数组和指针
本章介绍以下内容:
• 关键字:static
• 运算符:&、* (一元)
• 如何创建并初始化数组
• 指针(在已学过的基础上)、指针和数组的关系
• 编写处理数组的函数
• 二维数组
1 | 人们通常借助计算机完成统计每月的支出、日降雨量、季度销售额等任务。企业借助计算机管理薪资、库存和客户交易记录等。作为程序员,不可避免地要处理大量相关数据。通常,数组能高效便捷地处理这种数据。第 6 章简单地介绍了数组,本章将进一步地学习如何使用数组,着重分析如何编写处理数组的函数。这种函数把模块化编程的优势应用到数组。通过本章的学习,你将明白数组和指针关系密切。 |
关键概念
数组用于储存相同类型的数据。C把数组看作是派生类型 ,因为数组是建立在其他类型的基础上。也就是说,无法简单地声明一个数组。在声明数组时必须说明其元素的类型,如int 类型的数组、float 类型的数组,或其他类型的数组。所谓的其他类型也可以是数组类型,这种情况下,创建的是数组的数组(或称为二维数组)。
通常编写一个函数来处理数组,这样在特定的函数中解决特定的问题,有助于实现程序的模块化。在把数组名作为实际参数时,传递给函数的不是整个数组,而是数组的地址(因此,函数对应的形式参数是指针)。为了处理数组,函数必须知道从何处开始读取数据和要处理多少个数组元素。数组地址提供了“地址”,“元素个数”可以内置在函数中或作为单独的参数传递。第2种方法更普遍,因为这样做可以让同一个函数处理不同大小的数组。
数组和指针的关系密切,同一个操作可以用数组表示法或指针表示法。它们之间的关系允许你在处理数组的函数中使用数组表示法,即使函数的形式参数是一个指针,而不是数组。
对于传统的C数组,必须用常量表达式指明数组的大小,所以数组大小在编译时就已确定。C99/C11新增了变长数组,可以用变量表示数组大
小。这意味着变长数组的大小延迟到程序运行时才确定。
本章小结
数组 是一组数据类型相同的元素。数组元素按顺序储存在内存中,通过整数下标(或索引)可以访问各元素。在C 中,数组首元素的下标是0 ,所以对于内含n 个元素的数组,其最后一个元素的下标是n-1 。作为程序员,要确保使用有效的数组下标,因为编译器和运行的程序都不会检查下标的有效性。
C 把数组名解释为该数组首元素的地址。换言之,数组名与指向该数组首元素的指针等价。概括地说,数组和指针的关系十分密切。如果ar是一个数组,那么表达式ar[i] 和*(ar+i) 等价。对于C 语言而言,不能把整个数组作为参数传递给函数,但是可以传递数组的地址。然后函数可以使用传入的地址操控原始数组。如果函数没有修改原始数组的意图,应在声明函数的形式参数时使用关键字const
。在被调函数中可以使用数组表示法或指针表示法,无论用哪种表示法,实际上使用的都是指针变量。指针加上一个整数或递增指针,指针的值以所指向对象的大小为单位改变。也就是说,如果pd 指向一个数组的8字节double 类型值,那么pd加1 意味着其值加8 ,以便它指向该数组的下一个元素。C 语言传递多维数组的传统方法是把数组名(即数组的地址)传递给类型匹配的指针形参。声明这样的指针形参要指定所有的数组维度,除了第1 个维度。传递的第1 个维度通常作为第2 个参数。
变长数组提供第2种语法,把数组维度作为参数传递。
第十一章 字符串和字符串函数
本章介绍以下内容:
• 函数:gets() 、gets_s() 、fgets() 、puts() 、fputs() 、strcat() 、strncat() 、strcmp() 、strncmp() 、strcpy() 、strncpy() 、sprintf() 、strchr()
• 创建并使用字符串
• 使用C库中的字符和字符串函数,并创建自定义的字符串函数
• 使用命令行参数
1 | 字符串是C语言中最有用、最重要的数据类型之一。虽然我们一直在使用字符串,但是要学的东西还很多。C库提供大量的函数用于读写字符串、拷贝字符串、比较字符串、合并字符串、查找字符串等。通过本章的学习,读者将进一步提高自己的编程水平。 |
关键概念
许多程序都要处理文本数据。一个程序可能要求用户输入姓名、公司列表、地址、一种蕨类植物的学名、音乐剧的演员等。毕竟,我们用言语与现实世界互动,使用文本的例子不计其数。C程序通过字符串的方式来处理它们。
字符串 ,无论是由字符数组、指针还是字符串常量标识,都储存为包含字符编码的一系列字节,并以空字符串结尾。C提供库函数处理字符串,查找字符串并分析它们。尤其要牢记,应该使用strcmp() 来代替关系运算符,当比较字符串时,应该使用strcpy() 或strncpy() 代替赋
值运算符把字符串赋给字符数组。
本章小结
C字符串 是一系列char 类型的字符,以空字符(’\0’ )结尾。字符串可以储存在字符数组中。字符串还可以用字符串常量 来表示,里面都是字符,括在双引号中(空字符除外)。编译器提供空字符。因此,”joy” 被储存为4个字符j 、o 、y 和\0 。strlen() 函数可以统计字符串的长度,空字符不计算在内。
字符串常量也叫作字符串——字面量 ,可用于初始化字符数组。为了容纳末尾的空字符,数组大小应该至少比容纳的数组长度多1。也可以用字符串常量初始指向char 的指针。
函数使用指向字符串首字符的指针来表示待处理的字符串。通常,对应的实际参数是数组名、指针变量或用双引号括起来的字符串。无论是哪种情况,传递的都是首字符的地址。一般而言,没必要传递字符串的长度,因为函数可以通过末尾的空字符确定字符串的结束。
fgets() 函数获取一行输入,puts() 和fputs() 函数显示一行输出。它们都是stdio.h 头文件中的函数,用于代替已被弃用的gets()。
C库中有多个字符串处理 函数。在ANSI C中,这些函数都声明在string.h 文件中。C库中还有许多字符处理 函数,声明在ctype.h 文件中。
给main() 函数提供两个合适的形式参数,可以让程序访问命令行参数。第1个参数通常是int 类型的argc ,其值是命令行的单词数量。第2个参数通常是一个指向数组的指针argv ,数组内含指向char 的指针。每个指向char 的指针都指向一个命令行参数字符串,argv[0] 指向命
令名称,argv[1] 指向第1个命令行参数,以此类推。atoi() 、atol() 和atof() 函数把字符串形式的数字分别转换成int 、long 和double 类型的数字。strtol() 、strtoul() 和strtod() 函数把字符串形式的数字分别转换成long 、unsignedlong 和double 类型的数字。
第12章 存储类别、链接和内存管理
本章介绍以下内容:
• 关键字:auto 、extern 、static 、register 、const \volatile 、restricted 、_Thread_local 、_Atomic
• 函数:rand() 、srand() 、time() 、malloc() 、calloc() 、free()
• 如何确定变量的作用域(可见的范围)和生命期(它存在多长时间)
• 设计更复杂的程序
1 | C语言能让程序员恰到好处地控制程序,这是它的优势之一。程序员通过C的内存管理系统指定变量的作用域和生命期,实现对程序的控制。合理使用内存储存数据是设计程序的一个要点。 |
关键概念
C提供多种管理内存的模型。除了熟悉这些模型外,还要学会如何选择不同的类别。大多数情况下,最好选择自动变量。如果要使用其他类别,应该有充分的理由。通常,使用自动变量、函数形参和返回值进行函数间的通信比使用全局变量安全。但是,保持不变的数据适合用全局变量。
应该尽量理解静态内存、自动内存和动态分配内存的属性。尤其要注意:静态内存的数量在编译时确定;静态数据在载入程序时被载入内存。在程序运行时,自动变量被分配或释放,所以自动变量占用的内存数量随着程序的运行会不断变化。可以把自动内存看作是可重复利用的工作区。动态分配的内存也会增加和减少,但是这个过程由函数调用控制,不是自动进行的。
本章小结
内存用于存储程序中的数据,由存储期、作用域和链接表征。存储期可以是静态的、自动的或动态分配的。如果是静态存储期,在程序开始执行时分配内存,并在程序运行时都存在。如果是自动存储期,在程序进入变量定义所在块时分配变量的内存,在程序离开块时释放内存。如果是动态分配存储期,在调用malloc() (或相关函数)时分配内存,在调用free() 函数时释放内存。
作用域决定程序的哪些部分可以访问某数据。定义在所有函数之外的变量具有文件作用域,对位于该变量声明之后的所有函数可见。定义在块或作为函数形参内的变量具有块作用域,只对该块以及它包含的嵌套块可见。
链接描述定义在程序某翻译单元中的变量可被链接的程度。具有块作用域的变量是局部变量,无链接。具有文件作用域的变量可以是内部链接或外部链接。内部链接意味着只有其定义所在的文件才能使用该变量。外部链接意味着其他文件使用也可以使用该变量。
下面是C的5种存储类别(不包括线程的概念)。
• 自动 ——在块中不带存储类别说明符或带auto 存储类别说明符声明的变量(或作为函数头中的形参)属于自动存储类别,具有自动存储期、块作用域、无链接。如果未初始化自动变量,它的值是未定义的。
• 寄存器 ——在块中带register 存储类别说明符声明的变量(或作为函数头中的形参)属于寄存器存储类别,具有自动存储期、块作用域、无链接,且无法获取其地址。把一个变量声明为寄存器变量即请求编译器将其储存到访问速度最快的区域。如果未初始化寄存器变量,它的值是未定义的。
• 静态、无链接 ——在块中带static 存储类别说明符声明的变量属于“静态、无链接”存储类别,具有静态存储期、块作用域、无链接。只在编译时被初始化一次。如果未显式初始化,它的字节都被设置为0 。
• 静态、外部链接 ——在所有函数外部且没有使用static 存储类别说明符声明的变量属于“静态、外部链接”存储类别,具有静态存储期、文件作用域、外部链接。只能在编译器被初始化一次。如果未显式初始化,它的字节都被设置为0 。
• 静态、内部链接 ——在所有函数外部且使用了static 存储类别说明符声明的变量属于“静态、内部链接”存储类别,具有静态存储期、文件作用域、内部链接。只能在编译器被初始化一次。如果未显式初始化,它的字节都被设置为0 。动态分配的内存由malloc() (或相关)函数分配,该函数返回一个指向指定字节数内存块的指针。这块内存被free() 函数释放后便可重复使用,free() 函数以该内存块的地址作为参数。
类型限定符const 、volatile 、restrict 和_Atomic 。const限定符限定数据在程序运行时不能改变。对指针使用const 时,可限定指针本身不能改变或指针指向的数据不能改变,这取决于const 在指针声明中的位置。volatile 限定符表明,限定的数据除了被当前程序修改外还可以被其他进程修改。该限定符的目的是警告编译器不要进行假定的优化。restrict 限定符也是为了方便编译器设置优化方案。restrict限定的指针是访问它所指向数据的唯一途径。
第13章 文件输入/输出
本章介绍以下内容:
• 函数:fopen() 、getc() 、putc() 、exit() 、fclose()
fprintf() 、fscanf() 、fgets() 、fputs()
rewind() 、fseek() 、ftell() 、fflush()
fgetpos() 、fsetpos() 、feof() 、ferror()
ungetc() 、setvbuf() 、fread() 、fwrite()
• 如何使用C标准I/O系列的函数处理文件
• 文件模式和二进制模式、文本和二进制格式、缓冲和无缓冲I/O
• 使用既可以顺序访问文件也可以随机访问文件的函数
1 | 文件是当今计算机系统不可或缺的部分。文件用于储存程序、文档、数据、书信、表格、图形、照片、视频和许多其他种类的信息。作为程序员,必须会编写创建文件和从文件读写数据的程序。本章将介绍相关的内容。 |
关键概念
C程序把输入看作是字节流,输入流来源于文件、输入设备(如键
盘),或者甚至是另一个程序的输出。类似地,C 程序把输出也看作是字
节流,输出流的目的地可以是文件、视频显示等。
C如何解释输入流或输出流取决于所使用的输入/ 输出函数。程序可以不做任何改动地读取和存储字节,或者把字节依次解释成字符,随后可以把这些字符解释成普通文本以用文本表示数字。类似地,对于输出,所使用的函数决定了二进制值是被原样转移,还是被转换成文本或以文本表示数字。如果要在不损失精度的前提下保存或恢复数值数据,请使用二进制模式以及fread() 和fwrite() 函数。如果打算保存文本信息并创建能在普通文本编辑器查看的文本,请使用文本模式和函数(如getc() 和fprintf() )。
要访问文件,必须创建文件指针(类型是FILE * )并把指针与特定文件名相关联。随后的代码就可以使用这个指针(而不是文件名)来处理该文件。
要重点理解C如何处理文件结尾。通常,用于读取文件的程序使用一
个循环读取输入,直至到达文件结尾。C输入函数在读过文件结尾后才会
检测到文件结尾,这意味着应该在尝试读取之后立即判断是否是文件结尾。
本章小结
对于大多数C 程序而言,写入文件和读取文件必不可少。为此,绝大
多数C实现都提供底层I/O和标准高级I/O。因为ANSI C库考虑到可移植性,包含了标准I/O包,但是未提供底层I/O。
标准I/O包自动创建输入和输出缓冲区以加快数据传输。fopen() 函数为标准I/O打开一个文件,并创建一个用于存储文件和缓冲区信息的结构。fopen() 函数返回指向该结构的指针,其他函数可以使用该指针指定待处理的文件。feof() 和ferror() 函数报告I/O操作失败的原因。
C把输入视为字节流。如果使用fread() 函数,C 把输入看作是二进制值并将其储存在指定存储位置。如果使用fscanf() 、getc() 、fgets() 或其他相关函数,C 则将每个字节看作是字符码。然后fscanf() 和scanf() 函数尝试把字符码翻译成转换说明指定的其他类型。例如,输入一个值23 ,%f 转换说明会把23 翻译成一个浮点值,%d转换说明会把23 翻译成一个整数值,%s 转换说明则会把23 储存为字符串。getc() 和fgetc() 系列函数把输入作为字符码储存,将其作为单独的字符保存在字符变量中或作为字符串储存在字符数组中。类似地,fwrite() 将二进制数据直接放入输出流,而其他输出函数把非字符数据转换成用字符表示后才将其放入输出流。
ANSI C提供两种文件打开模式:二进制和文本。以二进制模式打开文
件时,可以逐字节读取文件;以文本模式打开文件时,会把文件内容从文
本的系统表示法映射为C表示法。对于UNIX和Linux系统,这两种模式完全相同。
通常,输入函数getc() 、fgets() 、fscanf() 和fread() 都从文件开始处按顺序读取文件。然而,fseek() 和ftell() 函数让程序可以随机访问文件中的任意位置。fgetpos() 和fsetpos() 把类似的功能扩展至更大的文件。与文本模式相比,二进制模式更容易进行随机访问。
第14章 结构和其它数据形式
本章介绍以下内容:
• 关键字:struct 、union 、typedef
• 运算符:. 、->
• 什么是C结构,如何创建结构模板和结构变量
• 如何访问结构的成员,如何编写处理结构的函数
• 联合和指向函数的指针
1 | 设计程序时,最重要的步骤之一是选择表示数据的方法。在许多情况下,简单变量甚至是数组还不够。为此,C提供了结构变量(structure variable )提高你表示数据的能力,它能让你创造新的形式。如果熟悉Pascal的记录(record ),应该很容易理解结构。如果不懂Pascal也没关系,本章将详细介绍C结构。我们先通过一个示例来分析为何需要C结构,学习如何创建和使用结构。 |
关键概念
我们在编程中要表示的信息通常不只是一个数字或一些列数字。程序可能要处理具有多种属性的实体。例如,通过姓名、地址、电话号码和其他信息表示一名客户;或者,通过电影名、发行人、播放时长、售价等表示一部电影DVD。C结构可以把这些信息都放在一个单元内。在组织程序
时这很重要,因为这样可以把相关的信息都储存在一处,而不是分散储存在多个变量中。
设计结构时,开发一个与之配套的函数包通常很有用。例如,写一个以结构(或结构的地址)为参数的函数打印结构内容,比用一堆printf() 语句强得多。因为只需要一个参数就能打印结构中的所有信息。如果把信息放到零散的变量中,每个部分都需要一个参数。另外,如果要在结构中增加一个成员,只需重写函数,不必改写函数调用。这在修改结构时很方便。
联合声明与结构声明类似。但是,联合的成员共享相同的存储空间,而且在联合中同一时间内只能有一个成员。实质上,可以在联合变量中储存一个类型不唯一的值。enum 工具提供一种定义符号常量的方法,typedef 工具提供一种为基本或派生类型创建新标识符的方法。指向函数的指针提供一种告诉函数应使用哪一个函数的方法。
本章小结
C结构提供在相同的数据对象中储存多个不同类型数据项的方法。可以使用标记来标识一个具体的结构模板,并声明该类型的变量。通过成员点运算符(. )可以使用结构模版中的标签来访问结构的各个成员。
如果有一个指向结构的指针,可以用该指针和间接成员运算符(->)代替结构名和点运算符来访问结构的各成员。和数组不同,结构名不是结构的地址,要在结构名前使用& 运算符才能获得结构的地址。
一贯以来,与结构相关的函数都使用指向结构的指针作为参数。现在的C允许把结构作为参数传递,作为返回值和同类型结构之间赋值。然而,传递结构的地址通常更有效。联合使用与结构相同的语法。然而,联合的成员共享一个共同的存储空间。联合同一时间内只能储存一个单独的数据项,不像结构那样同时储存多种数据类型。也就是说,结构可以同时储存一个int 类型数据、一个
double 类型数据和一个char 类型数据,而相应的联合只能保存一个int 类型数据,或者一个double 类型数据,或者一个char 类型数据。
通过枚举可以创建一系列代表整型常量(枚举常量)的符号和定义相
关联的枚举类型。
typedef 工具可用于建立C标准类型的别名或缩写。
函数名代表函数的地址,可以把函数的地址作为参数传递给其他函数,然后这些函数就可以使用被指向的函数。如果把特定函数的地址赋给一个名为pf 的函数指针,可以通过以下两种方式调用该函数:
1 |
|
第十五章 位操作
本章介绍以下内容:
• 运算符:~、& 、| 、^ 、<< 、>>
&= 、|= 、^= 、>>= 、<<=
• 二进制、十进制和十六进制记数法(复习)
• 处理一个值中的位的两个C工具:位运算符和位字段
• 关键字:_Alignas 、_Alignof
1 | 在C语言中,可以单独操控变量中的位。读者可能好奇,竟然有人想这样做。有时必须单独操控位,而且非常有用。例如,通常向硬件设备发送一两个字节来控制这些设备,其中每个位(bit )都有特定的含义。另外,与文件相关的操作系统信息经常被储存,通过使用特定位表明特定 |
关键概念
C区别于许多高级语言的特性之一是访问整数中单独位的能力。该特性通常是与硬件设备和操作系统交互的关键。C有两种访问位的方法。一种方法是通过按位运算符,另一种方法是
在结构中创建位字段。C11新增了检查内存对齐要求的功能,而且可以指定比基本对齐值更
大的对齐值。通常(但不总是),使用这些特性的程序仅限于特定的硬件平台或操
作系统,而且设计为不可移植的。
本章小结
计算硬件与二进制记数系统密不可分,因为二进制数的1和0可用于表示计算机内存和寄存器中位的开闭状态。虽然C不允许以二进制形式书写数字,但是它识别与二进制相关的八进制和十六进制记数法。正如每个二进制数字表示1位一样,每个八进制位代表3位,每个十六进制位代表4
位。这种关系使得二进制转为八进制或十六进制较为简单。
C 提供多种按位运算符,之所以称为按位是因为它们单独操作一个值中的每个位。~运算符将其运算对象的每一位取反,将1 转为0 ,0 转为1 。按位与运算符(& )通过两个运算对象形成一个值。如果两运算对象中相同号位都为1 ,那么该值中对应的位为1 ;否则,该位为0 。按位或运算符(| )同样通过两个运算对象形成一个值。如果两运算对象中相同号位有一个为1 或都为1 ,那么该值中对应的位为1 ;否则,该位为0 。按位异或运算符(^ )也有类似的操作,只有两运算对象中相同号位有一个为1 时,结果值中对应的位才为1 。
C还有左移(<< )和右移(>> )运算符。这两个运算符使位组合中的所有位都向左或向右移动指定数量的位,以形成一个新值。对于左移运算符,空出的位置设为0 。对于右移运算符,如果是无符号类型的值,空出的位设为0 ;如果是有符号类型的值,右移运算符的行为取决于实现。可以在结构中使用位字段操控一个值中的单独位或多组位。具体细节因实现而异。可以使用_Alignas 强制执行数据存储区上的对齐要求。
这些位工具帮助C 程序处理硬件问题,因此它们通常用于依赖实现的
场合中。
第十六章 C预处理器和C库
本章介绍以下内容:
• 预处理指令:#define 、#include 、#ifdef 、#else 、#endif 、#ifndef 、
#if 、#elif 、#line 、#error 、#pragma
• 关键字:_Generic 、_Noreturn 、_Static_assert
• 函数/宏:sqrt() 、atan() 、atan2() 、exit() 、atexit() 、assert() 、
memcpy() 、memmove() 、va_start() 、va_arg() 、va_copy() 、
va_end()
• C预处理器的其他功能
• 通用选择表达式
• 内联函数
• C库概述和一些特殊用途的方便函数
1 | C语言建立在适当的关键字、表达式、语句以及使用它们的规则上。然而,C标准不仅描述C语言,还描述如何执行C预处理器、C标准库有哪些函数,以及详述这些函数的工作原理。本章将介绍C预处理器和C库, |
关键概念
C标准不仅描述C语言,还描述了组成C语言的软件包、C预处理器和C标准库。通过预处理器可以控制编译过程、列出要替换的内容、指明要编译的代码行和影响编译器其他方面的行为。C库扩展了C语言的作用范围,为许多编程问题提供现成的解决方案。
本章小结
C预处理器和C库是C语言的两个重要的附件。C预处理器遵循预处理器指令,在编译源代码之前调整源代码。C库提供许多有助于完成各种任务的函数,包括输入、输出、文件处理、内存管理、排序与搜索、数学运算、字符串处理等。
第十七章 高级数据表示
本章介绍以下内容:
• 函数:进一步学习malloc()
• 用C表示不同类型的数据
• 新的算法,从概念上增强开发程序的能力
• 抽象数据类型(ADT)
1 | 学习计算机语言和学习音乐、木工或工程学一样。首先,要学会使用工具:学习如何演奏音阶、如何使用锤子等,然后解决各种问题,如降落、滑行以及平衡物体之类。到目前为止,读者一直在本书中学习和练习各种编程技能,如创建变量、结构、函数等。然而,如果想提高到更高层次时,工具是次要的,真正的挑战是设计和创建一个项目。本章将重点介绍这个更高的层次,教会读者如何把项目看作一个整体。本章涉及的内容可能比较难,但是这些内容非常有价值,将帮助读者从编程新手成长为老手。 |
关键概念
一种数据类型通过以下几点来表征:如何构建数据、如何储存数据、有哪些可能的操作。抽象数据类型(ADT)以抽象的方式指定构成某种类型特征的属性和操作。从概念上看,可以分两步把ADT 翻译成一种特定的编程语言。第1步是定义编程接口。在C中,通过使用头文件定义类型名,并提供与允许的操作相应的函数原型来实现。第2步是实现接口。在C中,可以用源代码文件提供与函数原型相应的函数定义来实现。
本章小结
链表、队列和二叉树是ADT在计算机程序设计中常用的示例。通常用动态内存分配和链式结构来实现它们,但有时用数组来实现会更好。
当使用一种特定类型(如队列或树)进行编程时,要根据类型接口来编写程序。这样,在修改或改进实现时就不用更改使用接口的那些程序。
参考资料
- 参考资料I:补充阅读
- 参考资料II:C运算符
- 参考资料III:基本类型和存储类别
- 参考资料IV:表达式、语句和程序流
- 参考资料V:新增了C99和C11的标准ANSI C库
- 参考资料VI:扩展的整数类型
- 参考资料VII:扩展的字符支持
- 参考资料VIII:C99/C11数值计算增强
- 参考资料IX:C与C++的区别
补充阅读
如果有一些与C 语言相关的问题或只是想扩展你的知识,可以浏览CFAQ (常见问题解答)的站点:c-faq.com