一.文件的打开
有这样一个结构体,它内部是文件信息区,文件信息区中的变化可以影响到硬盘中的数据。这个结构体的名字是FILE。我们如果想要写代码对文件进行各种操作,就需要一个指向文件信息区的指针,这个指针的类型是FILE*,通过它就可以间接对硬盘中的数据进行各种操作。
打开文件需要用到fopen函数,同时还需要确定打开方式,代码如下:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("text.txt", "r");//创建一个FILE类型的指针变量,指向打开的文件text.txt
if (pf == NULL) //如果文件不存在,即pf为空指针
{
printf("%s", strerror(errno));
return 1;
}
return 0;
}
其中printf("%s", strerror(errno));是用来打印错误信息的,要引用string.h和erron.h两个头文件。
还有一种打印错误信息的方法:
perror("fopen");
这样写不用引用string.h和erron.h两个头文件,结果会错误信息打印出来并且在前面加上“fopen:”。
常用的打开方式分为6种:
“r”:以“只读”的方式打开文件,只能从文件中读取数据。
“w”:以“只写”的方式打开文件,只能从文件中写入数据,并且每次打开时都会把所有数据清空。
有一点要注意:如果文件并不存在,以“w”方式打开时会自动创建新文件。
“a”:以“只写”的方式打开文件,可以在之前的数据后追加。
在它们的后面加上“b”,rb,wb,ab就是以二进制的形式读,写,追加数据。
二.文件的关闭
关闭文件要用到fclose函数,用法如下:
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "r");//创建一个FILE类型的指针变量,指向打开的文件text.txt
if (pf == NULL)
{
perror("fopen");
return 1;
}
fclose(pf);//关闭文件
pf=NULL; //为防止pf成为野指针,将其赋为空指针
return 0;
}
三.文件的常规读写
fputc和fgetc函数:
fputc函数用于将单个字符写入文件,一般是从键盘获取字符。这里可能有些让人疑惑,在以往的经验中,put应该是用于输出的,但这里似乎是“输入”。其实,可以把它理解成:从键盘中输入数据,输出到文件中,又称“写入”。用法如下:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("text.txt", "w"); //注意这里是“只写”的形式
if (pf == NULL)
{
printf("%s", strerror(errno));
return 1;
}
fputc('a', pf); //把字符a写入pf指向的文件中
fclose(pf);
pf = NULL;
return 0;
}
写入一串字符串也是没问题的:
char i = 0;
for (i = 'a';i <= 'z';i++)
{
fputc(i, pf);
}
fgetc函数用于将字符从文件中读出,在以往的经验中get似乎是用来输出的,这里可以理解为把字符从文件中输出(读出)。用法如下:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("text.txt", "r");//注意这里是“只读”的形式
if (pf == NULL)
{
printf("%s", strerror(errno));
return 1;
}
char ch = 0;
ch = fgetc(pf); //从文件中读出一个字符,存到ch里
printf("%c", ch); //输出读到的字符
fclose(pf);
pf = NULL;
return 0;
}
读出一串字符串也是没问题的:
char ch = 0;
while ((ch = fgetc(pf)) != EOF)//直到没有字符才停止
{
printf("%c ", ch);
}
fputs和fgets函数
它们和上面的fputc,fgetc很像,只是把“c”变成了“s”,c代表单个字符,s代表字符串。所以它们分别用来向文件中写入或从文件中读出字符串。用法如下:
fputs:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
printf("%s", strerror(errno));
return 1;
}
fputs("abcdef", pf);//将“abcdef”写入文件中
fclose(pf);
pf = NULL;
return 0;
}
fgets和fputs不同,它有三个参数。分别是:地址(用来存从文件中读出的数据),最大读取字符个数(一般是数组的大小)和输入流。用法如下:
#include<stdio.h>
#include<string.h>
#include<errno.h>
int main()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
printf("%s", strerror(errno));
return 1;
}
char arr[20] = { 0 };
fgets(arr, 20, pf);
puts(arr);
fclose(pf);
pf = NULL;
return 0;
}
fprintf和fscanf函数:
这两个函数专门用于格式化数据处理,fprintf可以理解为向文件中打印数据,即“写入”;fscanf可以理解为从文件中输出数据,即“读出”。
用法如下:
fprintf:
#include<stdio.h>
struct stu //创建一个结构体
{
char name[20];
int age;
};
int main()
{
struct stu s = { "zhangsan",23 };//定义结构体变量并初始化
FILE* pf = fopen("text.txt", "w");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fprintf(pf, "%s %d", s.name, s.age);
fclose(pf);
pf = NULL;
return 0;
}
对比printf,fprintf多了一个参数:
fprintf(pf, "%s %d", s.name, s.age);
第一个参数是输出流(决定了应该把数据打印到哪里),第三个是要写入的数据,第二个参数是它们的格式。
fscanf:
把上面的代码稍微修改一下:
#include<stdio.h>
struct stu //创建一个结构体
{
char name[20];
int age;
};
int main()
{
struct stu s = { 0 };//定义结构体变量并初始化为0
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fscanf(pf, "%s %d", s.name, &s.age);//name本身是字符串数组名,不用取地址
printf("%s %d", s.name, s.age);
fclose(pf);
pf = NULL;
return 0;
}
fscanf第一个参数是输入流(决定了从哪里读取数据),第三个是用来存放读出的数据的地址,第二个是它们的格式。
fread和fwrite函数:
这两个函数专门用来进行二进制的读写数据,如果以二进制的形式写入数据,就一定要以二进制的形式读出。用法如下:
fwrite:
#include<stdio.h>
struct stu
{
char name[20];
int age;
};
int main()
{
struct stu s = { "zhangsan",34 };
FILE* pf = fopen("text.txt", "wb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fwrite(&s, sizeof(struct stu), 1, pf);
fclose(pf);
pf = NULL;
return 0;
}
fwrite有四个参数:需要被写入的数据所在的地址,每个数据的字节大小,写入的数据的数量,输出流。(如果是结构体,就以结构体为单位)。
fread:
#include<stdio.h>
struct stu
{
char name[20];
int age;
};
int main()
{
struct stu s = { 0 };
FILE* pf = fopen("text.txt", "rb");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fread(&s, sizeof(struct stu), 1, pf);
printf("%s %d", s.name, s.age);
fclose(pf);
pf = NULL;
return 0;
}
fread也有四个参数:用来存放读出的数据的地址,每个数据的字节大小,读出的数据的数量,输入流。
四.标准输入流和标准输出流
在上面的函数参数中出现了输入流,输出流这种陌生的概念,下面来解释一下:
标准输入流:FILE* stdin。标准输出流:FILE* stdout 。
所谓“标准”,其实就是键盘和屏幕。标准输入流是键盘(从键盘读取数据),标准输出流是屏幕(将数据输出(打印)到屏幕上)。去掉“标准”,输入流和输出流也可以是指向文件的指针,可以从文件中读取数据而非键盘,可以将数据输出(打印)到文件中而非屏幕。
还记得上面这串代码吗:
fprintf(pf, "%s %d", s.name, s.age);
如果我把它改一下:
fprintf(stdout , "%s %d", s.name, s.age);
它就不会把数据打印(写入)文件中,而是直接打印在屏幕上,因为我第一个参数用的是标准输出流。
再看这串代码:
fgets(arr, 20, pf);
这是在从文件中读取一串字符存到数组arr中。
但是如果我改一下:
fgets(arr, 20, stdin);
现在它就变成从键盘中读取一串字符存到数组arr中了,因为我把第三个参数改成了标准输入流。
实际上,与文件无关的代码也经常会用到fgets函数来读取字符串,因为它比gets函数安全。
五.文件的随机读写:
正常情况下,读取文件数据总是从第一个字符开始的,但有些时候我们更想从某一个位置或末尾读取数据。这时候就要用到fseek,ftell 和 rewind 函数了。
fseek :
首先搞明白3个概念:SEEK_SET(起始位置),SEEK_CUR(当前位置),SEEK_END(末尾)。
它们分别被宏定义为0(起始),1(当前位置),2(末尾)。
看下面的代码:
#include<stdio.h>
int main()
{
FILE* pf = fopen("text.txt", "r");
if (pf == NULL)
{
perror("fopen");
return 1;
}
fseek(pf, 2, SEEK_SET);
char ch = fgetc(pf);
printf("%c\n", ch);
fclose(pf);
pf = NULL;
return 0;
}
如果我文件里存的是“abcdefg”,那输出的结果应该是 c。
fseek一共有3个参数,第一个参数是文件指针(不要理解成"流"),第三个参数代表是从哪一个位置开始,第二个参数是偏移位置。
在上面的代码中,想象有一个“指针”,起始位置是a,往后偏移两位就指向了c,所以此时输出的就是c。
如果想让文件中的“指针”以现在的位置为起点继续偏移,只需要将SEEK_SET改成SEEK_CUR就可以了。
再看下面的代码:
这回从末尾开始偏移。
往左偏移相当于是反方向,偏移距离要用负数。
这两个结果其实是有些让人疑惑的:文件里存的是“abcdefg”,假如末尾指向的是 f,那往左偏移2个距离得到的应该是 e,但输出的居然是 f !下面用一张画明白:
第二个结果是“g”,这是因为输出 f 后“箭头”自动向后面移动了 1位。
ftell:
如果我想知道现在箭头离起始位置有多远,就要用到ftell函数了。
如果在上面代码的基础上加上
printf("%d", ftell(pf));
那输出的结果应该是7,因为此时箭头指向末尾。
rewind:
如果我突然想让箭头回到起始位置,就可以用到这个函数。用法如下:
rewind(pf);
六.sprintf和sscanf函数:
sprintf用于把格式化的数据转换成字符串,用法如下:
#include<stdio.h>
struct stu
{
char name[20];
int age;
};
int main()
{
struct stu s = { "zhangsan",23 };
char buf[100] = { 0 };
sprintf(buf, "%s %d", s.name, s.age);//把name和age这两个数据全部转换成字符串,存到buf里
printf("%s", buf);
return 0;
}
虽然输出结果还是“zhangsan 23”,但这已经变成一个字符串了。
sscanf可以从字符串中转换出格式化的数据,用法如下:
#include<stdio.h>
struct stu
{
char name[20];
int age;
};
int main()
{
struct stu s = { "zhangsan",23 };
char buf[100] = { 0 };
sprintf(buf, "%s %d", s.name, s.age);
struct stu tmp = { 0 };//再定义一个结构体变量
sscanf(buf, "%s %d", tmp.name, &tmp.age);
printf("%s %d", tmp.name, tmp.age);
return 0;
}
从字符串中转换出格式化的数据,存到tmp结构体的成员中。