C语言总结(四)
文件操作
文件在今天的计算机系统中作用很重要。文件用来存放程序、文档、数据、表格、图片和其他很多种类的信息。作为一名程序员,必须编程来创建、写入和读取文件。编写程序从文件读取信息或者将结果写入文件是一种经常性的需求。C提供了强大的和文件进行通信的方法。使用这种方法我们可以在程序中打开文件,然后使用专门的I/O函数读取文件或者写入文件
一个文件通常就是磁盘上一段命名的存储区,但是对于操作系统来说,文件就会更复杂。例如,一个大文件可以存储在一些分散的区段中,或者还会包含一些操作系统可以确定其文件类型的附加数据,但是这是操作系统,而不是我们程序员所要关心的事情。我们应考虑如何在C程序中处理文件
流的概念
流是一个动态的概念,可以将一个字节形象地比喻成一滴水,字节在设备、文件和程序之间的传输就是流,类似于水在管道中传输,可以看出,流是对输入输出源的一种抽象,也是对传输信息的一种抽象。
C语言中,I/O操作可以简单地看作是从程序移进或移进字节,这种搬运过程便称为流。程序只需要关系是否正确地输出了字节数据,以及是否正确的输入了要读取字节数据,特定I/O设备的细节对程序员是隐藏的。
文本流,也就是我们常说的以文本模式读取文件。文本流的有些特性在不同的系统中可能不同。其中之一就是文本行的最大长度。标准规定至少允许254个字符,另一个可能不同的特性是文本行的结束方式。例如在Windows系统中,文本约定以一个回车符和一个换行符结尾。但是在linux下只使用一个换行符结尾。
二进制流中的字节将完全根据程序编写它们的形式写入文件中,而且完全根据它们从文件或设备读取的形式读入到程序中,它们并未做任何改变。这种类型的流适用于非文本数据,但是如果你不希望I/O函数修改文本文件的行末字符,也可以把他们用于文本文件。
比如说,在widows下,文件的换行符是\r\n,而在Linux下换行符则是\n.
我们程序中,经常看到的文本方式打开文件和二进制方式打开文件仅仅体现在换行符的处理上
当对文件使用文本方式打开的时候,读写的windows文件中的换行符\r\n会被替换成\n读到内存中,当在windows下写入文件的时候,\n被替换成\r\n再写入文件。如果使用二进制方式打开文件,则不进行\r\n和\n之间的转换。 那么由于Linux下的换行符就是\n,所以文本文件方式和二进制方式无区别。
文件流总览
- 程序为同时处于活动状态的每个文件声明一个指针变量,其类型为FILE*。这个指针指向这个FILE结构,当它处于活动状态时由流使用。
- 流通过fopen函数打开,为打开一个流,我们必须指定需要访问的文件或设备以及他们的访问方式(读、写、或者读写)。fopen和操作系统验证文件或设备是否存在并初始化FILE
- 根据需要对文件进行读写操作
- 最后调用fclose函数关闭流。关闭一个流可以防止与它相关的文件被再次访问,保证任何存储与缓冲区中的数据被正确写入到文件中,并释放FILE结构。
I/O函数以三种基本的形式处理数据:单个字符、文本行和二进制数据。对于每种形式都有一组特定的函数对它们处理。
家族名 | 目的 | 可用于所有流 | 只用于stdin和stdout |
---|---|---|---|
getchar | 字符输入 | fgetc、getc | getchar |
putchar | 字符输出 | fputc、putc | putchar |
gets | 文本行输入 | fgets | gets |
puts | 文本行输出 | fputs | puts |
scanf | 格式化输入 | fscanf | scanf |
printf | 格式化输出 | fprintf | printf |
文件指针
我们知道,文件是由操作系统管理的单元。当我们想操作一个文件的时候,让操作系统帮我们打开文件,操作系统把我们指定要打开文件的信息保存起来,并且返回给我们一个指针指向文件的信息。文件指针也可以理解为代指打开的文件。这个指针的类型为FILE类型。该类型定义在stdio.h头文件中。通过文件指针,我们就可以对文件进行各种操作。
对于一个ANSI c程序,运行时系统必须要提供至少三个流stdin(标准输入)、stdout(标准输出)和stderr(标准错误),他们都是指向FILE结构的指针。标准输入是缺省情况下的输入来源,标准输出时缺省情况下的输出设置。具体缺省值因编译器而异,通常标准输入为键盘设备、标准输出为终端或者屏幕。
ANSI C并未规定FILE的成员,不同编译器可能有不同的定义。VS下FILE信息如下:
struct _iobuf {
char *_ptr; //文件输入的下一个位置
int _cnt; //剩余多少字符未被读取
char *_base; //指基础位置(应该是文件的起始位置)
int _flag; //文件标志
int _file; //文件的有效性验证
int _charbuf; //检查缓冲区状况,如果无缓冲区则不读取
int _bufsiz; //文件的大小
char *_tmpfname; //临时文件名
};
typedef struct _iobuf FILE;
文件缓冲区
ANSI c标准采用“缓冲文件系统”处理数据文件,缓冲文件系统就是系统自动地在内存为程序中每一个正在使用的文件开辟一个文件缓冲区从内存向磁盘输出数据必须先送到内存中的缓冲区,装满缓冲区后才一起送到磁盘中,如果从磁盘向计算机读入数据,则一次从磁盘文件将一批数据输入到内存缓冲区,然后再从缓冲区逐个将数据送到程序数据区。
建立文件缓冲区有利于大大提高计算机的运行速度。减少磁盘的读写次数。
文件打开
文件打开操作表示将给用户指定的文件在内存分配一个FILE结构区,并将该结构的指针返回给用户程序,以后用户程序就可以用此FILE指针来实现指定文件的存取操作了,当使用打开函数时,必须给出文件名、文件操作方式
FILE * fopen(const char *filename, const char *mode);
功能:打开文件
参数:
filename 需要打开的文件名,包括路径
mode:打开文件的权限设置
返回值:
成功 文件指针
失败 NULL
方式 | 含义 |
---|---|
r | 打开,只读,文件必须存在 |
w | 只写,如果文件不存在则创建,如果文件已存在,将文件截断为0,再重新写入 |
a | 只能文件末尾追加数据,如果文件不存在则创建 |
rb | 打开一个二进制文件,只读 |
wb | 打开一个二进制文件只写 |
ab | 打开一个二进制文件,追加 |
r+ | 允许读和写,文件必须存在 |
w+ | 允许写和读,如果文件不存在则创建,如果文件已存在则把文件截断,重新写 |
a+ | 允许读和追加数据,如果文件不存在则创建 |
rb+ | 以读/写方式打开一个二进制文件 |
wb+ | 以读/写方式建立一个新的二进制文件 |
ab+ | 以读/写打开一个二进制文件进行追加 |
文件操作完成后,如果程序没有结束,必须要用fclose函数进行关闭,这是因为对打开文件写入的时候,若文件缓冲区的空间没有被写入的内容填满时,这些内容不会写到打开的文件中,只有对文件进行关闭操作时,停留在缓冲区的内容才能写道该文件中去,从而使文件完整。再者一旦关闭了文件,该文件对应的FILE结构将被释放,从而使关闭的文件得到保护。文件的关闭意味着释放了文件的缓冲区。
int fclose(FILE * stream)
功能:关闭先前打开的fopen文件,此动作让缓冲区的数据写入到文件中,并释放系统所提供的文件资源
参数:
stream 文件指针
返回值
成功 0
失败 -1
int fputc(int ch, FILE * stream);
功能:将ch转换为unsigned char后写入stream指定的文件中
参数:
ch:需要写入文件的字符
stream:文件指针
返回值:
成功:成功写入文件的字符
失败:返回-1
int fgetc(FILE * stream);
功能:从stream指定的文件中读取一个字符
参数:
stream:文件指针
返回值:
成功:返回读取到的字符
失败:-1
int feof(FILE * stream);
功能:检测是否读取到了文件结尾
参数:
stream:文件指针
返回值:
非0值:已经到文件结尾
0:没有到文件结尾
int fputs(const char * str, FILE * stream);
功能:将str所指定的字符串写入到stream指定的文件中, 字符串结束符 '\0' 不写入文件。
参数:
str:字符串
stream:文件指针
返回值:
成功:0
失败:-1
char * fgets(char * str, int size, FILE * stream);
功能:从stream指定的文件内读入字符,保存到str所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 '\0' 作为字符串结束。
参数:
str:字符串
size:指定最大读取字符串的长度(size - 1)
stream:文件指针
返回值:
成功:成功读取的字符串
读到文件尾或出错: NULL
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式给文件写入内容
参数:
ptr:准备写入文件数据的地址
size: size_t 为 unsigned int类型,此参数指定写入文件内容的块数据大小
nmemb:写入文件的块数,写入文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功写入文件数据的块数,此值和nmemb相等
失败:0
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
功能:以数据块的方式从文件中读取内容
参数:
ptr:存放读取出来数据的内存空间
size: size_t 为 unsigned int类型,此参数指定读取文件内容的块数据大小
nmemb:读取文件的块数,读取文件数据总大小为:size * nmemb
stream:已经打开的文件指针
返回值:
成功:实际成功读取到内容的块数,如果此值比nmemb小,但大于0,说明读到文件的结尾。
失败:0
int fprintf(FILE * stream, const char * format, ...);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到stream指定的文件中,指定出现字符串结束符 '\0' 为止。
参数:
stream:已经打开的文件
format:字符串格式,用法和printf()一样
返回值:
成功:实际写入文件的字符个数
失败:-1
int fscanf(FILE * stream, const char * format, ...);
功能:从stream指定的文件读取字符串,并根据参数format字符串来转换并格式化数据。
参数:
stream:已经打开的文件
format:字符串格式,用法和scanf()一样
返回值:
成功:实际从文件中读取的字符个数
失败: - 1
int fseek(FILE *stream, long offset, int whence);
功能:移动文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
offset:根据whence来移动的位移数(偏移量),可以是正数,也可以负数,如果正数,则相对于whence往右移动,如果是负数,则相对于whence往左移动。如果向前移动的字节数超过了文件开头则出错返回,如果向后移动的字节数超过了 文件末尾,再次写入时将增大文件尺寸。
whence:其取值如下:
SEEK_SET:从文件开头移动offset个字节
SEEK_CUR:从当前位置移动offset个字节
SEEK_END:从文件末尾移动offset个字节
返回值:
成功:0
失败:-1
long ftell(FILE *stream);
功能:获取文件流(文件光标)的读写位置。
参数:
stream:已经打开的文件指针
返回值:
成功:当前文件流(文件光标)的读写位置
失败:-1
void rewind(FILE *stream);
功能:把文件流(文件光标)的读写位置移动到文件开头。
参数:
stream:已经打开的文件指针
返回值:
无返回值
- 按照字符读写文件:fgetc(), fputc()
- 按照行读写文件:fputs(), fgets()
- 按照块读写文件:fread(), fwirte()
- 按照格式化读写文件:fprintf(), fscanf()
- 按照随机位置读写文件:fseek(), ftell(), rewind()
函数指针
函数类型
一个函数在编译时被分配一个地址,这个地址就称为函数的指针,函数名代表函数的入口地址。
函数的三要数:名称、参数、返回值。C语言中的函数有自己的特定的类型。
C语言中通过typedef为函数类型重命名
typedef int f(int, int);f为函数类型
我们可以使用一个指针变量来存放这个入口地址,然后通过该指针变量调用函数。
通过函数类型定义的变量是不能够直接执行,因为没有函数体,只能通过类型定义一个函数指针限制向某个具体函数,才能调用。
typedef int(p)(int int);
void my_func(int a, int b)
{
printf("%d%d\n", a, b);
}
void test()
{
p p1;
//p1(10, 20);//错误,不能直接调用
p* p2 = my_func;
p2(10, 20);//正确
}
函数指针
函数指针为指向函数的指针
- 函数指针定义方式(先定义函数类型,根据类型定义指针变量)
- 先定义函数指针类型,根据类型定义指针变量
- 直接定义函数指针变量
函数指针数组,数组元素都是函数指针
函数指针做函数参数(回调函数)
函数参数除了普通变量,还可以是函数指针变量:void fun(int (*p)(int a)){};
函数指针变量常见的用途之一是把指针作为参数传递到其他函数,指向函数的指针也可以作为参数,以实现函数地址的传递。
//加法计算器
int plus(int a,int b){
return a + b;
}
//减法计算器
int minus(int a,int b){
return a - b;
}
typedef int(*FUNC_POINTER)(int, int);
int caculator(int a, int b, FUNC_POINTER func)//其中func只是形参,还需调用时传入实际的函数指针
{
return func(a, b);
}
函数指针是指向函数的指针
指针函数是返回类型为指针的函数。