目录
第1关:使用FILE结构操作文本文件
任务描述
本关任务:编写函数,该函数从已有的当前目录下的文件a.txt
中读取并解析出其中的数值,并将结果写到当前目录下的文件b.txt
中。
相关知识
文件
文件是存储在某种长期储存设备(磁盘、光盘等)上的一段数据流。C 语言中把文件看成一个有序的字节流,每个文件都以文件结束标志( EOF )结束。
下图是包含 n 个字节的文件内容:
磁盘文件由操作系统中的文件系统统一管理,也只有文件系统才能直接操作文件。所以编写 C 程序来操作文件实际上是需要调用文件系统的接口函数来进行,我们学习文件的操作也就是学习一些 C 语言库中提供的函数。是不是感觉好简单啊:-)
。
文件类型
C 语言在对文件进行操作时,将文件分为文本文件和二进制文件。
-
文本文件:指可以用任何文字处理程序阅读和编辑的简单 ASCII 文件;
-
二进制文件:指一般含有特殊的格式或计算机代码,如图形文件和可执行文件等。
下面我们本章节所学的操作都是针对文本文件而言。
文件的操作
C 语言中对文件的操作一般分为三个步骤:打开文件、读写文件、关闭文件。
- 打开文件
打开文件后,操作系统为文件建立一个文件控制结构(文件控制块),并在内存中建立一个缓冲区,该缓冲区的数据对应文件的数据,之后的文件读写操作实际上是在缓冲区中进行。
- 读写文件
通过文件控制块实现文件的输入输出。
- 关闭文件
将文件缓冲区中的数据写回到磁盘文件中,并释放文件控制块。
操作文件的函数
用 FILE 结构操作文件是 C 语言提供的文件操作方式,只要包含头文件stdio.h
就能使用文件操作的相关函数。下面就给大家介绍一些函数的使用。
打开文件
fopen 是stdio.h
提供的文件打开函数。
如下面的程序可以打开一个文件用于读:
#include <stdio.h>
FILE *fp = fopen("a.txt","r"); // 打开一个供读取数据的文件a.txt。
其中函数的第一个参数是拟打开文件的路径和名字,可以包含相对路径或者绝对路径,如上面程序中就是打开当前目录下的a.txt
文件。第二个参数是打开方式,fopen
函数可以使用的打开方式如下表:
打开方式 | 描述 |
---|---|
r | 打开一个供读取数据的文件 |
w | 建立或打开一个供写入数据的文件,如果该文件已经存在,则废弃文件内容 |
a | 建立或打开一个供写入数据的文件,如果文件已经存在,则写入的数据将追加到文件的尾部 |
r+ | 打开一个已存在的文件,该文件可以写入和读出数据 |
w+ | 建立或打开一个可供读和写的文件,如果文件已存在,则废弃文件内容 |
a+ | 建立或打开一个可供读和写的文件,如果文件已存在,则写入的数据追加到文件的尾部 |
b | 打开一个二进制文件 |
如果文件打开成功,函数 fopen 将返回一个指向 FILE 结构的指针,该指针指向的 FILE 结构管理了被打开的那个文件。
如果文件打开失败(如打开一个不存在的文件用于读),那么该函数将返回 NULL。上面的程序将返回值赋值给FILE*
的指针 fp ,之后就可以通过 fp 来操作打开的文件了。
读写文件
从文本文件中读取一个字符可以使用 fgetc 函数:
int fgetc(FILE *stream);
该函数表示从文件指针 stream 指向的文件的当前位置读取一个字符,并以 int 类型返回。
从文本文件读取一行可以使用 fgets 函数:
char *fgets(char *string, int n, FILE *stream);
该函数表示从文件指针 stream 指向的文件的当前位置开始读取字符串,直到遇到换行符(读入该换行符),或到达文件结束位置,或读取了 n-1 个字符。读取的字符串存入 string 所指的内存单元中,并在所有读取的字符之后添加字符串结束标记\0
。
如果读取成功,函数返回 string,如果出错或读取前已经到达了文件结束的位置,将返回 NULL。
从文本文件中进行格式化读取可以使用 fscanf 函数:
int fscanf(FILE *stream, const char *format [,argument]...);
该函数的功能是从指定文件中将数据按照格式控制串 format 读出并转换成相应的类型以存入对应的参数中。如果读取成功,该函数返回转换成功的参数的个数,如果出错或读取前已经到达了文件结束的位置,将返回文件结束标志 EOF。
函数 fscanf 与 scanf 函数非常相似,只是 fscanf 函数多了一个参数 stream 以指向要读取数据的文件。
输出格式化数据到文本文件中可以使用 fprintf 函数:
int fprintf(FILE *stream, const char *format [,argument]…);
该函数的功能是将数据按照格式控制串 format 写入到文件指针 stream 指向的文件中。如果写入成功,函数返回写入的字节数,否则返回一个负数表示错误。
函数 fprintf 与 printf 函数相似,只是 fprintf 函数多了一个参数 stream 以表示要写入数据的文件。
关闭文件
使用 fclose 函数就可以把缓冲区内最后剩余的数据输出到内核缓冲区,并释放文件指针和有关的缓冲区。
函数原型为:
int fclose(FILE *fp);
如果流成功关闭,fclose 返回0,否则返回 EOF(−1)。(如果流为NULL,而且程序可以继续执行,fclose设定error number给EINVAL,并返回EOF。)
例如:
#include <stdio.h>
int main(){
FILE *fp;
fp = fopen("a.txt", "w");
fprintf(fp, "%s", "www.educoder.net");
fclose(fp);
return(0);
}
参考代码
#include <stdio.h>
// 函数extractDigit的功能:从文件a.txt中提取数值写入文件b.txt中
void extractDigit();
// 请在此添加代码,实现extractDigit函数
/********** Begin *********/
char readADigit(FILE *fi)
{
char c = fgetc(fi); // 从文件读取一个字符
if(c==EOF) // 是结束符则返回
{
return EOF;
}
while(c>'9' || c<'0') // 如果不是数字字符,则继续读取下一个字符
{
c = fgetc(fi); // 读取下一个字符
if(c==EOF) // 是结束符则返回
{
return EOF;
}
}
return c; // 返回读取的数字字符
}
void extractDigit()
{
FILE *fi = fopen("a.txt","r"); // 以读的方式打开文件a.txt
FILE *fo = fopen("b.txt","w"); // 以写的方式打开文件b.txt
if(fi==NULL || fo==NULL) // 如果某个文件打开失败,则返回
{
return;
}
char c;
int num=0,k=0;
c = readADigit(fi); // 读取一个数字字符
while(c!=EOF)
{
num=num*10+c-'0'; // 计算数字字符构成的整数
k++;
if(k==3) // 已经三位了
{
fprintf(fo,"%d ",num); // 将计算的整数写入文件指针fo指向的文件
k=0; // 重新计数
num=0; // 重新计算
}
c = readADigit(fi); // 读取下一个数字字符
}
if(k!=0) // 如果有不到三位的数值,则写入文件b.txt
{
fprintf(fo,"%d ",num);
}
fclose(fi); // 关闭文件fi
fclose(fo); // 关闭文件fo
}
/********** End **********/
第2关:使用FILE结构操作二进制文件
任务描述
本关任务:编写一个从二进制文件中读取服装信息的函数。
相关知识
上一章节我们学习了对文本文件的处理方式,下面我们来学习对二进制文件的处理函数。
打开文件
要以二进制的方式操作文件,需要首先以二进制的方式打开文件。以二进制方式打开文件只需要在打开方式中增加字符 b 即可。
例如:
FILE *fp = fopen("a.dat", "rb");
以上代码中文件的打开方式为 rb ,字符 r 表示打开的文件用于读,字符 b 表示以二进制的方式打开。
读写文件
打开文件后依然是文件读写。二进制文件的可以使用 fread 和 fwrite 函数。
fwrite 函数
函数 fwrite 的原型为:
size_t fwrite(const void *buffer, size_t size, size_t count, FILE *stream);
-
size_t 为类型 long 的别名。函数 fwrite 实现把内存中从指定位置开始的指定个数的字节以二进制的方式写入文件。
-
第一个参数 buffer 指向内存中要写入文件的数据的首地址。
-
第二个参数 size 是要写入文件的数据对象的大小,一般使用运算符 sizeof 计算数据对象所占空间的字节数。
-
第三个参数是要写入的数据对象的个数。
-
第四个参数是文件指针,指向要写入数据的文件。函数 fwrite 可以一次将从 buffer 开始的,size*count 个字节的数据写入指针 stream 指向的文件中。
例如下面的程序可以将一个整数以二进制方式写入文件:
int n = 100;
FILE *fp = fopen("a.dat","wb"); // 打开二进制文件 a.dat 用于写
fwrite(&n,sizeof(n),1,fp); // 将 n 以二进制形式写入文件
fread 函数
函数 fread 的原型为:
size_t fread(void *buffer, size_t size, size_t count, FILE *stream);
-
与 fwrite 相反,函数 fread 的作用是从文件的当前位置读取指定字节数的数据放入到内存的指定位置。
-
第一个参数 buffer 指向内存中要写入数据的位置。
-
函数 fread 的后 3 个参数与函数 fwrite 的后 3 个参数的意义相同,给出了要读取数据对象的字节数、数目及被读取的文件。
-
函数 fread 可以从指定文件的当前位置一次性读取 size*count 个字节的数据并存入 buffer 中。
例如下面的程序可以将文件 fp 中当前位置的整数以二进制方式读出到变量 n 中:
int n;
FILE *fp = fopen("a.dat","wb"); // 打开二进制文件 a.dat 用于写
fread(&n,sizeof(n),1,fp);
此外,二进制文件的读写实际上是把数据原封不动的从文件搬到内存,或者从内存搬到文件。这里的原封不动是指数据的存储形式,即数据在内存中的0|1
形式。
而文本文件的读写则不是,需要做一些转换。
例如把内存中的整数12以文本方式写入文件,12的存储格式为00000000 00000000 00000000 00001100
,程序需要把这个数据转换成字符串"12"
写入文件。
关闭文件
关闭文件函数和文本文件的关闭函数的使用事一样的,都是 fclose 函数。
参考代码
#include <stdio.h>
// 结构clothing
struct clothing {
char label[12]; // 编号
float price; // 价格
};
/*
函数readClothing:从文件fp中读取服装信息到cloth中
参数fp:指向打开的二进制文件,cloth:存放读出的服装信息
返回值:服装信息数量
说明:文件中首先以二进制方式存入了一个整数(服装信息数n),然后以二进制方式存入n种服装信息(clothing类型)
*/
int readClothing(FILE *fp, clothing cloth[])
{
// 请在此添加代码,补全函数readClothing
/********** Begin *********/
int n;
// 读一个整数(服装信息数)
fread(&n,sizeof(n),1,fp);
// 读出n种服装信息到cloth中
fread(cloth,sizeof(clothing),n,fp);
return n;
/********** End **********/
}
第3关:使用文件流操作文本文件
任务描述
本关任务:编写一个统计本月服装的销售情况的函数。
相关知识
文件流操作文件
C 还可以使用文件流的方式操作文件,使用文件流的方式操作文件需要包含头文件<fstream>
,代码如下:
#include <fstream>
头文件<fstream>
中定义了三个类:类 fstream、类 ifstream 和类 ofstream。
-
类 ifstream 实现文件的输入
-
类 ofstream 实现文件的输出
-
类 fstream 实现文件的输入输出
使用文件流操作文件依然可以分为三个步骤:打开文件、读写文件、关闭文件。
打开文件
打开文件用于读时可以使用类 fstream 或者 ifstream 函数。
- ifstream 函数
ifstream inFile("test.txt", ios::in);
-
inFile 是声明的 ifstream 的一个对象(也可以叫变量,只是这个变量里面包含的东西较多,类似于结构变量),声明该对象时会自动执行一个特殊的函数(构造函数,学习面向对象部分的时候会了解)。
-
test.txt
和 ios::in 是传递给该函数的参数。test.txt
是要打开的路径和文件名,ios::in 是文件打开的方式,表示打开文件用于输入。 -
执行该函数将会以读的方式打开当前目录下的文件
test.txt
。之后通过 inFile 调用一些函数就可以操作文件test.txt
了。
- fstream 函数
由于类 fstream 也可以打开文件用于输入,上面的语句也可以这样写:
fstream inFile("test.txt", ios::in);
类 fstream 的文件打开方式有:
打开方式 | 描述 |
---|---|
ios::in | 打开一个供读取的文件 |
ios::out | 打开一个供写入的文件 |
ios::app | 写入的所有数据将被追加到文件的末尾,此方式需要使用 ios::out |
ios::ate | 写入的数据将追加到文件的末尾,但也可写到其他地方,此方式不需要用 ios::out |
ios::trunc | 废弃当前文件内容 |
ios::nocreate | 如果要打开的文件并不存在,那么以此参数调用 open 函数将无法进行 |
ios::noreplace | 如果要打开的文件已存在,试图用 open 函数打开时将返回一个错误 |
ios::binary | 以二进制的形式打开一个文件 |
其中适合于文件读的打开方式也可以用于类 ifstream ,适合于文件写的打开方式也可以用于 ofstream,ios::binary 两者都可以用。
读写文件
- 文件的读取
如果以文本的方式操作文件(没有属性 ios::binary ),则读文件的语法和用 cin 从键盘输入的语法很像。例如下面的语句可以从文件test.txt
中读取一个整数和一个浮点数。
int n;
float f;
inFile >> n >> f;
- 文件的写入
文件的写入也和输出到屏幕的语法很像。
例如下面的程序将整数100和浮点数3.14写入文件a.txt
。
// 声明对象ofile,以读的方式打开文件a.txt
ofstream ofile("a.txt", ios::out);
// 将100、空格、3.14、换行符写入文件a.txt
ofile << 100 << " " << 3.14 << endl;
// 关闭文件
ofile.close();
文件关闭
文件的关闭不管是 ifstream、ofstream 还是 fstream 的对象,都可以使用相同的语法关闭文件,即xx.close();
。
参考代码
#include <iostream>
#include <fstream>
using namespace std;
/*
函数count:统计文件fin中每种服装的销售总额,并写入文件fout中
参数fin:文件每种服装的销售情况,fout:每种服装销售总额的写入文件
返回值:无
说明:文件fin中,每种服装信息占一行,分别为服装编号,销售件数,每件的销售价格(整型)。
文件fout:每种服装统计信息占一行,分别为服装编号,销售总额(整型),中间用一个空格隔开。
*/
void count(ifstream & fin, ofstream & fout)
{
// 请在此添加代码,补全函数count
/********** Begin *********/
char s[100];
fin>>s;
while(!fin.eof())
{
int i,n,c=0,t;
fin>>n;
for(i=0;i<n;i++)
{
fin>>t;
c+=t;
}
fout<<s<<" "<<c<<endl;
fin>>s;
}
/********** End **********/
}
第4关:使用文件流操作二进制文件
任务描述
本关任务:编写一个在文件中查找某种服装的数量并返回的函数。
相关知识
文件流操作二进制文件的读写
写二进制文件
以文件流的方式操作文件一样可以支持二进制方式的块读写。
例如:
ofstream cl("c.dat", ios::binary);
int n = 10;
cl.write((char*)&t,sizeof(t));
第一行程序申明了 ofstream 的对象 cl,并以二进制方式( ios::binary )打开文件c.dat
(如果文件c.dat
不存在,会先创建)用于输出( fstream 的对象的对象都是用于文件输出)。
第三行则将整数 t 以块写入的方式写入文件c.dat
。函数 write 的第一个参数是要写入文件的数据首地址,必须是char*
类型,要写入的数据是 t,所以该实参为&t
,并进行了类型转换。第二个参数是要写入文件的字节数,t 整型变量,所占字节数可以用sizeof(t)
求得。
读二进制文件
二进制的块读出方式和块写入方式类似。
例如下面的程序可以将写到文件c.dat
中的整数读出:
ifstream fl("c.dat", ios::binary);
int n;
fl.read((char*)&n,sizeof(n));
第一行程序申明 ifstream 的对象 fl ,并以二进制方式打开文件c.dat
用于读。
第三行从文件中读出一个整数。read 函数的第一个参数是读出的数据要放到内存中的位置,类型为char*
。读出的整数要赋值给 n,所以该实参为&n
,并进行了类型转换。第二个参数是读出的字节数,一个整数的字节数可以用sizeof(n)
求得。
参考代码
#include <fstream>
#include <string.h>
#include <iostream>
using namespace std;
// 结构clothing
struct clothing {
char label[12]; // 编号
int numberRemaining; // 剩余件数
};
/*
函数getNumber:在文件ifile中查找标签为lable的服装数量
参数ifile:存放服装信息的文件,label:要查找的服装标签
返回值:标签为label的服装数量
说明:文件中ifile中存放着服装信息,服装信息为以二进制写入的一个个clothing结构变量
*/
int getNumber(ifstream &ifile, char *label)
{
// 请在此添加代码,补全函数getNumber
/********** Begin *********/
clothing t;
// 读出种服装信息到t中
ifile.read((char*)&t,sizeof(clothing));
while(!ifile.eof())
{
if(strcmp(label, t.label)==0)
{
return t.numberRemaining;
}
ifile.read((char*)&t,sizeof(clothing));
}
return 0;
/********** End **********/
}