文件通常是在磁盘或固态硬盘上的一段已命名的存储区。C把文件看作是一系列连续的字节,每个字节都能被单独读取。C提供两种文件模式:文本模式和二进制模式。
文本模式和二进制模式
所有文件都内容都以二进制形式存储。但是,如果文件最初使用二进制编码的字符表示文本(就像C字符串那样),该文件就是文本文件,其中包含文本内容。如果文件中的二进制值代表机器语言代码或数值数据或图片或音乐编码,该文件就是二进制文件,其中包含二级制内容。
文本文件和二进制文件的区别在于它们存储数据的方式。文本文件是以字符的形式存储数据,而二进制文件是以字节的形式存储数据。在文本文件中,数据以人类可读的形式表示,例如字母、数字和符号。而在二进制文件中,数据以计算机能够理解的二进制形式表示,通常包含非文本格式的数据,如图像、音频、视频等。
因此,通过查看文件的内容可以大致判断是文本文件还是二进制文件。如果文件内容包含了人类可读的字符,则很可能是文本文件;如果文件内容呈现乱码或无法直接阅读,则可能是二进制文件。
为了规范文本文件的处理,C提供两种访问文件的途径: 二进制模式和文本模式。在二进制模式中,程序可以访问文件的每个字节。而在文本模式中,程序所见的内容和文件的实际内容不同。程序以文本模式读取文件时,把本地环境表示的行末尾或文件结尾映射为C模式。
I/O的级别
I/O(输入/输出)可以分为三个级别:
1. 高级I/O:这是最抽象的级别,通常由编程语言提供的库函数或框架实现。它允许开发人员使用更简单的接口来进行输入和输出操作,如文件读写、网络通信等。
2. 中级I/O:此级别介于高级和底层之间,通常涉及操作系统特定的API,例如Windows的Win32 API或Linux的系统调用。这些API提供了较低级别的控制,但相对于底层I/O来说,仍然提供了一定程度的封装和抽象。
3. 底层I/O:这是最接近硬件的级别,涉及直接与设备驱动程序或硬件接口进行交互,如操作文件系统、管理设备以及处理底层网络通信。
文件的打开和关闭
流和标准流
在编程中,流是一个抽象的概念,它代表着一个抽象的数据传输通道,这个通道一段连接着程序,另一端连接着外部设备,如键盘,显示器,磁盘文件等。流的概念屏蔽了不同外部设备的具体实现细节,使得程序员可以统一地处理各种设备的数据交换。
标准流是C语言中预定义的三种流:
- 标准输入流(stdin):通常关联至键盘,用于接收用户的输入。
- 标准输出流(stdout):默认连接到显示器,用于向用户显示信息。
- 标准错误流(stderr):同样输入到显示器,但通常用于显示程序运行过程中的错误信息或调试信息。
标准流让程序的输入输出变得更加统一和便捷,程序在启动时,这三个标准流会自动被打开,程序可以直接使用它们进行输入输出操作。这三种流的类型是:FILE*,文件指针。
fopen函数
fopen函数用于打开文件。该函数声明在stdio.h中。
声明:
FILE *fopen(const char *filename, const char *mode)
参数
- filename -- 字符串,表示要打开的文件名称。
- mode -- 字符串,表示文件的访问模式,可以是以下表格中的值:
"r" | 打开一个用于读取的文件。该文件必须存在。 |
"w" | 创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。 |
"a" | 追加到一个文件。写操作向文件末尾追加数据。如果文件不存在,则创建文件。 |
"r+" | 打开一个用于更新的文件,可读取也可写入。该文件必须存在。 |
"w+" | 创建一个用于读写的空文件。 |
"a+" | 打开一个用于读取和追加的文件。,只能从末尾添加内容 |
返回值
该函数返回一个 FILE 指针。否则返回 NULL,且设置全局变量 errno 来标识错误。
用法:
#include <stdio.h>
#include <stdlib.h>
int main()
{
FILE * fp;
fp = fopen ("file.txt", "w+");
fprintf(fp, "%s %s %s %d", "We", "are", "in", 2014);
fclose(fp);
return(0);
}
现在让我们使用下面的程序查看上面文件的内容:
#include <stdio.h>
int main ()
{
FILE *fp;
int c;
fp = fopen("file.txt","r");
while(1)
{
c = fgetc(fp);
if( feof(fp) )
{
break ;
}
printf("%c", c);
}
fclose(fp);
return(0);
}
程序成功打开文件后,fopen()将返回文件指针,其他I/O函数可以使用这个指针指定该文件。文件指针(该例是fp)的类型是指向FILE的指针,FILE是一个定义在stdio.H中的派生类型。文件指针并不指向实际的文件,它指向一个包含文件信息的数据对象,其中包含操作文件的I/O函数所用的缓冲区信息。因为标准库中的I/O函数使用缓冲区,所以它们不仅要知道缓冲区的位置,还要知道缓冲区被填充的程度以及操作哪一文件。标准I/O函数根据这些信息在必要时决定再次填充或清空缓冲区。文件指针(fp)指向的对象包含了这些信息。
fclose函数
fclose(fp)函数关闭fp指定的文件,必要时刷新缓冲区。对于较正式的程序,应该检查是否成功关闭文件。如果成功关闭,fclose()函数返回0,否则返回EOF。
如果磁盘已满、移动硬盘被移除或出现I/O错误,都会导致调用fclose()函数失败。
getc()和putc()函数
getc()和putc()函数与getchar()和putchar()函数类似,来看看区别
//从标准输入中获取一个字符
c1 = getchar();
//从fp指定的文件中获取一个字符
c2 = getc(fp);
//把字符c3放入FILE指针fpout指定的文件中
putc(c3,fpout);
在putc()函数的参数列表中,第1个参数是待写入的字符,第2个参数是文件指针。
getc()该函数以无符号 char 强制转换为 int 的形式返回读取的字符,如果到达文件末尾或发生读错误,则返回 EOF。
文件I/O
I/O函数都类似于文件I/O函数。它们的主要区别是,文件I/O函数要用FILE指针指定待处理的文件。
fprintf()函数和fcanf()函数
文件I/O函数fprintf()和fcanf()函数的工作方式与printf()和scanf()类似,区别在于前者需要用第1个参数指定待处理的文件。
fgets()和fputs()函数
下面是fgets()函数的声明
char *fgets(char *str, int n, FILE *stream)
- str -- 这是指向一个字符数组的指针,该数组存储了要读取的字符串。
- n -- 这是要读取的最大字符数(包括最后的空字符)。通常是使用以 str 传递的数组长度。
- stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要从中读取字符的流。
如果成功,该函数返回相同的 str 参数(传给它的第一个参数地址)。如果到达文件末尾或者没有读取到任何字符,str 的内容保持不变,并返回一个空指针。如果发生错误,返回一个空指针。
下面是fputs()函数的声明
int fputs(const char *str, FILE *stream)
- str -- 这是一个数组,包含了要写入的以空字符终止的字符序列。
- stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了要被写入字符串的流。
该函数返回一个非负值,如果发生错误则返回 EOF。
和puts()函数不同的是,fputs()在打印字符串时不会再其末尾添加换行符。
随机访问:fseek()和ftell()
fseek()函数
描述:
C 库函数 int fseek(FILE *stream, long int offset, int whence) 设置流 stream 的文件位置为给定的偏移 offset,参数 offset 意味着从给定的 whence 位置查找的字节数。
声明
int fseek(FILE *stream, long int offset, int whence)
参数
- stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
- offset -- 这是相对 whence 的偏移量,以字节为单位。
- whence -- 这是表示开始添加偏移 offset 的位置。它一般指定为下列常量之一:
常量 | 描述 |
---|---|
SEEK_SET | 文件的开头 |
SEEK_CUR | 文件指针的当前位置 |
SEEK_END | 文件的末尾 |
可以使用数值0L、1L、2L分别表示这3种模式。L后缀表明其值是long类型。
FILE* fp;
fseek(fp, 0L, SEEK_SET); //定位之文件开始处
fseek(fp, 10L, SEEK_SET); //定位置文件种的第10个字节
fseek(fp, 2L, SEEK_CUR); //从文件当前位置前移2个字节
fseek(fp, 0L, SEEK_END); //定位至文件结尾
fseek(fp, -10L, SEEK_END); //从文件结尾处回退10个字节
返回值
如果成功,则该函数返回零,否则返回非零值。
ftell()函数
描述
C 库函数 long int ftell(FILE *stream) 返回给定流 stream 的当前文件位置。
声明
long int ftell(FILE *stream)
参数
stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
返回值
返回的是参数指向文件的当前位置距文件开始处的字节数。
例子
#include <stdio.h>
int main()
{
FILE* fp;
long size;
fp = fopen("myfile.txt", "rb");
if (fp == NULL)
perror("Error opening file");
else
{
fseek(fp, 0, SEEK_END); // non-portable
size = ftell(fp);
fclose(fp);
printf("Size of myfile.txt: %ld bytes.\n", size);
}
return 0;
}
rewind()函数
rewind()函数让程序回到文件开始处,rewind()接受一个文件指针作为参数。没有返回值。
举例
#include <stdio.h>
int main()
{
int n;
FILE* fp;
char buffer[27];
fp = fopen("myfile.txt", "w+");
for (n = 'A'; n <= 'Z'; n++)
fputc(n, fp);
rewind(fp);
fread(buffer, 1, 26, fp);
fclose(fp);
buffer[26] = '\0';
printf(buffer);
return 0;
}
缓冲区
无缓冲输入和缓冲输入是计算机输入处理中两种不同的机制。
无缓冲输入
在无缓冲输入模式下,输入操作(如键盘输入)是实时进行的,每个输入的字符都会立即被程序处理。这意味着当你输入一个字符时,程序立刻收到这个字符,并可以立即使用它。在无缓冲模式下,通常不需要按下回车键来提交输入,输入操作会立即得到相应。在某些编程语言中,如C语言,可以通过函数如getchar()或getch()来实现无缓冲输入。
缓冲输入
相对而言,缓冲输入则有所不同。在缓冲输入模式下,输入的字符首先被存储在一个缓冲区中,直到达到一定条件(比如按下回车键)后提交给程序。这意味着你可能输入了一串字符,但这些字符并不会立即被程序处理,直到缓冲区被刷新或者达到某个提交条件。在许多情况下,缓冲输入可以提高效率,因为它允许对输入进行缓存,然后一次性处理,而不是每个字符都进行处理。
总的来说,无缓冲输入提供的是即时的输入相应,而缓冲输入则提供了一种缓存输入字符的方式,直到它们被处理或提交。在不同的应用场景下,可以根据需要选择最合适的输入机制。
为什么要有缓冲区?首先,把若干字符作为一个块进行传输比逐个发送这些字符节约时间。其次,如果用户打错字符,可以直接通过键盘修正错误。当最后按下Enter键时,传输的是正确的输入。
缓冲分为两类:完全缓冲I/O和行缓冲I/O。完全缓冲输入指的是当缓冲区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入中。缓冲区的大小取决于系统,常见的大小是512字节和4096字节。行缓冲I/O指的是在出现换行符是刷新缓冲区。键盘输入通常是行缓冲,所以在按下Enter键后才刷新缓冲区。
fgetpos()函数和fsetpos()函数
fseek()和ftell()潜在的问题是,它们都把文件大小限制在long类型能表示的范围内。。但是随着存储设备的容量迅猛增长,文件也越来越大。鉴于此,ANSI C新增了两个处理较大文件的新定位函数:fgetpos()和fsetpos()。
这两个函数不使用long类型的值表示位置,它们使用一种新类型:fpos_t(代表file position type,文件定位类型)。fpos_t类型不是基本类型,它根据其他类型来定义。fpos_t类型的变量或数据对象可以在文件种指定一个位置,它不能是数组类型,除此之外,没有其他限制。
fgetpos()函数
描述
C 库函数 int fgetpos(FILE *stream, fpos_t *pos) 获取流 stream 的当前文件位置,并把它写入到 pos。
声明
int fgetpos(FILE *stream, fpos_t *pos)
参数
- stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
- pos -- 这是指向 fpos_t 对象的指针。
返回值
如果成功,该函数返回零。如果发生错误,则返回非零值。
fsetpos()函数
描述
C 库函数 int fsetpos(FILE *stream, const fpos_t *pos) 设置给定流 stream 的文件位置为给定的位置。参数 pos 是由函数 fgetpos 给定的位置。
声明
int fsetpos(FILE *stream, const fpos_t *pos)
参数
- stream -- 这是指向 FILE 对象的指针,该 FILE 对象标识了流。
- pos -- 这是指向 fpos_t 对象的指针,该对象包含了之前通过 fgetpos 获得的位置。
返回值
如果成功,该函数返回零值,否则返回非零值,并设置全局变量 errno 为一个正值,该值可通过 perror 来解释。
例子
#include <stdio.h>
int main ()
{
FILE *fp;
fpos_t position;
fp = fopen("file.txt","w+");
fgetpos(fp, &position);
fputs("Hello, World!", fp);
fsetpos(fp, &position);
fputs("这将覆盖之前的内容", fp);
fclose(fp);
return(0);
}
让我们编译并运行上面的程序,这将创建一个文件 file.txt,它的内容如下。首先我们使用 fgetpos() 函数获取文件的初始位置,接着我们向文件写入 Hello, World!,然后我们使用 fsetpos() 函数来重置写指针到文件的开头,重写文件为下列内容:
这将覆盖之前的内容
现在让我们使用下面的程序查看上面文件的内容:
#include <stdio.h>
int main ()
{
FILE *fp;
int c;
fp = fopen("file.txt","r");
while(1)
{
c = fgetc(fp);
if( feof(fp) )
{
break ;
}
printf("%c", c);
}
fclose(fp);
return(0);
}
perror()函数
描述
C 库函数 void perror(const char *str) 把一个描述性错误消息输出到标准错误 stderr。首先输出字符串 str,后跟一个冒号,然后是一个空格。
声明
void perror(const char *str)
参数
str -- 这是 C 字符串,包含了一个自定义消息,将显示在原本的错误消息之前。
举例
#include <stdio.h>
int main ()
{
FILE *fp;
/* 首先重命名文件 */
rename("file.txt", "newfile.txt");
/* 现在让我们尝试打开相同的文件 */
fp = fopen("file.txt", "r");
if( fp == NULL ) {
perror("Error: ");
return(-1);
}
fclose(fp);
return(0);
}
让我们编译并运行上面的程序,这将产生以下结果,因为我们尝试打开一个不存在的文件:
Error: : No such file or directory