数据的输入和输出像水流一样将数据从一个地方流到另一个地方,这个过程称为“流”。
(1)C 和 C++ 的 输入输出策略
输入流的数据来源可能是键盘,也有可能是文件。
在 C 中的IO策略如下:
使用 scanf()、gets() 等函数从键盘读取数据,使用 printf()、puts() 等函数向屏幕上输出数据;
使用 fscanf()、fgets() 等函数读取文件中的数据,使用 fprintf()、fputs() 等函数向文件中写入数据;
在 C++ 中的IO策略如下:
C的IO解决方案在C++中也可以使用;
使用 cin 等函数从键盘读取数据,使用 cout 等函数向屏幕上输出数据;
C++ 的文件操作,需要使用流类,使用 ifstream 类读取文件中的数据,使用 ofstream 类向文件中写入数据;
fstream 类既可以读取文件中的数据,也可以将数据写入文件。
输入输出流的派生体系如下:
这些流类各自的功能分别为:
istream:常用于接收从键盘输入的数据;
ostream:常用于将数据输出到屏幕上;
ifstream:用于读取文件中的数据;
ofstream:用于向文件中写入数据;
iostream:继承自 istream 和 ostream 类,因为该类的功能兼两者于一身,既能用于输入,也能用于输出;
fstream:兼 ifstream 和 ofstream 类功能于一身,既能读取文件中的数据,又能向文件中写入数据。
C++ 常用的两个对象 cin 和 cout,它们定义在<iostream>库中,属于 C++ 的内置对象。
它们常用的用法是:
int n;
cout << "请输入数字:";
cin >> n;
cout << "输入数字为:" << n << endl;
由于 cin 和 cout 不是关键字,而是对象,所以可以调用对象的成员函数。
cin 的成员函数有:
成员方法名 | 功能 |
---|---|
getline(str,n,ch) | 从输入流中接收 n-1 个字符给 str 变量,当遇到指定 ch 字符时会停止读取,默认情况下 ch 为 '\0'。 |
get() | 从输入流中读取一个字符,同时该字符会从输入流中消失。 |
gcount() | 返回上次从输入流提取出的字符个数,该函数常和 get()、getline()、ignore()、peek()、read()、readsome()、putback() 和 unget() 联用。 |
peek() | 返回输入流中的第一个字符,但并不是提取该字符。 |
putback(c) | 将字符 c 置入输入流(缓冲区)。 |
ignore(n,ch) | 从输入流中逐个提取字符,但提取出的字符被忽略,不被使用,直至提取出 n 个字符,或者当前读取的字符为 ch。 |
operator>> | 重载 >> 运算符,用于读取指定类型的数据,并返回输入流对象本身。 |
cout 的成员函数有:
成员方法名 | 功能 |
---|---|
put() | 输出单个字符。 |
write() | 输出指定的字符串。 |
tellp() | 用于获取当前输出流指针的位置。 |
seekp() | 设置输出流指针的位置。 |
flush() | 刷新输出流缓冲区。 |
operator<< | 重载 << 运算符,使其用于输出其后指定类型的数据。 |
另外,cerr 和 clog,这两个对象也是 C++ 的内置对象,且也是输出对象,和 cout 的用法一致,包括成员函数也一致。
它们和 cout 的区别是:
(1)cout 除了可以将数据输出到屏幕上,通过重定向,还可以实现将数据输出到指定文件中;
而 cerr 和 clog 都不支持重定向,它们只能将数据输出到屏幕上;
(2)cout 和 clog 都有缓冲区,即它们在输出数据时,会先将要数据放到缓冲区,等缓冲区满或者手动换行(使用换行符 '\n' 或者 endl)时,才会将数据全部显示到屏幕上;
而 cerr 则没有缓冲区,它会直接将数据输出到屏幕上。
(2)输出字符串
使用 cout 对象可以输出字符串, cout 是 ostream 对象,调用 put 函数可以输出单个字符,代码如下:
cout.put('A');
put 函数的返回值是 ostream,所以如果想要输出多个字母,可以这样写:
cout.put('A').put('B').put('C');
那么,如何输出一个字符串呢?
调用 write 函数即可实现,代码如下:
cout.write(str, 1);
write 有两个参数,第一个参数是将要输出的字符串,第二个参数是输出字符串前多少个字符。
write 函数的返回值是 ostream,所以可以连续调用:
cout.write(str, 1).write(str, 2);
(3)输出当前输出流指针位置
调用输出流 tellp 方法可以获取当前输出流的指针位置,一般用于文件操作:
//文件输出流对象
ofstream outfile;
outfile.open("test.txt"); // 打开文件,如果文件不存在,则新建
const char* str = "zhangsan";
for (int i = 0; i < strlen(str); i++) {
outfile.put(str[i]); // 输出一个字符到文件中
long pos = outfile.tellp(); // 获取当前输出流指针位置
cout << pos << " ";
}
cout << endl;
outfile.close(); // 关闭流
输出结果是:
1 2 3 4 5 6 7 8
(4)输出流指针位置的跳跃
一般情况下,输出1的字符,输出流指针位置自动+1,输出n个字符,输出流指针位置自动+n。
使用输出流的 seekp 函数可以实现输出流指针的跳跃。
seekp 函数用于指定下一个进入输出缓冲区的字符所在的位置,演示代码如下:
//文件输出流对象
ofstream outfile;
outfile.open("test.txt"); // 打开文件,如果文件不存在,则新建
const char* str = "zhangsan";
for (int i = 0; i < strlen(str); i++) {
outfile.put(str[i]); // 输出一个字符到文件中
if (i == 2)
{
outfile.seekp(10);
}
}
test.txt文件中的内容是:
可以配合 tellp 函数打印当前指针位置,代码如下:
//文件输出流对象
ofstream outfile;
outfile.open("test.txt"); // 打开文件,如果文件不存在,则新建
const char* str = "zhangsan";
for (int i = 0; i < strlen(str); i++) {
outfile.put(str[i]); // 输出一个字符到文件中
}
cout << "当前输出流指针位置:" << outfile.tellp() << endl;
outfile.seekp(4); // 将输出流指针位置改成4,下次输出从4开始
cout << "当前输出流指针位置:" << outfile.tellp() << endl;
outfile.seekp(40); // 将输出流指针位置改成4
cout << "当前输出流指针位置:" << outfile.tellp() << endl;
outfile.close(); // 关闭流
输出结果是:
当前输出流指针位置:8
当前输出流指针位置:4
当前输出流指针位置:40
seekp 还有一个带有两个形参的函数,用法如下:
//文件输出流对象
ofstream outfile;
outfile.open("test.txt"); // 打开文件,如果文件不存在,则新建
const char* str = "zhangsanlisi";
for (int i = 0; i < strlen(str); i++) {
outfile.put(str[i]); // 输出一个字符到文件中
}
cout << "当前输出流指针位置:" << outfile.tellp() << endl;
outfile.seekp(4, ios::cur); // 从当前位置向正方向偏移4个单位
cout << "当前输出流指针位置:" << outfile.tellp() << endl;
outfile.seekp(-4, ios::end); // 从结尾位置向负方向偏移4个单位
cout << "当前输出流指针位置:" << outfile.tellp() << endl;
outfile.seekp(4, ios::beg); // 从起始位置向正方向偏移4个单位
cout << "当前输出流指针位置:" << outfile.tellp() << endl;
outfile.close(); // 关闭流
seekp 的第一个参数是偏移量,可以是正整数,也可以是负整数,正负表示偏移的方向。
第二个参数是从什么位置偏移,可以接受的值有三个:
ios::beg 从起始位置偏移
ios::cur 从当前位置偏移
ios::end 从结尾开始偏移
以上代码最终输出结果是:
当前输出流指针位置:12
当前输出流指针位置:16
当前输出流指针位置:8
当前输出流指针位置:4
(5)cout格式化输出
ostream 类可实现格式化输出,ostream 格式化相关的成员函数有:
成员函数 | 说明 |
---|---|
flags(fmtfl) | 当前格式状态全部替换为 fmtfl。注意,fmtfl 可以表示一种格式,也可以表示多种格式。 |
precision(n) | 设置输出浮点数的精度为 n。 |
width(w) | 指定输出宽度为 w 个字符。 |
fill(c) | 在指定输出宽度的情况下,输出的宽度不足时用字符 c 填充(默认情况是用空格填充)。 |
setf(fmtfl, mask) | 在当前格式的基础上,追加 fmtfl 格式,并删除 mask 格式。其中,mask 参数可以省略。 |
unsetf(mask) | 在当前格式的基础上,删除 mask 格式。 |
其中 fmtfl 和 mask 的可选值有:
标 志 | 作 用 |
---|---|
ios::boolapha | 把 true 和 false 输出为字符串 |
ios::left | 输出数据在本域宽范围内向左对齐 |
ios::right | 输出数据在本域宽范围内向右对齐 |
ios::internal | 数值的符号位在域宽内左对齐,数值右对齐,中间由填充字符填充 |
ios::dec | 设置整数的基数为 10 |
ios::oct | 设置整数的基数为 8 |
ios::hex | 设置整数的基数为 16 |
ios::showbase | 强制输出整数的基数(八进制数以 0 开头,十六进制数以 0x 打头) |
ios::showpoint | 强制输出浮点数的小点和尾数 0 |
ios::uppercase | 在以科学记数法格式 E 和以十六进制输出字母时以大写表示 |
ios::showpos | 对正数显示“+”号 |
ios::scientific | 浮点数以科学记数法格式输出 |
ios::fixed | 浮点数以定点格式(小数形式)输出 |
ios::unitbuf | 每次输出之后刷新所有的流 |
下面是一些举例说明:
【flags(fmtfl)】
当前格式状态全部替换为 fmtfl
bool a = true;
cout << a << endl;
bool类型本质上是一个整数,当a=true时,打印显示为1,当a=false时,打印显示为0。
如果想要打印显示为:true 或者 false,那么就需要输出格式化。
bool a = true;
cout.flags(ios::boolalpha);
cout << a << endl;
cout 对象的 flags 函数传入的输出格式为:ios::boolalpha(把 true 和 false 输出为字符串)。
【precision(n)】
设置输出浮点数的精度为 n
float a = 1.1f;
double b = a / 3;
cout << b << endl;
以上代码的打印结果是:
0.366667
如果要求只保留两位小数,那么需要将输出结果格式化:
float a = 1.1f;
double b = a / 3;
cout.precision(2);
cout << b << endl;
输出结果是:
0.37
【width(w)】
指定输出宽度为 w 个字符
cout.width(20);
cout << "zhangsan" << endl;
以上代码的输出结果是:
zhangsan
输出宽度为20,默认右对齐。
对齐方式分为:左对齐和右对齐,可以设置flag保证其对其方式:
左对齐:
cout.flags(ios::left);
右对齐:
cout.flags(ios::right);
【正数符号的输出】
有关负数和正数的输出,负数的输出都是带有 -
号的,正数的输出默认不带 +
号,如果想要输出正数的符号,就需要对正数的输出进行格式化:
cout.flags(ios::showpos);
cout << 100 << endl;
输出结果是:
+100
结合 ios::internal
使用,将数值的符号位在域宽内左对齐,数值右对齐,中间由填充字符填充:
cout.width(20);
cout.flags(ios::showpos | ios::internal);
cout << 100 << endl;
输出结果是:
+ 100
【fill(c)】
在指定输出宽度的情况下,输出的宽度不足时用字符 c 填充(默认情况是用空格填充)
cout.width(20);
cout << "zhangsan" << endl;
以上代码的输出结果是:
zhangsan
输出结果前面默认用空格填充,相当于:
cout.width(20);
cout.fill();
cout << "zhangsan" << endl;
但是,如果想要将左边的空格用其它字符填充,需要调用 fill 函数填充字符:
cout.width(20);
cout.fill('#');
cout << "zhangsan" << endl;
输出结果是:
############zhangsan
【进制输出】
cout 有关进制的输出格式有:
ios::dec:设置整数的基数为 10
ios::oct:设置整数的基数为 8
ios::hex:设置整数的基数为 16
代码如下:
int a = 177;
cout.flags(ios::dec); // 10进制输出
cout << a << endl;
cout.flags(ios::oct); // 8进制输出
cout << a << endl;
cout.flags(ios::hex); // 16进制输出
cout << a << endl;
以上代码的输出结果为:
177
261
b1
其中,8进制和16进制的输出结果很奇怪,它们并不是很标准化(8进制应该以0开头,16进制应该以0x开头),使用输出格式:ios::showbase
可以让进制数显示的更加标准,代码如下:
int a = 177;
cout.flags(ios::dec); // 10进制输出
cout << a << endl;
cout.flags(ios::oct | ios::showbase); // 8进制输出
cout << a << endl;
cout.flags(ios::hex | ios::showbase); // 16进制输出
cout << a << endl;
输出结果是:
177
0261
0xb1
【unsetf(mask)】
在当前格式的基础上,删除 mask 格式
cout.width(20);
cout.flags(ios::showpos | ios::internal);
cout << 100 << endl;
以上代码的输出结果是:
+ 100
ios::showpos:表示正数的符号输出
ios::internal:表示符号左对齐,数值右对齐
如果想要去掉 ios::internal,就需要调用 unsetf 函数:
cout.width(20);
cout.flags(ios::showpos | ios::internal);
cout.unsetf(ios::internal);
cout << 100 << endl;
此时的输出结果是:
+100
【setf(fmtfl, mask)】
在当前格式的基础上,追加 fmtfl 格式,并删除 mask 格式。其中,mask 参数可以省略
如果想要添加某输出格式,可以使用 setf 函数:
cout.width(20);
cout.flags(ios::showpos);
cout.setf(ios::internal);
cout << 100 << endl;
以上代码 setf 只有一个参数,它可以带有两个参数,但是目前的测试结果是有问题的。
【强制输出浮点数的小点和尾数 0】
使用输出格式 ios::showpoint
可以强制输出浮点数的小点和尾数 0:
float a = 1;
cout.flags(ios::showpoint);
cout << a << endl;
输出结果是:
1.00000
【科学计数法格式输出】
使用输出格式:ios::scientific
可以将浮点数以科学记数法格式输出,代码如下:
float a = 1.11f;
cout.flags(ios::scientific);
cout << a << endl;
输出结果是:
1.110000e+00
如果想恢复成小数输出,可以重新执行小数输出格式:ios::fixed
,代码如下:
float a = 1.11f;
cout.flags(ios::scientific);
cout << a << endl;
cout.flags(ios::fixed);
cout << a << endl;
输出格式为:
1.110000e+00
1.110000
【大写格式输出】
使用输出格式:ios::uppercase
可以实现大写格式输出,但是只适用于科学计数法和16进制,演示代码如下:
float a = 1.11f;
cout.flags(ios::scientific | ios::uppercase); // 科学计数法小写字母转大写字母
cout << a << endl;
int b = 177;
cout.flags(ios::hex | ios::showbase | ios::uppercase); // 16进制的小写字母转大写字母
cout << b << endl;
输出结果是:
1.110000E+00
0XB1
(6)使用流操纵算子格式化输出
<iomanip> 头文件中定义的一些常用的格式控制符,它们都可用于格式化输出:
流操纵算子 | 作 用 | 是否常用 |
---|---|---|
dec(默认) | 以十进制形式输出整数 | 常用 |
hex | 以十六进制形式输出整数 | 常用 |
oct | 以八进制形式输出整数 | 常用 |
fixed | 以普通小数形式输出浮点数 | 常用 |
scientific | 以科学计数法形式输出浮点数 | 常用 |
left | 左对齐,即在宽度不足时将填充字符添加到右边 | 常用 |
right(默认) | 右对齐,即在宽度不足时将填充字符添加到左边 | 常用 |
setbase(b) | 设置输出整数时的进制,b=8、10 或 16 | 常用 |
setw(w) | 指定输出宽度为 w 个字符,或输入字符串时读入 w 个字符。注意,该函数所起的作用是一次性的,即只影响下一次 cout 输出。 | 常用 |
setfill(c) | 在指定输出宽度的情况下,输出的宽度不足时用字符 c 填充(默认情况是用空格填充) | 常用 |
setprecision(n) | 设置输出浮点数的精度为 n。 在使用非 fixed 且非 scientific 方式输出的情况下,n 即为有效数字最多的位数,如果有效数字位数超过 n,则小数部分四舍五人,或自动变为科学计 数法输出并保留一共 n 位有效数字。 在使用 fixed 方式和 scientific 方式输出的情况下,n 是小数点后面应保留的位数。 |
常用 |
setiosflags(mask) | 在当前格式状态下,追加 mask 格式,mask 参数可选择表 2 中的所有值。 | 常用 |
resetiosflags(mask) | 在当前格式状态下,删除 mask 格式,mask 参数可选择表 2 中的所有值。 | 常用 |
boolapha | 把 true 和 false 输出为字符串 | 不常用 |
noboolalpha(默认) | 把 true 和 false 输出为 0、1 | 不常用 |
showbase | 输出表示数值的进制的前缀 | 不常用 |
noshowbase (默认) | 不输出表示数值的进制.的前缀 | 不常用 |
showpoint | 总是输出小数点 | 不常用 |
noshowpoint(默认) | 只有当小数部分存在时才显示小数点 | 不常用 |
showpos | 在非负数值中显示 + | 不常用 |
noshowpos(默认) | 在非负数值中不显示 + | 不常用 |
uppercase | 十六进制数中使用 A~E。若输出前缀,则前缀输出 0X,科学计数法中输出 E | 不常用 |
nouppercase (默认) | 十六进制数中使用 a~e。若输出前缀,则前缀输出 0x,科学计数法中输出 e。 | 不常用 |
internal | 数值的符号(正负号)在指定宽度内左对齐,数值右对 齐,中间由填充字符填充。 | 不常用 |
其用法可以举例说明:
int a = 177;
cout << hex << a << endl; // 16进制格式输出
cout << setbase(8) << a << endl; // 8进制格式输出
float b = 1.11f;
cout << scientific << b << endl; // 科学计数法格式输出
float c = 1.110000e+00;
cout << fixed << c << endl; // 小数格式输出
string e = "zhangsan";
cout << setw(20) << left << setfill('C') << e << endl; // 宽度为20,左对齐,用字符C填充
cout << setprecision(2) << 1.1111 << endl; // 设置浮点数的精度,保留2位小数
float f = 1.11f;
cout << setiosflags(ios::scientific) << f << endl; // 在当前格式状态下,追加 mask 格式
cout << resetiosflags(ios::scientific) << f << endl; // 在当前格式状态下,删除 mask 格式
输出结果是:
b1
261
1.110000e+00
1.110000
zhangsanCCCCCCCCCCCC
1.11
0x1.1c28f60000000p+0
1.11
(7)输入输出重定向
在默认情况下,cin 只能接收从键盘输入的数据,cout 也只能将数据输出到屏幕上。但通过重定向,cin 可以将指定文件作为输入源,即接收文件中早已准备好的数据,同样 cout 可以将原本要输出到屏幕上的数据转而写到指定文件中。
C++ 中,实现重定向的常用方式有 3 种,分别是:
(1)freopen()函数实现重定向
FILE* file = new FILE();
string lineStr;
freopen_s(&file, "in.txt", "r", stdin); // 将标准输入流重定向到 in.txt 文件
freopen_s(&file, "out.txt", "w", stdout); // 将标准输出重定向到 out.txt文件
while (cin >> lineStr)
{
cout << lineStr << endl;
}
fclose(stdout);
fclose(stdin);
此时,cin 和 cout 被重定向为文件的输入输出,那么怎么恢复到控制台输出呢?
恢复到控制台输出:
freopen_s(&file, "CON", "w", stdout);
恢复到控制台输入(目测无效):
freopen_s(&file, "CON", "r", stdin);
(2)rdbuf()函数实现重定向
ifstream fin("in.txt"); //打开 in.txt 文件,等待读取
ofstream fout("out.txt"); // 打开 out.txt 文件,等待写入
streambuf* oldcin;
streambuf* oldcout;
string linStr;
oldcin = cin.rdbuf(fin.rdbuf()); // 用 rdbuf() 重新定向
oldcout = cout.rdbuf(fout.rdbuf()); //用 rdbuf() 重新定向
while (cin >> linStr) // // 从input.txt文件读入,并写入 out.txt
{
cout << linStr << endl;
}
cin.rdbuf(oldcin); // 恢复键盘输入
cout.rdbuf(oldcout); //恢复屏幕输出
fin.close();
fout.close();
(3)通过控制台实现重定向
cmd打开控制台,输入:xxx.exe,执行应用的可执行文件"xxx.exe"可以直接运行代码,但是这样无法做到 cin 和 cout 的重定向;
如果输入:xxx.exe <in.txt >out.txt 的话,可以做到重定向;
<in.txt:将 cin 从控制台输入重定向到文件(in.txt)输入,当前目录要确保存在文件 in.txt;
>out.txt:将 cout 从控制台输出重定向到文件(out.txt)输出。
编辑代码:
string lineStr;
while(cin >> lineStr)
cout << lineStr << endl;
在控制台输入:xxx.exe <in.txt >out.txt
此时发现,in.txt 中的文本已经写到out.txt文件中。
(8)如何忽视输入的指定字符
int n;
cin.ignore();
cin >> n;
cout << n;
cin 的 ignore 函数可以实现忽视输入的字符,以上代码中 ignore() 忽视一个字符,假如输入:
12345
那么输出结果是:
2345
ignore 函数还可以含有一个形式参数
istream & ignore(int n =1);
表示跳过几个字符。
ignore 函数还可以含有两个形式参数:
istream & ignore(int n =1, int delim = EOF);
此函数的作用是跳过输入流中的 n 个字符,或跳过 delim 及其之前的所有字符,哪个条件先满足就按哪个执行。
两个参数都有默认值,因此 cin.ignore() 就等效于 cin.ignore(1, EOF), 即跳过一个字符。
两个参数的举例代码:
int n;
cin.ignore(5, '1');
cin >> n;
cout << n;
输入和输出结果是:
ABC123
23
(9)查看输入流下一个字符
cin 的 peek() 函数可以查看输入流下一个字符。
[本章完...]