1 C语言概述
1.7 简单的C程序介绍
需要说明的是,C 语言规定对 scanf
和 printf
这两个函数可以省去对其头文件的包含命令。所以在本例中 也可以删去第二行的包含命令#include<stdio.h>
。
C语言规定,源程序中所有用到的变量都必须先说明, 后使用,否则将会出错。说明部分是 C 源程序结构中很重要的组成部分。
1.9 C源程序的结构特点
- 一个C语言源程序可以由一个或多个源文件组成。
- 每个源文件可由一个或多个函数组成。
- 一个源程序不论由多少个文件组成,都有一个且只能有一个
main
函数,即主函数。 - 源程序中可以有预处理命令(
include
命令仅为其中的一种),预处理命令通常应放在源文件或源程序的最前面。 - 每一个说明,每一个语句都必须以分号结尾。但预处理命令,函数头和花括号“}”之后不能加分号。
- 标识符,关键字之间必须至少加一个空格以示间隔。若已有明显的间隔符,也可不再加空格来间隔。
1.12 C语言词汇
C 规定,标识符只能是字母(A~Z,a~z)、数字(0~9)、下划线(_)组成的字符串,并且其第一个 字符必须是字母或下划线。
标准 C 不限制标识符的长度,但它受各种版本的 C 语言编译系统限制,同时也受到具体机器的限制。
例如在某版本 C 中规定标识符前八位有效,当两个标识符前八位相同时,则被认为是同一个标识符。
在标识符中,大小写是有区别的。
2 程序的灵魂——算法
一个程序应包括:
- 对数据的描述。在程序中要指定数据的类型和数据的组织形式,即数据结构(data structure)。
- 对操作的描述。即操作步骤,也就是算法(algorithm)。
Nikiklaus Wirth 提出的公式:
教材认为:
2.3 算法的特性
- 有穷性:一个算法应包含有限的操作步骤而不能是无限的。
- 确定性:算法中每一个步骤应当是确定的,而不能应当是含糊的、模棱两可的。
- 有零个或多个输入。
- 有一个或多个输出。
- 有效性:算法中每一个步骤应当能有效地执行,并得到确定的结果。
2.4 怎样表示一个算法
2.4.3 三种基本结构和改进的流程图
三种基本结构的共同特点:
- 只有一个入口;
- 只有一个出口;
- 结构内的每一部分都有机会被执行到;
- 结构内不存在“死循环”。
2.5 结构化程序设计方法
- 自顶向下;
- 逐步细化;
- 模块化设计;
- 结构化编码。
3 数据类型、运算符与表达式
3.1 C语言的数据类型
在C语言中,数据类型可分为:基本数据类型,构造数据类型,指针类型,空类型四大类。
基本数据类型:基本数据类型最主要的特点是,其值不可以再分解为其它类型。
构造数据类型:构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或又是一个构造类型。在 C 语言中,构造类型有以下几种:
- 数组类型
- 结构体类型
- 共用体(联合)类型
3.2 常量与变量
3.2.1 常量和符号常量
用标识符代表一个常量,称为符号常量。
符号常量与变量不同,它的值在其作用域内不能改变,也不能再被赋值。
使用符号常量的好处是:
- 含义清楚;
- 能做到“一改全改”。
符号常量的本质相当于一个文本题换的占位符,在与处理的时候编译器会将标识符替换为常量
3.3 整型数据
3.3.1 整型常量的表示方法
八进制整常数:
八进制整常数必须以 0 开头,即以 0 作为八进制数的前缀。数码取值为 0~7。八进制数通常是无符号数。
十六进制整常数:
十六进制整常数的前缀为 0X 或 0x。其数码取值为 0~9,A~F 或 a~f。
整型常数的后缀:
十进制无符号整常数的范围为 0~65535,有符号数为 -32768~+32767。八进制无符号数的 表示范围为 0~0177777。十六进制无符号数的表示范围为0X0~0XFFFF 或 0x0~0xFFFF。
长整型数是用后缀“L”或“l”来表示的。
无符号数也可用后缀表示,整型常数的无符号数的后缀为“U”或“u”。
前缀,后缀可同时使用以表示各种类型的数。
如 0XA5Lu 表示十六进制无符号长整数 A5,其十进制为 165。
3.3.2 整型变量
1.整型数据在内存中的存放形式
数值是以补码表示的:
- 正数的补码和原码相同;
- 负数的补码:将该数的绝对值的二进制形式按位取反再加1。
左面的第一位是表示符号的。
2.整型变量的分类
1) 基本型:类型说明符为 int
,在内存中占 2 个字节。
2) 短整量:类型说明符为 short int
或 short
。所占字节和取值范围均与基本型相同。
3) 长整型:类型说明符为 long int
或 long
,在内存中占 4 个字节。
4) 无符号型:类型说明符为 unsigned
。
无符号型又可与上述三种类型匹配而构成:
- 无符号基本型:类型说明符为
unsignedint
或unsigned
。 - 无符号短整型:类型说明符为
unsigned short
。 - 无符号长整型:类型说明符为
unsigned long
。
各种无符号类型量所占的内存空间字节数与相应的有符号类型量相同。但由于省去了符号位,故不能表示负数。
3.整型变量的定义
在书写变量定义时,应注意以下几点:
- 允许在一个类型说明符后,定义多个相同类型的变量。各变量名之间用逗号间隔。类型说明符与变量名之间至少用一个空格间隔。
- 最后一个变量名之后必须以“;”号结尾。
- 变量定义必须放在变量使用之前。一般放在函数体的开头部分。
3.4 实型数据
3.4.1 实型常量的表示方法
十进制数形式:由数码 0~ 9 和小数点组成。注意,必须有小数点。
指数形式:由十进制数,加阶码标志“e”或“E”以及阶码(只能为整数,可以带符号)组成。一般形式为:
a E n(a 为十进制数,n 为十进制整数)
其值为 $a*10^n$ 。
标准C允许浮点数使用后缀。后缀为“f”或“F”即表示该数为浮点数。如 356f 和 356.是等价的。
3.4.3 实型常数的类型
实型常数不分单、双精度,都按双精度 double 型处理。
3.5 字符型数据
3.5.1 字符常量
字符常量是用单引号括起来的一个字符。
在C语言中,字符常量有以下特点:
- 字符常量只能用单引号括起来,不能用双引号或其它括号。
- 字符常量只能是单个字符,不能是字符串。
- 字符可以是字符集中任意字符。但数字被定义为字符型之后就不能参与数值运算。如’5’和 5 是不同的。’5’是字符常量,不能参与运算。
3.5.2 转义字符
常用的转义字符及其含义:
转义字符 | 转义字符的意义 | ASCII代码 |
---|---|---|
\\n | 回车换行 | 10 |
\\t | 横向跳到下一制表位置 | 9 |
\\b | 退格 | 8 |
\\r | 回车 | 13 |
\\f | 走纸换页 | 12 |
\\\\ | 反斜线符”\\” | 92 |
\\’ | 单引号符 | 39 |
\\” | 双引号符 | 34 |
\\a | 鸣铃 | 7 |
\\ddd | 1\~3为八进制数所代表的字符 | |
\\xhh | 1\~2位十六进制数所代表的字符 |
广义地讲,C语言字符集中的任何一个字符均可用转义字符来表示。表中的\\ddd 和\\xhh 正是为此而提出 的。ddd 和 hh 分别为八进制和十六进制的 ASCII 代码。
3.5.4 字符数据在内存中的存储形式及使用方法
C语言允许对整型变量赋以字符值,也允许对字符变量赋以整型值。 在输出时,允许把字符变量按整型量输出,也允许把整型量按字符量输出。
整型量为二字节量,字符量为单字节量,当整型量按字符型量处理时,只有低八位字节参与处理。
3.5.5 字符串常量
字符串常量和字符常量是不同的量。它们之间主要有以下区别:
- 字符常量由单引号括起来,字符串常量由双引号括起来。
- 字符常量只能是单个字符,字符串常量则可以含一个或多个字符。
- 可以把一个字符常量赋予一个字符变量,但不能把一个字符串常量赋予一个字符变量。在C语言中没有相应的字符串变量。但是可以用一个字符数组来存放一个字符串常量。
- 字符常量占一个字节的内存空间。字符串常量占的内存字节数等于字符串中字节数加 1。增加的一个字节中存放字符”\\0” (ASCII 码为 0)。这是字符串结束的标志。
3.6 变量赋初值
应注意,在定义中不允许连续赋值,如 a=b=c=5
是不合法的。
3.7 各类数值型数据之间的混合运算
自动转换遵循以下规则:
- 若参与运算量的类型不同,则先转换成同一类型,然后进行运算。
- 转换按数据长度增加的方向进行,以保证精度不降低。如
int
型和long
型运算时,先把int
量转成long
型后再进行运算。 - 所有的浮点运算都是以双精度进行的,即使仅含
float
单精度量运算的表达式,也要先转换成double
型,再作运算。 char
型和short
型参与运算时,必须先转换成int
型。在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边量的类型将转换为左边量的类型。如果右边量的数据类型长度左边长时,将丢失一部分数据,这样会降低精度,丢失的部分按四舍五入向前舍入。
强制类型转换
- 类型说明符和表达式都必须加括号(单个变量可以不加括号)
- 无论是强制转换或是自动转换,都只是为了本次运算的需要而对变量的数据长度进行的临时性转换,而不改变数据说明时对该变量定义的类型。
3.9 赋值运算符和赋值表达式
3.复合的赋值运算符
复合赋值符这种写法,对初学者可能不习惯,但十分有利于编译处理,能提高编译效率并产生质量较高的目标代码。
3.10 逗号运算符和逗号表达式
表达式 1,表达式 2,…表达式 n
整个逗号表达式的值等于表达式 n 的值。
并不是在所有出现逗号的地方都组成逗号表达式,如在变量说明中,函数参数表中逗号只是用作各变量之间的间隔符。
3.11 小结
3.11.2 基本类型的分类及特点
类型说明符 | 字节 | 数值范围 | |
---|---|---|---|
字符型 | char | 1 | C字符集 |
基本整型 | int | 2 | -32768~32767 |
短整型 | short int | 2 | -32768~32767 |
长整型 | long int | 4 | -214783648~214783647 |
无符号整型 | unsigned | 2 | 0~65535 |
无符号长整型 | unsigned long | 4 | 0~4294967295 |
单精度实型 | float | 4 | 3/4E-38~3/4E+38 |
双精度实型 | double | 8 | 1/7E-308~1/7E+308 |
4 最简单的C程序设计—顺序程序设计
从程序流程的角度来看,程序可以分为三种基本结构, 即顺序结构、分支结构、循环结构。 这三种基 本结构可以组成所有的各种复杂程序。
4.1 C语句概述
C 语句可分为以下五类:
- 表达式语句:表达式语句由表达式加上分号“;”组成。
- 函数调用语句:由函数名、实际参数加上分号“;”组成。
- 控制语句:控制语句用于控制程序的流程,以实现程序的各种结构方式。它们由特定的语句定义符组成。
- 条件判断语句:if语句、switch语句;
- 循环执行语句:dowhile语句、while语句、for语句;
- 转向语句:break语句、goto语句、continue语句、return语句。
- 复合语句:把多个语句用括号{}括起来组成的一个语句称复合语句。
- 空语句:只有分号“;”组成的语句称为空语句。空语句是什么也不执行的语句。在程序中空语句可用来作空循环体。
4.2 赋值语句
注意:
- 由于在赋值符“=”右边的表达式也可以又是一个赋值表达式。
- 注意在变量说明中给变量赋初值和赋值语句的区别。给变量赋初值是变量说明的一部分,赋初值后的变量与其后的其它同类变量之间仍必须用逗号间隔,而赋值语句则必须用分号结尾。
- 在变量说明中,不允许连续给多个变量赋初值。
- 注意赋值表达式和赋值语句的区别。赋值表达式是一种表达式,它可以出现在任何允许表达式出现的地方,而赋值语句则不能。
4.3 数据输入输出的概念及在C语言中的实现
考虑到printf
和scanf
函数使用频繁,系统允许在使用这两个函数时可不加
1 |
或
1 |
4.4 字符数据的输入输出
4.4.1 putchar 函数(字符输出函数)
putchar
函数是字符输出函数,其功能是在显示器上输出单个字符。
对控制字符则执行控制功能,不在屏幕上显示。
使用本函数前必须要用文件包含命令:
1 |
或
1 |
4.4.2 getchar函数(键盘输入函数)
getchar
函数的功能是从键盘上输入一个字符。
通常把输入的字符赋予一个字符变量,构成赋值语句。
使用 getchar
函数还应注意几个问题:
getchar
函数只能接受单个字符,输入数字也按字符处理。输入多于一个字符时,只接收第一个字符。- 使用本函数前必须包含文件“
stdio.h
”。
4.5 格式输入与输出
4.5.1 printf函数(格式输出函数)
格式字符串
格式字符串的一般形式为:
1 | [标志][输出最小宽度][.精度][长度]类型 |
其中方括号[]
中的项为可选项。
各项的意义介绍如下:
1) 类型:类型字符用以表示输出数据的类型,其格式符和意义如下表所示:
格式字符 | 意义 |
---|---|
d | 以十进制形式输出带符号整数(正数不输出符号) |
o | 以八进制形式输出无符号整数(不输出前缀 0) |
x,X | 以十六进制形式输出无符号整数(不输出前缀 0x) |
u | 以十进制形式输出无符号整数 |
f | 以小数形式输出单、双精度实数 |
e,E | 以指数形式输出单、双精度实数 |
g,G | 以%f 或%e 中较短的输出宽度输出单、双精度实数 |
c | 输出单个字符 |
s | 输出字符串 |
2) 标志:标志字符为-、+、#、空格四种,其意义下表所示:
标志 | 意义 |
---|---|
- | 结果左对齐,右边填空格 |
+ | 输出符号(正号或负号) |
空格 | 输出值为正时冠以空格,为负时冠以负号 |
# | 对 c,s,d,u 类无影响;对 o 类,在输出时加前缀 o;对 x 类,在输出时加前缀 0x;对 e,g,f 类当结果有小数时才给出小数点 |
3) 输出最小宽度:用十进制整数来表示输出的最少位数。若实际位数多于定义的宽度,则按实际位数输
出,若实际位数少于定义的宽度则补以空格或 0。
4) 精度:精度格式符以“.
”开头,后跟十进制整数。本项的意义是:如果输出数字,则表示小数的位数;
如果输出的是字符,则表示输出字符的个数;若实际位数大于所定义的精度数,则截去超过的部分。
5) 长度:长度格式符为 h,l
两种,h
表示按短整型量输出,l
表示按长整型量输出。
4.5.2 scanf函数(格式输入函数)
格式字符串
格式字符串的一般形式为:
1 | %[*][输入数据宽度][长度]类型 |
其中有方括号[]
的项为任选项。各项的意义如下:
1) 类型:表示输入数据的类型,其格式符和意义如下表所示。
格式 | d |
---|---|
o | 字符意义 |
x | 输入十进制整数 |
u | 输入八进制整数 |
f 或 e | 输入十六进制整数 输入无符号十进制整数 输入实型数(用小数形式或指数形式) |
c | 输入单个字符 |
s | 输入字符串 |
2) “*
”符:用以表示该输入项,读入后不赋予相应的变量,即跳过该输入值。
如:
1 | scanf("%d %*d %d",&a,&b); |
当输入为:1 2 3
时,把1
赋予a
,2
被跳过,3
赋予b
。
3) 宽度:用十进制整数指定输入的宽度(即字符数)。
例如:
1 | scanf("%5d",&a); |
输入:12345678
只把 12345
赋予变量 a
,其余部分被截去。
又如:
1 | scanf("%4d%4d",&a,&b); |
输入:12345678
将把 1234
赋予 a
,而把 5678
赋予 b
。
4) 长度:长度格式符为l
和h
,l
表示输入长整型数据(如%ld
)和双精度浮点数(如%lf
)。h
表示输入短整型数据。
使用 scanf 函数还必须注意以下几点:
1) scanf
函数中没有精度控制,如:scanf("%5.2f",&a);
是非法的。不能企图用此语句输入小数为 2 位的实数。
2) scanf
中要求给出变量地址,如给出变量名则会出错。如 scanf("%d",a);
是非法的,应改为scnaf("%d",&a);
才是合法的。
3) 在输入多个数值数据时,若格式控制串中没有非格式字符作输入数据之间的间隔则可用空格,TAB或回车作间隔。C 编译在碰到空格,TAB,回车或非法数据(如对“%d
”输入“12A
”时,A
即为非法数据)时即认为该数据结束。
4) 在输入字符数据时,若格式控制串中无非格式字符,则认为所有输入的字符均为有效字符。
例如:
1 | scanf("%c%c%c",&a,&b,&c); |
输入为:d e f
则把’d
‘赋予 a
, ‘`' 赋予
b,'
e'赋予
c。
  只有当输入为:
def  时,才能把'
d'赋于
a,'
e'赋予
b,'
f'赋予
c`。
如果在格式控制中加入空格作为间隔, 如:
1 | scanf ("%c %c %c",&a,&b,&c); |
则输入时各数据之间可加空格。
5 分支结构程序
5.1 关系运算符和表达式
5.1.1 关系运算符及其优先次序
在六个关系运算符中,<,<=,>,>=
的优先级相同,高于==
和!=
,==
和!=
的优先级相同。
5.3 if语句
5.3.2 if语句的嵌套
C语言规定,else
总是与它前面最近的 if
配对。
5.3.3 条件运算符和条件表达式
使用条件表达式时,还应注意以下几点:
1) 条件运算符的运算优先级低于关系运算符和算术运算符,但高于赋值符。
因此max=(a>b)?a:b
可以去掉括号而写为 max=a>b?a:b
2) 条件运算符?和:是一对运算符,不能分开单独使用。
3) 条件运算符的结合方向是自右至左。
例如: a>b?a:c>d?c:d
应理解为 a>b?a:(c>d?c:d)
6 循环
6.2 goto语句以及用goto语句构成循环
goto
语句是一种无条件转移语句, 与 BASIC 中的 goto
语句相似。goto
语句的使用格式为:goto 语句标号;
其中标号是一个有效的标识符,这个标识符加上一个“:
”一起出现在函数内某处, 执行goto
语句后,程序 将跳转到该标号处并执行其后的语句。另外标号必须与 goto
语句同处于一个函数中,但可以不在一个循环层 中。通常 goto
语句与 if
条件语句连用, 当满足某一条件时, 程序跳到标号处运行。
goto
语句通常不用,主要因为它将使程序层次不清,且不易读,但在多层嵌套退出时, 用goto
语句则比较合理。
6.7 几种循环的比较
- 四种循环都可以用来处理同一个问题,一般可以互相代替。但一般不提倡用
goto
型循环。 while
和do-while
循环,循环体中应包括使循环趋于结束的语句。for 语句功能最强。- 用
while
和do-while
循环时,循环变量初始化的操作应在while
和do-while
语句之前完成,而for
语句可以在表达式 1 中实现循环变量的初始化。
6.8 break和continue语句
6.8.1 break语句
注意:
break
语句对if-else
的条件语句不起作用。- 在多层循环中,一个
break
语句只向外跳一层。
7 数组
7.1 一维数组的定义和引用
7.1.2 一维数组元素的引用
下标只能为整型常量或整型表达式。如为小数时,C 编译将自动取整。
7.1.3 一维数组的初始化
数组初始化是在编译阶段进行的。这样将减少 运行时间,提高效率。
7.2 二维数组的定义和引用
7.2.3 二维数组的初始化
二维数组可按行分段赋值,也可按行连续赋值。
例如对数组 a[5][3]
:
1) 按行分段赋值可写为:
1 | int a[5][3]={ {80,75,92},{61,65,71},{59,63,70},{85,87,90},{76,77,85} }; |
2) 按行连续赋值可写为:
1 | int a[5][3]={ 80,75,92,61,65,71,59,63,70,85,87,90,76,77,85}; |
这两种赋初值的结果是完全相同的。
对于二维数组初始化赋值还有以下说明:
- 可以只对部分元素赋初值,未赋初值的元素自动取0值。
- 如对全部元素赋初值,则第一维的长度可以不给出。
7.3 字符数组
7.3.6 字符串处理函数
gets
函数并不以空格作为字符串输 入结束的标志,而只以回车作为输入结束。
8函数
8.1 概述
在C语言中,所有的函数定义,包括主函数 main
在内,都是平行的。也就是说,在一 个函数的函数体内,不能再定义另一个函数,即不能嵌套定义。
8.3 函数的参数和函数的值
8.3.2 函数的返回值
如函数值为整型,在函数定义时可以省去类型说明。
8.7 数组作为函数参数
形参数组和实参数组的长度可以不相同,因为在调用时,只传送首地址而不检查形参数组的长度。当形参数组的长度与实参数组不一致时,虽不至于出现语法错误(编译能通过),但程序执行结果将与实际不符,这是应予以注意的。
在函数形参表中,允许不给出形参数组的长度,或用一个变量来表示数组元素的个数。
多维数组也可以作为函数的参数。在函数定义时对形参数组可以指定每一维的长度,也可省去 第一维的长度。
8.8 局部变量和全局变量
8.8.2 全局变量
全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。在函数中使用全局变量,一般应作全局变量说明。 只有在函数内经过说明的全局变量才能使用。全局变量的说明符为extern
。但在一个函数之前定义的全局变量,在该函数内使用可不再加以 说明。
8.9 变量的存储类别
8.9.1 动态存储方式与静态动态存储方式
动态存储区存放以下数据:
- 函数形式参数;
- 自动变量(未加
static
声明的局部变量); - 函数调用实的现场保护和返回地址;
8.9.4 register变量
为了提高效率,C 语言允许将局部变量得值放在 CPU 中的寄存器中,这种变量叫“寄存器变量”,用关键 字 register
作声明。
说明:
- 只有局部自动变量和形式参数可以作为寄存器变量;
- 一个计算机系统中的寄存器数目有限,不能定义任意多个寄存器变量;
- 局部静态变量不能定义为寄存器变量。
8.9.5 用extern声明外部变量
外部变量(即全局变量)是在函数的外部定义的,它的作用域为从变量定义处开始,到本程序文件的末 尾。如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件终了。如果在定义点之前的 函数想引用该外部变量,则应该在引用之前用关键字 extern
对该变量作“外部变量声明”。表示该变量是一个 已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。
9 预处理命令
9.2 宏定义
9.2.1 无参宏定义
对于宏定义还要说明以下几点:
- 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的代换,字符串中可以含任何字符,可以是常数,也可以是表达式,预处理程序对它不作任何检查。如有错误,只能在编译已被宏展开后的源程序时发现。
- 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起置换。
- 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用
#undef
命令。 - 宏名在源程序中若用引号括起来,则预处理程序不对其作宏代换。
- 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名。在宏展开时由预处理程序层层代换。
- 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。
- 可用宏定义表示数据类型,使书写方便。
宏定义只是简单的字符串代换,是在预处理完成的,而 typedef
是在编译时处理的,它不是作简单的代 换,而是对类型说明符重新命名。被命名的标识符具有类型定义说明的功能。
9.2.2 带参宏定义
C语言允许宏带有参数。在宏定义中的参数称为形式参数,在宏调用中的参数称为实际参数。
对带参数的宏,在调用中,不仅要宏展开,而且要用实参去代换形参。 带参宏定义的一般形式为:
1 |
在字符串中含有各个形参。 带参宏调用的一般形式为:
1 | 宏名(实参表); |
对于带参的宏定义有以下问题需要说明:
- 带参宏定义中,宏名和形参表之间不能有空格出现。
- 在带参宏定义中,形式参数不分配内存单元,因此不必作类型定义。而宏调用中的实参有具体的值。要用 它们去代换形参,因此必须作类型说明。这是与函数中的情况不同的。在函数中,形参和实参是两个不同的 量,各有自己的作用域,调用时要把实参值赋予形参,进行“值传递”。而在带参宏中,只是符号代换,不存 在值传递的问题。
- 在宏定义中的形参是标识符,而宏调用中的实参可以是表达式。
- 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。
- 宏定义也可用来定义多个语句,在宏调用时,把这些语句又代换到源程序内。
9.3 文件包含
使用尖括号表示在包含文件目录中去查找(包含目录是由用户在设置环境时设置的),而不在源文件目录去查找;
使用双引号则表示首先在当前的源文件目录中查找,若未找到才到包含目录中去查找。
9.4 条件编译
第一种形式:
1 |
|
它的功能是,如果标识符已被 #define
命令定义过则对程序段 1 进行编译;否则对程序段 2 进行编 译。如果没有程序段 2(它为空),本格式中的#else
可以没有。
第二种形式:
1 |
|
与第一种形式的区别是将“ifdef
”改为“ifndef
”。它的功能是,如果标识符未被#define
命令定义过 则对程序段 1 进行编译,否则对程序段 2 进行编译。这与第一种形式的功能正相反。
第三种形式:
1 |
|
它的功能是,如常量表达式的值为真(非 0),则对程序段 1 进行编译,否则对程序段 2 进行编译。
11 结构体与共用体
11.2 结构类型变量的说明
1.先定义结构,再说明结构变量。
2.在定义结构类型的同时说明结构变量。
1 | struct 结构名 |
3.直接说明结构变量。
1 | struct |
11.7 结构指针变量的说明和使用
11.7.1 指向结构变量的指针
其访问的一般形式为:(*结构指针变量).成员名
或为:结构指针变量->成员名
应该注意(*结构指针变量)
两侧的括号不可少,因为成员符“.
”的优先级高于“*
”。如去掉括号写 作*结构指针变量.成员名
则等效于*(结构指针变量.成员名)
,这样,意义就完全不对了。
11.7.3 结构指针变量作函数参数
在 ANSI C 标准中允许用结构变量作函数参数进行整体传送。但是这种传送要将全部成 员逐个传送,特别是成员为数组时将会使传送的时间和空间开销很大,严重地降低了程序的 效率。因此最好的办法就是使用指针,即用指针变量作函数参数进行传送。这时由实参传向 形参的只是地址,从而减少了时间和空间的开销。
12 位运算
12.1 位运算符C语言提供了六种位运算符
&
按位与|
按位或^
按位异或~
取反<<
左移>>
右移
12.1.5 左移运算
左移运算符“<<
”是双目运算符。其功能把“<<
”左边的运算数的各二进位全部左移若 干位,由“<<
”右边的数指定移动的位数,高位丢弃,低位补 0。
12.1.6 右移运算
右移运算符“>>
”是双目运算符。其功能是把“>>
”左边的运算数的各二进位全部右移若 干位,“>>
”右边的数指定移动的位数。
应该说明的是,对于有符号数,在右移时,符号位将随同移动。当为正数时,最高位补 0,而为负数时,符号位为 1,最高位是补 0 或是补 1 取决于编译系统的规定。Turbo C 和 很多系统规定为补 1。
12.2 位域(位段)
有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位。例 如在存放一个开关量时,只有 0 和 1 两种状态,用一位二进位即可。为了节省存储空间,并 使处理简便,C语言又提供了一种数据结构,称为“位域”或“位段”。
所谓“位域”是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。 每个域有一个域名,允许在程序中按域名进行操作。这样就可以把几个不同的对象用一个字 节的二进制位域来表示。
1.位域的定义和位域变量的说明
位域定义与结构定义相仿,其形式为:
1 | struct 位域结构名 |
其中位域列表的形式为:
1 | 类型说明符 位域名: 位域长度 |
对于位域的定义尚有以下几点说明:
1) 一个位域必须存储在同一个字节中,不能跨两个字节。如一个字节所剩空间不够存放另一位域时,应从下一单元起存放该位域。也可以有意使某位域从下一单元开始。 例如:
1 | struct bs |
在这个位域定义中,a 占第一字节的 4 位,后 4 位填 0 表示不使用,b 从第二字节开始,占用4位,c占用4位。
2) 由于位域不允许跨两个字节,因此位域的长度不能大于一个字节的长度,也就是说不能超过 8 位二进位。
3) 位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。
例如:
1 | struct k |
2.位域的使用
位域的使用和结构成员的使用相同,其一般形式为:
1 | 位域变量名.位域名 |
位域允许用各种格式输出。
从以上分析可以看出,位域在本质上就是一种结构类型,不过其成员是按二进位分配的。
13 文件
13.3 文件的打开与关闭
13.3.1 文件的打开(fopen函数)
文件使用方式 | 意义 |
---|---|
“rt” | 只读打开一个文本文件,只允许读数据 |
“wt” | 只写打开或建立一个文本文件,只允许写数据 |
“at” | 追加打开一个文本文件,并在文件末尾写数据 |
“rb” | 只读打开一个二进制文件,只允许读数据 |
“wb” | 只写打开或建立一个二进制文件,只允许写数据 |
“ab” | 追加打开一个二进制文件,并在文件末尾写数据 |
“rt+” | 读写打开一个文本文件,允许读和写 |
“wt+” | 读写打开或建立一个文本文件,允许读写 |
“at+” | 读写打开一个文本文件,允许读,或在文件末追加数据 |
“rb+” | 读写打开一个二进制文件,允许读和写 |
“wb+” | 读写打开或建立一个二进制文件,允许读和写 |
“ab+” | 读写打开一个二进制文件,允许读,或在文件末追加数据 |
对于文件使用方式有以下几点说明:
- 文件使用方式
r,w,a,t,b,+
六个字符拼成,各字符的含义是:- r(read): 读
- w(write): 写
- a(append): 追加
- t(text): 文本文件,可省略不写 b(banary): 二进制文件
- +: 读和写
- 凡用“
r
”打开一个文件时,该文件必须已经存在,且只能从该文件读出。 - 用“
w
”打开的文件只能向该文件写入。若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件。 - 若要向一个已存在的文件追加新的信息,只能用“
a
”方式打开文件。但此时该文件必须是存在的,否则将会出错。 - 在打开一个文件时,如果出错,
fopen
将返回一个空指针值NULL
。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。 - 把一个文本文件读入内存时,要将ASCII码转换成二进制码,而把文件以文本方式写入 磁盘时,也要把二进制码转换成 ASCII码,因此文本文件的读写要花费较多的转换时间。 对二进制文件的读写不存在这种转换。
- 标准输入文件(键盘),标准输出文件(显示器),标准出错输出(出错信息)是由系统打开 的,可直接使用。
13.4 文件的读写
13.4.1 字符读写函数fgetc和fputc
2.写字符函数fputc
fputc
函数的功能是把一个字符写入指定的文件中,函数调用的形式为:
1 | fputc(字符量,文件指针); |
fputc
函数有一个返回值,如写入成功则返回写入的字符,否则返回一个EOF
。可用此来判断写入是否成功。
13.4.2 字符串读写函数fgets和fputs
1.读字符串函数fgets
函数的功能是从指定的文件中读一个字符串到字符数组中,函数调用的形式为:
1 | fgets(字符数组名,n,文件指针); |
其中的 n
是一个正整数。表示从文件中读出的字符串不超过 n-1
个字符。在读入的最 后一个字符后加上串结束标志’\0
‘。
对 fgets 函数有两点说明:
- 在读出
n-1
个字符之前,如遇到了换行符或EOF
,则读出结束。 fgets
函数也有返回值,其返回值是字符数组的首地址。
13.5 文件的随机读写
13.5.1 文件定位
1 | rewind(文件指针); |
它的功能是把文件内部的位置指针移到文件首。
fseek
函数用来移动文件内部位置指针,其调用形式为:
1 | fseek(文件指针,位移量,起始点); |
其中:
“文件指针”指向被移动的文件。
“位移量”表示移动的字节数,要求位移量是 long
型数据,以便在文件长度大于 64KB 时不会出错。当用常量表示位移量时,要求加后缀“L
”。
“起始点”表示从何处开始计算位移量,规定的起始点有三种:文件首,当前位置和文件尾。
其表示方法如下表。
起始点 | 表示符号 | 数字表示 | ||||
---|---|---|---|---|---|---|
文件首 | SEEK_SET | 0 | ||||
当前位置 | SEEK_CUR | 1 | 文件末尾 | SEEK_END | 2 |
还要说明的是fseek
函数一般用于二进制文件。在文本文件中由于要进行转换,故往往计算的位置会出现错误。
13.6 文件检测函数
C语言中常用的文件检测函数有以下几个。
13.6.1 文件结束检测函数feof函数
调用格式:
1 | feof(文件指针); |
功能:判断文件是否处于文件结束位置,如文件结束,则返回值为 1,否则为 0。
13.6.2 读写文件出错检测函数
ferror
函数调用格式:
1 | ferror(文件指针); |
功能:检查文件在用各种输入输出函数进行读写时是否出错。如 ferror
返回值为 0 表示未出错,否则表示有错。
13.6.3 文件出错标志和文件结束标志置 0 函数
clearerr
函数调用格式:
1 | clearerr(文件指针); |
功能:本函数用于清除出错标志和文件结束标志,使它们为 0 值。
资源下载
《C语言程序设计(第四版)谭浩强》
下载教材PDF 下载笔记PDF