I/O 类
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
iostream 中包含了 istream 和 ostream, 即从流读取数据, 向流写入数据. 而 iostream 则继承了这两个库, 即读写流. 由于 iostream 继承于 istream 和 ostream , 因此在没有熟悉 I/O 中的函数及操纵符之前尽量使用 istream 和 ostream 声明和定义对象
每个 iostream 对象可以使用操纵符来修改流的格式状态, 在之前学习 C++ 中常见的 endl 就是一个操纵符, 其作用是输出一个换行并刷新缓冲区. 下面来介绍一些常用的格式化的 I/O 操纵符
格式化 I/O 操纵符
boolalpha / noboolalpha
将 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;
}
dec / hex / oct
输出十进制 / 十六进制 / 八进制
#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;
}
showbase / noshowbase
对整型值输出表示进制的前缀 / 对不生成表示进制的前缀
#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;
}
showpos / noshowpos
对非负数显示 + / 对非负数不显示 +
#include <iostream>
using namespace std;
int main(void)
{
int x = 10;
cout << showpos << x << endl; //输出 +10
cout << noshowpos << x << endl; //输出 10
return 0;
}
uppercase / noupppercase
在十六进制值中打印 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;
}
scientific
浮点值显示为科学科学记数法
#include <iostream>
using namespace std;
int main(void)
{
double x = 123;
cout << scientific << x << endl; // 输出 1.230000e+02
return 0;
}
fixed
浮点值显示为定点十进制
#include <iostream>
using namespace std;
int main(void)
{
double x = 123;
cout << fixed << x << endl; //输出 123.000
return 0;
}
showpoint / noshowpoint
对浮点值总是显示小数点 / 只有当浮点值包含小数部分时才显示小数点
#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;
}
skipws / noskipws
输入运算符跳过空白符 / 输入运算符不跳过空白符
#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;
}
hexfloat
浮点值显示为十六进制
#include <iostream>
using namespace std;
int main(void)
{
double x = 0.125;
cout << hexfloat << x << endl; //输出0x8p-6
return 0;
}
defaultfloat
重置浮点数格式为十进制
#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
setw(w) 读或写值的宽度为 w 个字符
left / right / internal
在值的右侧添加填充字符 / 在值的左侧添加填充字符 / 在符号和值之间添加填充字符
三种操纵符需要 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;
}
部分在 iomanip 中的操纵符
setfill
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
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
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 操作 (单字节)
未格式化 I/O 操作允许我们将一个流当作一个无解释的字节序列来处理
is.get(ch) / os.put(ch)
从 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.get()
将 is 的下一个字节作为 int 返回
#include <iostream>
using namespace std;
int main(void)
{
int x;
x = cin.get(); //输入 A
cout << x << endl; //输出 65
return 0;
}
is.putback(ch)
将字符 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.unget()
将 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;
}
is.peek()
将下一个字节作为 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()
putback() 可以将指定要后退的字符放入流中, unget() 则是将最后读取的字符放入流中
未格式化的 I/O 操作 (多字节)
is.get
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
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
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.gcount
返回上一个未格式化读取操作从 is 读取的字节数
#include <iostream>
using namespace std;
char str[14];
int main(void)
{
cin.get(str, 14, ' '); //输入Hello World!
cout << cin.gcount(); //输出 5
return 0;
}
os.write
将字符数组 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
sstream 定义了三个类型来支持内存 I/O, 这些类型可以向 string 写入数据, 从 string 读取流, 就像 string 是一个 I/O 流一样. 此外 sstream 也继承了 iostream 的部分特性, 例如各种操纵符等.
istringstream 从 string 读取数据, ostringstream 向 string 写入数据, 而头文件 stringstream 既可从 string 读取数据也可向 string 写入数据. 下面记录一些 sstream 中的基本操作. 同 iostream , 尽量先使用 istringstream 和 stringstream声明和定义对象
sstream strm(s)
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.str()
返回 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;
}
strm.str(s)
将 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 的重载运算符 >> 和 <<
由于 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
fstream 类继承自 ifstream 和 ofstream, 即读取文件流中的信息, 写入文件流中的信息, 而 fstream 则既可以读取文件流中的信息又可以写入文件流中的信息. 下面记录了一些 fstream 中的基本操作. 同 iostream , 先使用 ifstream 和 ofstream 声明和定义对象
fstream fstrm(s)
创建一个 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;
}
fstream fstrm(s, mode)
在前一个构造函数上添加了打开文件的方式
文件模式 | 解释 |
---|---|
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;
}
fstream.open(s)
打开名为 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;
}
fstrm.close()
关闭与 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;
}
fstrm.is_open()
判断文件是否已经打开且尚未关闭, 返回一个 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 函数标记当前位置, seek 和 tell 函数都有 g(get) 和 p(put) 版本, 分别指 "获取 (读取) 数据" 和 "放置 (写入) 数据"
seekg(off, from) / seekp(off, from)
在一个输入流或输出流中将标记定位到 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()
返回一个输入流中 (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;
}
seekg(pos) / seekp(pos)
在一个输入流或输出流中将标记重定位到给定的绝对地址. 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一模一样的可执行文件
}