指针(补)
基本概念
(1)在计算机中,所有的数据都是存放在存储器中的。
(2)一般把存储器中的一个字节称为一个内存单元,不同的数据类型所占用的内存单元数不等,如整型量占2个单元,字符量占1个单元等。
(3)为了正确地访问这些内存单元,必须为每个内存单元编上号。
(4)根据一个内存单元的编号即可准确地找到该内存单元。内存单元的编号也叫做地址。
(5)根据内存单元的编号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。
(6)内存单元的指针和内存单元的内容是两个不同的概念。
(7)对于一个内存单元来说,单元的地址即为指针,其中存放的数据才是该单元的内容。
(8)在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。因此,一个指针变量的值就是某个内存单元的地址或称为某内存单元的指针。
(9)严格地说,一个指针是一个地址,是一个常量。
(10)一个指针变量却可以被赋予不同的指针值,是变量。但常把指针变量简称为指针。即 “指针” 是指地址,是常量,“指针变量”是指取值为地址的变量。定义指针的目的是为了通过指针去访问内存单元。
(11)在一个指针变量中存放一个数组或一个函数的首地址时,因为数组或函数都是连续存放的,通过访问指针变量取得了数组或函数的首地址,也就找到了该数组或函数。
(12)凡是出现数组,函数的地方都可以用一个指针变量来表示,只要该指针变量中赋予数组或函数的首地址即可。
如上图所示,字符变量C,其内容为 “K” (ASCII 码为十进制数75),C占用了011A号单元(地址用十六进数表示)。指针变量P,内容为011A,即称为Р指向变量C,或说Р是指向变量C的指针。
变量的指针与指针变量
变量的指针:即变量的地址;
指针变量:存放变量地址,指向变量。
指针变量
定义指针变量
类型说明符 *变量名;一个指针变量只能指向同类型的变量;
指针变量的引用:几点说明:
(1)指针变量同普通变量一样,使用之前不仅要定义说明,而且必须赋予具体的值;
(2)未经赋值的指针变量不能使用,否则将造成系统混乱,甚至死机;
(3)指针变量的赋值只能赋予地址,决不能赋予任何其它数据,否则将引起错误;
(4)在C语言中,变量的地址是由编译系统分配的,对用户完全透明,用户不知道变量的具体地址。
(1) &:取地址运算符;如,&a 表示变量 a 的地址;
(2) *:指针运算符;
其中,需注意:
(1)取地址运算符& 是单目运算符,其结合性为自右至左,其功能是取变量的地址。
(2)取内容运算符 ∗ *∗是单目运算符,其结合性为自右至左,用来表示指针变量所指的变量。在∗ *∗运算符之后跟的变量必须是指针变量。
指针变量初始化
int a, b;
int *p = &a;
p = &b;
*p = 100; //使 p 指向的地址的值为100 ,即a =100
以下为错误:
p = 1000; //不能把一个数赋予指针变量;指针变量的赋值只能是地址
*p = &b; //被赋值的针变量前不能再加“*"说明符
指针变量的引用
int i = 200, x;
int *ip;
ip = &i;
x = *ip; //等价于 x = i; *ip 访问的是地址为1800的存储区域
指针变量和一般变量一样,存放在它们之中的值是可以改变的,如:
通过指针访问它所指向的一个变量是以间接访问的形式进行的,所以比直接访问一个变量要费时间,且不直观,因为通过指针要访问哪一个变量,取决于指针的值(即指向)。
指针变量作函数参数
函数的参数不仅可以是整型、实型、字符型等数据,还可以是指针类型。它的作用是将一个变量的地址传送到另一个函数中。
swap_1(int x, int y) //注:该函数不能实现对应实参的互换,互换只发生在函数域内
{
int temp;
temp = x;
x = y;
y = temp;
}
swap(int *p1, int *p2)
{
int temp;
temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main()
{
int a, b;
int *pointer_1, *pointer_2;
a = 5, b = 9;
pointer_1 = &a;
pointer_2 = &b;
if(a<b)
{
swap(pointer_1, pointer_2); //可实现 a,b值的调换,即 a=9;b=5
swap_1(a, b); //不能调换a,b值,即 a=5;b=9
}
}
指针变量的运算
赋值运算:
(1)指针变量初始化;
(2)把一个变量的地址赋予指向相同数据类型的指针变量;
int a, *p;
p = &a; //把整型变量a的地址赋予整型指针变量 p
(3)把一个指针变量的值赋予指向相同类型变量的另一个指针变量;
int a, *p = &a, *p1;
p1 = p; //把 p 的地址赋予指针变量 p1
(4)把数组首地址赋予指向数组的指针变量;
int a[5], *p;
p = a; //数组名表示数组首地址
或
p = &a[0]; //数组第一个元素的地址也是数组的首地址
或
*p = a; //赋值
(5)把字符串的首地址赋予指向字符类型的指针变量;
char *pc;
pc = "ABC"; //这里并不是把整个字符串装入指针变量,而是把存放该字符串的字符数组的首地址装入指针变量。
(6)把函数的入口地址赋予指向函数的指针变量。
int (*pf)();
pf = f; // f 为函数名
加减算术运算
对于指向数组的指针变量,可以加上或减去一个整数n。注意:
(1)指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。
(2)数组指针变量向前或向后移动一个位置和地址加1或减1在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。
(3)如指针变量加1,即向后移动1个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。
(4)指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的。
int a[5], *p;
p = a; //p指向数组a,也是指向a[0]
p = p + 2; //p指向a[2],即 p的值为&pa[2]
两个指针变量之间的运算
只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义。
(1)两指针变量相减:
两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数);
(2)两指针变量不能进行加法运算;
(3)两指针变量进行关系运算:
指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。
(4)指针变量可同 0 比较;
(5)对指针变量赋 0 值和不赋值是不同的:
指针变量未赋值时,可以是任意值,是不能使用的,否则将造成意外错误;
而指针变量赋 0 值后,则可以使用,只是它不指向具体的变量而已。
int a[5], *p1, *p2;
p1 = a; //p指向数组a,也是指向a[0]
p1 == p2; //表示 p1 和 p2 指向同一数组元素
p1 > p2; //表示 p1 处于高地址位置;
p1 == 0; //表示 p 是空指针,不指向任何变量
p1 != 0; //表示 p 不是空指针
p1 = 0; //空指针
数组指针和指向数组的指针变量:
所谓数组的指针是指数组的起始地址,数组元素的指针是数组元素的地址。
指向数组元素的指针
基本概念:
一个数组是由连续的一块内存单元组成的。数组名就是这块连续内存单元的首地址。一个数组也是由各个数组元素(下标变量)组成的。每个数组元素按其类型不同占有几个连续的内存单元,一个数组元素的首地址也是指它所占有的内存单元的首地址。C语言规定,数组名代表数组的首地址,也就是第0号元素的地址;
通过指针引用数组元素
如果指针变量p 已指向数组中的一个元素,则 p+1指向同一数组中的下一个元素。若 p 的初值为 &a 或 &a[0],则
(1)p+i 和 a+i 就是 a[i] 的地址,或者说它们指向a数组的第i个元素;
(2)∗ *∗(p+i) 或 ∗ *∗(a+i) 就是 p+i 或 a+i 所指向的数组元素,即a[i];
(3)指向数组的指针变量也可以带下标,即 p[i] 与 *(p+i) 等价。
需注意:
(1)指针变量可以实现本身的值的改变。如 p++ 是合法的;而 a++ 是错误的。因为 a 是数组名,它是数组的首地址,是常量。
(2)需注意指针变量的当前值;
(3)需数组的长度是固定的,但指针变量可以指到数组以后的内存单元,系统并不认为非法;
数组名做函数参数
数组名可以作函数的实参和形参,数组名就是数组的首地址,实参向形参传送数组名实际上就是传送数组的地址,形参得到该地址后也指向同数组。
如果有一个实参数组,想在函数中改变此数组的元素的值,实参与形参的对应关系有:
(1)形参和实参都是数组名;
(2)实参用数组名,形参用指针变量;
(3)实参、形参都用指针变量;
(4)实参为指针变量,形参为数组名;
指向多维数组的指针和指针变量
多维数组的地址,以二维数组为例,a[3][4] ={{0,1,2,3},{4,5,6,7},{8,9,10,11}};
,设其首地址为 1000,则各变量首地址及其值如下:
指向多维数组的指针变量(以二维为例):类型说明符 (*指针变量名)[长度];
其中,“长度”指将二维数组分解为多个一维数组时,一维数组的长度,即二维数组的列数。
应注意 “(*指针变量名)” 两边的括号不可少,如缺少括号则表示是指针数组,意义就完全不同了。
将二维数组a[3][4] ={{0,1,2,3},{4,5,6,7},{8,9,10,11}}
分解为一维数组:a[0], a[1], a[2] 后
int (*p)[4];
p = a; // 指针 p 指向二维数组 a;p+i 指向一维数组 a[i]
*(p+i)+j 为二维数组第 i 行,第 j 列的地址
*(*(p+i)+j) 为二维数组第 i 行,第 j 列的值
字符串的指针指向字符串的针指变量
C语言中,可用两种方法访问一个字符串:
用字符数组存放字符串
char string[] = "ABC"; //string是数组名,它代表字符数组的首地址。
用字符串指针指向一个字符串,
需注意字符串指针变量和指向字符的指针变量的区别。
char *s = "ABC"; //表示s是一个指向字符串的指针变量。把字符串的首地址赋予s.
char c, *p =&c; //表示p是一个指向字符变量c的指针变量。
//把一个字符串的内容复制到另一个字符中
void cpystr(char *pss, char *pds)
{
//pps指向源字符串, pds指向目标字符串
while((*pds=*pps)!='\0')
{
pds++;
pps++;
}
}
int main()
{
char *p1 = "ABC", b[10], *p2;
p2 = b;
cpystr(p1, p2);
}
其中, cpystr 函数可简化为:
void cpystr(char *pss, char *pds)
{ while((*pds++ = *pss++)!='\0');}
或
void cpystr(char *pss, char *pds)
{ while((*pds++ = *pss++));}
// '\0'的 ASCII码值为0,while语句其表达式为0则结束循环
使用字符串指针变量与字符数组的区别
(1)字符串指针变量本身是一个变量,用于存放字符串的首地址。
(2)字符串本身是存放在以该首地址为首的一块连续的内存空间中,以 ‘\0’ 作为串的结束。
(3)字符数组是由于若干个数组元素组成的,它可用来存放整个字符串。
(1)对字符串指针方式,可写为:
char *ps = "ABC";
或
char *ps;
ps = "ABC";
(2)对数组方式,只能写作:
char st[] = {"ABCD"};
不能写为:
char st[10];
st = {"ABCD"};
其只能对字符数组的各个元素逐个赋值
函数指针变量
基本概念:
(1)在C语言中,一个函数总是占用一段连续的内存区,而函数名就是该函数所占内存区的首地址。
(2)可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。把这种指向函数的指针变量称为”函数指针变量”。
(3)函数指针变量定义的一般形式为:类型说明符 (*指针变量名)();
其中 “类型说明符” 表示被指函数的返回值的类型。“(∗ *∗指针变量名)“表示”∗ *∗"后面的变量是定义的指针变量。最后的空括号表示指针变量所指的是一个函数。
(4)调用函数的一般形式为:(*指针变量名)(实参表);
int max(int a, int b);
{
if(a>b) return a;
else return b;
}
int main()
{
int max(int a, int b);
int x = 3, y = 4, z;
int (*pf)(); //定义函数指针变量,表示pf是一个指向函数入口的指针变量,该函数的返回值(函数值)是整型。
pf = max; //把被调函数的入口地址(函数名)赋子该函薮指针变量,
z = (*pf)(x,y); //用函数指针变量形式调用函数
}
需注意:
(1)函数指针变量不能进行算术运算,这是与数组指针变量不同的。
(2)数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。
(3)函数调用中"(∗ *∗指针变量名)"的两边的括号不可少,其中的 * 不应该理解为求值运算,在此处只是一种表示符号。
指针型函数
函数类型是指函数返回值的类型。在C语言中允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数,气一般形式为:
类型说明符 *函数名(形参表)
{
...../函数体/
}
函数名之前加了 “*" 号表明这是一个指针型函数,即返回值是一个指针。
指针数组
(1)一个数组的元素值为指针则是指针数组。
(2)指针数组是一组有序的指针的集合。
(3)指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量。
(4)指针数组说明的一般形式为:类型说明符 *数组名[数组长度]
int *pa[3]; //表示pa是一个指针数组,它有三个数组元素,每个元素值都是一个指针,指向整型变量。
(5)可用一个指针数组来指向一个二维数组。指针数组中的每个元素被赋予二维数组每一行的首地址,即指向一个一维数组。
int a[3][3] = {1,2,3,4,5,6,7,8,9};
int *pa[3] = {a[0],a[1],a[2]};
int *p = a[0];
//其中*a[i]表示i行0列元素值;
//*(*(a+i)+i)表示i行i列的元素值;
//*pa[i]表示i行0列元素值;
//由于p与a[0]相同,故p[i]表示0行i列的值:*(p+i)表示0行i列的值
(6)应该注意指针数组和二维数组指针变量的区别。这两者虽然都可用来表示二维数组,但是其表示方法和意义是不同的。
(7)二维数组指针变量是单个的变量,其一般形式中 (*指针变量名) 两边的括号不可少。
(8)指针数组类型表示的是多个指针(一组有序指针)在一般形式中 *指针数组名 两边不能有括号。
int (*p)[3]; //表示一个指向二维数组的指针变量。该二维数组的列数为3或分解为一维数组的长度为
int *p[3]; //表示p是一个指针数组,有三个下标变量p[0],p[1],p[2]均为指针变量。
(9)指针数组也常用来表示一组字符串,这时指针数组的每个元素被赋予一个字符串的首地址。指向字符串的指针数组的初始化更为简单。
char *string[] = {"ABC", "EFD"};
指向指针的指针:
(1)如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。
(2)通过指针访问变量称为间接访问。由于指针变量直接指向变量,所以称为“单级间址”。
(3)通过指向指针的指针变量来访问变量则构成“二级间址”。
//name是一个指针数组,它的每一个元素是一个指针型数据,其值为地址;
char *name[] = {"Follow me", "BASIC", "Great Wa11", "FORTRAN", "Computer desighn"};
char **p; //相当于 *(*p)
p = name; //p是指向指针的指针变量
说明:
(1)p前面有两个∗ *∗号,相当于∗ *∗(∗ *∗p)。
(2)如果没有最前面的∗ *∗,那就是定义了一个指向字符数据的指针变量。
(3)前面又加一个∗ *∗号,表示指针变量 p 是指向一个字符指针型变量的。也就是 p 所指向的另一个指针变量。
(4)name+1是 name[i] 的地址,即指向指针型数据的指针(地址)。
(5)设置一个指针变量 p,使它指向指针数组元素。p 就是指向指针型数据的指针变量。
main 函数的参数
(1)前面介绍的main函数都是不带参数的。因此main后的括号都是空括号。
(2)实际上,main函数可以带参数,这个参数可以认为是 main函数的形式参数。
(3)C语言规定main 函数的参数只能有两个,习惯上这两个参数写为 argc和 argv。
(4)C语言还规定argc(第一个形参)必须是整型变量, argv(第二个形参)必须是指向字符串的指针数组。
(5)main函数的函数头可写为:main(int argc, char argv[])
(6)main函数不能被其它函数调用,因此不可能在程序内部取得实际值。
(7)main 函数的参数值是从操作系统命令行上获得的。当我们要运行一个可执行文件时,在 DOS提示符下键入文件名,再输入实际参数即可把这些实参传送到main的形参中去。
(8)DOS提示符下命令行的一般形式为:C:>可执行文件名 参数 参数…;
(9)特别注意,main 的两个形参和命令行中的参数在位置上不是一一对应的。因为, main的形参只有二个,而命令行中的参数个数原则上未加限制。(10)argc参数表示了命令行中参数的个数(注意:文件名本身也算一个参数),argc 的值是在输入命令行时由系统按实际参数的个数自动赋予的。
例,命令行:
C:\>E24 BASIC foxpro FORTRAN
//由于文件名E24本身也算一个参数,所以共有4个参数,因此argc取得的值为4。
//argv参数是字符串指针数组,其各元素值为命令行中各字符串(参数均按字符串处理)的首地址。
//指针数组的长度即为参数个数。
//数组元素初值由系统自动赋予。
有关指针的汇总
有关指针的数据类型:
指针运算
int *p;
(1)指针变量加减一个整数:一个指针变量加(减)一个整数并不是简单地将原值加(减)一个整数,而是将该指针变量的原值(是一个地址)和它指向的变量所占用的内存单元字节数加(减)。
p++;
p--;
p+i;
p-i;
p+=i;
p-=i;
(2)指针变量赋值:讲一个变量的地址赋给另一个指针变量
p = &a; //将变量 a 的地址赋给 p
p = array; //将数组 array 的首地址赋给 p
p = &array[i]; //将数组 array 的第 i 个元素的地址赋给 p
p = max; //max为已定义的函数,将 max 的入口地址赋给 p
p1 = p2 ; // p1 和 p2 都是指针变量,将 p2 的值赋给 p1
注意,以下是错误的:
p = 1000; //指针不能赋实值
(3)指针变量可以有空值,即该指针变量不指向任何变量;
p = NULL;
(4)两个指针变量可以相减:如果两个指针变量指向同一个数组的元素,则两个指针变量值之差是两个指针之间的元素个数;但不能相加。
(5)两个指针变量比较:如果两个指针变量指向同一个数组的元素,则两个指针变量可以进行比较。指向前面的元素的指针变量“小于”指向后面的元素的指针变量。
动态内存分配
概述
C语言中,数组的长度是预先定义好的,在整个程序中固定不变,即不允许动态数组类型。但是在实际的编程中,所需的内存空间取决于实际输入的数据,而无法预先确定。因此,以内内存管理函数,常用的有:
内存空间分配函数 malloc:
调用形式:(类型说明符*)malloc(size)
其功能是在内存的动态存储区中分配一块长度为"size"字节的连续区域。函数的返回值为该区域的首地址。
其中,“类型说明符”表示把该区域用于何种数据类型;( 类 型 说 明 符 ∗ ) (类型说明符*)(类型说明符∗) 表示把返回值强制转换为该类型指针;“size"是一个无符号数。
pc = (char *)malloc(100);
//表示分配100个字节的内存空间,并强制转换为字符数组类型,
//函数的返回值为指向该字符数组的指针,把该指针赋予指针变量pc.
内存空间分配函数 calloc:
调用形式:(类型说明符*)calloc(n, size)
其功能为在内存动态存储区中分配 n 块长度为"size"字节的连续区域。函数的返回值为该区域的首地址;calloc函数与malloc函数的区别仅在于一次可以分配n块区域。
ps = (struct date *)calloc(2, sizeof(struct date));
//其中的sizeof(struct date)是求date的结构长度。
//该语句的意思是:按stu的长度分配2块连续区域,强制转换为stu类型,并把其首地址赋予指针变量ps.
内存空间释放函数 free:
调用形式 free(指针名);
其功能为释放指针所指向的一块内存空间,该指针指向被释放区域的首地址。被释放区应是由malloc或calloc函数所分配的区域。
枚举类型
概述
在实际问题中,有些变量的取值被限定在一个有限的范围内。例如,一个星期内只有七天,一年只有十二个月,一个班每周有六门课程等等。如果把这些量说明为整型,字符型或其它类型显然是不妥当的。
为此,C语言提供了一种称为“枚举”的类型。在“枚举”类型的定义中列举出所有可能的取值,被说明为该“枚举”类型的变量取值不能超过定义的范围。注意,枚举类型是一种基本数据类型,而不是一种构造类型,因为它不能再分解为任何基本类型。
枚举类型的定义
enum 枚举名{枚举值表};
在枚举值表中应罗列出所有可用值,这些值被称为枚举元素。
枚举变量的声明
enum weekday{sun, mou, tue, wed, thu, fri, sat}; //定义枚举类型
enum weekday a, b, c; //先定义,后声明
enum weekday{sun, mou, tue, wed, thu, fri, sat}a, b, c; //同时定义和声明
enum {sun, mou, tue, wed, thu, fri, sat}a, b, c; //直接声明
枚举变量的赋值
- 枚举值是常量,不是变量。不能在程序中用赋值语句再对它赋值。
- 枚举元素本身由系统定义了一个表示序号的数值,从0开始顺序定义为0,1,2…,如在weekday中,sun值为0,mon 值为1,…, sat 值为6。
- 只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量
- 如一定要把数值赋予枚举变量,则必须用强制类型转换。
- 枚举元素不是字符常量也不是字符串常量,使用时不要加单、双引号。
a = sum; b = mon; //正确的
a = 0; b = 1; //错误的
a = (enum weekday)2; //强制类型转换,把数值赋予枚举变量,相当于 a = tue
类型定义符 typedef
C语言还允许由用户自己定义类型说明符,也就是说允许由用户为数据类型取“别名”。类型定义符typedef即可用来完成此功能。
其一般形式为:typedef 原类型名 新类型名,其中原类型名中含有定义部分,新类型名一般用大写表示,以便于区别。
typedef int INTEGER //用INTEGER来代替int 作整型变量的类型说明了。
INTEGER a, b; //等价于 int a, b
用 typedef 定义数组、指针、结构等类型将带来很大的方便,不仅使程序书写简单而且使意义更为明确,因而增强了可读性。
typedef char NAME[20]; //表示NAME是字符数组类型,数组长度为 20
NAME a1, a2, a3; //等效于 char a1[20], a2[20], a3[20]
typedef struct stu
{
char name[20];
int age;
char sex;
}STU;
STU boy1, boy2; //定义STU表示stu的结构类型,然后可用STU来说明结构变量
有时也可用宏定义来代替 typedef 的功能,但是宏定义是由预处理完成的,而 typedef 则是在编译时完成的,后者更为灵活方便。
文件
C 文件概述
所谓“文件”是指一组相关数据的有序集合。这个数据集有一个名称,叫做文件名。文件通常是驻留在外部介质(如磁盘等)上的,在使用时才调入内存中来。
从不同的角度可对文件作不同的分类。从用户的角度看,文件可分为普通文件和设备文件两种。普通文件 是指驻留在磁盘或其它外部介质上的一个有序数据集,可以是源文件、目标文件、可执行程序;也可以是一组待输入处理的原始数据,或者是一组输出的结果。对于源文件、目标文件、可执行程序可以称作程序文件,对输入输出数据可称作数据文件。设备文件 是指与主机相联的各种外部设备,如显示器、打印机、键盘等。在操作系统中,把外部设备也看作是一个文件来进行管理,把它们的输入、输出等同于对磁盘文件的读和写。
通常把显示器定义为 标准输出文件,一般情况下在屏幕上显示有关信息就是向标准输出文件输出。如前面经常使用的printf, putchar函数就是这类输出。键盘通常被指定为 标准输入文件,从键盘上输入就意味着从标准输入文件上输入数据。scanf, getchar函数就属于这类输入。
从文件编码的方式来看,文件可分为 ASCII 码文件 和 二进制码文件 两种。ASCII 文件也称为文本文件,这种文件在磁盘中存放时每个字符对应一个字节,用于存放对应的ASCII 码;二进制文件是按二进制的编码方式来存放文件的。
ASCII 码文件可在屏幕上按字符显示,例如源程序文件就是ASCII 文件,用DOS命令TYPE可显示文件的内容。由于是按字符显示,因此能读懂文件内容。二进制文件虽然也可在屏幕上显示,但其内容无法读懂。
C系统在处理这些文件时,并不区分类型,都看成是字符流,按字节进行处理。输入输出字符流的开始和结束只由程序控制而不受物理符号(如回车符)的控制。因此也把这种文件称作 “流式文件”。
文件指针
在C语言中用一个指针变量指向一个文件,这个指针称为文件指针。通过文件指针就可对它所指的文件进行各种操作。
其定义的一般形式为:FILE *指针变量标识符;
其中 FILE 为大写,它实际上是由系统定义的一个结构,该结构中含有文件名、文件状态和文件当前位置等信息,在编写源程序时不必关心FILE结构的细节。
FILE *fp;
//表示fp是指向FILE结构的指针变量,通过fp即可找存放某个文件信息的结构变量,然后按结构变量提供的信息找到该文件,实施对文件的操作。
//习惯上也笼统地把fp称为指向一个文件的指针。
文件的打开与关闭
文件在进行读写操作之前要先打开,使用完毕要关闭。所谓打开文件,实际上是建立文件的各种有关信息,并使文件指针指向该文件,以便进行其它操作。关闭文件则断开指针与文件之间的联系,也就禁止再对该文件进行操作。在C语言中,文件操作都是由库函数来完成的。
文件打开( fopen 函数):
其使用的一般形式为:文件指针名 = fopen(文件名,使用文件方式);
其中,文件指针名必须是被说明为FILE 类型的指针变量;文件名是被打开文件的文件名;使用文件方式是指文件的类型和操作要求;文件名是字符串常量或字符串数组。
FILE *fp;
fp = fopen("c:\\abc2", "rb"); //表示打开 C 盘下的 abc2 (二进制)文件,只允许进行读操作;
//两个反斜线 “//” 中的第一个表示转义字符,第二个表示根目录。
使用文件的 12 种方式
文件使用方式 | 意义 |
---|---|
“rt” | 只读打开一个文本文件,只允许读数据 |
“wt” | 只写打开或建立一个文本文件,只允许写数据 |
“at” | 追加打开一个文本文件,只允许读数据 |
“rb” | 只读打开一个二进制文件,只允许读数据 |
“wb” | 只写打开或建立一个二进制文件,只允许写数据 |
“ab” | 追加打开一个二进制文件,并在文件末尾写数据 |
“rt+” | 读写打开一个文本文件,允许读和写 |
“wt+” | 读写打开或建立一个文本文件,允许读和写 |
“at+” | 读写打开一个文本文件,允许读或者在文件末尾追加数据 |
“rb+” | 读写打开一个二进制文件,允许读和写 |
“wb+” | 读写打开或建立一个二进制文件,允许读和写 |
“ab+” | 读写打开一个二进制文件,允许读或者在文件末尾追加数据 |
对于文件使用方式的几点说明:
(1)共由 6 个字符 r, w, a, t, b, +
拼接而成,各个字符的含义为:
字符 | 含义 |
---|---|
r(read) | 读 |
w(write) | 写 |
a(append) | 追加 |
t(text | 文本文件(可省略) |
b(banary) | 二进制文件 |
+ | 读、写 |
(2)凡用 “r” 打开一个文件时,该文件必须已经存在,且只能从该文件读出;
(3)用 “w” 打开的文件只能向该文件写入。若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件;
(4)若要向一个已存在的文件追加新的信息,只能用 “a” 方式打开文件。但此时该文件必须是存在的,否则将会出错。
(5)在打开一个文件时,如果出错,fopen 将返回一个空指针值NULL。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。因此常用以下程序段打开文件:
FILE *fp;
if(fp = fopen("c:\\abc2", "rb")==NULL)
{
printf("\nerror open!");
getch(); //从键盘输入一个字符,但不在屏幕上显示;
//(其作用为等待,只有当用户从键盘任意敲一个键时,程序再继续执行)
exit(1); //敲键后执行 exit(1) 退出程序
}
(6)把一个文本文件读入内存时,要将ASCII 码转换成二进制码,而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII 码,因此文本文件的读写要花费较多的转换时间。对二进制文件的读写不存在这种转换。
(7)标准输入文件(键盘),标准输出文件(显示器),标准出错输出(出错信息)是由系统打开的,可直接使用。
文件关闭( fclose 函数):
fclose(文件指针);
正常完成关闭文件操作时,fclose 函数返回值为0。如返回非零值则表示有错误发生。
文件读写
字符读写函数
以字符(字节)为单位的读写函数。每次可从文件读出或向文件写入一个字符。
1 . 读字符函数 fgetc,从指定文件中读一个字符,形式为:字符变量 = fgetc(文件指针);
ch = fgetc(fp); //从打开的文件fp中读取一个字符并送入ch 中。
说明:
(1)在 fgetc 函数调用中,读取的文件必须是以读或读写方式打开的;
(2)读取字符的结果也可以不向字符变量赋值,但这样读出的字符不能保存;
(3)在文件内部有一个位置指针。用来指向文件的当前读写字节。在文件打开时,该指针总是指向文件的第一个字节。使用fgetc函数后,该位置指针将向后移动一个字节。因此可连续多次使用fgetc函数,读取多个字符。应注意文件指针和文件内部的位置指针不是一回事。文件指针是指向整个文件的,须在程序中定义说明,只要不重新赋值,文件指针的值是不变的。文件内部的位置指针用以指示文件内部的当前读写位置,每读写一次,该指针均向后移动,它不需在程序中定义说明,而是由系统自动设置的。
2 . 写字符函数 fputc,把一个字符写入指定的文件中,形式为:fputc(字符量, 文件指针);
其中,待写入的字符量可以是字符常量或变量。
fputc('a', fp); //把字符 a 写入 fp 所指向的文件中
说明:
(1)被写入的文件可以用写、读写、追加方式打开,用写或读写方式打开一个已存在的文件时将清除原有的文件内容,写入字符从文件首开始。如需保留原有文件内容,希望写入的字符以文件末开始存放,必须以追加方式打开文件。被写入的文件若不存在,则创建该文件。
(2)每写入一个字符,文件内部位置指针向后移动一个字节。
(3)fputc 函数有一个返回值,如写入成功则返回写入的字符,否则返回一个EOF。可用此来判断写入是否成功。
字符串读写函数
1 . 读字符串函数 fgets,从指定的文件中读一个字符串到字符数组中,形式为:fgets(字符数组名, n, 文件指针);
其中的n是一个正整数。表示从文件中读出的字符串不超过n-1个字符。在读入的最后一个字符后加上串结束标志 ’\0’ 。
fgets(str, n, fp); //从 fp 所指的文件中读出 n-1 个字符送入字符数组 str 中
说明:
(1)在读出n-1个字符之前,如遇到了换行符或EOF,则读出结束。
(2)fgets 函数也有返回值,其返回值是字符数组的首地址。
2 . 写字符串函数 fputs,向指定的文件写入一个字符串,其形式为:fputs(字符串, 文件指针);
其中字符串可以是字符串常量,也可以是字符数组名,或指针变量。
fputs("abcd", fp); //将字符串 abcd 写入 fp 所指的文件中
数据块读写函数
读数据块函数:fread(buffer, size, count, fp);
写数据块函数:fwrite(buffer, size, count, fp);
其中,buffer 是一个指针,在fread函数中,它表示存放输入数据的首地址。在fwrite函数中,它表示存放输出数据的首地址;
size表示数据块的字节数;
count表示要读写的数据块块数;
fp表示文件指针。
fread(fa, 4, 5, fp);
//从fp所指的文件中,每次读4个字节(一个实数)送入实数组fa中,连续读5次,即读5个实数到fa中。
格式化读写函数
fscanf函数,fprintf 函数与前面使用的scanf和 printf函数的功能相似,都是格式化读写函数。两者的区别在于fscanf 函数和 fprintf函数的读写对象不是键盘和显示器,而是磁盘文件。
格式化读函数:fscanf(文件指针,格式字符串, 输入表列);
格式化写函数:fprintf(文件指针,格式字符串, 输出表列);
文件的随机读写
顺序读写,即读写文件只能从头开始,顺序读写各个数据。但在实际问题中常要求只读写文件中某一指定的部分。为了解决这个问题可移动文件内部的位置指针到需要读写的位置,再进行读写,这种读写称为随机读写。实现随机读写的关键是要按要求移动位置指针,这称为文件的定位。
文件定位
1 . rewind 函数,将文件内部的位置指针移动到文件首。形式为:rewind(文件指针);
2 . fseek 函数,移动文件内部位置指针,形式为:fseek(文件指针, 位移量, 起始点);
注意, fseek 函数一般用于二进制文件。
其中,“文件指针” 指向被移动的文件;
“位移量"表示移动的字节数,要求位移量是long型数据,以便在文件长度大于64KB时不会出错。当用常量表示位移量时,要求加后缀”L”。
"起始点"表示从何处开始计算位移量,规定的起始点有三种:文件首,当前位置和文件尾,其表示方法为:
起始点 | 表示符号 | 数字表示 |
---|---|---|
文件首 | SEEK_SET | 0 |
当前位置 | SEEK_CUR | 1 |
文件末尾 | SEEK_END | 2 |
fseek(fp, 100L, 0); //把位置指针移动到离文件首 100 个字节出
文件检测函数
1 . 文件结束检测函数(feof 函数),功能为判断文件是否处于文件结束位置,如文件结束,则返回值为1,否则为0。
格式为:feof(文件指针);
2 . 读写文件出错检测函数(ferror 函数),功能为检查文件在用各种输入输出函数进行读写时是否出错。如 ferror 返回值为 0 表示未出错,否则表示有错。
格式为:ferror(文件指针);
3 . 文件出错标志和文件结束标志置 0 函数(clearerr 函数),用于清除出错标志和文件结束标志,使它们为 0 值。
格式为:clearerr(文件指针);