1 程序设计和C语言
1.4 最简单的C语言程序
C语言允许用两种注释方式:
(1)以//
开始的单行注释。如上面介绍的注释。这种注释可以单独占一行,也可以出现在一行中其他内容的右侧。此种注释的范围从//
开始,以换行符结束。也就是说这种注释不能跨行。如果注释内容一行内写不下,可以用多个单行注释,如下面两行是连续的注释行:
1 | //如注释内容一行内写不下 |
(2)以/*
开始,以*/
结束的块式注释。这种注释可以包含多行内容。它可以单独占一行(在行开头以/*
开始,行末以*/
结束),也可以包含多行。编译系统在发现一个/*
后,会开始找注释结束符*/
,把二者间的内容作为注释。但应注意的是在字符串中的//
和/*
都不作为注释的开始。而是作为字符串的一部分。如:
1 | printf("//how do you do!\n"); |
在C89只允许用/*...*/
形式的注释,而C++则允许用//
形式的注释,//
注释被称为“风格”的注释。但许多C编译系统在C99之前就已支持这种方便的注释方法,C99正式将//
注释纳入C语言新标准。
C99建议把
main
函数指定为int
型(整型),它要求函数带回一个整数值。在main
函数中,在执行的最后设置一个“return 0;
”语句。当主函数正常结束时,得到的函数值为0;当执行main
函数过程中出现异常或错误时,函数值为一个非0的整数。这个函数值是返回给调用main
函数的操作系统的。程序员可以利用操作指令检查main
函数的返回值,从而判断main
函数是否已正常执行,并据此决定以后的操作。如果在程序中不写“return 0;
”语句,有的C编译系统会在目标程序中自动加上这一语句,因此。主函数正常结束时,也能使函数值为0为使程序规范和可移植,希望读者写的程序一律将main
函数指定为int
型,并在main
函数的最后加一个“return 0;
”语句。
1.4.2 C语言程序的结构
不同编译系统所提供的库函数个数和功能是不完全相同的。
2 算法——程序的灵魂
2.1 程序=算法+数据结构
著名计算机科学家沃思(Nikiklaus Wirth)提出一个公式:
直到今天,这个公式对于过程化程序来说依然是适用的。
2.4 算法的特性
- 有穷性
- 确定性
- 有零个或多个输入
- 由一个或多个输出
- 有效性
2.5 怎样表示一个算法
2.5.3 三种基本结构和改进的流程图
- 顺序结构
- 选择结构
- 循环结构
2.5.4 用N-S流程图表示算法
2.6 结构化程序设计方法
采取以下方法来保证得到结构化的程序:
- 自顶向下;
- 逐步细化;
- 模块化设计;
- 结构化编码。
3 最简单的C程序设计——顺序程序设计
3.2 数据的表现形式及其运算
3.2.1 常量和变量
实型常量
注意: e或E之前必须有数字,且e或E后面必须为整数。
字符常量
从其字面形式上即可识别的常量称为“字面常量”或“直接常量”。字面常量是没有名字的不变量。
符号常量
用#define
指令,指定用一个符号名称代表一个常量。
使用符号常量的好处:
- 含义清楚
- 在需要改变程序中多处用到的同一个常量时,能做到“一改全改”。
注意:要区分符号常量和变量,不要把符号常量误认为变量。符号常量不占内存,只是一个临时符号,代表一个值,在预编译后这个符号就不存在了,故不能对符号常量赋新值。为与变量名相区别,习惯上符号常量用大写表示,如PI,PRICE等。
常变量
可以说,常变量是有名字的不变量,而常量是没有名字的不变量。
从使用的角度看,常变量具有符号常量的优点,而且使用更方便。有了常变量以后,可以不必多用符号常量。
标识符
C语言规定标识符只能由字母、数字和下画线3种字符组成,且第1个字符必须为字母或下画线。
注意:编译系统认为大写字母和小写字母是两个不同的字符。
3.2.3 整型数据
基本整形(int型)
Turbo C 2.0为每一个整型数据分配2个字节(16个二进位),而Visual C++为每一个整型数据分配4个字节(32位)。在存储单元中的存储方式是:用整数的补码(complement)形式存放。
求负数的补码的方法是:先将此数的绝对值写成二进制形式,然后对其所有二进位按位取反,再加1。(正数的补码是它自已。)
如果给整型变量分配4个字节(Visual C++ ),其能容纳的数值范围为$-2^{31}\sim (2^{31}-1)$,即$-2\;147\;483\;648\sim 2\;147\;483\;647$。
短整型(short int)
Visual C++ 编译系统分配给短整型2个字节。范围是$-32\;768\sim 32\;767$。
长整型(long int)
类型名为long int
或long
。Visual C++对一个long
型数据分配4个字节(即32位),因此long int
型变量的值的范围是$-2^{31}\sim (2^{31}-1)$,即$-2\;147\;483\;648\sim 2\;147\;483\;647$。
双长整型(long long int)
类型名为long long int
或long long
,一般分配8个字节。这是C 99新增的类型,但许多C编译系统尚未实现。
C 标准没有具体规定各种类型数据所占用存储单元的长度,这是由各编译系统自行决定的。C标准只要求long
型数据长度不短于int
型,short
型不长于int
型。即
整型变量的符号属性
类型 | 字节数 | 取值范围 |
---|---|---|
$\text{int}$(基本整型) | 4 | $-2\;147\;483\;648\sim 2\;147\;483\;647$,即$-2^{31}\sim(2^{31}-1)$ |
$\text{unsigned int}$(无符号基本整型) | 4 | $0\sim4\;294\;967\;295$,即$0\sim (2^{32} -1)$ |
$\text{short}$(短整型) | 2 | $-32\;768\sim32\;767$,即$-2^{15}\sim(2^{15}-1)$ |
$\text{unsigned short}$(无符号短整型) | 2 | $0\sim 65\;535$,即 $0\sim (2^{15}-1)$ |
$\text{long}$(长整型) | 4 | $-2\;147\;483\;648\sim2\;147\;483;647$,即$-2^{31}\sim(2^{31}-1)$ |
$\text{unsigned long}$(无符号长整型) | 4 | $0\sim4\;294\;967\;295$,即$0\sim(2^{32}-1)$ |
$\text{long long}$(双长型) | 8 | $-9\;223\;372\;036\;854\;775\;808\sim9\;223\;372\;036\;854\;775\;807$,即$-2^{63}\sim(2^{63}-1)$ |
$\text{unsigned long long}$(无符号双长整型) | 8 | $0\sim 18\;446\;744\;073\;709\;551\;615$,即$0\sim (2^{64}-1)$ |
可以在类型符号前面加上修饰符unsigned
,表示指定该变量是“无符号整数”类型。如果加上修饰符signed
,则是“有符号类型”。如果既未指定为signed
也未指定为unsigned
的,默认为“有符号类型”。
对无符号整型数据用“%u
“格式输出。%u
表示用无符号十进制数的格式输出。
3.2.4 字符型数据
字符与字符代码
ASCII代码最多用7个二进位就可以表示。所以在C语言中,指定用一个字节(8位)存储一个字符(所有系统都不例外)。此时,字节中的第1位置为0。
在使用有符号字符型变量时,允许存储的值为一$128\sim 127$,但字符的代码不可能为负值,所以在存储字符时实际上只用到$0\sim 127$这一部分,其第1位都是0。
3.2.5 浮点型数据
由于小数点位置可以浮动,所以实数的指数形式称为浮点数。
float型
小数部分占的位(bit)数愈多,数的有效数字愈多,精度也就愈高。指数部分占的位数愈多,则能表示的数值范围愈大。float
型数据能得到6位有效数字,数值范围为$-3.4\times 10^{-38}\sim 3.4\times 10^{38}$。
double型
在C语言中进行浮点数的算术运算时,将float
型数据都自动转换为double
型,然后进行运算。
long double型
而Visual C++则对long double
型和double
型一样处理,分配8个字节。请读者在使用不同的编译系统时注意其差别。
类型 | 字节数 | 有效数字 | 数值范围(绝对值) |
---|---|---|---|
$\text{float}$ | $4$ | $6$ | $0$以及$1.2\times 10^{-38}\sim 3.4\times 10^{38}$ |
$\text{double}$ | $8$ | $15$ | $0$以及$2.3\times 10^{-308}\sim 1.7\times 10^{308}$ |
$\text{long double}$ | $8$ | $15$ | $0$以及$2.3\times 10^{-308}\sim 1.7\times 10^{308}$ |
$\text{long double}$ | $16$ | $19$ | $0$以及$3.4\times 10^{-4932}\sim 1.1\times 10^{4932}$ |
3.2.6 怎样确定常量的类型
整型常量
在一个整数的末尾加大写字母L
或小写字母l
,表示它是长整型(long int
)。但在Visual C++中由于对int
和long int
型数据都分配4个字节,因此没有必要用long int
型。
浮点型常量
加字母F
或f
,就表示是float
型常量,分配4个字节。如果在实型常量后面加大写或小写的L
,则指定此常量为long double
型。
注意:要区分类型与变量。
有些读者容易弄不清类型和变量的关系,往往把它们混为一谈。应当看到它们是既有联系又有区别的两个概念。每一个变量都属于一个确定的类型,类型是变量的一个重要的属性。变量是占用存储单元的,是具体存在的实体,在其占用的存储单元中可以存放数据。而类型是变量的共性,是抽象的,不占用存储单元,不能用来存放数据。
3.3 运算符和表达式
3.3.2 基本的算术运算符
但是,如果除数或被除数中有一个为负值,则舍入的方向是不固定的。多数C编译系统(如Visual C++ )采取“向零取整”的方法,取整后向零靠拢。
%
运算符要求参加运算的运算对象(即操作数)为整数,结果也是整数。
3.3.3 自增(++)、自减(—)运算符
建议谨慎使用++
和--
运算符,只用最简单的形式,即i++
,i--
。而且把它们作为单独的表达式,而不要在一个复杂的表达式中使用++
或--
运算符。
3.3.4 算术表达式和运算符的优先级与结合性
关于“结合性”的概念在其他一些高级语言中是没有的,是C语言的特点之,希望能弄清楚。
3.3.6 强制类型转换运算符
(类型名)(表达式)
注意,表达式应该用括号括起来。
需要说明的是,在强制类型转换时,得到一个所需类型的中间数据,而原来变量的类型未发生变化。
3.4 C语句
3.4.1 C语句的作用和分类
控制语句
C语言只有9种控制语句:
if()else..
(条件语句)for()...
(循环语句)while()...
(循环语句)do...while()
(循环语句)continue
(结束本次循环语句)break
(中止执行switch
或循环语句)switch
(多分支选择语句)return
(从函数返回语句)goto
(转向语句,在结构化程序中基本不用goto
语句)
复合语句
注意:复合语句中最后一个语句末尾的分号不能忽略不写。
3.4.2 最基本的语句——赋值语句
复合的赋值运算符
凡是二元(二目)运算符,都可以与赋值符一起组合成复合赋值符。有关算术运算的复合赋值运算符有+=
,-=
,*=
,/=
,%=
。
赋值表达式
左值的意思是它可以出现在赋值运算符的左侧,它的值是可以改变的。
赋值过程中的类型转换
将浮点型数据(包括单、双精度)赋给整型变量时,先对浮点数取整,即舍弃小数部
分,然后赋予整型变量。
将一个double
型数据赋给float
变量时,先将双精度数转换为单精度,即只取$6\sim 7$位.有效数字,存储到float
型变量的4个字节中。应注意双精度数值的大小不能超出float
型变量的数值范围。
将一个占字节多的整型数据赋给一个占字节少的整型变量或字符变量时,只将其低字节原封不动地送到被赋值的变量(即发生“截断”)。
赋值表达式和赋值语句
在if
的条件中可以包含赋值表达式,但不能包含赋值语句。
注意:要区分赋值表达式和赋值语句。
赋值表达式的末尾没有分号,而赋值语句的末尾必须有分号。在一个表达式中可以包含一个或多个赋值表达式,但绝不能包含赋值语句。
变量赋初值
不能写成int a= b=c= 3;
3.5 数据的输入输出
3.5.1 输入输出举例
printf
在使用格式声明,输出小数位小于实际小数位时按四舍五人处理。
如果输出多个数据,各占一行,而用同一个格式声明(如%7.2f
),即使输出的数据整数部分值不同,但输出时上下行必然按小数点对齐,使输出数据整齐美观。
3.5.2 有关数据输入输出的概念
C语言本身不提供输入输出语句,没有输人输出语句就可以避免在编译阶段处理与硬件有关的问题,可以使编译系统简化,而且通用性强,可移植性好,在各种型号的计算机和不同的编译环境下都能适用,便于在各种计算机上实现。
这两种#include
指令形式的区别是:用尖括号形式(如<stdio.h>
)时,编译系统从存放C编译系统的子目录中去找所要包含的文件(如stdio.h
),这称为标准方式。如果用双撇号形式(如"stdio.h"
),在编译时,编译系统先在用户的当前目录(一般是用户存放源程序文件的子目录)中寻找要包含的文件,若找不到,再按标准方式查找。如果用#include
指令是为了使用系统库函数,因而要包含系统提供的相应头文件,这时以用标准方式为宜,以提高效率。如果用户想包含的头文件不是系统提供的相应头文件,而是用户自己编写的文件(这种文件一般都存放在用户当前目录中),这时应当用双撇号形式,否则会找不到所需的文件。
3.5.3 用printf函数输出数据
c
格式符用来输出一个字符。如果整数比较大,则把它的最后一个字节的信息以字符形式输出。
f
格式符用来输出实数(包括单、双精度、长双精度),以小数形式输出不指定输出数据的长度,由系统根据数据的实际情况决定数据所占的列数。系统处理的方法一般是:实数中的整数部分全部输出,小数部分输出6位。
指定数据宽度和小数位数,用%m.nf
。对其后一位采取四舍五人方法处理。如果把小数部分指定为0,则不仅不输出小数,而且小数点也不输出。
输出的数据向左对齐,用%-m.nf
。在m.n
的前面加一个负号,其作用与%m.nf
形式作用基本相同,但当数据长度不超过m
时,数据向左靠,右端补空格。如:
e
格式符。用格式声明%e
指定以指数形式输出实数。格式符e
也可以写成大写E
形式,此时输出的数据中的指数不是以小写字母e
表示而以大写字母E
表示。
i
格式符。作用与d
格式符相同,按十进制整型数据的实际长度输出。
o
格式符。以八进制整数形式输出。将内存单元中的各位的值(0或1)按八进制形式输出,因此输出的数值不带符号,即将符号位也一起作为八进制数的一部分输出。按内存单元中实际的二进制数按3位一组构成八进制数形式,
x
格式符。以十六进制数形式输出整数。
u
格式符。用来输出无符号(unsigned
)型数据,以十进制整数形式输出。
g
格式符。用来输出浮点数,系统自动选f
格式或e
格式输出,选择其中长度较短的格式,不输出无意义的0。
除了X
,E
,G
外,其他格式字符必须用小写字母,如%d
不能写成%D
。如果想输出字符“%
”,应该在“格式控制字符串”中用连续两个“%
”表示
格式字符 | 说明 |
---|---|
d ,i |
以带符号的十进制形式输出整数(正数不输出符号) |
o (代表一个正整数) |
以八进制无符号形式输出整数(不输出前导符0) |
x ,X (代表一个正整数) |
以十六进制无符号形式输出整数(不输出前导符0x),用x则输出十六进制数的a~f时以小写形式输出、用X时,则以大写字母输出 |
u |
以无符号十进制形式输出整数 |
c |
以字符形式输出,只输出一个字符 |
s |
输出字符串 |
f |
以小数形式输出单、双精度数,隐含输出6位小数 |
e ,E |
以指数形式输出实数,用e时指数以“e”表示(如 1.2e+02),用E时指数以 “E”表示(如1.2E+02) |
g ,G |
选用%f或%e格式中输出宽度较短的一种格式,不输出无意义的0。用G时。若以指数形式输出,则指数以大写表示 |
字符 | 说明 |
---|---|
l |
长整型整数,可加在格式符 d、o、x、u前面 |
m (代表一个正整数) |
数据最小宽度 |
n (代表一个正整数) |
对实数,表示输出n位小数;对字符串,表示截取的字符个数 |
- |
输出的数字或字符在域内向左靠 |
3.5.4 用scanf函数输入数据
*
,本输人项在读人后不赋给相应的变量
如果在格式控制字符串中除了格式声明以外还有其他字符,则在输入数据时在对应的位置上应输人与这些字符相同的字符。
在用“%c
”格式声明输人字符时,空格字符和“转义字符”中的字符都作为有效字符输入。
输入数值时,在两个数值之间需要插入空格(或其他分隔符),以使系统能区分两个数值。在连续输入字符时,在两个字符之间不要插入空格或其他分隔符。
在输人数值数据时,如输人空格、回车、Tab键或遇非法字符(不属于数值的字符),认为该数据结束。
3.5.5 字符输入输出函数
用putchar
函数既可以输出能在显示器屏幕上显示的字符,也可以输出屏幕控制字符。
getchar
函数没有参数。按Enter键后,字符才送到计算机中。
执行getchar
函数不仅可以从输入设备获得一个可显示的字符,而且可以获得在屏幕上无法显示的字符,如控制字符。
4 选择结构程序设计
4.2 用if语句实现选择结构
4.2.2 if语句的一般形式
if
语句无论写在几行上,都是一个整体,属于同一个语句。
4.4 逻辑运算符和逻辑表达式
4.4.1 逻辑运算符及其优先次序
“!
“为三者中最高的。
4.6 选择结构的嵌套
else
总是与它上面的最近的未配对的if
配对。
4.7 用switch语句实现多分支选择结构
在执行完一个case
标号后面的语句后,就从此标号开始执行下去,不再进行判断。
在case
子句中虽然包含了一个以上执行语句,但可以不必用花括号括起来,会自动顺序执行本case
标号后面所有的语句。当然加上花括号也可以。
5 循环结构程序设计
5.4 用for语句实现循环
C99允许在for
语句的“表达式1”中定义变量并赋初值。显然,这可以使程序简练,灵活方便。但应注意:所定义的变量的有效范围只限于for
循环中,在循环外不能使用此变量。
5.7 改变循环执行的状态
5.7.1 用break语句提前终止循环
break
语句只能用于循环语句和switch
语句之中,而不能单独使用
6 利用数组处理批量数据
6.1 怎样定义和引用一维数组
6.1.1 怎样定义一维数组
常量表达式中可以包括常量和符号常量,如“int a[3+5];
“是合法的。不能包含变量,如“int a[n];
“是不合法的。也就是说,C语言不允许对数组的大小作动态定义,即数组的大小不依赖于程序运行过程中变量的值。
6.1.3 一维数组的初始化
在定义数组时对全部数组元素赋予初值。
可以只给数组中的一部分元素赋值。
如果想使一个数组中全部元素值为0,可以写成int a[10]= {0};
,未赋值的部分元素自动设定为0。
在对全部数组元素赋初值时,由于数据的个数已经确定,因此可以不指定数组长度。
如果在定义数值型数组时,指定了数组的长度并对之初始化,凡未被“初始化列表”指定初始化的数组元素,系统会自动把它们初始化为0如果是字符型数组,则初始化为’\0
‘,如果是指针型数组,则初始化为NULL
,即空指针。
6.2 怎样定义和引用二维数组
6.2.3 二维数组的初始化
分行给二维数组赋初值。
可以将所有数据写在一个花括号内,按数组元素在内存中的排列顺序对各元素赋初值。
可以对部分元素赋初值。
如果对全部元素都赋初值(即提供全部初始数据),则定义数组时对第1维的长度可以不指定,但第2维的长度不能省。
在定义时也可以只对部分元素赋初值而省略第1维的长度,但应分行赋初值。
6.3 字符数组
6.3.2 字符数组的初始化
如果花括号中提供的初值个数(即字符个数)大于数组长度,则出现语法错误。如果初值个数小于数组长度,则只将这些字符赋给数组中前面那些元素,其余的元素自动定为空字符(即’\0
‘)。
C系统在用字符数组存储字符串常量时会自动加一个’\0
‘作为结束符。在定义字符数组时应估计实际字符串长度,保证数组长度始终大于字符串实际长度。
1 | char c[]={"I am happy"); |
也可以省略花括号,直接写成
1 | char c[]= "I am happy"; |
注意字符串的两端是用双撇号而不是单撇号括起来的。字符串常量的最后由系统加上一个’\0
‘。
字符数组并不要求它的最后一个字符为’\0
‘,甚至可以不包含’\0
‘。为了使处理方法一致,便于测定字符串的实际长度,以及在程序中作相应的处理,在字符数组中也常常人为地加上一个’\0
‘。
如果一个字符数组中包含一个以上’\0
‘,则遇第一个’\0
‘时输出就结束。
scanf
函数中系统会自动在字符串后面加一个’\0
‘结束符。数组中未被赋值的元素的值自动置’\0
‘。
7 用函数实现模块化程序设计
7.1 为什么要用函数
函数间可以互相调用,但不能调用main
函数。main
函数是被操作系统调用的。
7.4 对被调用函数的声明和函数原型
函数的首行(即函数首部)称为函数原型(function prototype)。实际上,在函数声明中的形参名可以省写,而只写形参的类型。编译系统只关心和检查参数个数和参数类型,而不检查参数名,因为在调用函数时只要求保证实参类型与形参类型一致,而不必考虑形参名是什么。
注意:对函数的“定义”和“声明”不是同一回事。函数的定义是指对函数功能的确立,包括指定函数名、函数值类型、形参及其类型以及函数体等,它是一个完整的、独立的函数单位。而函数的声明的作用则是把函数的名字、函数类型以及形参的类型、个数和顺序通知编译系统,以便在调用该函数时系统按此进行对照检查(例如,函数名是否正确,实参与形参的类型和个数是否一致),它不包含函数体。
写在所有函数前面的外部声明在整个文件范围中有效。
7.7 数组作为函数参数
7.7.3 多维数组名作函数参数
可以省略第一维的大小说明。但是不能把第2维以及其他高维的大小说明省略。
7.8 局部变量和全局变量
有效范围为从定义变量的位置开始到本源文件结束。
7.9 变量的存储方式和生存期
7.9.2 局部变量的存储类别
这种变量叫做寄存器变量,用关键字register
作声明。由于现在的计算机的速度愈来愈快,性能愈来愈高,优化的编译系统能够识别使用频繁的变量,从而自动地将这些变量放在寄存器中,而不需要程序设计者指定。
7.9.3 全局变量的存储类别
如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字extern
对该变量作“外部变量声明”,表示把该外部变量的作用域扩展到此位置。有了此声明,就可以从“声明”处起,合法地使用该外部变量。
用extern
声明外部变量时,类型名可以写也可以省写。例如,“extern int A,B,C;
”也可以写成“extern A,B,C;
“。因为它不是定义变量,可以不指定类型,只须写出外部变量名即可。
在编译时遇到extern
时,先在本文件中找外部变量的定义,如果找到,就在本文件中扩展作用域;如果找不到,就在连接时从其他文件中找外部变量的定义。如果从其他文件中找到了,就将作用域扩展到本文件;如果再找不到,就按出错处理。
这种加上static
声明、只能用于本文件的外部变量称为静态外部变量。
对于局部变量来说,声明存储类型的作用是指定变量存储的区城(静态存储区或动态存储区)以及由此产生的生存期的问题,而对于全局变量来说,由于都是在编译时分配内存的,都存放在静态存储区,声明存储类型的作用是变量作用城的扩展问题。
用static
声明一个变量的作用是:
- 对局部变量用
static
声明,把它分配在静态存储区,该变量在整个程序执行期间不
释放,其所分配的空间始终存在。 - 对全局变量用
static
声明,则该变量的作用域只限于本文件模块(即被声明的文
件中)。
7.10 关于变量的声明和定义
对”int a;
“而言,它既是声明,又是定义;而对“extern a;
”而言,它是声明而不是定义。前者称为定义性声明(defining declaration),或简称定义(definition);后者称为引用性声明(referencing declaration)。把建立存储空间的声明称定义,而把不需要建立存储空间的声明称为声明。
7.11 内部函数和外部函数
7.11.1 内部函数
如果一个函数只能被本文件中其他函数所调用,它称为内部函数。在定义内部函数时,在函数名和函数类型的前面加static
,即:
1 | static 类型名函数名(形参表); |
内部函数又称静态函数,因为它是用static
声明的。使用内部函数,可以使丽数的作用域只局限于所在文件。这样,在不同的文件中即使有同名的内部函数,也互不干扰,不必担心所用函数是否会与其他文件模块中的函数同名。
7.11.2 外部函数
如果在定义函数时,在函数首部的最左端加关键字extern
,则此函数是外部函数,可供其他文件调用。
1 | extern int fun (int a, int b) |
C语言规定,如果在定义函数时省略extern
,则默认为外部函数。
8 善于利用指针
8.2 指针变量
8.2.2 怎样定义指针变量
一个变量的指针的含义包括两个方面,一是以存储单元编号表示的纯地址(如编号为2000的字节),一是它指向的存储单元的数据类型(如int,char,float等)。
8.3 通过指针引用数组
8.3.1 数组元素的指针
使用指针法能使目标程序质量高(占内存少,运行速度快)。
8.3.2 在引用数组元素时指针的运算
两个地址不能相加,如p1+p2
是无实际意义的。
8.3.3 通过指针引用数组元素
指向数组元素的指针变量也可以带下标。
由于++
和*
同优先级,结合方向为自右而左,因此它等价于* (p++)
。先引用p
的值,实现* p
的运算,然后再使p
自增1。
8.3.5通过指针引用多维数组
”int(*p)[4]
“表示定义p
为一个指针变量,它指向包含4个整型元素的一维数组。注意,*p
两侧的括号不可缺少。
8.4 通过指针引用字符串
8.4.3 使用字符指针变量和字符数组的比较
数组可以在定义时对各元素赋初值,但不能用赋值语句对字符数组中全部元素整体赋值。
字符数组中各元素的值是可以改变的(可以对它们再赋值),但字符指针变量指向的字符串常量中的内容是不可以被取代的(不能对它们再赋值)。
8.5 行指向函数的指针
8.5.3 怎样定义和使用指向函数的指针变量
定义指向函数的指针变量的一般形式为
1 | 类型名 (*指针变量名)(函数参数表列); |
由于优先级的关系,“*指针变量名”要用圆括号括起来。
定义指向函数的指针变量,并不意味着这个指针变量可以指向任何函数,它只能指向在定义时指定的类型的函数。
对指向函数的指针变量不能进行算术运算。
8.7 指针数组和多重指针
8.7.3 指针数组作main函数的形参
其实,main
函数中的形参不一定命名为arge
和argv
,可以是任意的名字,只是人们习惯用argc
和argv
而已。
8.8 动态内存分配与指向它的指针变量
8.8.3 void指针类型
不要把“指向void
类型”理解为能指向”任何的类型”的数据,而应理解为“指向空类型”或“不指向确定的类型”的数据。
8.9 有关指针的小结
如果写成123.0,则认为是单精度实数,按单精度实型的存储形式存放。
虽然在Visual C++中也为指针变量分配4个字节,但不同于整型数据的存储形式。
一个地址型的数据实际上包含3个信息:
- 表示内存编号的纯地址。
- 它本身的类型,即指针类型。
- 以它为标识的存储单元中存放的是什么类型的数据,即基类型。
有关指针变量的归纳比较:
变量定义 | 类型表示 | 含义 |
---|---|---|
int i; |
int |
定义整型变量 i |
int * p; |
int * |
定义 p 为指向整型数据的指针变量 |
int a[5]; |
int [5] |
定义整型数组 a,它有5个元素 |
int * p[4]; |
int * [4] |
定义指针数组 p,它由4个指向整型数据的指针元素组成 |
int ( * p)[4]; |
int ( * )[4] |
p 为指向包含4个元素的一维数组的指针变量 |
int f(); |
int () |
f 为返回整型函数值的函数 |
int * p(); |
int * () |
p 为返回一个指针的函数,该指针指向整型数据 |
int ( * p)(); |
int ( * )() |
p 为指向函数的指针,该函数返回一个整型值 |
int **p; |
int ** |
p 是一个指针变量,它指向一个指向整型数据的指针变量 |
void * p; |
void * |
p 是一个指针变量,基类型为 void(空类型),不指向具体的对象 |
在stdio.h
头文件中对NULL
进行了定义:
1 |
系统保证使该单元不作它用(不存放有效数据)。
9 用户自己建立数据类型
9.1 定义和使用结构体变量
9.1.1 自己建立结构体类型
声明一个结构体类型的一般形式为:
1 | struct 结构体名 |
9.1.2 定义结构体类型变量
计算机对内存的管理是以“字”为单位的(许多计算机系统以4个字节为一个“字”)。如果在一个“字”中只存放了一个字符,虽然只占一个字节,但该“字”中的其他3个字节不会接着存放下一个数据,而会从下一个“字”开始存放其他数据。因此在用
sizeof
运算符测量student1
的长度时,得到的不是理论值63,而是64,是4的倍数。不同的编译系统对结构体变量在内存中分配空间有不同的规定。
C 99标准允许对某一成员初始化,如:
1 | struct Student b={.name="Zhang Fang"}; |
其他未被指定初始化的数值型成员被系统初始化为0,字符型成员被系统初始化为’\0
‘,指针型成员被系统初始化为NULL
。
“.
”是成员运算符,它在所有的运算符中优先级最高。
同类的结构体变量可以互相赋值。
9.3 结构体指针
9.3.3 用结构体变量和结构体变量的指针作函数参数
这种传递方式在空间和时间上开销较大。
9.5 共用体类型
9.5.1 什么是共用体类型
共用体变量所占的内存长度等于最长的成员的长度。
9.5.2 引用共用体变量的方式
不能引用共用体变量,而只能引用共用体变量中的成员。
9.5.3 共用体类型数据的特点
同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一个成员,而不是同时存放几个。
可以对共用体变量初始化。但初始化表中只能有一个常量,
共用体变量中起作用的成员是最后一次被赋值的成员,在对共用体变量中的一个成员赋值后,原有变量存储单元中的值就取代。
不能对共用体变量名赋值,也不能企图引用变量名来得到一个值。
C 99允许同类型的共用体变量互相赋值。
以前的C规定不能把共用体变量作为函数参数,但可以便用指向共用体变量的指针作函数参数。C 99允许用共用体变量作为函数参数。
9.6 业使用枚举类型
C编译对枚举类型的枚举元素按常量处理,故称枚举常量。不要因为它们是标识符(有名字)而把它们看作变量,不能对它们赋值。也可以人为地指定枚举元素的数值,在定义枚举类型时显式地指定。
枚举元素的比较规则是按其在初始化时指定的整数来进行比较的。
9.7 用typedef声明新类型名
命名一个新的类型名代表数组类型
1 | typedef int Num[ 100]; //声明Num为整型数组类型名 |
#define
是在预编译时处理的,它只能作简单的字符串替换,而typedef
是在编译阶段处理的。
10 对文件的输入输出
10.1 C文件的有关基本知识
指向文件的指针变量并不是指向外部介质上的数据文件的开头,而是指向内存中的文件信息区的开头。
10.2 打开与关闭文件
所谓“打开”是指为文件建立相应的信息区(用来存放有关文件的信息)和文件缓冲区(用来暂时存放输入输出的数据)。
10.2.1 用fopen函数打开数据文件
文件使用方式 | 含义 | 如果指定的文件不存在 |
---|---|---|
r(只读) | 为了输入数据,打开一个已存在的文本文件 | 出错 |
w(只写) | 为了输出数据,打开一个文本文件 | 建立新文件 |
a(追加) | 向文本文件尾添加数据 | 出错 |
rb(只读) | 为了输入数据,打开一个二进制文件 | 出错 |
wb(只写) | 为了输出数据,打开一个二进制文件 | 建立新文件 |
ab(追加) | 向二进制文件尾添加数据 | 出错 |
“r+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“w+”(读写) | 为了读和写,建立一个新的文本文件 | 建立新文件 |
“a+”(读写) | 为了读和写,打开一个文本文件 | 出错 |
“rb+”(读写) | 为了读和写,打开一个二进制文件 | 出错 |
“wb+”(读写) | 为了读和写,建立一个新的二进制文件 | 建立新文件 |
“ab+”(读写) | 为读写打开一个二进制文件 | 出错 |
如果不能实现“打开”的任务此时fopen
函数将带回一个空指针值NULL
。
其实,带b
和不带b
只有一个区别,即对换行的处理。
输出的数据形式是由程序中采用什么读写语句决定的。例如,用fscanf
和fprintf
函数是按ASCII方式进行输入输出,而fread
和fwrite
函数是按二进制进行输入输出。
程序中可以使用3个标准的流文件——标准输入流、标准输出流和标准出错输出
流。系统定义了3个文件指针变量stdin
、stdout
和stderr
。
10.2.2 用fclose函数关闭数据文件
如果不关闭文件就结束程序运行将会丢失数据。
fclose
函数也带回一个值,当成功地执行了关闭操作,则返回值为0;否则返回EOF
(-1)。
10.3 顺序读写数据文件
10.3.1 怎样向文件读写字符
在文件的所有有效字符后有一个文件尾标志。当读完全部字符后,文件读写位置标记就指向最后一个字符的后面,即指向了文件尾标志。如果再执行读取操作,则会读出-1(不要理解为最后有一个结束字节,在其中存放了数值-1。它只是一种处理方法)。文件尾标志用标识符EOF
(end of file)表示,EOF
在stdio.h
头文件中被定义为-1。
10.3.2 怎样向文件读写一个字符串
1 | char * fgets(char* str, int n, FILE* fp); |
其中,n
是要求得到的字符个数,但实际上只从fp
所指向的文件中读入n-1
个字符,然后在最后加一个“\0
”字符,这样得到的字符串共有n
个字符,把它们放到字符数组str中。如果在读完n-1
个字符之前遇到换行符“\n
”或文件结束符EOF
,读入即结束,但将所遇到的换行符“\n
”也作为一个字符读入。
10.3.3 用格式化的方式读写文本文件
在内存与磁盘频繁交换数据的情况下,最好不用fprintf
和fscanf
函数,而用下面介绍的fread
和fwrite
函数进行二进制的读写。
10.3.4 用二进制方式向文件读写一组数据
文本读写函数:用来向文本文件读写字符数据的函数(如fgetc
, fgets
, fputc
,fputs
,fscanf
,fprintf
等)。
二进制读写函数:用来向二进制文件读写二进制数据的函数(如getw
, putw
,fread
,fwrite
等)。
10.4 随机读写数据文件
用rewind
函数使文件位置标记指向文件开头。
用fseek
函数改变文件位置标记。
起始点 | 名字 | 用数字代表 |
---|---|---|
文件开始位置 | SEEK_SET | 0 |
文件当前位置 | SEEK_CUR | 1 |
文件末尾位置 | SEEK_END | 2 |
用ftell
函数测定文件位置标记的当前位置
10.5 文件读写的出错检测
应该注意,对同一个文件每一- 次调用输人输出丽数,都会产生一个新的ferror
函数值,因此,应当在调用一个输入输出函数后立即检查ferror
函数的值,否则信息会丢失。在执行fopen
函数时,ferror
函数的初始值自动置为0。
clearerr
的作用是使文件出错标志和文件结束标志置为0。
资源下载
《C语言程序设计(第五版)谭浩强》及《C语言程序设计:学习辅导(第五版)谭浩强》
下载教材PDF 下载笔记PDF