1.3 C语言基本语法概述
1.3.1 数据类型
1.C语言的数据类型
所谓数据类型是按被定义变量的性质、表示形式、占据存储空间的多少、构造特点来划分的。在C语言中,数据类型可分为基本数据类型、构造数据类型、指针类型、空类型4大类,如图1-1所示。
图1-1 C语言数据类型
(1)基本数据类型 基本数据类型最主要的特点是,其值不可以再分解为其他类型。也就是说,基本数据类型是自我说明的。C语言的基本数据类型包括整型、实型、字符型和枚举型。
(2)构造数据类型 构造数据类型是根据已定义的一个或多个数据类型用构造的方法来定义的。也就是说,一个构造类型的值可以分解成若干个“成员”或“元素”。每个“成员”都是一个基本数据类型或者是一个构造类型。在C语言中,构造类型有以下几种:①数组类型;②结构体类型;③共用体类型。
(3)指针类型 指针是一种特殊的具有重要作用的数据类型。其值用来表示某个变量在内存储器中的地址。虽然指针变量的取值类似于整型量,但这是两个类型完全不同,不能混为一谈。
(4)空类型 空类型是指在定义的时候不确定数据类型,而在使用的时候通过强制转换来确定的数据类型。空类型一般用关键字void修饰。例如void *p2;。
2.常量与变量
对于基本数据类型量,按其取值是否可改变又分为常量和变量两种。在程序执行过程中,其值不发生改变的量称为常量,其值可以发生改变的量称为变量。它们可与数据类型结合起来分类。例如,可分为整型常量、整型变量、浮点常量、浮点变量、字符常量、字符变量、枚举常量、枚举变量等。在程序中,常量是可以不经说明直接引用的,而变量则必须先定义后使用。整型量包括整型常量和整型变量。
3.基本数据类型
(1)整型 整型变量的基本类型符为int。根据数值的范围,可以将变量定义为基本整型、短整型和长整型。基本整型以int表示;短整型以short int或short表示;长整型以long int或long表示。
(2)实型 实型分为单精度(float型)、双精度(double型)和长双精度型(long double)三类。
(3)字符型 字符型变量以char表示。
基本类型的定义通常为:类型关键字 变量名[=初始化数据]。例如int a;或float s=0.0;。
1.3.2 运算符和表达式
C语言的运算符如表1-1所示。
表1-1 C语言的运算符
1.3.3 程序基本结构
C语言程序设计的基本结构可以分为顺序结构、选择(分支)结构和循环结构。
1.顺序结构
顺序结构是C语言最基本的结构,程序按照书写的顺序执行,从编译预处理开始,直到最后一条语句结束。该过程中没有跳转、循环等结构。
2.选择(分支)结构
选择结构是用来判定所给定的条件是否满足,根据判定的结果(“真”或“假”)决定执行给出的两种操作之一。选择结构主要是通过if语句和switch语句实现的。
(1)if语句
if语句是用来判定所给定的条件是否满足,根据判定的结果(“真”或“假”)决定执行给出的两种操作之一。
C语言提供了四种形式的if语句:
① 单分支格式 if(表达式)语句 ② 双分支格式 if(表达式) 语句1 else 语句2 ③ 多分支格式 if(表达式1) 语句1 else if(表达式2) 语句2 else if(表达式3) 语句3 … else if(表达式m) 语句m else语句n ④ 嵌套的分支格式 if(表达式1) if(表达式2) 语句1 else语句2 else if(表达式3) 语句3 else语句4
(2)switch语句
switch语句是多分支选择语句,用来实现多分支选择结构。它的一般形式如下:
switch(表达式) {case常量表达式1:语句1 case常量表达式2:语句2 … case 常量表达式n:语句n default: 语句n+1 }
3.循环结构
程序中出现反复执行相同的操作时,就要用到循环结构。C语言中的循环语句有三种,即while 、do while和for。用goto语句和if语句也能构成循环。
(1)while语句
while语句的一般形式为:
while(表达式)
循环体
其执行过程为:
① 计算while后面圆括号中表达式的值,若其结果为非0,转②;否则转③。
② 执行循环体,转①。
③ 退出循环,执行循环体下面的语句。
(2)do~while语句
do~while语句的一般形式为:
do
循环体while(表达式);
其执行过程为:
① 执行循环体,转②。
② 计算while后面圆括号中表达式的值,若其结果为非0,转1;否则转3。
③ 退出循环,执行循环体下面的语句。
(3)for语句
for语句的一般形式为:
for(表达式1;表达式2;表达式3)
循环体
其执行过程为:
① 计算表达式1,转②。
② 计算表达式2,若其值为非0,转③;否则转⑤。
③ 执行循环体,转④。
④ 计算表达式3,转②。
⑤ 退出循环,执行循环体下面的语句。
(4)break语句
break语句的一般形式为:
break;
用于switch语句时,退出switch语句,程序转至switch语句下面的语句;用于循环语句时,退出它所在的循环体,程序转至循环体下面的语句。
(5)continue语句
continue语句的一般形式为:
continue;
continue语句的功能是结束本次循环,跳过循环体中尚未执行的部分,进行下一次是否执行循环的判断。在while语句和do~while语句中,continue把程序控制转到while后面的表达式处,在for语句中continue把程序控制转到表达式3处。
1.3.4 数组
1.一维数组
数组是具有相同数据类型的数据的有序集合,并且用唯一的名字来标识,其元素可以通过下标来引用。
通常,一维数组的定义方式为:类型标识符 数组名[元素个数];
例如:
char a[10];
其中:“数组名”与普通变量的命名规则相同,是一个合法的标识符;“类型符”说明该数组元素的数据类型;方括号[]中的“元素个数”必须是一个整型常量,不能含有变量,称为数组长度,即数组中的元素个数。在存储属性上,数组与普通变量相同,可以在数组定义时用auto、static和register进行修饰限制,也可以定义外部数组。一维数组元素的引用有以下几种方式:
(1)通过数组的首地址引用数组元素。
(2)通过指针来引用一维数组元素。
(3)用带下标的指针变量引用一维数组元素。
数组不能作为整体来操作(字符串除外),只能单独使用数组的元素,而每个元素是用数组名和下标来表示的:数组名[下标]。
其中,下标是一个整型表达式,从0开始。
一个带下标的数组元素与普通变量完全相同,或者说,数组名、[ ]和下标构成了一种特殊的变量名。
2.多维数组
在多维数组里常用的是二维数组,这里重点讲述二维数组的相关知识。
二维数组的定义格式是:类型说明符 数组名[常量表达式][常量表达式];。
二维数组是一种特殊的一维数组:它其中的一个元素又是一个一维数组;二维数组中元素排列的顺序是:按行存放,即在内存中先顺序存放第一行的元素,再存放第二行的元素。
二维数组元素的引用:若定义int *p,a[3][4];。
(1)通过地址引用二维数组元素,地址的表示形式有5种:
① a[i][j];
② *(a[i]+j);
③ *(*(a+i)+j);
④(*(a+i))[j];
⑤ *(&a[0][0]+4*i+j)或*(a[0]+4*i+j)
其中0≤i<3,0≤j<4;
(2)通过建立一个指针数组来引用二维数组元素。如定义一个指向一维数组的指针变量:int(*p)[4];,通过p间接访问二维数组中的数组元素,其形式为:*(*(p+i)+j)。
3.字符数组
字符数组的定义与普通数组的定义类似,只是“类型标识符”固定为“char”。字符数组元素的引用与普通数组相同。在C语言中,可以将字符串作为字符数组来处理。为了测定字符串的实际长度,C语言规定一个“字符串结束标志”,以字符'\0'代表。
有了结束标志'\0'后,字符数组的长度就显得不那么重要了。在程序中,往往依靠检测'\0'的位置来判定字符串是否结束,而不是根据数组的长度来决定字符串长度。
因此,在定义存储字符串的字符数组时,总需要将数组的长度定义为字符串中包含的字符个数加1并附加上字符'\0'。
提示:'\0'、0和'0'的区别:字符'\0'就是ASCII表中的第一个字符,它的值为整数0,故字符'\0'相当于整数0。但字符'0'是一个数字字符,ASCII码值为48,即字符'0'相当于整数48。
1.3.5 函数
一个较大的程序一般应分为若干个程序模块,每一个模块用来实现一个特定的功能。在C语言中用函数来实现模块的功能。
一个C语言程序可由一个主函数和若干个其他函数构成。由主函数调用其他函数,其他函数之间也可以相互调用,但不能调用主函数。同一个函数可以被一个或多个函数调用任意多次。
1.函数的分类
从用户使用角度看,函数有两种:①标准函数即库函数;②用户自定义函数。
从函数的形式看,函数分为两类:①无参函数;②有参函数。
2.函数的定义
(1)无参函数的定义形式
类型标识符 函数名() {声明部分 语句 }
用“类型标识符”指定函数值的类型,即函数应该带回来的值的类型。无参函数一般不需要带回函数值,因此,可以不写“类型标识符”。
(2)有参函数的定义形式
类型标识符 函数名(形式参数表列) {声明部分 语句 }
如果在定义函数时不指定函数类型,系统会隐含指定函数类型为int型。
(3)空函数
空函数的一般定义形式为: 类型标识符 函数名() {}
调用这样的函数时,什么工作也不做,没有任何实际作用。只是先占一个位置,说明此处要调用一个函数。这样做,程序的结构清楚,可读性好,可扩充性强,对程序结构影响不大。
3.形式参数与实际参数
在调用函数时,大多数情况下,主调函数和被调用函数之间有数据传递关系。在定义函数时,函数名后面括弧中的变量名称为“形式参数”(简称“形参”),在主调函数中调用一个函数时,函数名后面括弧中的参数(可以是一个表达式)称为“实际参数”(简称“实参”)。
关于形参和实参的几点说明:
(1)在定义函数中指定的形参,在未出现函数调用时,它们并不占内存中的存储单元。只有在发生函数调用时,函数的形参才分配内存单元;
(2)实参可以是常量、变量或表达式;
(3)在被定义的函数中,必须指定形参的类型;
(4)实参与形参的类型应相同或赋值兼容;
(5)C语言规定,实参变量对形参变量的数据传递是“值传递”,即单向传递,只由实参传给形参,而不能由形参传回来给实参。在内存中,实参单元与形参单元是不同的单元。
4.参数值的传递
参数值的传递分两种:值传递和传地址。
传地址一般是指指针变量作形参。
5.函数调用
(1)局部变量及其使用
在一个函数内部定义的变量是内部变量,它只在本函数范围内有效,也就是说只有在本函数内才能使用它们,在此函数以外是不能使用这些变量的,称为局部变量。
关于局部变量的说明如下:
· 主函数main中定义的变量同样也只在主函数中有效。主函数也不能使用其他函数中定义的变量。
· 不同函数中可以使用相同名字的变量,它们代表不同的对象,互不干扰。
· 形式参数也是局部变量。
· 在一个函数内部,可以在复合语句中定义变量,这些变量只在本复合语句中有效,这种复合语句也可称为“分程序”或“程序块”。
(2)全局变量及其使用
与局部变量定义类似,在函数之外定义的变量称为外部变量,外部变量是全局变量(也称为全程变量)。全局变量可以为本文件中其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束。
1.3.6 编译预处理
1.不带参数的宏定义
用一个指定的标识符(即名字)来代表一个字符串,它的一般形式为:#define标识符字符串。
这种方法使用户能以一个简单的名字代替一个长的字符串,因此把这个标识符(名字)称为“宏名”,在预编译时将宏名替换成字符串,这个过程称为“宏展开”,#define是宏定义命令。
2.带参数的宏定义
不是进行简单的字符串替换,还要进行参数替换。其定义的一般形式为:#define宏名(参数表)字符串。
对带参数的宏定义是这样展开置换的:在程序中如果有带实参的宏,则按#define命令行中指定的字符串从左到右进行置换。如果串中包含宏中的形参,则将程序语句中相应的实参代替形参。如果宏定义中的字符串中的字符不是参数字符,则保留。
3.文件包含
所谓“文件包含”处理是指一个源文件可以将另一个源文件的全部内容包含进来,即将另外的文件包含到本文件之中。
C语言提供了#include命令用来实现“文件包含”的操作。它有以下两种常用的形式:
① #include "文件名"
② #include <文件名>
两者的区别在于:用尖括号时,系统到存放C库函数头文件所在的目录中寻找要包含的文件,称为标准方式。用双引号时,系统先在用户当前目录中寻找要包含的文件,若找不到,再按标准方式查找。
1.3.7 指针
1.指针变量的定义和引用
(1)指针
指针是C语言中的一个重要的概念,也是C语言的一个重要特色。可以认为,不掌握指针就是没有掌握C语言的精华。
在C语言中,将地址形象化地称为“指针”,意思是通过它能找到以它为地址的内存单元,一个变量的地址称为该变量的“指针”。
(2)指针变量
变量的指针就是变量的地址,存放变量地址的变量是指针变量,用来指向另一个变量。为了表示指针变量和它所指向的变量之间的联系,在程序中用“*”符号表示“指向”。
定义指针变量的一般形式为:基类型 *指针变量名;
(3)与指针相关的运算符
有两个与指针相关的运算符:
1)&:取地址运算符;
2)*:指针运算符(或称“间接访问”运算符)。
对“&”和“*”运算符做如下3点说明(假设已定义变量a和指针变量pointer_a):
① 如果已执行了语句“pointer_a=&a; ”,若有&*pointer_a,它的含义是什么呢?“&”和“*”两个运算符的优先级别相同,但按自右向左方向结合,因此,先进行*pointer_a的运算,它就是变量a,再执行&运算。因此,&*pointer_a与&a相同,即变量a的地址。
② *&a的含义又是什么呢?先进行&a运算,得到a的地址,再进行*运算。即&a所指向的变量,*&a和*pointer_a的作用是一样的,它们等价于变量a,即*&a与a等价。
③(*pointer_a)++相当于a++。注意括号是必要的,如果没有括号,就成为了*pointer_a++。由于++在pointer_a的右侧,是“后加”,因此,先对pointer_a的原值进行*运算,得到a的值,然后使pointer_a的值改变,这样pointer_a不再指向a了。
2.数组与指针
(1)指向数组元素的指针
定义一个指向数组元素的指针变量的方法,与定义普通的指针变量相同。例如:
int a[10];/*定义a为包含10个整型数据的数组*/
int p; /*定义p为指向整型变量的指针变量*/
下面是对该指针变量的赋值(引用):
p=&a[0];
表示把a[0]元素的地址赋给指针变量p,也就是说,p指向了a数组的第0号元素。
C语言规定数组名代表数组的首地址(指针),也就是第0号元素的地址。因此,下面两个语句等价:p=&a[0];p=a;。
注意,数组a不代表整个数组,上述“p=a”的作用是“把数组的首地址赋给指针变量p”,而不是“把数组a各元素的值赋给p”。
(2)通过指针引用数组元素
C语言规定:如果指针变量p已指向数组中的一个元素,则p+1指向同一数组中的下一个元素(而不是将p值简单地加1)。例如,数组元素是实型,每个元素占4个字节,则p+1意味着使p的值(地址)加4个字节,以使它指向下一个元素。p+1所代表的地址实际上是p+1×d,d是一个数组元素所占的字节数。
如果p的初值为&a[0],则:
1)p+i和a+i就是a[i]的地址,或者说,它们指向a数组的第i个元素。a代表数组首地址,a+i也是地址。
2)*(p+i)或*(a+i)是p+i或a+i所指向的数组元素,即a[i]。例如,*(p+5)或*(a+5)就是a[5]。即*(p+5)=*(a+5)=a[5]。所以[ ]实际上是变址运算符,即a[i]按a+i计算地址,然后找出此地址单元中的地址;
3)指向数组的指针变量也可以带下标,如p[i]与*(p+i)等价。所以引用一个数组元素,可以用以下两种形式:
① 下标法,如a[i]形式;
② 指针法,如*(a+i)或*(p+i)。其中a是数组名,p是指向数组的指针变量,其初值为p=a。
3.指针与多维数组
这里多维数组的地址都以二维数组为例。假设已定义int a[3][4]={{1,3,5,7},{9,11,13,15},{17,19,21,23}};,a是数组名。从二维数组的角度来看,a代表整个二维数组的首地址,也就是第0行的首地址。a+1代表第1行的首地址。二维数组元素的各种地址的表示形式如表1-2所示(假设a数组的首地址为2000)。
表1-2 二维数组元素的各种地址的表示形式
要注意的是:a是二维数组名,代表数组首地址,但不能企图用*a来得到a[0][0]的值。*a相当于*(a+0),即a[0],它是0行0列元素的地址。
4.字符串与指针
(1)字符串的表示形式在C语言程序中,可以有两种方法访问一个字符串。
① 用字符数组存放一个字符串,然后输出该字符串;
② 用字符指针指向一个字符串。例如char *string= "I am Chinese!";。C语言对字符串常量是按字符数组处理的,在内存开辟了一个字符数组用来存放字符串常量。上述语句在定义字符指针变量string时把字符串首地址(即存放字符串的字符数组的首地址)赋给string。上述语句等价于:
char *string; string= "I am Chinese!";
而不等价于:
char *string; *string= "I am Chinese!";
(2)字符指针变量和字符数组 虽然字符数组和字符指针变量都能实现字符串的存储和运算,但它们之间是有区别的,不应混为一谈,主要区别有以下几点。
① 字符数组由若干个元素组成,每个元素中存放一个字符。字符指针变量中存放的地址(字符串的首地址),而不是将字符串放到字符指针变量中。
② 赋值方式。对字符数组只能对各个元素赋值,不能用以下办法对字符数组赋值。
char str[15]; str="I am Chinese!";
而对字符指针变量,则可以采用下面的方法赋值:
char *str; str="I am Chinese!";
③ 对字符指针变量赋初值:
char *string= "I am Chinese!";
等价于
char *string; string= "I am Chinese!";
而对字符数组的初始化
char str[15]={ "I am Chinese!"};
不能等价于
char str[15]; str[ ]= "I am Chinese!";
即数组可以在定义时整体赋初值,但不能在赋初值语句中整体赋值。
④ 如果定义了一个字符数组,在编译时为它分配内存单元,它有确定的地址。而定义一个字符指针变量时,给指针变量分配内存单元,在其中可以放一个地址值,也就是说,该指针变量可以指向一个字符型数据,但如果未对它赋予一个地址值,则它并未具体指向一个确定的字符数据。
⑤ 指针变量的值是可以改变的。
⑥ 用指针变量指向一个格式字符串,可以用它代替printf函数中的格式字符串。
5.函数与指针
可以用指针变量指向整型变量、字符串、数组,也可以指向一个函数。一个函数在编译时被分配一个入口地址。这个入口地址就称为函数的指针。可以用一个指针变量指向函数,然后通过该指针变量调用此函数。
关于指向函数的指针变量的说明如下:
(1)指向函数的指针变量的一般定义形式为:数据类型(*指针变量名)();。这里的“数据类型”是指函数返回值的类型。
(2)函数的调用可以通过函数名调用,也可以通过函数指针调用(即用指向函数的指针变量调用)。
(3)(*p)()表示定义一个指向函数的指针变量,它不是固定指向哪一个函数,而只是表示定义了这样一个类型的变量,它专门用来存放函数的入口地址。
(4)在给函数指针变量赋值时,只需给出函数名而不必给出参数,如p=max;,因为是将函数入口地址赋给p,而不牵涉到实参与形参的结合问题,不能写成“p=max(a,b); ”的形式。
(5)用函数指针变量调用函数时,只需将(*p)代替函数名即可(p为指针变量名),在(*p)之后的括弧中根据需要要写上实参。如语句“c=(*p)(a,b);”表示:“调用由p指向的函数,实参为a、b,得到的函数值赋给c”。
(6)对指向函数的指针变量,如p+n、p++、p--等运算是无意义的。
6.返回指针值的函数
一个函数可以带回一个整型值、字符值和实型值等,也可以带回指针型的数据,即地址。其概念与以前类似,只是带回的值的类型是指针类型而已。
这种带回指针值的函数,其定义形式为:类型名 *函数名(参数表列)。
例如:
int *a(int x,int y)
a是函数名,调用它以后能得到一个指向整型数据的指针(地址)。x、y是函数a的形参,为整型。请注意在*a两侧没有括号,在a的两侧分别为*运算符和()运算符。而()优先级高于*,因此,a先和()结合。显然这是函数形式。这个函数前面有一个*,表示此函数是指针型函数(函数值是指针)。最前面的int表示返回的指针指向整型变量。
7.指针数组和指向指针的指针
(1)指针数组
一个数组,其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都相当于一个指针变量。一维指针数组的定义形式为:类型名 *数组名[数组长度];。
例如:
int *p[4];
由于[ ]比*优先级高,因此p先与[4]结合,形成p[4]形式,这显然是数组形式,它有4个元素。然后再与p前面的“*”结合,“*”表示此数组是指针类型的,每个数组元素(相当于一个指针变量)都可指向一个整型变量。
它特别适合于用来指向若干个字符串,使字符串处理更加方便灵活。
数组指针是特别容易与指针数组混淆的概念,两者的区别总结如下。
1)指针数组
例如:
int * a[SIZE];
特点:① 是一个数组,共有SIZE个元素,其中每个元素都是指针类型的,并且每个元素的基类型是整型;② sizeof(a)的大小为SIZE*sizeof(int *),即SIZE个指针变量所占的空间。
用法:当一个程序中需要同时用到多个指针时,最好定义一个指针数组。
2)数组指针(指向数组的指针)
例如:
int (*p)[SIZE];
特点:① 是一个指针变量,这个指针变量的基类型是一个有SIZE个元素的整型数组;② sizeof(p)的大小为sizeof(int *),即一个指针变量所占的空间。
用法:当需要用指针来指向一个多维数组时,就应该使用数组指针。
(2)指向指针的指针
定义指向指针的指针变量的一般形式为:类型名 **变量名;
例如:char **p;
p前面有两个*号,*运算符的结合性为从右到左,因此**p相当于*(*p),显然*p是指针变量的定义形式。如果没有最前面的*,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个*号,表示指针变量p是指向一个字符指针变量的(即指向字符型数据的指针变量)。
其实这种方式就是“间接访问”变量的方式。利用指针变量访问另一个变量就是“间接访问”。如果在一个指针变量中存放一个目标变量的地址,这就是“单级间址”。指向指针的指针用的是“二级间址”方法。
1.3.8 结构体和共用体
1.结构体
C语言允许用户自己指定这样一种数据结构,称为“结构体”(structure),它相当于其他高级语言中的“记录”。
为了能在程序中使用结构体类型的数据,应当定义结构体类型的变量,并在其中存放具体的数据。可以采取以下三种方法定义结构体类型变量。
(1)先声明结构体类型再定义变量名,例如:
定义了student1和student2为struct student类型的变量,即它们是具有struct student类型的结构体。
应当注意,将一个变量定义为标准类型(基本数据类型)与定义为结构体类型的不同之处在于后者不仅要求指定变量为结构体类型,而且要求指定为某一特定的结构体类型。
(2)在声明类型的同时定义变量,例如:
struct student {int num; char name[20]; char sex; int age; float score; char addr[30]; }student1,student2;
它的作用与第一种方法相同,即定义了两个struct student类型的变量student1、student2。这种形式定义的一般形式为:
struct结构体名 { 成员表列 }变量名表列;
(3)直接定义结构体类型变量
其一般形式为:
struct { 成员表列 }变量名表列;
其中不出现结构体名。
2.共用体
有时需要使几种不同类型的变量存放到同一段内存单元中。例如,可把一个整型变量、一个字符型变量、一个实型变量放在同一个地址开始的内存单元中。也就是使用覆盖技术,几个变量互相覆盖。这种使几个不同的变量共占同一段内存的结构,称为“共用体”类型结构。
(1)共用体类型数据的定义方法
定义共用体类型变量的一般形式为:
union共用体名
{成员表列
}变量表列;
其他的与结构体相类似,但它们的含义是不同的。结构体变量所占内存长度是各成员占的内存长度之和。每个成员分别占有其自己的内存单元。共用体变量所占的内存长度等于最长的成员的长度。有些C语言的书把union直译为“联合”,所以又称为“联合体”。
(2)共用体类型数据的引用方法
只有先定义了共用体变量才能引用它。不能引用共用体变量,而只能引用共用体变量中的成员。
(3)共用体类型数据的特点
在使用共用体类型数据时要注意以下一些特点:
· 同一个内存段可以用来存放几种不同类型的成员,但在每一瞬时只能存放其中一种,而不是同时存放几种。也就是说,每一瞬时只有一个成员起作用,其他的成员不起作用,即不是同时都存在和起作用。
· 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去了作用,即最后一次赋值有效。
· 共用体变量的地址和它的各成员的地址都是同一地址。
· 不能对共用体变量名赋值,也不能企图引用变量名来得到一个值,更不能在定义共用体变量时对它初始化。
· 不能把共用体变量作为函数参数,也不能使函数带回共用体变量,但可以使用指向共用体变量的指针(这种用法与结构体变量相仿)。
· 共用体类型可以出现在结构体类型定义中,也可以定义共用体数组。反之,结构体也可以出现在共用体类型定义中,数组也可以作为共用体的成员。
1.3.9 文件
文件通常是驻留在外部介质(如磁盘等)上的,在使用时才调入内存中。
C语言把文件看作是一个字符(字节)的序列,即由一个一个字符(字节)的数据顺序组成的。根据数据的组织形式,可分为ASCII文件和二进制文件。
1.文件类型指针
在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。通过文件指针就可对它所指的文件进行各种操作。
定义说明文件指针的一般形式为:
FILE *指针变量标识符;
其中FILE应为大写,它实际上是由系统定义的一个结构,该结构含有文件名、文件状态和文件当前位置等信息。在编写源程序时不必关心FILE结构的细节。
例如:
FILE *fp;
表示fp是指向FILE结构的指针变量,通过fp即可找存放某个文件信息的结构变量,然后,按结构变量提供的信息找到该文件,实施对文件的操作。习惯上也笼统地把fp称为指向一个文件的指针。
2.文件的打开
fopen函数用来打开一个文件,其调用的一般形式为:
文件指针名=fopen(文件名,使用文件方式);
其中,“文件指针名”必须是被说明为FILE类型的指针变量;“文件名”是被打开文件的文件名;“使用文件方式”是指文件的类型和操作要求。“文件名”是字符串常量或字符串数组。
例如:
FILE *fp;
fp=("file a","r");
其意义是在当前目录下打开文件file a,只允许进行“读”操作,并使fp指向该文件。
常用的文件读/写方式如表1-3所示。
表1-3 文件读/写方式
提示:如果不能打开一个文件,即打开文件失败,则fopen()函数将返回一个空指针NULL。
3.文件的关闭
在使用完一个文件后应该关闭它,以防止它再被误用。用fclose函数关闭文件。
fclose函数调用的一般形式为:fclose(文件指针);
例如:
fclose(fp);
fclose函数也带回一个值,当顺利地执行了关闭操作,则返回值为0;否则返回EOF(即-1)。EOF是在stdio.h文件中定义的符号常量,值为-1。
4.文件的读/写
文件打开之后,就可以对其进行读/写操作。在C语言中提供了多种文件读/写的函数。
(1)fputc()和fgetc()函数
1)fputc函数的功能是把一个字符写到磁盘文件上去。
其调用的一般形式为:fputc(ch,fp);。
其中,ch是要输出的字符,它可以是一个字符常量,也可以是一个字符变量。fp是文件指针变量。其作用是将字符ch输出到fp所指向的文件中去。fputc函数也带回一个值:如果输出成功,则返回值就是输出的字符;如果输出失败,则返回EOF(或-1)。
2)fgetc()函数的功能是从指定文件读入一个字符,该文件必须是以读或读/写方式打开,一般调用形式为:ch=fgetc(fp);。
其中fp为文件型指针变量,ch为字符变量。fgetc函数带回一个字符,赋给ch。如果在执行fgetc函数读字符时遇到文件结束符,函数返回一个文件结束标志EOF(即-1)。
(2)fputs()和fgets()函数
1)fputs函数的功能是向指定的文件输出一个字符串。
其调用的一般形式为:fputs(*str,fp);。
其中,str是指向某个字符串的指针,它可以是一个字符串常量,也可以是一个字符型指针变量。fp是文件指针变量。其作用是将str所指向的字符串输出到fp所指向的文件中去。fputs函数也带回一个值:如果输出成功,则返回值就是输出的字符串;如果输出失败,则返回EOF(或-1)。
2)fgets函数的功能是从指定的文件读入一个字符串。
其调用的一般形式为:fgets(str,n,fp);
其中,str是字符数组。fp是文件指针变量。n为要求得到的字符个数,但只从fp指向的文件输入n-1个字符,然后在最后加一个'\0'字符,因此,得到的字符串共有n个字符。如果在读完n-1个字符之前遇到换行符或EOF,读入就结束。fgets函数返回值为str的首地址。
(3)freed()和fwrite()函数
1)fread函数的功能是读入一个数据块。
其调用的一般形式为:fread(buffer,size,count,fp);。
2)fwrite函数的功能是写入一个数据块。
其调用的一般形式为:fwrite(buffer,size,count,fp);。
其中,buffer是一个指针,对fread来说,它是读入数据的存放地址;对fwrite来说,是要输出数据的地址(以上指的是起始地址)。size是要读/写的字节数,count是要进行读/写多少个size字节的数据项,fp是文件型指针。
如果fread或fwrite调用成功,则函数返回值为count的值,即输入或输出数据项的完整个数。
(4)fscanf()和fprinf()函数
fprintf函数的功能是将内存中的数据转换成对应的字符,并以ASCII码形式输出到文本文件中。
其调用的一般形式为:fprintf(文件指针,格式控制字符串,输出表列);
fscanf函数的功能是根据文本文件中的格式进行输入数据。
其一般调用形式为:fscanf(文件指针,格式控制字符串,输入表列);
5.文件的定位
文件中有一个读/写位置指针,指向当前的读/写位置,每次读/写1个(或1组)数据后,系统自动将位置指针移动到下一个读/写位置上。如果想改变系统这种读/写规律,可使用有关文件定位的函数。
(1)rewind函数
rewind函数的功能是使位置指针重新返回文件的开头。此函数没有返回值。
其调用的一般形式为:rewind(fp);。
(2)fseek函数和随机读/写
用fseek函数可以实现改变文件的位置指针。
它调用的一般形式为:fseek(pf,offset,orgin);。
(3)ftell函数
ftell函数的功能是获得文件的当前位置指针的位置。
其调用的一般形式:long t=ftell(fp);。
6.文件的状态
(1)feof函数
feof函数的功能是判断文件是否结束。
其调用的一般形式为:feof(fp);。
若到文件尾,函数指为真。
(2)ferror()函数
ferror函数的功能是检测在调用各种输入/输出函数时,该函数是否出错。
其调用的一般形式为:ferror(fp);。
fp为文件指针。如果ferror返回值为0(假),表示未出错。如果返回一个非零值,表示出错。应该注意的是,对同一个文件每一次调用输入/输出函数,均产生一个新的ferror函数值,因此,应当在调用一个输入/输出函数后,立即检查ferror函数的值,否则信息会丢失。
在执行fopen函数时,ferror函数的初始值自动置为0。
(3)clearer()函数
clearer函数的功能是使文件错误标志和文件结束标志置为0。
其调用的一般形式为:clearer(fp);。
fp为文件指针。
只要出现错误标志,就一直保留,直到对同一文件调用clearer函数或rewind函数,或任何一个其他输入/输出函数。
使用以上函数都要求包含头文件stdio.h。在本节的内容中,fp是一个已经定义好的文件指针。
7.C库文件
C系统提供了丰富的系统文件,称为库文件。C的库文件分为两类,一类是扩展名为“.h”的文件,称为头文件,在前面的包含命令中已多次使用过。在“.h”文件中包含了常量定义、类型定义、宏定义、函数原型以及各种编译选择设置等信息。另一类是函数库,包括了各种函数的目标代码,供用户在程序中调用。通常在程序中调用一个库函数时,要在调用之前包含该函数原型所在的“.h”文件。下面给出Turbo C的全部“.h”文件。
① ALLOC.H说明内存管理函数(分配、释放等)。
② ASSERT.H定义assert调试宏。
③ BIOS.H说明调用IBM—PC ROM BIOS子程序的各个函数。
④ CONIO.H说明调用DOS控制台I/O子程序的各个函数。
⑤ CTYPE.H包含有关字符分类及转换的名类信息(如isalpha和toascii等)。
⑥ DIR.H包含有关目录和路径的结构、宏定义和函数。
⑦ DOS.H定义和说明MSDOS和8086调用的一些常量和函数。
⑧ ERRON.H定义错误代码的助记符。
⑨ FCNTL.H定义在与open库子程序连接时的符号常量。
⑩ FLOAT.H包含有关浮点运算的一些参数和函数。
GRAPHICS.H说明有关图形功能的各个函数,图形错误代码的常量定义,对不同驱动程序的各种颜色值以及函数用到的一些特殊结构。
IO.H包含低级I/O子程序的结构和说明。
LIMIT.H包含各环境参数、编译时间限制、数的范围等信息。
MATH.H说明数学运算函数,还定义了HUGE VAL宏,说明了matherr和matherr子程序用到的特殊结构。
MEM.H说明一些内存操作函数(其中大多数也在STRING.H中说明)。
PROCESS.H说明进程管理的各个函数,spawn…和EXEC …函数的结构说明。
SETJMP.H定义longjmp和setjmp函数用到的jmp buf类型,说明这两个函数。
SHARE.H定义文件共享函数的参数。
SIGNAL.H定义SIG[ZZ(Z] [ZZ)]IGN和SIG[ZZ(Z] [ZZ)]DFL常量,说明rajse和signal两个函数。
STDARG.H定义读函数参数表的宏(如vprintf,vscarf函数)。
STDDEF.H定义一些公共数据类型和宏。
STDIO.H定义Kernighan和Ritchie在UNIX System V中定义的标准和扩展的类型和宏。还定义了标准I/O预定义流:stdin,stdout和stderr,说明I/O流子程序。
STDLIB.H说明一些常用的子程序:转换子程序、搜索/ 排序子程序等。
STRING.H说明一些串操作和内存操作函数。
SYS\STAT.H定义在打开和创建文件时用到的一些符号常量。
SYS\TYPES.H说明ftime函数和timeb结构。
SYS\TIME.H定义时间的类型time[ZZ(Z)[ZZ]]t。
TIME.H定义时间转换子程序asctime、localtime和gmtime的结构,ctime、difftime、gmtime、localtime和stime用到的类型,并提供这些函数的原型。
VALUE.H定义一些重要常量,包括依赖于机器硬件的和为与Unix System相兼容而说明的一些常量,包括浮点和双精度值的范围。
8.文件小结
① C系统把文件当作一个“流”,按字节进行处理。
② C文件按编码方式分为二进制文件和ASCII文件。
③ C语言中,用文件指针标识文件,当一个文件被打开时,可取得该文件指针。
④ 文件在读/写之前必须打开,读/写结束必须关闭。
⑤ 文件可按只读、只写、读/写、增加四种操作方式打开,同时还必须指定文件的类型是二进制文件还是文本文件。
⑥ 文件可按字节、字符串、数据块为单位读/写,文件也可按指定的格式进行读/写。
⑦ 文件内部的位置指针可指示当前的读/写位置,移动该指针可以对文件实现随机读/写。