几个基本概念
什么是IO:
I/O输入/输出(Input/Output),分为IO设备和IO接口两个部分。
在C语言中的I/O,个人理解就是把数据传输到程序就是I,从程序中把数据传输出去就是O。
很多时候,数据来源就是文件,所有很多时候我们把对文件的操作,也叫做I/O操作了。
关于这样的I/O操作,在Linux操作系统上分两种,其中一个是标准IO,另一个是系统调用IO。
系统调用IO与标准IO的区别:
系统调用IO:由操作系统直接提供的操作文件的接口函数,和操作系统绑定,没有缓存,更接近硬件,可以操作普通文件与驱动文件。
标准IO:由标准C库(第三方库)提供的操作文件的接口函数(对操作系统提供的系统IO进行二次封装),有缓存,只可以操作普通文件。
C语言中的文件
我们对文件的概念已经非常熟悉了,比如常见的 word 文档、txt 文件、c源文件等。
文件是数据源的一种,最主要的作用是保存数据。
在C语言程序中,文件有着更广泛的定义:文件通常是在磁盘或硬盘上的已命名的储存区。
要看到是通常,那么就说明会有不寻常的。对于C程序来说,设备也可以看成是文件!
例如显示器(标准输出文件 stdout,标准错误输出stderr)和键盘(标准输入文件 stdin)。
我们不去探讨硬件设备是如何被映射成文件的,大家只需要记住,在C语言中硬件设备可以看成文件!
另外,在C程序看来,文件只有两种形式:
(1)文本文件
(2)二进制文件
根据我们以往的经验,文本文件通常用来保存肉眼能认识的字符,比如.txt文件、.c文件、.dat文件等,用文本编辑器打开这些文件,我们能够顺利看懂文件的内容。
二进制文件通常用来保存视频、图片、程序等不可阅读的内容,用文本编辑器打开这些文件,会看到一堆乱码,根本看不懂。
但是从物理上讲,二进制文件和字符文件并没有什么区别,它们都是以二进制的形式保存在磁盘上的数据。
我们之所以能看懂文本文件的内容,是因为文本文件中采用的是 ASCII、UTF-8、GBK 等字符编码,文本编辑器可以识别出这些编码格式,并将编码值转换成字符展示出来。
而二进制文件使用的是 mp4、gif、exe 等特殊编码格式,文本编辑器并不认识这些编码格式,只能按照字符编码格式胡乱解析,所以就成了一堆乱七八糟的字符,有的甚至都没见过。
总起来说,不同类型的文件有不同的编码格式,必须使用对应的程序(软件)才能正确解析,否则就是一堆乱码,无法使用。
文件流
文件是数据源的一种,除了文件,还有数据库、网络、键盘等;
数据传递到内存也就是保存到C语言的变量(例如整数、字符串、数组、缓冲区等)。
我们把数据在数据源和程序(内存)之间传递的过程叫做数据流(Data Stream)。相应的,数据从数据源到程序(内存)的过程叫做输入流(Input Stream),从程序(内存)到数据源的过程叫做输出流(Output Stream)。
输入输出(Input output,IO)是指程序(内存)与外部设备(键盘、显示器、磁盘、其他计算机等)进行交互的操作。几乎所有的程序都有输入与输出操作,如从键盘上读取数据,从本地或网络上的文件读取数据或写入数据等。通过输入和输出操作可以从外界接收信息,或者是把信息传递给外界。
一般情况下,我们可以把打开文件说成打开了一个流。
C语言fopen和fclose函数
C语言中操作文件的正确流程为:打开文件 --> 读写文件 --> 关闭文件。
所谓打开文件,就是获取文件的有关信息,例如文件名、文件状态、当前读写位置等,
这些信息会被保存到一个 FILE 类型的结构体变量中。
关闭文件,就是断开与文件之间的联系,释放结构体变量,同时禁止再对该文件进行操作。
使用 <stdio.h> 头文件中的 fopen() 函数即可打开文件,它的用法为:
FILE *fopen(char *filename, char *mode);
filename为文件名(包括文件路径)
mode为打开模式,它们都是字符串。
fopen() 函数的返回值类型是(FILE *)
FILE 是 <stdio.h> 头文件中的一个结构体,它专门用来保存文件信息。
我们不用关心 FILE 的具体结构,只需要知道它的用法就行。
例如:fopen.c
#include <stdio.h>
int main(){
FILE * fp=fopen("./demo.txt","w");
return 0;
}
//编译执行可执行文件后,若当前文件夹中没有demo.txt,则创建之,
gcc -g fopen.c -o fopen.exe
./fopen
表示以“只读”方式打开当前目录下的 demo.txt 文件,并使 fp 指向该文件,
这样就可以通过 fp 来操作 demo.txt 了。fp 通常被称为文件指针。若在文件中加了内容,我再次执行可执行文件./fopen
,会清空demo.txt了。
再来看一个例子:
FILE *fp = fopen("D:\\demo.txt","rb+");
表示以二进制方式打开 D 盘下的 demo.txt 文件,允许读和写。
--------文本方式和二进制方式并没有本质上的区别,只是对于换行符的处理不同。
C语言程序将\n作为换行符,类 UNIX/Linux 系统在处理文本文件时也将\n作为换行符,所以程序中的数据会原封不动地写入文本文件中,反之亦然。
但是 Windows 系统却不同,它将\r\n作为文本文件的换行符。
--------在 Windows 系统中,如果以文本方式打开文件,当读取文件时,程序会将文件中所有的\r\n转换成一个字符\n。也就是说,如果文本文件中有连续的两个字符是\r\n,则程序会丢弃前面的\r,只读入\n。
当写入文件时,程序会将\n转换成\r\n写入。也就是说,如果要写入的内容中有字符\n,则在写入该字符前,程序会自动先写入一个\r。
总起来说,对于 Windows 平台,为了保险起见,我们最好用"t"来打开文本文件,用"b"来打开二进制文件。对于 Linux 平台,使用"t"还是"b"都无所谓,既然默认是"t",那我们什么都不写就行了。
判断文件是否打开成功
打开文件出错时,fopen() 将返回一个空指针,也就是 NULL,我们可以利用这一点来判断文件是否打开成功,请看下面的代码:
FILE *fp;
if( (fp=fopen("D:\\demo.txt","rb") == NULL ){
printf("Fail to open file!\n");
exit(0); //退出程序(结束程序)
}
以上代码是文件操作的规范写法,在打开文件时一定要判断文件是否打开成功,因为一旦打开失败,后续操作就都没法进行了,往往以“结束程序”告终。
fclose函数
当进行打开文件操作时, 操作系统会建立文件登记表中记录该文件处于打开状态,除了申请进行打开文件操作的进程,其他程序不能对文件进行访问和写入。
文件一旦使用完毕,应该用 fclose() 函数把文件关闭,以释放相关资源,避免数据丢失。fclose() 的用法为:
int fclose(FILE *fp);
fp 为文件指针。例如:
int fclose(fp);
文件正常关闭时,fclose() 的返回值为0,如果返回非零值则表示有错误发生。
实例演示
最后,我们通过一段完整的代码来演示 fopen 函数的用法,这个例子会一行一行地读取文本文件的所有内容:
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp =NULL;
//判断一个,有没有成功打开文件,如果打开出错,直接退出程序
if((fp = fopen("./demo.txt","rt")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//打开文件后,就对文件进行读写操作。。。。
//用完了文件后,一定要关闭
if(fclose(fp) == 0){
puts("关闭文件成功!!!");
}else{
puts("文件关闭失败");
exit(0);
}
return 0;
}
//编译与执行
gcc -g .\fopen_close.c -o fopenclose.exe
./fopenclose
关闭文件成功!!!
C语言fgetc和fputc函数用法
在C语言中,读写文件比较灵活,既可以每次读写一个字符,也可以读写一个字符串,甚至是任意字节的数据(数据块)。每次可以从文件中读取一个字符,或者向文件中写入一个字符。
主要使用两个函数,分别是 fgetc() 和 fputc()。
字符读取函数 fgetc
fgetc 是 file get char 的缩写,意思是从指定的文件中读取一个字符。fgetc() 的用法为:
int fgetc (FILE *fp);
fp 为文件指针。
fgetc() 读取成功时返回读取到的字符,读取到文件末尾或读取失败时返回EOF。
EOF 是 end of file 的缩写,表示文件末尾,是在 stdio.h 中定义的宏,它的值是一个负数,往往是 -1。
fgetc() 的返回值类型之所以为 int,就是为了容纳这个负数(char不能是负数)。
注意:不能武断地认为EOF 就是 -1,也可以是其他负数,这要看编译器的实现。
fgetc() 的用法举例:
char ch;
FILE *fp = fopen("./demo.txt", "r+");
ch = fgetc(fp);
表示从当前目录中的demo.txt文件中读取一个字符,并保存到变量 ch 中。在文件内部有一个位置指针,用来指向当前读写到的位置,也就是读写到第几个字节。
在文件打开时,该指针总是指向文件的第一个字节。使用 fgetc() 函数后,该指针会向后移动一个字节,所以可以连续多次使用 fgetc() 读取多个字符。
注意:这个文件内部的位置指针与C语言中的指针不是一回事。
位置指针仅仅是一个标志,表示文件读写到的位置,也就是读写到第几个字节,它不表示地址。
文件每读写一次,位置指针就会移动一次,它不需要你在程序中定义和赋值,而是由系统自动设置,对用户是隐藏的。
【示例】在屏幕上显示 当前目录中的demo.txt 文件的内容。
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp =NULL;
char ch;
//判断一个,有没有成功打开文件,如果打开出错,直接退出程序
if((fp = fopen("./demo.txt","rt")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//打开文件后,就对文件进行读写操作。。。。
while((ch=fgetc(fp))!=EOF){
printf("%c",ch);
}
//用完了文件后,一定要关闭
if(fclose(fp) == 0){
puts("\n关闭文件成功!!!");
}else{
puts("文件关闭失败");
exit(0);
}
return 0;
}
//编译执行
>gcc -g .\fopen_close.c -o fopenclose.exe
>./fopenclose
xiongshaowen
关闭文件成功!!!
对 EOF 的说明
EOF 本来表示文件末尾,意味着读取结束,但是很多函数在读取出错时也返回 EOF,那么当返回 EOF 时,到底是文件读取完毕了还是读取出错了?
我们可以借助 stdio.h 中的两个函数来判断,分别是 feof() 和 ferror()。
feof() 函数用来判断文件内部指针是否指向了文件末尾,它的原型是:
int feof ( FILE * fp );
当指向文件末尾时返回非零值,否则返回零值。
ferror() 函数用来判断文件操作是否出错,它的原型是:
int ferror ( FILE *fp );
出错时返回非零值,否则返回零值。
需要说明的是,文件出错是非常少见的情况,上面的示例基本能够保证将文件内的数据读取完毕。
如果追求完美,也可以加上判断并给出提示:
while((ch=fgetc(fp))!=EOF){
printf("%c",ch);
}
if((ferror(fp))==0){
printf("\n正常读完文件!");
}else{
printf("\n文件读取出了错误 !");
}
字符写入函数 fputc
fputc 是 file output char 的所以,意思是向指定的文件中写入一个字符。fputc() 的用法为:
int fputc ( int ch, FILE *fp );
ch 为要写入的字符,fp 为文件指针。
fputc() 写入成功时返回写入的字符,失败时返回 EOF,返回值类型为 int 也是为了容纳这个负数。
例如:
fputc('a', fp);
或者:
char ch = 'a';
fputc(ch, fp);
表示把字符 ‘a’ 写入fp所指向的文件中。
注意:写和读一样,每写入一个字符,文件内部位置指针向后移动一个字节。
【示例】从键盘输入一行字符,写入文件。
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp =NULL;
char ch;
//判断一个,有没有成功打开文件,如果打开出错,直接退出程序
if((fp = fopen("./demo.txt","r+")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//打开文件后,就对文件进行写操作。。。。
printf("\n请输入要写入文件的字符!以换行结束: ");
while((ch = getchar())!='\n'){
fputc(ch,fp);
}
printf("\n写入成功!");
//用完了文件后,一定要关闭
if(fclose(fp) == 0){
puts("\n关闭文件成功!!!");
}else{
puts("文件关闭失败");
exit(0);
}
return 0;
}
C语言fgets和fputs函数
fgetc() 和 fputc() 函数每次只能读写一个字符,速度较慢;
实际开发中往往是每次读写一个字符串或者一个数据块,这样能明显提高效率。
读字符串函数 fgets
fgets() 函数用来从指定的文件中读取一个字符串,并保存到字符数组中,它的用法为:
char *fgets ( char *str, int n, FILE *fp );
str 为字符数组,n 为要读取的字符数目,fp 为文件指针。
返回值:读取成功时返回字符数组首地址,也即 str;
读取失败时返回 NULL;
如果开始读取时文件内部指针已经指向了文件末尾,那么将读取不到任何字符,也返回 NULL。
注意,读取到的字符串会在末尾自动添加 ‘\0’,n 个字符也包括 ‘\0’。
也就是说,实际只读取到了 n-1 个字符,如果希望读取 100 个字符,n 的值应该为 101。
例如:
#define N 101
char str[N];
FILE *fp = fopen("D:\\demo.txt", "r");
fgets(str, N, fp);
需要重点说明的是,在读取到 n-1 个字符之前如果出现了换行,或者读到了文件末尾,则读取结束。
这就意味着,不管 n 的值多大,fgets() 最多只能读取一行数据,不能跨行。
在C语言中,没有按行读取文件的函数,我们借助 fgets(),将 n 的值设置很大,每次就可以读取到一行行的数据。
【示例】一行一行地读取文件。
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp = NULL;
char ch;
char str[101] = {
0}; //假设读100个字符,我们要额外留下一个位置装'\0'
//判断一个,有没有成功打开文件,如果打开出错,直接退出程序
if((fp = fopen("./demo.txt","r+")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//读当前文件demo.txt,已经有几行内容了
while(fgets(str,101,fp) != NULL){
printf("%s",str);
}
//用完了文件后,一定要关闭
if(fclose(fp) == 0){
puts("\n关闭文件成功!!!");
}else{
puts("\n文件关闭失败");
exit(0);
}
}
//编译执行
>gcc -g frwstring.c -o frwstring
> ./frwstring
熊少文
xuhuifeng
xionglingzhou
sbsbsb
aaaaaa
bbbbbb
cccccc
写字符串函数 fputs
fputs() 函数用来向指定的文件写入一个字符串,它的用法为:
int fputs( char *str, FILE *fp );
str 为要写入的字符串,fp 为文件指针。写入成功返回非负数,失败返回 EOF。例如:
char *str = "hello world";
FILE *fp = fopen("./demo.txt", "at+");
fputs(str, fp);
表示把把字符串 str 写入到 D:\demo.txt 文件中。
【示例】向上例中建立的 d:\demo.txt 文件中追加一个字符串。
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp = NULL;
char ch;
char str[101] = {
0}; //假设读100个字符,我们要额外留下一个位置装'\0'
//判断一个,有没有成功打开文件,如果打开出错,直接退出程序
if((fp = fopen("./demo.txt","r+")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//写入字符串到当前demo.txt中
printf("\n请输入字符串!");
gets(str); //gets函数,请注意不要越界101
fputs(str,fp);
//读当前文件demo.txt,已经有几行内容了,fp指针要赋初始值,即我们要重新打开文件,初始到最开始的位置
printf("读取的内容如下:\n");
//判断一个
if((fp = fopen("./demo.txt","r")) == NULL){
puts("fail to open file!!!");
exit(0);
}
while(fgets(str,101,fp) != NULL){
printf("\n%s",str);
}
//用完了文件后,一定要关闭
if(fclose(fp) == 0){
puts("\n关闭文件成功!!!");
}else{
puts("\n文件关闭失败");
exit(0);
}
}
注意,上面的代码,读出的是上一次写进文件后的内容不是本次的内容,这种问题以后再讲,这里只是讲一下写字符串的功能
C语言fread和fwrite的用法
fgets() 有局限性,每次最多只能从文件中读取一行内容,因为 fgets() 遇到换行符就结束读取,上面代码我们用了while循环读了多行。
如果希望读取多行内容,需要使用 fread() 函数;fputs一次只能写入一行,想一次写入多行数据,相应地写入函数为 fwrite()。
对于 Windows 系统,使用 fread() 和 fwrite() 时应该以二进制的形式打开文件!因为换行时有两个字符‘‘\r\n’
fread() 函数用来从指定文件中读取块数据块。所谓块数据,也就是若干个字节的数据,可以是一个字符,可以是一个字符串,可以是多行数据,并没有什么限制。
fread() 的原型为:
size_t fread ( void *ptr, size_t size, size_t count, FILE *fp );
fwrite() 函数用来向文件中写入块数据,它的原型为:
size_t fwrite ( void * ptr, size_t size, size_t count, FILE *fp );
对参数的说明:
(1)ptr 为内存区块的指针,它可以是数组、变量、结构体等。
fread() 中的 ptr 用来存放读取到的数据,fwrite() 中的 ptr 用来存放要写入的数据。
(2)size:表示每个数据块的字节数。
(3)count:表示要读写的数据块的块数。
(4)fp:表示文件指针。
(5)理论上,每次读写 size*count
个字节的数据。
(6)size_t 是在 stdio.h 和 stdlib.h 头文件中使用 typedef 定义的数据类型,无符号整数,常用来表示数量。
返回值:返回成功读写的块数,也即 count。如果返回值小于 count:
(1)对于 fwrite() 来说,肯定发生了写入错误,可以用 ferror() 函数检测。
(2)对于 fread() 来说,可能读到了文件末尾,可能发生了错误,可以用 ferror() 或 feof() 检测。
【示例】从键盘输入一个数组,将数组写入文件再读取出来。
#include <stdio.h>
#include <stdlib.h>
int main(){
FILE * fp = NULL;
char ch;
//判断一个,有没有成功打开文件,如果打开出错,直接退出程序 ,'wb+'二进制读写文件,重写时先清清除原内容
if((fp = fopen("./demo.txt","wb+")) == NULL){
puts("fail to open file!!!");
exit(0);
}
//练习用fread和fwrite读写文件的数据
int a[5]={
0},b[5]={
0};
int i=0;
int size = sizeof(int); //数据块数
for(i=0;i<5;i++){
scanf("%d"