输入/输出流(I/O)【C++】
1、I / O流的概念
(1.1)基本概念
在 C++ 语言中,数据的输入和输出(简写为 I/O )包括对标准输入设备键盘和标准输出设备显示器、对在外存磁盘上的文件和对内存中指定的字符串存储空间进行输入输出这三个方面:
(1)输入设备和标准输出设备的输入输出简称为标准 I/O;
(2)对在外存磁盘上文件的输入输出简称为文件 I/O;
(3)对内存中指定的字符串存储空间的输入输出简称为串 I/O。
C++ 中把数据之间的传输操作称作流。在 C++ 中,流既可以表示数据从内存传送到某个载体或设备中,即输出流;也可以表示数据从某个载体或设备传送到内存缓冲区变量中,即输入流。在进行 I/O 操作时,首先打开操作,使流和文件发生联系,建立联系后的文件才允许数据流入或流出,输入或输出结束后,使用关闭操作使文件与流断开联系。
字节流 :字节流分为字符流(也称文本流)和二进制流。
字符流:在数据传输时需作转换,效率较低。但可以直接编辑,显示或打印。
二进制流:将字节流的每个字节以二进制方式解释,它在数据传输时不作任何转换,故效率高。但无法人工阅读,可移植性较差。
文件:文件是一般指存储在外部介质上数据的集合。计算机中的程序、数据、文档通常都组织成文件存放在外存储器中。
缓冲:系统在主存中开辟的、用来临时存放输入输出数据的区域,成为输入输出缓冲区(简称缓冲区)。
命名空间:就是在程序的不同的模块中使用相同的名字表示不同的事物(实体)。目的是提供一种机制,使大程序的各个部分中因出现重名而导致冲突的可能性降到最低。
(1.2)C++ 建立自己输入输出系统原因
(1)因为 C++ 的输入输出系统比 C 语言更安全、更可靠;
(2)在 C++ 中需要定义众多的用户自定义类型(如结构体、类等),但是使用 C 语言中的 printf和 scanf 是无法对这些数据进行输入输出操作的。
2、输入/输出流类体系
(2.1)流类库
C++ 为实现数据的输入和输出定义了一个庞大的流类库,它包括的类主要有 ios,istream,ostream,iostream,ifstream,ofstream,fstream等。类 istream 和 ostream 是类 ios 的公有派生类,分别提供输入和输出操作。
类名 | 说明 | 包含在哪个头文件 |
抽象流基类 | ||
ios | 流基类 | iostream |
输入流类 | ||
istream | 通用输入流类和其它输入流的基类 | iostream |
ifstream | 输入文件流类 | fstream |
istrstream | 输入字符串流类 | sstream |
输出流类 | ||
ostream | 通用输出流类和其它输出流的基类 | iostream |
ofstream | 输出文件流类 | fstream |
ostrstream | 输出字符串流类 | sstream |
输入输出流类 | ||
iostream | 通用输入/输出流类和其它输入/输出流的基类 | iostream |
fstream | 输入/输出文件流类 | fstream |
strstream | 输入/输出字符串流类 | sstream |
流缓冲区类 | ||
streambuf | 抽象流缓冲区基类 | streambuf |
filebuf | 磁盘文件的流缓冲区类 | fstream |
stringbuf | 字符串的流缓冲区类 | sstream |
(2.2)标准流对象
预先定义的标准流对象:
cin 标准输入
cout 标准输出
cerr 标准错误输出,没有缓冲,发送给它的内容立即被输出
clog 类似于 cerr,但是有缓冲,缓冲区满时被输出
【例】使用流 cout 和 cerr 实现数据的输出。
#include <iostream> using namespace std; int main() { float a, b; cerr << "输入a和b的值:" << endl; cin >> a >> b; if (b != 0) cout << a << "/" << b << "=" << a / b << endl; else cerr << "除数为零!" << endl; return 0; }
3、I/O 流操作
(3.1)I/O 流的格式化
格式控制符 | 功能 | 适用 |
dec | 设置为十进制 | I/O |
hex | 设置为十八进制 | I/O |
oct | 设置为八进制 | I/O |
WS | 提取空白字符 | I |
endl | 插入一个换行符 | O |
flush | 刷新符 | O |
resetioflag(long) | 取消指定的标志 | I/O |
格式控制函数名 | 功能 | 适用 |
setioflags(long) | 设置指定的标志 | I/O |
setfill(int) | 设置为填充字符 | O |
setprecision(int) | 设置实数的精度 | O |
setw(int) | 设置宽度 | O |
ends | 插入一个表示字符串结束的 NULL字符 |
函数名 | 与之功能相同的控制符 | 说明 |
precision(n) | setpricision(n) | 设置实数的精度为 n 位。 |
width(n) | sewt(n) | 设置字段的宽度为 n 位。 |
fill(c) | setfill(c) | 设置填充字符 c。 |
setf() | setiosflags() | 设置输出状态,括号中应给出格式状态的格式标志。 |
unsetf() | resetioflags() | 终止已经设置的输出格式状态,括号应指定内容。 |
skipws | 在输入中跳过空白。 |
left | 左对齐,用填充字符填充右边。 |
right | 右对齐,用填充字符填充左边。 |
internal | 在规定的宽度内,指定前缀符号之后,数值之前,插入指定的填充字符。 |
showbase | 插入前缀符号以表明整数的数制。 |
dec | 设置整数的基数为 10。以十进制形式格式化数值(默认进制)。 |
oct | 设置整数的基数为 8。以八进制形式格式化数值。 |
hex | 设置整数的基数为 16。以十六进制形式格式化数值。 |
showpoint | 对浮点数值显示小数点和尾部的0。 |
uppercase | 对于十六进制数显示大写字母A~F,对于科学格式显示大写字母E。 |
showpos | 对于非负数显示正号(+)。 |
scientific | 以科学格式显示浮点数值。 |
fixed | 以定点格式显示浮点数值(没有指数部分)。 |
unitbuf | 在每次插入之后转储并清除缓冲区的内容。 |
【例1】控制输出宽度。
#include <iostream> using namespace std; int main() { double values[] = { 1.23,454.24,2458.5,18645.23 }; for (int i = 0; i < 4; i++) { cout.width(10); cout << values[i] << endl; } return 0; }
【例2】使用 * 填充。
#include <iostream> using namespace std; int main() { double values[] = { 1.23,454.24,2458.5,18645.23 }; for (int i = 0; i < 4; i++) { cout.width(10); cout.fill('*'); cout << values[i] << endl; } return 0; }
【例3】使用 setw 指定宽度。
#include <iostream> #include <iomanip> using namespace std; int main() { double values[] = { 1.23,454.24,2458.5,18645.23 }; char* names[] = { "Zoot","Andy","John","Al" }; for (int i = 0; i < 4; i++) { cout << setw(6) << names[i] << setw(10) << values[i] << endl; } return 0; }
【例4】使用 setfill 设置填充字符。
#include <iostream> #include <iomanip> using namespace std; int main() { for(int n=1;n<8;n++){ cout << setfill(' ') << setw(n) << " " << setfill('A') << setw(15 - 2 * n) << "a" << endl; } return 0; }
【例5】设置对齐方式。
#include <iostream> #include <iomanip> using namespace std; int main() { double values[] = { 1.23,454.24,2458.5,18645.23 }; char* names[] = { "Zoot","Andy","John","Al" }; for (int i = 0; i < 4; i++) cout << setiosflags(ios_base::left) << setw(6) << names[i] << resetiosflags(ios_base::left) << setw(10) << values[i] << endl; return 0; }
【例6】控制输出精度。
#include <iostream> #include <iomanip> using namespace std; int main() { double values[] = { 1.23,454.24,2458.5,18645.23 }; char* names[] = { "Zoot","Andy","John","Al" }; // cout<<setiosflags(ios_base::scientific); // cout<<setiosflags(ios_base::fixed); for (int i = 0; i < 4; i++) cout << setiosflags(ios_base::left) << setw(6) << names[i] << resetiosflags(ios_base::left) << setw(10) << setprecision(1) << values[i] << endl; return 0; }
【例7】用setw、hex、dec指定输出数据的域宽和数制。
#include<iostream> #include<iomanip> using namespace std; int main() { int a = 256, b = 128; cout << setw(8) << a << "b = " << b << endl; cout << hex << a << "b = " << dec << b << endl; return 0; }
【例8】数据数制之间的转换。
#include<iostream> #include<iomanip> using namespace std; int main() { int x = 30, y = 300, z = 1024; cout << x << " " << y << " " << z << endl; //按十进制输出 cout << oct << x << " " << y << " " << z << endl; //按八进制输出 cout << hex << x << " " << y << " " << z << endl; //按十六进制输出 cout << setiosflags(ios::showbase | ios::uppercase); //设置基指示符和数值中的字母大写输出 cout << x << " " << y << " " << z << endl; //仍按十六进制输出 cout << resetiosflags(ios::showbase | ios::uppercase); //取消基指示符和数值中的字母大写输出 cout << x << " " << y << " " << z << endl; //仍按十六进制输出 cout << dec << x << " " << y << " " << z << endl; //按十进制输出 return 0; }
(3.2)用流成员函数实现输入 / 输出
(1)put() 函数。put() 函数把一个字符写到输入流中。功能:用于输出一个字符。
put() 函数常用的调用形式为:
cout.put(单字符);
或者
cout.put(字符型变量);
【例】put() 函数应用举例。
#include<iostream> #include<iomanip> using namespace std; int main() { char* p = "ENGLISH"; for (int i = 6; i >= 0; i--) cout.put(*(p + i)); cout.put('\n'); return 0; }
注意:
(1)put() 函数的参数不但可以是字符,还可以是字符的 ASCII 代码(也可以是一个整型表达式)。
(2)可以在一个语句中连续调用 put() 函数。
(2)get() 函数。非格式化函数的功能与提取运算符 “>>” 很相像。get() 函数在读入数据时包括空白字符。功能:是从输入流中读取一个字符(包括空白符),赋给字符变量,如果读取成功则函数返回值非零值,如果读取失败(遇文件结束符 EDF)则函数返回零值。
get() 函数常用的调用形式为:
cin.get(字符型变量)
【例】get 函数应用举例。
#include<iostream> #include<iomanip> using namespace std; int main() { char ch; while ((ch = cin.get()) != EOF) cout.put(ch); return 0; }
(3)getline() 函数。getline() 成员函数的功能是允许从输入流中读取多个字符,并且允许指定输入终止字符(默认值是换行字符),在读取完成后,从读取的内容中删除该终止字符。功能:从输入流读取 n-1 个字符,赋给指定的字符数组(或字符指针指向的数组),然后插入一个字符串结束标志 ‘\n’。如果在读取 n-1 个字符之前遇到指定的终止字符,则提前结束读取,然后插入一个字符串结束志 '\n'。
getline() 函数常用的调用形式为:
cin.getline(字符数组,字符个数 n,终止标志字符)
或者
cin.getline(字符指针,字符个数 n,终止标志字符)
【例】getline() 函数应用举例。
#include<iostream> #include<iomanip> using namespace std; int main() { char line[100]; cout << "Type a line terminated by 't'" << endl; cin.getline(line, 81, 't'); cout << line << endl; return 0; }
4、文件流和文件的输入输出
(4.1)文件的概念
在磁盘上保存的信息是按文件的形式组织的,每个文件都对应一个文件名,并且属于某个物理盘或逻辑盘的目录层次结构中一个确定的目录之下。
一个文件名由文件主名和扩展名两部分组成,它们之间用圆点(即小数点)分开,扩展名可以省略,当省略时也要省略掉前面的圆点。
文件主名是由用户命名的一个有效的 C++ 标识符,为了同其他软件系统兼容,一般让文件主名为不超过 8 个有效字符的标识符,同时为了便于记忆和使用,最好使文件主名的含义与所存的文件内容相一致。
在 C++ 程序中使用的保存数据的文件按存储格式分为两种类型:
(1)字符格式文件,简称字符文件,又称 ASCII 码文件或文本文件,它的每个字节存放一个 ASCII代码,代表一个字符。
(2)内部格式文件,简称字节文件,又称二进制文件,是把内存中的数据,按其在内存中的存储形式原样写到磁盘上存放。
C++ 程序文件,利用其他各种语言编写的程序文件,用户建立的各种文本文件,各种软件系统中的帮助文件等,因都是 ASCII 码文件,所以都可以在 C++ 中作为字符文件使用。
C++ 系统把各种外部设备也看作为相应的文件。如把标准输入设备键盘和标准输出设备显示器看作为标准输入输出文件,其文件名(又称设备名)为 con,当向它输出信息时就是输出到显示器,当从它输入信息时就是从键盘输入。
(4.2)文件的打开和关闭
使用一个文件流,应遵循以下步骤:
(1)打开一个文件。其目的是将一个文件流对象与某个磁盘文件联系起来;
(2)使用文件流对象的成员函数,将数据写入到文件中或从文件中读取数据;
(3)关闭已打开的文件,即将文件流对象与磁盘文件脱离联系。
流可以分为三类:输入流、输出流以及输入/输出流,相应地必须将流说明为 ifstream、ofstream 以及 fstream 类的对象。例如:
ifstream ifile; //说明一个输入流
ofstream ofile; //说明一个输出流
fstream iofile; //说明一个输入/输出流
说明了流对象之后,可使用函数 open() 打开文件。文件的打开即是在流与文件之间建立一个连接。open() 函数原型为:
void open ( const char * filename, int mode, int prot = filebuf::openprot )
函数名称 | 说明 |
open() | 打开一个文件并把它与流关联。 |
close() | 关闭文件。 |
fail() | 测试是否打开文件失败,失败其值为 1。 |
eof() | 测试是否文件尾,是则为 1。 |
read() | 从流中读出一组字节。 |
write() | 把一组字节写入流中。 |
seekp() | 修改输出流文件的指针位置。 |
seekg() | 修改输入流文件的指针位置。 |
tellp() | 获得输出流文件的当前位置。 |
tellg() | 获得输入流文件的当前位置。 |
文件方式 | 说明 |
in | 读方式打开文件。 |
out | 单用,打开文件时,若文件不存在,则产生一个空文件;若文件存在,则清空文件。 |
ate | 必须与 in、out 或 noreplace 组合使用。若 out | ate,其作用是在文件打开时将文件指针移至文件末尾,文件原有内容不变,写入的数据追加到文件末尾。 |
app | 是以写追加方式打开文件,当文件存在时,它等价于 out | ate,而文件不存在时,它等价于 out。 |
trunc | 打开文件时,若单用,则与 out 等价。 |
nocreate | 打开文件时,若文件不存在,则打开文件失败。这种方式总是与读或写方式组合使用,但不能与 noreplace 组合使用。 |
noreplace | 用来创建一个新文件,不单用,总是与写方式组合使用。若与 ate 或 app 组合使用,也可以打开一个已有文件。 |
binary | 以二进制方式打开文件,总是与读或写方式组合使用。不以 binary 方式打开的文件,都是文本文件。 |
对文件的打开方式的说明:
(1)文件的打开方式可以为上述的一个枚举常量,也可以为多个枚举常量构成的按位或表达式。
(2)使用 open 成员函数打开一个文件时,若由字符指针参数所指定的文件不存在,则建立该文件。
(3)当打开方式中不含有 ios::ate 或 ios::app 选项时,则文件指针被自动移到文件的开始位置,即字节地址为 0 的位置。
(4)当用输入文件流对象调用 open 成员函数打开一个文件时,打开方式参数可以省略,默认按 ios::in 方式打开,若打开方式参数中不含有 ios::in 选项时,则会自动被加上。
(4.3)文件的读写
文件读写方法:
(1)使用流运算符直接读写;
(2)使用流成员函数。
常用的输出流成员函数如下:
put 函数;write 函数;get 函数;read 函数;getline 函数
文本文件的输入和输出:
【例1】向 E 盘上的 write.txt 文件输出 0 ~ 10 之间的整数。
#include <iostream> #include <fstream> using namespace std; int main() { // 定义输出文件流,并打开相应文件,若失败则 f1 带回 0 值 ofstream f1("E://write.txt"); if (!f1) { // f1 打开失败时进行错误处理 cerr << "E:// write.txt file not open!" << endl; exit(1); } for (int i = 0; i <= 10; i++) f1 << i << " "; // 向 f1 文件流输出 i 值 f1.close(); // 关闭 f1 所对应的文件 return 0; }
【例2】从上例所建立的 E://write.txt 文件中输入全部数据并且依次显示在屏幕上。
#include <iostream> #include <fstream> using namespace std; int main() { // 定义输入文件流,并打开相应文件,若失败则 file1 带回 0 值 ifstream file1("E://write.txt",ios::in | ios::_Nocreate); if (!file1) { // file1 打开失败时进行错误处理 cerr << "E:// write.txt file not open!" << endl; exit(1); } int x; while (file1 >> x) // 依次从文件中输入整数到 x,当读到的是文件结束符时条件表达式的值为 0 cout << x << " "; cout << endl; file1.close(); // 关闭 file1 所对应的文件 return 0; }
【例3】在现有的 write.txt 文件后追加信息。
#include <iostream> #include <fstream> using namespace std; int main() { cout << "Opening output file..." << endl; ofstream ofile("E://write.txt", ios::app); if (!ofile.fail()) { cout << "Appending to file..." << endl; ofile << "这是一个没有注释的例子!" << endl; } else cout << "open file." << endl; return 0; }
【例4】复制一个文本文件到一个目标文件当中。
#include <iostream> #include <fstream> using namespace std; void main() { char ch; char f1[256], f2[256]; cout << "请输入源文件名:" << endl; cin >> f1; cout << "请输出目标文件名:" << endl; ifstream in(f1, ios::in | ios::_Nocreate); ofstream out(f2); if (!in) { cout << endl << "不能打开源文件:" << f1; return; } if (!out) { cout << endl << "不能打开目标文件:" << f2; return; } while (in >> ch) out << ch; in.close(); out.close(); cout << endl << "复制完毕!" << endl; }
【例5】设文本文件 write.txt 中有若干实数,每个实数之间用空格或换行符隔开。求出文件中的这些实数的平均值和实数的个数。
#include <iostream> #include <fstream> using namespace std; void main() { float t; float sum = 0; int count = 0; ifstream in(" e://write.txt ", ios::in | ios::_Nocreate); if (!in) { cout << "不能打开输入文件:\n"; return; } while (in >> t) { sum += t; count++; } cout << endl << "实数的平均值为:" << sum / count << ",实数的个数为:" << count; in.close(); }
二进制文件的写操作作业成员函数 write():
ostream& ostream::write ( const char*t, int n );
ostream& ostream::write ( const unsigned char*t, int n );
ostream& ostream::write ( const signed char*t, int n );
【例1】将 1 ~ 100 之间的所有偶数存入二进制文件 data.dat 中。
#include <iostream> #include <fstream> using namespace std; void main() { ofstream out("E://data.dat", ios::out | ios::binary); if (!out) { cout << "data2.dat\n"; return; } for (int i = 2; i < 100; i += 2) out.write((char*)&i, sizeof(int)); out.close(); cout << "\n程序执行完毕!\n"; }
【例2】 写一个整型数组和一个浮点型数组到二进制文件 data1.dat中,然后从 data1.dat中读取数据,并显示。
#include <iostream> #include <fstream> using namespace std; void main() { int i_number[5] = { 10,20,30,40,50 }; float f_number[5] = { 1.53,2.2,3.0,4.0,5.55 }; int int_arr[5]; float float_arr[5]; ofstream out("data1.dat"); //打开一个文件 if (!out) { cout << "can not open data1\n"; return; } out.write((char*)&f_number, sizeof(f_number)); //把数组中的浮点数写入文件 out.write((char*)&i_number, sizeof(i_number)); //把数组中的整数数写入文件 out.close(); ifstream in("data1.dat"); if (!in) { cout << "can not open data1\n"; return; } in.read((char*)&float_arr, sizeof(float_arr)); //从文件把浮点数读入数组中 in.read((char*)&int_arr, sizeof(int_arr)); //从文件把整数读入数组中 in.close(); for (int i = 0; i < 5; i++) //显示浮点数 { cout << float_arr[i] << " "; } cout << endl; for (int i = 0; i < 5; i++) //显示整数 { cout << int_arr[i] << " "; } cout << endl; cout << "程序执行完毕!\n"; return; }
5、字符串流
字符串流类包括输入字符串流类 istrstream,输出字符串流类 ostrstream 和输入输出字符串流类 strstream 三种。它们都被定义在系统头文件 strsteam.h 中。只要在程序中带有该头文件,就可以使用任一种字符串流类定义字符串流对象。每个字符串流对象简称为字符串流。
字符串流对应的访问空间是内存中由用户定义的字符数组,而文件流对应的访问空间是外存上由文件名确定的文件存储空间。
建立输出字符串流对象。ostrstream 类提供的构造函数的原型为:
ostrstream::ostrstream(char*buffer, int n, int mode = ios::out);
建立输入字符串流对象。istrstream 类提供了两个带参的构造函数,istrstream 类提供的构造函数的原型为:
istrstream::istrstream ( char*buffer );
istrstream::istrstream ( char*buffer, int n );
建立输入输出字符串流对象。strstream 类提供的构造函数的原型为:
strstream::strstream ( char*buffer, int n, int mode );
【例】 提取一个字符串中的每一个整数,并把它们依次存入到一个字符串流中,最后向屏幕输出这个字符串流。字符串从键盘输入。字符串流结束符使用的特殊字符 “$” 。
#include <iostream> #include <strstream> using namespace std; void main() { char a[50]; char b[50]; istrstream sin(a); // 定义一个输入字符串流 sin,使用的字符数组为 a ostrstream sout(b, sizeof(b)); // 定义一个输出字符串流 sout,使用 b 数组 cin.getline(a, sizeof(a)); char ch = ' '; int x; while (ch != '$') { if (ch >= 48 && ch <= 57) { sin.putback(ch); sin >> x; // 从流中读入一个整数,当碰到非数字字符时则就认为一个整数结束。 sout << x << " "; // 将 x 输出到字符串流 sout 中 } sin.get(ch); // 从 sin 流中读入下一个字符 } sout << '$' << ends; // 向 sout 流输出作为结束符的 '$' 字符和一个字符串结束符 '\0' cout << b; // 输出字符串流 sout 对应的字符串 cout << endl; }