C/C++ 语法教程(13)——文件输入输出
Contents
文件输入输出
很多时候,输入输出并不在命令行中进行,我们会需要直接对数据文件进行读写操作,这就需要用到各类文件输入输出的方法。
C 文件读写
freopen()
方法
函数原型为:
FILE *freopen(const char *filename, const char *mode, FILE *stream);
filename
就是需要打开的文件的名字,moode
是文件访问模式,包括以下几种:
模式 | 描述 |
---|---|
"r" | 打开一个用于读取的文件。该文件必须存在。 |
"w" | 创建一个用于写入的空文件。如果文件名称与已存在的文件相同,则会删除已有文件的内容,文件被视为一个新的空文件。 |
"a" | 追加到一个文件。写操作向文件末尾追加数据。如果文件不存在,则创建文件。 |
"r+" | 打开一个用于更新的文件,可读取也可写入。该文件必须存在。 |
"w+" | 创建一个用于读写的空文件。 |
"a+" | 打开一个用于读取和追加的文件。 |
一般使用前两种。
stream
是指向 FILE
对象的指针,该 FILE
对象标识了要被重新打开的流,常用的就是 stdin
标准输入流和 stdout
标准输出流。
freopen("a.txt","r",stdin);
这样一个语句的作用,就是将原来通过标准输入流 stdin
输入的函数、流转为从文件 a.txt
中读入,比如 scanf()
、getchar()
等等函数。
同理,改成 "w"
就可以将 printf()
、puts()
、putchar()
等函数改为输出到 a.txt
。
具体可以看一下重定向到 stdout
的例子:
#include <stdio.h>
int main ()
{
FILE *fp;
printf("该文本重定向到 stdout\n");
fp = freopen("file.txt", "w+", stdout);
printf("该文本重定向到 file.txt\n");
fclose(fp);
return 0;
}
最后 fclose()
这个 fp
流,相当于关闭这个文件并保存。
这也是一般 OI 比赛中,选手们较为常用的方式。
然而这种重定向的方法往往会覆盖 stdin
和 stdout
这两个常用的输入输出流,而且即便经过了 fclose()
,这个重定向也不会还原,所以不建议使用这种重定向方法。
fopen()
方法
函数原型为:
FILE *fopen( const char * filename, const char * mode );
这里的 filename
和 mode
的值与含义与上面 freopen
中所说的一样。
它们的区别是,freopen()
是以特定模式打开一个文件,并将其重定向到某个流上,而 fopen()
只是以特定模式打开了文件,对其的操作还需要另外的函数。
同样,想关闭 fopen
打开的文件,也需要使用 fclose()
函数。
接下来介绍一下对特定文件流(FILE *
)的读写函数。
写入文件
最简单的函数是:
int fputc( int c, FILE *fp );
使用方法也就是 fputc('a',fp);
,跟 putchar()
的区别就是加了一个文件流的参数(也就是 fopen()
后获得的返回值)。
除此之外,还有:
int fputs( const char *s, FILE *fp );
int fprintf( FILE *fp,const char *format, ...);
两函数跟 puts()
和 printf
的区别只是在不同的位置加上了文件流的参数。
举个例子:
#include <stdio.h>
int main()
{
FILE *fp = NULL;
fp = fopen("/tmp/test.txt", "w+");
fprintf(fp, "This is testing for fprintf...\n");
fputs("This is testing for fputs...\n", fp);
fputc('*', fp);
fclose(fp);
return 0;
}
读取文件
最简单的函数是:
int fgetc( FILE *fp );
使用方法也就是 ch=fgetc(fp);
,跟 getchar()
的区别就是加了一个文件流的参数(也就是 fopen()
后获得的返回值)。
除此之外,还有:
char *fgets( char *buf, int n, FILE *fp );
int fscanf( FILE *fp, const char *format, ...);
函数 fgets()
从 fp
所指向的输入流中读取 $n - 1$ 个字符。它会把读取的字符串复制到缓冲区 buf
,并在最后追加一个 null
字符来终止字符串。
如果这个函数在读取最后一个字符之前就遇到一个换行符 '\n'
或文件的末尾 EOF
,则只会返回读取到的字符,包括换行符。
当然 fgets()
跟 gets()
一样不建议使用,fscanf()
跟 scanf()
差距同样只是一个文件流参数。
再举个例子:
#include <stdio.h>
int main()
{
FILE *fp = NULL;
char buff[255];
fp = fopen("/tmp/test.txt", "r");
fscanf(fp, "%s", buff);
printf("1: %s\n", buff );
fgets(buff, 255, (FILE*)fp);
printf("2: %s\n", buff );
fgets(buff, 255, (FILE*)fp);
printf("3: %s\n", buff );
fclose(fp);
return 0;
}
其读取上一部分创建的文件,输出为:
1: This
2: is testing for fprintf...
3: This is testing for fputs...
二进制 I/O 函数
下面两个函数用于二进制输入和输出:
size_t fread(void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
size_t fwrite(const void *ptr, size_t size_of_elements,
size_t number_of_elements, FILE *a_file);
这两个函数都是用于存储块的读写 - 通常是数组或结构体。
这个可以自己研究,先留个坑。
C++ 文件与流
C++ 的流
数据类型 | 描述 |
---|---|
ofstream | 该数据类型表示输出文件流,用于创建文件并向文件写入信息。 |
ifstream | 该数据类型表示输入文件流,用于从文件读取信息。 |
fstream | 该数据类型通常表示文件流,且同时具有 ofstream 和 ifstream 两种功能,这意味着它可以创建文件,向文件写入信息,从文件读取信息。 |
一般使用上述流进行文件处理,需要同时包含头文件 iostream
和 fstream
。
打开文件
首先对于这三种流,都具备成员函数(类似于上一讲的成员变量)open()
:
void open(const char *filename, ios::openmode mode);
filename
当然还是文件(目录和)名称,但是 mode
和之前有所不同:
模式标志 | 描述 |
---|---|
ios::app | 追加模式。所有写入都追加到文件末尾。 |
ios::ate | 文件打开后定位到文件末尾。 |
ios::in | 打开文件用于读取。 |
ios::out | 打开文件用于写入。 |
ios::trunc | 如果该文件已经存在,其内容将在打开文件之前被截断,即把文件长度设为 $0$。 |
如果需要使用多种模式的结合,可以使用 |
运算符将其结合。
举两个例子:
ofstream outfile;
outfile.open("file1.dat", ios::out | ios::trunc );
fstream afile;
afile.open("file2.dat", ios::out | ios::in );
特别地,如果在定义流时,便知道需要打开的文件,比如需要向 1.in
中读取信息,向 1.out
中写入信息,可以直接这么打开文件:
ifstream fin("1.in");
ofstream fout("1.out");
关闭文件
同样这些流有一个成员函数 close()
:
void close();
只需要使用以下方法即可关闭文件:
outfile.close();
afile.close();
写入文件
可以使用 ofstream
和 fstream
,然后使用 <<
运算符,用和 cout
几乎没有区别的方法写入。
比如对于上述中的 outfile
流:
outfile<<"Hello World!"<<endl;
读取文件
可以使用 ifstream
和 fstream
,然后使用 >>
运算符,用和 cin
几乎没有区别的方法写入。
比如对于上述中的 afile
流:
int a,b;
afile>>a>>b;
一个实例
#include <fstream>
#include <iostream>
using namespace std;
int main ()
{
char data[100];
// 以写模式打开文件
ofstream outfile;
outfile.open("afile.dat");
cout << "Writing to the file" << endl;
cout << "Enter your name: ";
cin.getline(data, 100);
// 向文件写入用户输入的数据
outfile << data << endl;
cout << "Enter your age: ";
cin >> data;
cin.ignore();
// 再次向文件写入用户输入的数据
outfile << data << endl;
// 关闭打开的文件
outfile.close();
// 以读模式打开文件
ifstream infile;
infile.open("afile.dat");
cout << "Reading from the file" << endl;
infile >> data;
// 在屏幕上写入数据
cout << data << endl;
// 再次从文件读取数据,并显示它
infile >> data;
cout << data << endl;
// 关闭打开的文件
infile.close();
return 0;
}
上述程序在命令行产生的结果可能是:
Writing to the file
Enter your name: Zara
Enter your age: 9
Reading from the file
Zara
9
上面的程序还使用了 cin
的 getline()
和 ignore
成员函数,前者用于读取一行(第二个参数是对于读取字符数的限制,如果超过了即便没有换行也停止读入),后者用于忽略之前读入语句后的多余字符。
对于这两个成员函数的更多使用可以自行百度。
文件位置指针
包括 ifstream
的 seekg()
和 ofstream
的 seekp()
,相关信息自行了解。
关于输入文件流的特别操作
如何判断文件结束?
一般通过 >>
运算符读入的流(包括 cin
),会有一个返回值表示是否读入成功。
每个文件都会有一个结束标志符 EOF
,如果文件结束,则读入 EOF
并返回 $0$。
所以可以通过 if (fin>>str)
等类似方法判断文件结束(输入是否成功)。
理论上 ifstream
流和 fstream
流还有一个专门的函数 eof()
判断文件结束,但对于文章末尾的换行符配合读入字符串的读入使用时,往往会出现问题,所以不建议使用。
习题
- 给定一个文件,由大小写英文和标点符号组成。依照 ASCII 值的从小到大的顺序,统计输出各字符的数量。
从
input.txt
读入。文件中包含大小写英文、标点符号、空格等 ASCII 码可见字符,以及换行符、tab 等。
输入文件小于 $100\ \mathrm{kb}$。
输出到
output.txt
中。依照 ASCII 值的大小,统计输出各字符的数量。
其中,没有出现的字符不用输出,不可见字符(换行符、tab 等)不用统计,空格不用统计。
输出格式每行为
x:y
,其中x
为对应字符,y
为数量。具体格式参考见样例输出。样例输入:
Hello World! hahaha!
样例输出:
!:2 H:1 W:1 a:3 d:1 e:1 h:3 l:3 o:2 r:1
- 给定一个文件,里面为一篇英文文章。要求将文章中的每句话颠倒输出。每句话以英文句号(
.
)为分隔。从
input.txt
读入。文件包含大小写英文字符、标点符号、空格、换行符。每句话长度小于 $100000$。
输入文件小于 $100\ \mathrm{kb}$。
输入保证每句话都以句号结束。
输出到
output.txt
中。将每句话颠倒后输出。
输出时去掉所有空格和换行。
样例输入:
Hi,Tsinghua.This is a pen. Ha ha ha.
样例输出:
auhgnisT,iH.nepasisihT.ahahaH.
No Comments