本文主要是介绍C++之纵横“输入输出流“,你想要的样式任你选,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
1️⃣输入输出基本概念🐧
1️⃣⏺️1️⃣输入输出流🕊️
计算机程序在执行时被调入内存,此时将程序称为内部程序。程序在执行过程中需要从外部设备输入数据,处理后将结果数据输出到外部设备。对输入而言,外部设备可能是键盘和数据文件等;对输出而言,外部设备可能是显示器、打印机、数据文件等。这些外部设备有一些对应于真正的物理设备,有一些不是物理设备(如数据文件),因此有时将外部设备称为逻辑设备。
数据在逻辑设备和内存之间的传递就像流水一样,所以形象地称之为数据流。如图1所示。
1️⃣⏺️2️⃣文本流、二进制流和数据文件🕊️
数据流分成文本流和二进制流,分别对应于文本文件和二进制文件,数据是由一串ASCII
字符,存储的是字符的ASCII
码。例如,对于数据12345
,如果把它放进文本流中,它被看成字符串"12345"
,在流中的信息为一串ASCII
码,其十六进制形式和二进制形式如图2所示。
在文本文件中,文本数据12345
占用5字节。
例如Windows操作系统中的记事本是文本文件(.t文件),再如C++源文件(.cpp文件)也是文本文件。
在二进制流中的数据是其内存映像,即数据在内存中的内存表示形式是什么,它在二进制流中的形式就是什么。例如若数值12345
以int型量存储,其在内存中的形式和二进制流中的形似相同,如图3所示。
在二进制文件中,整型量12345
占用4字节。
在数据流中,不管是文本流或二进制流,都是以字节文单位衡量数据量的多少。 |
1️⃣⏺️3️⃣缓冲🕊️
在进行输入输出时,计算机中央处理器(CPU)要跟外部设备打交道。每进行一次输入输出时,CPU都要驱动一次外部设备,而外部设备的操作速度与CPU的运行速度相比是非常慢的,即外部设备的操作速度与CPU的操作速度不匹配。计算机的输入输出处理采用“缓冲”技术来解决这个问题。以输出操作为例,在内存中开辟了一个缓冲区,程序中的每次输出都不是立刻直接输出到外部设备上的,而是首先送往缓冲区中,当缓冲区满后或缓冲区不满时但程序干预时,才将缓冲区中的数据成批送往输出设备,这样的处理方式可以减少系统对外部设备的驱动次数,提高输出速度。
引入缓冲区的目的是为了解决CPU的运行速度和外部设备操作速度不匹配的矛盾。现将数据写入缓冲区,再成批输出。成批写出数据比一个一个写出数据节约时间,因为减少了物理设备驱动的次数。带缓冲的输入输出系统如图4所示。
从图 4 中可以看出,在带缓冲区的输入输出系统中,CPU不是直接跟外部设备交换数据,而是通过内存交换缓冲区与外部设备交换数据。CPU速度与内存的存取速度相差不大,所以CPU与内存缓冲区交换数据(即缓冲输入输出)基本上不影响程序本身的执行速度。
C++也提供了非缓冲输入输出系统,用在需要的场合,如在输出出错信息时,需要立刻输出,而不是经过缓冲再输出。
2️⃣输入输出类库🐧
2️⃣⏺️1️⃣基本输入输出流类体系🕊️
C++预先定义了很多类,给用户提供了一些基本的通用基础程序(又称基础类库),用户只要弄清楚这些类的功能,直接使用即可。编程者可以在这些基础程序之上派生出属于自己得应用程序,这样可以节省很多软件开发的时间。不同的应用程序有不同的类库。请注意本篇介绍的输入输出流类体系是基于 VS 2013的。
对于输入输出操作,系统也预先定义了一组“类”,用于完成数据的“流动(输入/输出)”,称为输入输出类库。类库中定义了多个类,这些类彼此有继承和派生关系,形成了基本输入输出流类体系,如图5所示。
图5中每个框中都是一个类名,这些类分别在头文件
xiosbase
、ios
、ostream
、istream
、iostream
中定义。ios_base
表示流的一般特征,其中定义了有关输入输出的格式控制等所需的一些基本的枚举常量。ios
是ios_base
的派生类,它是其他输入输出流类的基类。istream
类用于管理输入,提供了一些输入方法。ostream
类用于管理输出,提供了一些输出方法。ios
和istream
类和ostream
类的虚基类。iostream
类是从istream
和ostream
多重继承而来,它继承了输入输出方法。图5中streambuf
不是ios
类的派生类,它的作用是管理输入输出流类的缓冲区。ios
类中有一个指针成员,指向streambuf
类的对象。在VS 2013中,这五个类ios
、istream
、ostream
、iostream
、streambuf
还有另一个等价额名字,即basic_ios
、basic_istream
、basic_ostream
、basic_iostream
、basic_streambuf
。
在头文件iostream
中定义了几个输入输出流类的对象,用于完成输入输出工作,它们是:
1🕊️
cin
是istream
类的对象,即输入输出对象,默认对应标准输入设备,通常是键盘。
2🕊️cout
是ostream
类的对象,即输出流对象,默认对应标准输出设备,通常是显示器。
3🕊️cerr
是ostream
类的对象,即输出流对象,默认对应标准错误流设备,通常是显示器,非缓冲输出。
4🕊️clog
是ostream
类的对象,即输出流对象,默认对应标准错误流设备,通常是显示器,缓冲输出。
因此程序中若需要进行标准设备的输入输出操作,即需要使用cin
和cout
对象,则程序必须包含头文件iostream
。cin
和cout
的使用读者应该很熟悉了。那么如何使用cerr
和clog
?实际上cerr
与clog
都是输出流对象,使用它们的语法格式与cout
是一样的,它们的区别:cerr
是非缓冲输出,即直接把错误信息送到显示器;而clog
是缓冲输出,即输出信息首先送往内存输出缓冲区,带缓冲区满了或在一定的出发条件下,系统再将缓冲区中的内容送往显示器。
|*🐧程序1:使用cerr、clog的格式🎈*|
#include<iostream>
#include<cmath>
using namespace std;
int main()
{double x;cout << "Please input x:";cin >> x;if(x < 0)cerr << x << " is not a positive number!\n";elseclog << "The square root of " << x << " is " << sqrt(x) << endl;return 0;
}
本例中的cout
、cerr
和clog
的作用相同,均用于输出。例如,程序运行一次情况如下:
|-🐧程序运行结果🎈-|
Please input x:24.99
The square root of 24.99 is 4.999
2️⃣⏺️2️⃣用运算符重载实现基本数据类型量的输入输出🕊️
C++如何实现通过对象cin
和cout
完成基本数据类型量的输入输出工作?>>称为提取运算符,<<称为插入运算符。对于C++的运算符>>,其基本含义是完成位运算中的右移;但C++预先将其重载为"提取运算符",实现对C++基本数据类型数据的提取(从输入流中提取数据,即输入)。运算符重载函数是在istream
类的定义中作为成员函数实现的(在 VS 2013 头文件中实际的定义书写与下述给出的代码不一样,但本质是一样的):
class istream : virtual public ios{public : ······istream& operator >> (char *); // A, 字符串输入重载函数······istream& operator >> (char &); // B, char型量输入重载函数······istream& operator >> (int &); // C, int型量输入重载函数······istream& operator >> (double &);// D, double型量输入重载函数······};
可以看出,在istream
类中对C++的所有基本类型的量均重载了提取运算符。由于>>运算符的结合性是自左向右的,C++编译器将cin >> x >> y;
解释成(cin.operator >> (x)).operator >> (y);
。在重载函数内部由系统实现输入操作,用户不必关心输入实现的细节。
同理,在ostream
类的类体中对C++的所有基本数据类型的量均给出了插入运算符重载函数的定义,将基本的位运算中的左移运算符<<重载为"插入运算符",向输出流中插入数据,完成输出。
|*🐧程序2:在ostream类中,重载提取运算符>>和插入运算符<<的含义🎈*|
#include<iostream>
using namespace std;
int main()
{int x, y;cin >> x >> y; // 等价于:(cin.operator >> (x)).operator >> (y)cout << x << endl; // 等价于:(cout.operator << (x)).operator << (endl);cout << y << endl; // 等价于:(cout.operator << (y)).operator << (endl);return 0;
}
注意:上述istream 类中的A、B、C、D四行中重载函数的返回值均为istream 类的对象的引用,这里是为了实现连续输入操作,如本例中的(cin.operator >> (x)) 完成 cin >> x ,重载函数的返回值为cin 本身。当x 的输入完成后,将(cin.operator >> (x)) 代换成cin ,继续执行cin.operator >> (y) ,即进行cin >> y 操作。 |
对C++所有基本类型的量,在istream 类和ostream 类中均重载了提取和插入运算符,注意这些重载函数都是类(指istream 类和ostream 类)的成员函数实现的。而对编程者定义的新的数据类型,C++不可能预选定义提取和插入运算符重载函数,因为C++不可能预料编程定义何种新类型,这时需要编程者自行定义提取和插入运算符的重载函数参见 ⭐5️⃣ 节。 |
2️⃣⏺️3️⃣缺省的输入输出格式🕊️
1️⃣缺省的(默认的)输入格式
在使用cin
和提取运算符>>输入数据时,默认按下述几点处理:
1)cin 是缓冲流,当一行输入结束按回车键(Enter)时,操作系统将用户输入的内容放入内存输入缓冲区,然后输入操作函数才开始从缓冲区提取数据。 |
2)对整型变量,默认以十进制数输入。 |
3)输入数据的类型必须与提取数据的变量类型一致,否则会出错。但这种错误不会终止程序的执行,而是将系统的出错状态对应错误标志置为“真”。例如,对语句int x, y; cin >> x >> y; ,若程序执行时输入:e 15<Enter> ,则系统提取x 时就出错了,但程序不会终止,还会继续执行。 |
4)输入数据时,在缺省的情况下,数据之间要以“空白字符”分割。C++中有三空白字符,分别为:<Space>键(空格键)、<Tab>键(制表键)或<Enter>键(回车键)。 |
例如:对语句
int x, y; cin >> x >> y;
,运行时输入Space2Enter
,或输入1Tab2Enter
或输入1Enter2Enter
,都能将1
和2
正确输入给x
和y
。
又如:对语句
char s1[20], s2[20]; cin >> s1 >> s2;
,在输入字符串时,输入abSpacecdEnter
,或输入abTabcdEnter
或输入abEntercdEnter
,都能将ab
和cd
正确输入给s1
和s2
。
注意:回车键Enter又两个作用:
- ①表示一行结束,即将当前行的输入数据连同回车自身送入输入缓冲区,然后系统进行提取操作。
- ②作为输入数据的分隔符。
⚠️如果输入的是单个字符,则字符之间有无分隔符均可;若输入的是其他类型的量,则数据之间一定要用空白字符分隔。如char c1, c2, c3; cin >> c1 >> c2 >> c3;
,在程序运行时,可输入aSpacebTabcEnter
,或输入abTabcEnter
,或输入aEnterbEntercEnter
,或输入abcEnter
,在上述情况下,三个变量c1,c2,c3
获得的值均为'a'、'b'、'c'
。应注意,一行最后输入的总是回车,因为输入回车后,系统才会开始提取工作。
⚠️使用缺省方式输入数据有一定的局限,如输入字符类型数据时,无法将空格作为一个字符输入;在输入字符串时,无法输入带空格的字符串。如char s[20]; cin >> s;
若运行时输入Good morning!
Enter。
2️⃣缺省的(默认的)输出格式
不同类型量的缺省的输出格式不同:
1)输出整型量:默认输出的数制是十进制、域宽为0、右对齐、空格填充。 |
2)输出实型量:默认以小数点格式输出,精度6位(即有效数字是6位数)、域宽为0、左对齐、空格填充。若整数部分操过7位或有效数字在小数点后第4位之后,则自动转为科学计数法格式输出。 |
3)输出字符或字符串:默认的输出格式是域宽为0、右对齐、空格填充。 |
⚠️注意:上述的“域宽”应该是最小域宽,即当输入一个数据时,所占用的最少字符个数。如果数据的实际宽度超过了规定的“域宽”,则按实际宽度输出。“域宽为0”表示按实际长度输出,因为数据的宽度肯定大于0。如果数据的实际小于规定的“域宽”,则用预先设定的填充字符将输出的字符个数填充到与“域宽”相同的宽度。可以用操纵子setw()
设置一个数据的最小输出域宽。
|*🐧程序3:标准输出的默认输出格式🎈*|
#include<iostream>
#include<iomanip>
using namespace std;
int main()
{double d1 = 12.3456789, d2 = 123456.789, d3 = 0.0000123456;cout << d1 << ',' << d2 << ',' << d3 << endl;cout << setw(10) << d1 << ',' << setw(10) << d2 << ','<< setw(10) << d3 << endl; // Achar s[10] = "abcd", c = 'k';cout << s << ',' << setw(4) << c << endl;return 0;
}
|-🐧程序运行结果🎈-|
12.3457,123457,1.23456e-00512.3457, 123457,1.23456e-005
abcd, k
👀解说:变量d1
和d2
的值的有效数字操过了6位,按默认精度只输出了前6位,第7位四舍五入。变量d3
的第一位有效数字在小数后第4位之后,则自动转为科学计数法格式输出。setw(10)
表示设定下一个输出数据的最小域宽为10,它仅对其后的第1个输出项有效,因此程序中的A行在输出d1
、d2
、d3
之前都做了setw(10)
设定。以保证每个输出量的最小域宽均为10。A行变量d1
实际输出7个字符(含小数点),而设定的输出最小域宽为10,因默认的对齐为右对齐,所以前面补三个空格。变量d3
实际输出12个字符,操作了设定的最小域宽10,则按实际域宽输出。
一般来说,使用缺省的输入输出格式就可以了。但有时为了一些特殊要求(如希望按左对齐方式输出数据或希望输出八进制数、十六进制数据等),系统提供了改变输出格式的方式,将在⭐3️⃣节中介绍。
3️⃣输入输出格式控制🐧
缺省的输入输出格式不能满足一切要求,本节介绍两种方法让编程者根据需要设置输入输出格式。两种方法:使用成员函数进行格式控制和使用操纵算子进行格式控制。
3️⃣⏺️1️⃣使用成员函数进行格式控制🕊️
🐧🕊️在ios_base
类中给出了若干成员函数用于控制输入输出格式,亦定义了一些用于格式控制的标志位,所有的标志位构成格式状态字。表1列出了常用格式控制成员函数原型及其功能。表2列出了ios_base
格式标志位的含义,其中最后三行组合标志位。格式控制函数需要使用ios_base
格式标志。
解读:表2中ios_base::
作用域限定的标志位均可以用ios::
代替,如ios_base::left
亦可以写为ios::left
,因为这些标志位虽然是在ios_base
类中定义的,但ios
是ios_base
的派生类,而istream
和ostream
又是ios
的派生类,所以通过istream
和ostream
的对象cin
或cout
只用这些标志位也可以用ios::
限定,即用ios_base::
或ios
限定均可,参见图5。为了方便,后续例子基本上都使用ios
做限定。
🎈
cin
和cout
是ios
类的公有派生类(即istream
类和ostream
类)的对象,由于派生类继承了基类的成员函数,cin
和cout
自然可以使用表 1 中的成员函数实现输入输出格式控制。
|*🐧程序4:使用成员函数,控制输出格式🧸*|
#include<iostream>
using namespace std;int main()
{int x = 165, y = 142; cout.setf(ios::oct, ios::basefield); // A,设置为八进制输出格式 cout << x << ',' << y << endl;cout.setf(ios::showbase); // B,输出表示数制的前导符号cout << x << ',' << y << endl;cout.width(8); // C,设置输出域宽为8,只对其后第一个数据有效cout.fill('@'); // 设置填充字符为'@',一直有效cout.setf(ios::left, ios::adjustfield); // D,设置输出为左对齐cout << x << ',' << y << endl;cout.setf(ios::hex, ios::basefield); // E,设置为十六进制输出格式cout << x << ',' << y << endl;cout.unsetf(ios::showbase); // F,取消输出表示数制的前导字符cout << x << ',' << y << endl;return 0;
}
|-🐧程序运行结果🧸-|
245,216
0245,0216
0245@@@@,0216
0xa5,0x8e
a5,8e
🦉:程序中A行函数调用的意义为,将第二个参数指定的原三种数制标志位清除,然后设置为第一个参数指定的数值ios::oct
,即八进制,本质上就是从三种数制中选其中的一中进行行设置。B行设置的意义是,输出时数据前面带表示数制的前导字符。八进制数的前导字符为0
,十六进制数的前导字符为0x
。C行设置的意义是,输出数据的最小域宽为8,只对其后第一个输出数据项有效。D行设置的意义是,设置为从第二个参数指定的三种对齐方式中选择一种即第一个参数指定的左对齐方式。E行设置的意义与A行类似。F行的设置表示取消输出表示数制的前导字符。
|*🐧程序5:使用成员函数,控制输入格式🧸*|
#include<iostream>
using namespace std;int main()
{char c, str[80];int i = 0;cin >> c; // A,在此行之前加入语句 cin.unsetf(ios::skipws);while(c != '\n'){str[i++] = c;cin >> c;} str[i] = '\0';cout << str << endl;return 0;
}
🦉:此程序的运行状况如何?表面上看来,当输入abcd
<Enter>后,程序输出abcd
后停止。但是程序会一直不停地运行下去:
|-🐧程序交互🧸-|
abcd<Enter>
efgh<Enter>
\n<Enter>
···
🦉:原因是:默认情况下,ios::skipws
的状态为“真”,表示跳过“空白字符”,即空白字符作为数据之间的分割符不被提取,“<Enter>”是空白字符之一,其意义是'\n'
,'\n'
不会被提取,因此变量c
的值永远不会等于'\n'
。解决办法是在程序A行之前加入语句cin.unsetf(ios::skipws);
,其意义是将ios::skipws
的状态改为“假”,即不能跳过空白字符,这时输入abcd
<Enter>后,while
循环结束。
|*🐧程序5:使用成员函数,控制输入格式🧸*|
#include<iostream>
using namespace std;int main()
{char c1, c2, c3;cin.unsetf(ios::skipws); // A,设置为不跳过空白字符cin >> c1 >> c2 >> c3; // Bcout << c1 << c2 << c3 << '#' << endl;cin.setf(ios::skipws); // C,设置为跳过空白字符int x, y;cin.setf(ios::hex, ios::basefield); // 设置输入数据为十六进制数cin >> x >> y;cout << x << ',' << y << endl;return 0;
}
🦉:在A行,设置不跳过空白字符,即在进行提取操作时,空白字符也作为一个字符被提取,而不是作为输入数据之间的分隔符。
|-🐧程序交互🧸-|
a b c
a b#
10 20
12,16
🦉:在进B行的提取操作后,c1
、c2
、c3
提取得到的值分别是'a'
、
(空格)、'b'
,这可以从输出得到验证。此时输入缓冲区中的下一个字符是字母c
前面的空格。程序在C行重新设置为跳过空白字符。下面x
和y
提取的值分别是十六进制的c
和10
,它们对应的十进制数分别是12
和16
,于是输出12
和16
。输出缓冲区中余下的20
就没有用了。
|-🐧程序交互:将程序中的C行删除🧸-|
a b c
a b#
0,0
🦉:产生这个结果的原因是:在正确提取了c1
、c2
、c3
的值后,输入缓冲区中的下一个字符是空格,继续提取x
的值时出错,后续也无法提取y
的值。这种提取数据时出现的错误并不中断程序的执行,程序继续执行完毕,但提取的数据不正确。
|*🐧程序7:使用成员函数,控制浮点数的输出精度🧸*|
#include<iostream>
using namespace std;
int main()
{double x = 12.3456789;int pre = cout.precision(); // 获取缺省精度cout << "default precision = " << pre << endl;cout << x << endl;cout.precision(4); // 设置精度cout << x << endl;cout.precision(6); // 设置精度cout.setf(ios::fixed, ios::floatfield); // A,设置以小数点表示法格式输出 cout << x << endl;cout.precision(2);cout << x << endl;cout.setf(ios::scientific, ios::floatfield); // B,设置以科学表示法格式输出cout << x << endl; return 0;
}
|-🐧程序运行结果🧸-|
default precision = 6
12.3457
12.35
12.345679
12.35
1.23e+001
🦉:特别要注意浮点数精度的含义,在没有设置以小数点表示法格式或以科学表示法格式输出之前,精度的含义是指输出数据的有效数字位数。一旦设置了以小数点表示法格式或以科学表示法格式输出(程序的A行或B行)之后,精度的含义就变成小数点后数字的位数了
3️⃣⏺️2️⃣使用操纵算子进行格式控制🕊️
对于前面介绍的使用成员函数进行格式控制的方法,每次进行格式控制是都需要通过
cin
和cout
独立调用成员函数,即独立写一行函数调用语句。下面介绍使用操纵算子(manipulator,又译为“操作子”)进行格式控制的方法,格式控制内嵌在cin
、cout
语句中,不需要独立写一行语句。表3中列出了常用操纵算子及其功能,它们在C++不同的头文件中定义。注意,说使用表中最后五个带参数的操纵子,则需要包含头文件iomanip
。
|*🐧程序8:使用操纵算子控制输入输出格式🧸*|
#include<iostream>
#include<iomanip>
using namespace std;
int main()
{int x, y , a, b;cin >> hex >> x >> y; // 输入十六进制数cin >> oct >> a >> b; // 输入八进制数cout << setw(8) << setfill('*') << x << ','<< setfill('$') << setw(4) << y &l
这篇关于C++之纵横“输入输出流“,你想要的样式任你选的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!