C++ 不直接处理输入输出, 而是通过一组定义在标准库中的类型来处理 I/O . 这些类型支持从设备读取数据, 向设备写入数据的 I/O 操作, 设备可以是文件, 控制台窗口等. 还有一些类型允许内存 I/O , 即, 从 string 读取数据, 向 string 写入数据
I/O 类主要包含了 iostream, sstream, fstream. iostream 定义了用于读写流的基本类型, fstream 定义了读写命名文件的类型, sstream 定义了读写内存 string 对象的类型
I/O 对象无法拷贝和赋值. 此外, 由于 I/O 操作的函数读写一个 I/O 对象会改变其状态, 因此 I/O 操作函数传递的参数和返回的引用不能为 const
I/O 操作与生俱来的问题是可能发生错误, 因此 I/O 库中定义了一些函数和标志. 即条件状态. 关于这些条件状态可以自行搜索
iostream 中包含了 istream 和 ostream, 即从流读取数据, 向流写入数据. 而 iostream 则继承了这两个库, 即读写流. 由于 iostream 继承于 istream 和 ostream , 因此在没有熟悉 I/O 中的函数及操纵符之前尽量使用 istream 和 ostream 声明和定义对象
每个 iostream 对象可以使用操纵符来修改流的格式状态, 在之前学习 C++ 中常见的 endl 就是一个操纵符, 其作用是输出一个换行并刷新缓冲区. 下面来介绍一些常用的格式化的 I/O 操纵符
将 true 和 false 输出为字符串 / 将 true 和 false 输出为 1, 0
#include <iostream> using namespace std; int main(void) { cout << boolalpha << true << ' ' << false << endl; //输出 true false cout << noboolalpha << true << ' ' << false << endl; //输出 1 0 return 0; }
输出十进制 / 十六进制 / 八进制
#include <iostream> using namespace std; int main(void) { int x = 10; cout << dec << x << endl; //输出 10 cout << hex << x << endl; //输出 a cout << oct << x << endl; //输出 12 return 0; }
对整型值输出表示进制的前缀 / 对不生成表示进制的前缀
#include <iostream> using namespace std; int main(void) { int x = 10; cout << showbase << dec << x << endl; //输出 10 (十进制无前缀) cout << showbase << hex << x << endl; //输出 0xa cout << showbase << oct << x << endl; //输出 012 cout << noshowbase << dec << x << endl; //输出 10 cout << noshowbase << hex << x << endl; //输出 a cout << noshowbase << oct << x << endl; //输出 12 return 0; }
对非负数显示 + / 对非负数不显示 +
#include <iostream> using namespace std; int main(void) { int x = 10; cout << showpos << x << endl; //输出 +10 cout << noshowpos << x << endl; //输出 10 return 0; }
在十六进制值中打印 0X, 在科学记数法中打印 E / 在十六进制值中打印 0x, 在科学记数法中打印 e
#include <iostream> using namespace std; int main(void) { int x = 123; cout << hex << uppercase << showbase <<x << endl; //输出 0X7B cout << hex << nouppercase << showbase << x << endl; //输出 0x7b return 0; }
浮点值显示为科学科学记数法
#include <iostream> using namespace std; int main(void) { double x = 123; cout << scientific << x << endl; // 输出 1.230000e+02 return 0; }
浮点值显示为定点十进制
#include <iostream> using namespace std; int main(void) { double x = 123; cout << fixed << x << endl; //输出 123.000 return 0; }
对浮点值总是显示小数点 / 只有当浮点值包含小数部分时才显示小数点
#include <iostream> using namespace std; int main(void) { double x = 1.000; cout << showpoint << x << endl; //输出 1.000000 cout << noshowpoint << x << endl; //输出 1 return 0; }
输入运算符跳过空白符 / 输入运算符不跳过空白符
#include <iostream> using namespace std; int main(void) { char x; cin >> noskipws; while(cin >> x) //输入1 2 3 cout << x; //输出1 2 3 cout << endl; return 0; }
浮点值显示为十六进制
#include <iostream> using namespace std; int main(void) { double x = 0.125; cout << hexfloat << x << endl; //输出0x8p-6 return 0; }
重置浮点数格式为十进制
#include <iostream> using namespace std; int main(void) { double x = 0.125; cout << hexfloat << x << endl; //输出0x8p-6 cout << defaultfloat << x << endl; //输出0.125 return 0; }
setw(w) 读或写值的宽度为 w 个字符
在值的右侧添加填充字符 / 在值的左侧添加填充字符 / 在符号和值之间添加填充字符
三种操纵符需要 iomanip 库中的 setw 来实现
#include <iostream> #include <iomanip> using namespace std; int main(void) { int x = 12345; cout << left << setw(12) << x << endl; //输出12345 cout << right << setw(12) << x << endl; //输出 12345 cout << showpos << internal << setw(12) << x << endl; //输出+ 12345 return 0; }
setfill(ch), 用 ch 填补空白
#include <iostream> #include <iomanip> using namespace std; int main(void) { int x = 12345; cout << setfill('*') << setw(12) <<x << endl; //输出*******12345 return 0; }
setprecision(n), 将浮点精度设置为 n
#include <iostream> #include <iomanip> #include <cmath> using namespace std; int main(void) { cout << setprecision(12) << sqrt(2) << endl; //输出1.41421356237 return 0; }
关于 os.precision
os.precision(n) 作用与 setprecision 相同, 不过其返回值为设置前的浮点数精度 (默认为6), 因此往往它作为单独的语句.
setbase(n), 将整数输出为 b 进制
#include <iostream> #include <iomanip> #include <cmath> using namespace std; int main(void) { int x = 12345; cout << setbase(10) << showbase << x << endl; //输出12345 cout << setbase(16) << showbase << x << endl; //输出0x3039 cout << setbase(8) << showbase << x << endl; //输出030071 return 0; }
未格式化 I/O 操作允许我们将一个流当作一个无解释的字节序列来处理
从 istream is 读取下一个字节存入字符 ch 中. 返回 is / 将字符 ch 输出到 ostream os. 返回 os
#include <iostream> using namespace std; int main(void) { char ch; cin.get(ch); //输入 a cout.put(ch); //输出 a return 0; }
将 is 的下一个字节作为 int 返回
#include <iostream> using namespace std; int main(void) { int x; x = cin.get(); //输入 A cout << x << endl; //输出 65 return 0; }
将字符 ch 放回 is. 返回 is
#include <iostream> using namespace std; int main(void) { char a, b; cin.putback('c'); cin >> a; cin.putback('d'); cin >> b; cout << a << ' ' << b << endl; // 输出 a b return 0; }
将 is 向后移动一个字节. 返回 is
#include <iostream> using namespace std; int main(void) { char a, b, c; cin >> a >> b >> c; //输入abcdef cout << a << b << c << endl; //输出abc cin.unget(); //将 c 放回输入流中 char firstchar, secondchar; cin >> firstchar >> secondchar; cout << firstchar << secondchar << endl; //输出cd return 0; }
将下一个字节作为 int 返回, 但不从流中删除它
#include <iostream> using namespace std; int main(void) { char a, b, c; cin >> a >> b >> c; //输入abcdef cout << a << b << c << endl; //输出abc int x = cin.peek(); cout.put(x); //输出d return 0; }
putback() 可以将指定要后退的字符放入流中, unget() 则是将最后读取的字符放入流中
is.get(sink, size, delim) 从 is 中读取最多 size 个字节, 并保存在字符数组中, 字符数组的起始地址由 sink 给出. 读取过程中直到遇到字符 delim 或读取了 size 个字节或遇到文件尾时停止. 如果遇到了 delim , 则将其留在输入流中, 不读取出来存入 sink 中
#include <iostream> using namespace std; char str[14]; int main(void) { cin.get(str, 14, ' '); //输入Hello World cout << str << endl; //输出Hello cin >> str; cout << str << endl; //输出 World return 0; }
is.getline(sink, size, delim) 与 get 版本类似, 但会读取并丢弃 delim
#include <iostream> using namespace std; char str[14]; int main(void) { cin.getline(str, 14, 'W'); //输入Hello World! cout << str << endl; //输出Hello cin >> str; cout << str << endl; //输出 orld! return 0; }
is.read(sink, size) 读取最多 size 个字节, 存入字符数组 sink 中. 返回 is.
#include <iostream> using namespace std; char str[14]; int main(void) { cin.read(str, 14); //输入Hello World!! cout << str << endl; //输出Hello World!! return 0; }
返回上一个未格式化读取操作从 is 读取的字节数
#include <iostream> using namespace std; char str[14]; int main(void) { cin.get(str, 14, ' '); //输入Hello World! cout << cin.gcount(); //输出 5 return 0; }
将字符数组 source 中的 size 个字节写入 os. 返回 os
#include <iostream> using namespace std; int main(void) { const char* str = "Hello, World!"; cout.write(str, 14); //输出Hello, World! return 0; }
sstream 定义了三个类型来支持内存 I/O, 这些类型可以向 string 写入数据, 从 string 读取流, 就像 string 是一个 I/O 流一样. 此外 sstream 也继承了 iostream 的部分特性, 例如各种操纵符等.
istringstream 从 string 读取数据, ostringstream 向 string 写入数据, 而头文件 stringstream 既可从 string 读取数据也可向 string 写入数据. 下面记录一些 sstream 中的基本操作. 同 iostream , 尽量先使用 istringstream 和 stringstream声明和定义对象
strm 是一个 sstream 对象, 保存 string s 的一个拷贝. 此构造函数是 explicit 的 (即无法进行隐式类型转换)
#include <iostream> #include <string> #include <sstream> using namespace std; int main(void) { string str = "Hello, World!"; stringstream STR(str); //此时 STR 中保存了一个 str 的副本 return 0; }
返回 strm 所保存的 string 的拷贝
#include <iostream> #include <string> #include <sstream> using namespace std; int main(void) { string str = "Hello, World!"; stringstream STR(str); cout << STR.str() << endl; //输出 Hello, World! return 0; }
将 string s 拷贝到 strm 中
#include <iostream> #include <string> #include <sstream> using namespace std; int main(void) { string str1 = "Hello, World!"; string str2 = "Hello"; stringstream STR(str1); cout << STR.str() << endl; //输出Hello, World! STR.str(str2); cout << STR.str() << endl; //输出Hello return 0; }
由于 stringstream 是继承 iostream (istream, ostream ) 和 istream, ostream 的类, 因此其中也包含了它们中的重载运算符 >> 和 <<, 在 stringstream 中使用它们能够非常方便的进行字符串与数字之间的转换
#include <iostream> #include <string> #include <sstream> using namespace std; int main(void) { stringstream sstr; sstr << 12345; cout << sstr.str() << endl; //输出12345 sstr.str(""); //每次stingstream对象操作完后尽量将其清空 sstr.clear(); //清除stringstream的 I/O 状态 sstr << showbase << hex << 0xfffff; cout << sstr.str() << endl; //输出0xfffff sstr.str(""); sstr.clear(); int x = 0; int y = 0; sstr << dec << "12345"; sstr >> x; cout << x << endl; //输出12345 sstr.str(""); sstr.clear(); sstr << hex << "0xfffff"; sstr >> y; cout << showbase << hex << y << endl; //输出0xfffff sstr.str(""); sstr.clear(); return 0; }
fstream 类继承自 ifstream 和 ofstream, 即读取文件流中的信息, 写入文件流中的信息, 而 fstream 则既可以读取文件流中的信息又可以写入文件流中的信息. 下面记录了一些 fstream 中的基本操作. 同 iostream , 先使用 ifstream 和 ofstream 声明和定义对象
创建一个 fstream, 并打开名为 s 的文件. s 可以是 string, 也可以是 C 风格的字符串, 这些构造函数都是 explicit 的
#include <iostream> #include <string> #include <fstream> using namespace std; int main(int argc, char* argv[]) { string path = "C:\\notepad.exe"; //我将C:\Windows\System32\notepad.exe复制到了C:\ fstream fstrm(path); return 0; }
在前一个构造函数上添加了打开文件的方式
文件模式 | 解释 |
---|---|
in | 以读的方式打开 |
out | 以写的方式打开 |
app | 每次写操作均定义到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行 I/O |
#include <iostream> #include <string> #include <fstream> using namespace std; int main(int argc, char *argv[]) { string path = "C:\\text.txt"; fstream fstrm(path, ios::out); //在C盘创建一个text.txt return 0; }
打开名为 s 的文件, 并将文件与 fstream 绑定. 其他可参考 fstream 的构造函数
#include <iostream> #include <string> #include <fstream> using namespace std; int main(int argc, char *argv[]) { string path = "C:\\text.txt"; fstream fstrm; fstrm.open(path, ios::out); //将在C盘创建一个text.txt return 0; }
关闭与 fstream 绑定的文件, 返回 void
#include <iostream> #include <string> #include <fstream> using namespace std; int main(int argc, char *argv[]) { string path = "C:\\text.txt"; fstream fstrm; fstrm.open(path, ios::out); fstrm.close(); //与path绑定的fstrm已关闭 return 0; }
判断文件是否已经打开且尚未关闭, 返回一个 bool 值
#include <iostream> #include <string> #include <fstream> using namespace std; int main(int argc, char *argv[]) { string path = "C:\\text.txt"; fstream fstrm; fstrm.open(path, ios::out); cout << fstrm.is_open() << endl; //输出 1 fstrm.close(); cout << fstrm.is_open() << endl; //输出 0 return 0; }
标准库提供了一对函数, 来定位 (seek) 到流中给定的位置, 以及告诉 (tell) 我们当前位置. 在大多数系统中, cin、cout、cerr 和 clog 的流不支持随机访问.
seek 函数给定位置, tell 函数标记当前位置, seek 和 tell 函数都有 g(get) 和 p(put) 版本, 分别指 "获取 (读取) 数据" 和 "放置 (写入) 数据"
在一个输入流或输出流中将标记定位到 from 之前或之后 off 个字符, from 可以是下列值之一
beg : 偏移量相对于流开始的位置
cur : 偏移量相对于流当前的位置
end : 偏移量相对于流结束的位置
#include <iostream> #include <string> #include <fstream> using namespace std; int main(int argc, char *argv[]) { string path = "C:\\notepad.exe"; fstream fstrm(path); fstrm.seekg(0, ios::end); return 0; }
返回一个输入流中 (tellg) 或输出流中 (tellp) 标记的当前位置
#include <iostream> #include <string> #include <fstream> using namespace std; int main(int argc, char *argv[]) { string path = "C:\\notepad.exe"; fstream fstrm(path); fstrm.seekg(0, ios::end); cout << fstrm.tellg() << endl; //输出132096 return 0; }
在一个输入流或输出流中将标记重定位到给定的绝对地址. pos 通常是前一个 tellg 或 tellp 的返回值, 在此不再赘述
fstream 同样继承了 iostream , 因此 fstream 创建的对象可以使用部分 iostream 的操作
#include <iostream> #include <string> #include <fstream> using namespace std; char* text = nullptr; int text_size = 0; int main(int argc, char* argv[]) { string path = "C:\\notepad.exe"; string new_path = "C:\\a.exe"; ifstream file_in(path, ios::binary | ios::in); //二进制读取notepad.exe if (file_in.is_open()) { file_in.seekg(0, ios::end); text_size = file_in.tellg(); //得出文件的字节数 text = new char[text_size] {0}; file_in.seekg(0, ios::beg); //将偏移量移动至文件起始处开始读取 file_in.read(text, text_size); } else cout << "Failed !" << endl; ofstream file_out(new_path, ios::binary | ios::out); //二进制写入a.exe, 没有a.exe则会创建一个 if (file_out.is_open()) { file_out.seekp(0, ios::beg); //将偏移量移动至文件起始处开始写入 file_out.write(text, text_size); } return 0; //C盘会出现一个名为a.exe, 内容和notepad.exe一模一样的可执行文件 }