说明一下,我用的是g++7.1.0编译器,标准库源代码也是这个版本的。
一直以来,我们每次使用cout输出数据的时候,如果要换行,都知道使用形如cout << endl;
这样的形式,那么endl到底是什么呢,它是怎么样实现输出一个换行符的功能的,以前我没有思考过,但现在我想弄懂它,下面就一起看一下吧。
1.endl操作符的实现
在标准库头文件<ostream>
中,我找到了endl的操作符重载函数,如下:
template<typename _CharT, typename _Traits>
inline basic_ostream<_CharT, _Traits>&
endl(basic_ostream<_CharT, _Traits>& __os)
{ return flush(__os.put(__os.widen('\n'))); }
它是一个内联的函数模板,是一个全局的函数模板,endl正是它的函数名,它的作用是往缓冲区写入一个换行符并且立即从缓冲区刷新到外部设备中。
那么endl是怎么与<<操作符关联起来的呢,我们在ostream头文件ostream类的声明中又发现了以下代码:
__ostream_type&
operator<<(__ostream_type& (*__pf)(__ostream_type&))
{
// _GLIBCXX_RESOLVE_LIB_DEFECTS
// DR 60. What is a formatted input function?
// The inserters for manipulators are *not* formatted output functions.
return __pf(*this);
}
这个操作符的入参是一个__ostream_type& (*__pf)(__ostream_type&)
类型的函数指针,这个类型与endl的类型是一致的,从而,我们现在知道了endl的实现过程。
与endl同样实现的总共是亲兄弟三个,他们类型一样,且都是对缓冲区进行操作,如下:
操作符 | 说明 |
---|---|
endl | 输出一个换行符到缓冲区,且即时刷新到外部设备 |
ends | 输出一个空字符到缓冲区 |
flush | 调用flush函数,把数据从缓冲区刷新到外部设备 |
2. 格式化操作符
说完endl的亲兄弟,接下来说一说它的堂兄弟们,那就是格式化操作符,在某些书籍上也叫做操纵算子,操纵算子用法与endl一样,也是形如cout << oct
这样的形式,但它不会对缓冲区直接进行操作,它是对后续的数据输出进行格式化,类似c语言的%d一样,且操纵算子的实现方式与endl类似,只是<<的返回类型与参数类型不一样而已,这里就不再多说。
操纵算子分为两类,一类是无参的,定义在ios_base.h头文件中,还有一类是有参的,定义在iomanip头文件中。
2.1 无参操纵算子
操纵算子 | 说明 |
---|---|
boolalpha | 针对bool类型变量,不是输出0和1,而是输出true或者false |
noboolalpha | boolalpha的反向操作 |
showbase | 在输出八进制或者十六进制的时候,加上0x这样的前缀,主要它要放在进制操作符的前面 |
noshowbase | showbase的反向操作 |
showpoint | 强制打印小数点 |
noshowpoint | showpoint的反向操作 |
showpos | 针对非负的数字,强制加上+号输出 |
noshowpos | showpos的反向操作 |
skipws | 它是一个输入类操作符,作用是在输入时跳过空格,这一点与不使用skipws时是一致的 |
noskipws | 这里主要是noskipws会改变>>的默认输入方式,会把空格,制表符等也读入 |
uppercase | 在输出十六进制这样的数据时,对里面的字母进行大写,注意它对输出字符类型或者字符串类型是不起作用的 |
nouppercase | uppercase的反向操作 |
unitbuf | 每次输出以后都刷新缓冲区 |
nounitbuf | unitbuf的反向操作 |
internal | 在设置了输出宽度的情况下,符号左对齐,值右对齐,中间使用空格填充 |
left | 在设置了输出宽度的情况下,输出整体左对齐,没有设置输出宽度,说对齐都是耍流氓 |
right | 在设置了输出宽度的情况下,输出整体右对齐,iostream流默认右对齐 |
dec | 十进制输出,对浮点数不起效果,只对整型有效果 |
hex | 十六进制输出,对浮点数不起效果,只对整型有效果 |
oct | 八进制输出,对浮点数不起效果,只对整型有效果 |
fixed | 定点十进制进行输出,默认输出6位小数位,小数位不足补0,超出的四舍五入,保留6位 |
scientific | 科学计数法输出 |
hexfloat | 十六进制形式输出浮点数 |
defaultfloat | 对浮点数输出恢复默认状态 |
一个使用案例如下:
#include <iostream>
using namespace std;
int main()
{
// Initializing the integer
double x = 10.2333336;
//将浮点数x以十六进制形式输出,且字母都为大写
cout << uppercase << hexfloat << x << endl;
cout.width(12);
double y = -11.222;
//取消指定的浮点数格式,按默认格式输出
cout << defaultfloat;
//符号左对齐,数值右对齐,中间填充空格
cout << internal << y << endl;
return 0;
}
输出结果如下:
0X1.47777806A1DABP+3
- 11.222
2.2 有参操纵算子
有参的操纵算子实际上是在无参的基础上实现的,是对无参操纵算子的补充,且对无参操纵算子的使用起到了简化的作用。
首先还是看一看有参操纵算子有哪些,如下:
操纵算子 | 参数类型 | 说明 |
---|---|---|
resetiosflags | ios_base::fmtflags,此类型是一个枚举类型,包含了上述的无参操纵算子,多的格式之间以单竖线分隔 | 输入输出都可使用,重置当前流的格式 |
setiosflags | ios_base::fmtflags | 输入输出都可使用,增加当前流的格式 |
setbase | int | 输入输出都可使用,设置进制,参数值可为8,10,16,如果是其他值则表示使用默认的 |
setfill | 无固定类型,是一个函数模板 | 输入输出都可使用,设定对齐时的填充字符,虽说是模板,但参数一般建议使用char类型 |
setprecision | int | 输入输出都可使用,设置精度,注意默认情况下这里的精度并不是指小数位,而是包含整数位在内,总共可以显示多少位数字,但是如果事先使用fixed指定了的话,那该精度就是单指小数位了 |
setw | int | 输入输出都可使用,设置宽度 |
get_money | 有两个参数,第一个参数是一个函数模板,但根据iomanip头文件,它应该是一个long double类型或者string类型,此时该参数其实是一个出参,输入的数据存储在该参数里面,第二个参数是一个bool类型,表示是否国际化 | 输入使用,根据设置的区域文化和编码以及输入的对应的货币表达式,获取相应的数据 |
put_money | 有两个参数,第一个参数是一个函数模板,但根据iomanip头文件,它应该是一个long double类型或者string类型,第二个参数是一个bool类型,表示是否国际化 | 输出使用,根据设置的区域文化和编码,输出相应的货币表达式 |
put_time | 第一个参数是const std::tm类型指针,第二个类型是对时间进行格式化的格式字符串 | 根据第二个参数指定格式输出tm中数据 |
get_time | 第一个参数是const std::tm类型指针,第二个类型是对时间进行格式化的格式字符串 | 根据第二个参数指定格式把数据填充到tm中 |
带参数的这些操作函数,前面6个其实是比较好理解的,但是后面四个用起来就比较麻烦了,而且单独使用也是不起作用的,下面我们就后面四个操作符,看一下使用案例,如下:
#include <iomanip>
#include <iostream>
#include <time.h>
#include <sstream>
using namespace std;
void test01()
{
locale loc_de = locale("en_US.utf-8");
cout.imbue(loc_de);
const string str("720000000000");
cout << put_money(str) << endl;
string str2;
cin.imbue(loc_de);
cin >> get_money(str2);//这里要按照imbue设置的区域和编码进行输入,形如:72,000,000
cout << "str2=" << str2 << endl;
time_t t;
time(&t);
tm *tmp = localtime(&t);
cout << put_time(tmp, "%y %a") << endl;
tm time1;
istringstream iss("15:12:00 2021");
iss >> get_time(&time1, "%H:%M:%S %Y");
cout << "hour:" << time1.tm_hour << ',' << "min:" << time1.tm_min << ',' << "sec:" << time1.tm_sec << endl;
}
int main()
{
test01();
return 0;
}
输出显示如下:
[root@mylinux ~]# ./a.out
7,200,000,000.00
12,00,00 #注意这里是屏幕输入的
str2=120000
21 Thu
hour:15,min:12,sec:0
[root@mylinux ~]#
后面四个函数的使用就涉及到程序国际化以及区域文化的问题,比如浮点数,在我们大中国是72000.12,那么到了美国可能又是用72,000.12来表示,关于区域文化,这里就不展开说明了。