建议老师们将本文收藏,待学生的需要时发给学生,免费口舌。
本文引用自作者编写的下述图书; 本文允许以个人学习、教学等目的引用、讲授或转载,但需要注明原作者"海洋饼干叔
叔";本文不允许以纸质及电子出版为目的进行抄摘或改编。
1.《Python编程基础及应用》,陈波,刘慧君,高等教育出版社。免费授课视频 Python编程基础及应用
2.《Python编程基础及应用实验教程》, 陈波,熊心志,张全和,刘慧君,赵恒军,高等教育出版社Python编程基础及应用实验教程
3. 《简明C及C++语言教程》,陈波,待出版书稿。免费授课视频
在C++里,我们使用cout进行控制台文本输出。这在学习编程的阶段很常用,但在真实的工作场合却极少使用,毕竟大部分的应用程序都是基于图形界面,而不是终端的。甚至,在C/C++的某些应用场合,比如单片机编程里,嵌入式设备甚至连屏幕都没有。
考虑到部分OJ系统中的在线编程题可能对输出格式作出精细要求,这里我们对cout控制输出进行“详细”讨论:包括cout的基本工作原理,以及通过cout进行精细格式输出的方法。
对工作原理不感兴趣,只想快速知道HOW的有编程经验的读者请直接阅读本文的第6部分。
下述代码可以帮助我们理解通过cout的插入操作符(insertion operator<<)进行控制台文本输出的基本原理。
//Project - COUT
#include
using namespace std;
int main() {
cout << "pi = " << 3.14159 << endl;
operator<<(cout,"pi = ").operator<<(3.14159).operator<<(endl);
return 0;
}
上述代码的执行结果为:
pi = 3.14159
pi = 3.14159
在iostream头文件中,很容易找到cout的定义。cout是一个类型为ostream的对象,其被连接到了标准输出流,即控制台。
extern ostream cout; /// Linked to standard output
上述程序的第6行与第7行完全等价。相关代码的执行过程如下:
<<操作符在C语言里用作左移位操作,C++的标准模板库通过定义与该操作符“同名”的函数,扩展了该操作符的功能:向cout输出对象内容。
总结:iostream定义了多个重载的operator<<()操作符函数,这些不同版本的函数接受不同类型的参数,包括int, char, float, double, const char*, string等,并将这些参数对象转换成字符串,并输出到控制台屏幕上。
//Project - HexOct
#include
#include
using namespace std;
int main(){
int b = 0x17; //十六进制 hexadecimal
int c = 017; //八进制 octal
int d = 0b01111110; //二进制 binary
cout << "0x17 = " << b << ", 017 = " << c << ", 0b01111110 = " << d << endl;
printf("17: %x, %d, %o\n", 17, 17, 17);
cout << "17: " << hex << 17 << ", " << dec << 17 << ", " << oct << 17 << endl;
hex(cout);
cout << "17: " << 17 << ", ";
dec(cout);
cout << 17 << ", ";
oct(cout);
cout << 17 << endl;
return 0;
}
上述代码的执行结果为:
0x17 = 23, 017 = 15, 0b01111110 = 126
17: 11, 17, 21
17: 11, 17, 21
17: 11, 17, 21
上述代码可以看出,通过执行cout << hex,可以改变cout的内部状态,使用在后续输出数值时使用16进制。cout << dec (十进制),cout << oct (八进制)同理。
事实上,这里的hex, dec, oct是一种被称之为操作算子(manipulator)的特殊函数。下述3行代码事实上等价:
cout << hex;
cout.operator<<(hex);
hex(cout);
在ios_base.h中我们可以找到hex()函数的定义:
inline ios_base&
hex(ios_base& __base)
{
__base.setf(ios_base::hex, ios_base::basefield);
return __base;
}
在形式上,cout << hex被解释成多轮函数调用,首先是:
cout.operator<<(hex);
这个被重载的operator<<()函数将hex函数名当成一个函数指针,然后通过这个函数指针调用hex()函数:
hex(cout);
而hex()函数又通过cout的setf()函数发挥作用。读者或许会疑惑说,这么多层的函数调用是否会降低代码的执行效率,事实上,由于相关函数多是内联(inline)的,编译器的优化会消除这些“形式”上的不必要的函数调用。
#include
#include
using namespace std;
int main(){
cout << "12345678901234567890" << endl;
cout << "--------------------" << endl;
cout.width(5);
cout << "N";
cout.width(15);
cout << "2**N" << endl;
cout << "--------------------" << endl;
for (auto n=0;n<=10;n++){
cout.width(5);
cout << n;
cout.width(15);
cout << pow(2,n) << endl;
}
return 0;
}
上述代码的执行结果为:
12345678901234567890
--------------------
N 2**N
--------------------
0 1
1 2
2 4
3 8
4 16
5 32
6 64
7 128
8 256
9 512
10 1024
cout的成员函数width( )可以设定下一个输出项的宽度,但其作用范围仅限后一个输出项。当输出项的实际宽度小于设定宽度时,其左侧以空格填充。可以看到,上述程序借助于width( )函数,输出了一个严格右对齐的表格,第一列的宽度为5,第2列的宽度为15。
通过fill( )成员函数,可以修改填充字符。请参见下述程序及其执行结果。
#include
using namespace std;
int main(){
cout.fill('*'); //修改填充字符为*
cout.width(5);
cout << "idx";
cout.width(15);
cout << "content";
return 0;
}
上述程序的执行结果为:
**idx********content
通过cout的precision( )函数可以设置其输出浮点数时的精度。见下述代码。
#include
using namespace std;
int main(){
float v1 = 17.90f;
float v2 = 3.1415926535798932f;
cout << "before .precision(2)" << endl;
cout << "v1 = " << v1 << endl;
cout << "v2 = " << v2 << endl;
cout.precision(2);
cout << "after .precision(2)" << endl;
cout << "v1 = " << v1 << endl;
cout << "v2 = " << v2 << endl;
return 0;
}
执行结果为:
before .precision(2)
v1 = 17.9
v2 = 3.14159
after .precision(2)
v1 = 18
v2 = 3.1
上述执行结果与我们的期望有很大不同。
在默认情况下,cout输出浮点数的精度为6,且这个精度并不是指小数点后的位数,而是所有的位数。cout会借助于四舍五入的方法输出指定“精度”的字符串,同时会舍弃末尾多余的0。
执行结果的第2行显示,17.90被舍弃掉末尾的0,输出为17.9。
执行结果的第3行显示,3.1415926535798932被四舍五入为3.14159,正好6位数字。
接下来,cout.precision(2)设定浮点数输出精度为2,此时,17.90被四舍五入输出为18,3.1415926535798932被四舍五入输出为3.1,都是两位数字。
如果期望cout输出的浮点数的位数确定,当位数不足时用0补齐,可以执行cout.setf(ios_base::showpoint)。
#include
using namespace std;
int main(){
float v1 = 17.90f;
float v2 = 3.1415926535798932f;
cout.setf(ios_base::showpoint);
cout.precision(4);
cout << "v1 = " << v1 << endl;
cout << "v2 = " << v2 << endl;
return 0;
}
执行结果为:
v1 = 17.90
v2 = 3.142
多数人更愿意”精度“被定义为小数点后的位数。
cout.setf(ios_base::floatfield, ios_base::fixed)将cout设置为定点小数模式,在该模式下,”精度”表示小数点后的位数,见下述程序。
#include
using namespace std;
int main(){
float v1 = 17.90f;
float v2 = 3.1415926535798932f;
cout.precision(4);
cout.setf(ios_base::floatfield,ios_base::fixed);
cout << "v1 = " << v1 << endl;
cout << "v2 = " << v2 << endl;
return 0;
}
执行结果为:
v1 = 17.9000
v2 = 3.1416
cout.setf(ios_base::floatfield, ios_base::scientific)将cout设置为科学计数法模式,在该模式下,“精度”也表示小数点后的位数,见下述程序。
#include
using namespace std;
int main(){
float v1 = 17.90f;
float v2 = 3.1415926535798932f;
cout.precision(4);
cout.setf(ios_base::floatfield,ios_base::scientific);
cout << "v1 = " << v1 << endl;
cout << "v2 = " << v2 << endl;
return 0;
}
执行结果为:
v1 = 1.7900e+001
v2 = 3.1416e+000
cout的setf( )成员函数有两个原型。
原型1:fmtflags setf(fmtflags)
fmtflags常量 | 用途 |
---|---|
ios_base::boolalpha | 将bool型输出为true或flase |
ios_base::showbase | 显示进制(八进制 - 0, 十六进制 - 0x) |
ios_base::showpoint | 总显示小数点,位数不足时补0 |
ios_base::uppercase | 对16进制输出或者科学计数法中的E使用大写字母 |
ios_base::showpos | 为正数输出显示+号 |
请结合下述代码及其执行结果来理解上述fmtflags常量的用途。
#include
using namespace std;
int main(){
int v = 17;
cout << "true = " << true << endl; //true被输出为"1"
cout.setf(ios_base::boolalpha); //按"true"/"false"输出布尔型
cout << "false = " << false << endl; //false被输出为"false"
cout.setf(ios_base::showpos); //正数显示+号,但通常只在10进制时有效
cout << "v1 = " << v << endl;
cout << "v2 = " << hex << v << endl;
cout.setf(ios_base::showbase); //显示0,0x等进制符号
cout.setf(ios_base::uppercase);//相关字母大写
cout << "v3 = " << hex << v << endl;
return 0;
}
上述代码的执行结果为:
true = 1 //ios_base::boolalpha设置前,true被输出为1
false = false //ios_base::boolalpha设置后,false被输出为false
v1 = +17 //ios_base::showpos导致十进制正数前出现+号
v2 = 11 //16进制输出
v3 = 0X11 //ios_base::showbase导致0x的添加,ios_base::uppercase使得0x变成0X
对于8进制和16进制,ios_base::showpos通常无效,因为在多数的应用场景下,人们只会使用8进制或者16进制来表达无符号的整数,而无符号整数,是没有正负概念的。
在完成某项格式设置之后,如果期望还原该项设置,可以使用unsetf( )函数。举例如下。
#include
using namespace std;
int main(){
int v = 17;
cout.setf(ios_base::showpos);
cout << v << endl;
cout.unsetf(ios_base::showpos);
cout << v << endl;
return 0;
}
上述代码的执行结果为:
+17
17
原型2:fmtflags setf(fmtflags, fmtflags)
参数1 | 参数2 | 用途 |
---|---|---|
ios_base::dec | ios_base::basefield | 使用10进制 |
ios_base::oct | 使用8进制 | |
ios_base::hex | 使用16进制 | |
ios_base::fixed | ios_base::floatfield | 使用定点小数格式 |
ios_base::scientific | 使用科学计数法 | |
ios_base::left | ios_base::adjustfield | 左对齐 |
ios_base::right | 右对齐 | |
ios_base::internal | 符号(+, -, 0, 0x等)左对齐,值右对齐 |
下述代码及其执行结果演示了通过setf( )函数的第2个原型来设置输出内容左/右对齐的方法。
#include
#include
using namespace std;
int main(){
cout << "12345678901234567890" << endl;
cout << "--------------------" << endl;
cout.width(5);
cout.setf(ios_base::left,ios_base::adjustfield);
cout << "N";
cout.width(15);
cout.setf(ios_base::right,ios_base::adjustfield);
cout << "2**N" << endl;
cout << "--------------------" << endl;
cout.setf(ios_base::showpos);
for (auto n=-2;n<=10;n++){
cout.width(5);
cout.setf(ios_base::left,ios_base::adjustfield);
cout << n;
cout.width(15);
cout.setf(ios_base::right,ios_base::adjustfield);
cout << pow(2,n) << endl;
}
return 0;
}
上述代码的执行结果为:
12345678901234567890
--------------------
N 2**N
--------------------
-2 +0.25
-1 +0.5
+0 +1
+1 +2
+2 +4
+3 +8
+4 +16
+5 +32
+6 +64
+7 +128
+8 +256
+9 +512
+10 +1024
从执行结果可见,上述表格的第1列为左对齐,第2列则为右对齐。
下述代码的第5行与第6~7行效果相同,它们都按照16进制格式输出17,结果为11。
#include
using namespace std;
int main(){
cout << hex << 17 << endl;
cout.setf(ios_base::hex,ios_base::floatfield);
cout << 17 << endl;
return 0;
}
上述代码的执行结果为:
11
11
如本文的第2部分所述,hex事实上是一个被称为**操作算子(manipulator)**的函数,该函数事实上通过调用setf(ios_base::hex, ios_base::floatfield)发挥作用。显然,操作算子更加用户友好。
下表介绍了常用的操作算子。
操作算子 | 等价setf( )调用 | 用途 |
---|---|---|
cout << boolalpha | setf(ios_base::boolalpha) | 用true/false表达布尔型 |
noboolalpha | unsetf(ios_base::boolalpha) | 还原前项设置 |
showbase | setf(ios_base::showbase) | 使用0/0x表达8/16进制 |
noshowbase | unsetf(ios_base::showbase) | 还原前项设置 |
showpoint | setf(ios_base::showpoint) | 总显示小数点,位数不足时补0 |
noshowpoint | unsetf(ios_base::showpoint) | 还原前项设置 |
showpos | setf(ios_base::showpos) | 在10进制正数前显示+号 |
noshowpos | unsetf(ios_base::showpos) | 还原前项设置 |
uppercase | setf(ios_base::uppercase) | 对16进制输出或者科学计数法中的E使用大写字母 |
nouppercase | unsetf(ios_base::uppercase) | 还原前项设置 |
internal | setf(ios_base::internal,ios_base::adjustfield) | 符号(+, -, 0, 0x等)左对齐,值右对齐 |
left | setf(ios_base::left,ios_base::adjustfield) | 左对齐 |
right | setf(ios_base::right,ios_base::adjustfield) | 右对齐 |
oct | setf(ios_base::oct,ios_base::basefield) | 8进制 |
dec | setf(ios_base::dec,ios_base::basefield) | 10进制 |
hex | setf(ios_base::hex,ios_base::basefield) | 16进制 |
fixed | setf(ios_base::fixed,ios_base::floatfield) | 定点小数 |
scientific | setf(ios_base::scientific,ios_base::floatfield) | 科学计数法 |
此外,通过头文件iomanip还可以引入另外几个重要的操作算子。iomanip是input & output manipulator的缩写。
操作算子 | 等效于 | 用途 |
---|---|---|
setw(n) | cout.width(n) | 设置输出宽度 |
setprecision(n) | cout.precision(n) | 设置输出”精度” |
setfill(x) | cout.fill(x) | 设置填充字符 |
下述代码简单演示了操作算子的使用方法。
#include
#include
#include
using namespace std;
int main(){
cout << fixed;
cout << setw(6) << left << "N"
<< setw(12) << left << "log2N"
<< setw(12) << right << "log10N" << endl;
cout << "-----------------------------------------" << endl;
for (auto n = 1; n<=10; n++){
cout << setw(6) << setfill('-') << left << n << setfill(' ');
cout << setw(12) << setprecision(4) << left << log2(n);
cout << setw(12) << setprecision(5) << right << log10(n) << endl;
}
return 0;
}
上述代码的执行结果为:
N log2N log10N
-----------------------------------------
1-----0.0000 0.00000
2-----1.0000 0.30103
3-----1.5850 0.47712
4-----2.0000 0.60206
5-----2.3219 0.69897
6-----2.5850 0.77815
7-----2.8074 0.84510
8-----3.0000 0.90309
9-----3.1699 0.95424
10----3.3219 1.00000
为了帮助更多的年轻朋友们学好编程,作者在B站上开了两门免费的网课,一门零基础讲Python,一门零基础C和C++一起学,拿走不谢!
如果你觉得纸质书看起来更顺手,目前Python有两本,C和C++在出版过程中。