曲径通幽论坛

 找回密码
 立即注册
搜索
查看: 3260|回复: 3
打印 上一主题 下一主题

setf, unsetf, flags 与 I/O 数据格式化

[复制链接]

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
跳转到指定楼层
楼主
发表于 2011-11-27 01:20:55 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
一般情况下,程序中输入或输出信息都是使用默认的格式。但是我们也可以用 ios 的成员函数或者是使用“操控符” 来精确控制 I/O 数据格式。

每个流都有一组格式标志位来控制信息的格式。在类 ios 中定义了一个位掩码枚举类型 fmtflags ,这些枚举值定义在 ios 的基类 -- 类 ios_base 中。比如在 linux 中,这个值定义在 /usr/include/c++/4.4.3/bits/ios_base.h 中,且定义如下:
[C++] 纯文本查看 复制代码
  enum _Ios_Fmtflags
    {
      _S_boolalpha      = 1L << 0,
      _S_dec            = 1L << 1,
      _S_fixed          = 1L << 2,
      _S_hex            = 1L << 3,
      _S_internal       = 1L << 4,
      _S_left           = 1L << 5,
      _S_oct            = 1L << 6,
      _S_right          = 1L << 7,
      _S_scientific     = 1L << 8,
      _S_showbase       = 1L << 9,
      _S_showpoint      = 1L << 10,
      _S_showpos        = 1L << 11,
      _S_skipws         = 1L << 12,
      _S_unitbuf        = 1L << 13,
      _S_uppercase      = 1L << 14,
      _S_adjustfield    = _S_left | _S_right | _S_internal,
      _S_basefield      = _S_dec | _S_oct | _S_hex,
      _S_floatfield     = _S_scientific | _S_fixed,
      _S_ios_fmtflags_end = 1L << 16
    };

我们就使用这些枚举值来设置或清除格式标志位。

skipws 被设置时,在从流输入的时候,开头的空白字符(包括空格符、制表符以及换行符)将被忽略;当标志被清除时,则不会忽略这些字符。

当 left 标志位被设置时,输出格式左对齐。当 right 被设置时,输出格式右对齐。

internal 被设置时,如果输出的是一个数值,那么数值的数字字符之间将用空格填充以进行对齐。如果这些标志位都没有被设置,那么默认情况下使用右对齐。

默认情况下,数值以十进制形式输出。如果设置 oct ,那么数值将以八进制形式输出。如果设置 hex ,那么数值将以十六进制形式输出。如果要重新使用默认值,那么可以设置标志位 dec 。

如果设置了 showbase,那么在输出数值的同时还输出数值的基数。例如,如果是指的基数是十六进制,那么数值 1F 将输出为 0x1F 。

在显示数值的科学计数法时,字母 e 是小写的。同样,在输出十六进制时,字符 x 也是小写的。当标志位 uppercase 被设置时,这些符号将以大写格式输出。

如果设置 showpos ,那么在输出正数时会再数值前面附加加号。

如果设置 showpoint ,那么在输出浮点数时,将会输出小数点和末尾的 0,无论是否需要。

如果设置了 scientific ,那么浮点数将以科学计数法格式输出。

如果设置了 fixed,那么浮点数将以普通格式输出。如果这两个标志都没有设置,那么编译器自动选择合适的格式输出浮点数。

如果设置了 unitbuf,那么在每次插入操作完成后,缓冲区中的内容将被刷新。

如果设置了 boolalpha ,那么布尔值将使用关键字 true 或 false 进行输入输出。



我们使用函数 setf() 来设置标志位,改函数是 ios 的成员函数,其通用形式如下:
fmtflags setf(fmtflags flags);
改函数将返回流以前的格式标志位,并将 flags 指定的标志位设为流当前的输出格式标志位。比如如果想设置 showbase 标志位,那么可使用下面语句:
stream.setf(ios::showbase)
其中,stream 表示需要设置标志位的流对象。这里使用了 ios:: 来指定 showbase 所在的作用域。因为 showbase 是在类 ios 中定义的枚举常量值,所以在使用它的时候必须在前面加上 ios 来限定它的作用域。

下面程序演示了使用 setf() 来设置 showpos 和 scientific 标志的情况:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;


int main()
{
        cout.setf(ios::showpos);
        cout.setf(ios::scientific);


        cout << 123 << " " << 123.23 << endl;


        return 0;
}

运行输出:
./setf
+123 +1.232300e+02
如果需要同时设置多个标志位,那么可使用 OR 将这些标志位连接起来,比如:
cout.setf(ios::scientific | ios::showpos);

相对应的,可以使用 unsetf() 函数来清除标志位,其原型如下:
void unsetf(fmtflags flags);
其中,flags 指定的标志位将被清除(对其他的标志位没有影响)。

又是我们需要的只是流当前的标志位,在这种情况下,可以使用函数 flags() ,函数原型如下:
fmtflags flags();
该函数将返回流的当前标志位。

下面演示了 flags() 和 unsetf() 函数的用法:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;


void showflags(ios::fmtflags f);


int main()
{
        ios::fmtflags f;


        f = cout.flags();


        showflags(f);
        cout.setf(ios::showpos);
        cout.setf(ios::scientific);


        f = cout.flags();
        showflags(f);


        cout.unsetf(ios::scientific);


        f = cout.flags();
        showflags(f);


        return 0;
}


void showflags(ios::fmtflags f)
{
        long i;
        for (i = 0x4000; i; i = i >> 1)
                if (i & f) cout << "1 ";
                else cout << "0 ";


        cout << "\n";
}

运行输出:
./unsetflags
0 0 1 0 0 0 0 0 0 0 0 0 0 1 0
0 0 1 1 0 0 1 0 0 0 0 0 0 1 0
0 0 1 1 0 0 0 0 0 0 0 0 0 1 0
编译器不同,上面程序的输出结果也可能不同。

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
沙发
 楼主| 发表于 2011-11-27 10:00:45 | 只看该作者

设置输出域的宽度,精度以及填充字符

除了可以设置数值的输出格式外,还可以设置输出数值的宽度,精度以及填充字符等。实现这些功能有下面几个函数:
streamsize width(streamsize len);
char fill(char ch);
streamsize precision(streamsize num);

width() 设置输出域的宽度为 len ,并返回当前输出域的宽度。

fill() 设置当前的填充字符为 ch,并返回当前的填充字符。填充字符的作用是:如果将要输出的数值宽度小于当前输出域的宽度,那么将一定数量的填充字符插入到数值中以使输出的字符数等于输出域的宽度。

precision() 设置小数位数为 num,并返回小数点的小数位数(默认情况下,小数点后面有 6 位小数)。

streamsize 只是整数的某种形式。

下面程序测试了上面 3 个函数的用法:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

int main()
{
    cout.setf(ios::showpos);
    cout.setf(ios::scientific);
    cout << 123 << " " << 123.23 << "\\n";

    cout.precision(2);    //在小数点后输出两位
    cout.width(10);        //输出宽度为10个字符
    cout << 123 << " ";
    cout.width(10);        //输出宽度为10个字符
    cout << 123.23 << endl;

    cout.fill('#');
    cout.width(10);        //在10个字符宽的输出域中使用'#'字符填充
    cout << 123 << " ";
    cout.width(10);        //输出宽度为10个字符
    cout << 123.23 << endl;

    return 0;
}

运行输出:
./width
+123 +1.232300e+02
      +123  +1.23e+02
######+123 #+1.23e+02
在某些情况下,需要在每次输出之前重新设置输出域的宽度。这就是为什么上面程序反复调用 width() 的原因。

上面的 width(), precision() 和 fill() 也有返回但不修改当前设置的重载形式:
char fill();
streamsize width();
streamsize precision();
由上可见,他们都不带有参数。

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
板凳
 楼主| 发表于 2011-11-27 11:51:22 | 只看该作者

I/O 操控符

像我们在 cout 输出换行时使用的 endl 就是 I/O 操控符 --- 它们也是一种特殊的函数,使用这种函数也可以修改流中的参数格式。下表列出了标准的操控符函数:
操控符函数用途
输入/输出
boolalpha设置boolaphal表置位
输入/输出
dec 设置 dec 标志位
输入/输出
endl输出换行符并刷新流的缓冲区
输出
ends输出空字符
输出
fixed设置 fixed 标志位
输出
flush刷新流的缓冲区
输出
hex设置 hex 标志位
输入/输出
internal设置 internal 标志位
输出
left设置 left 标志位
输出
noboolalpha清除 boolaphal 标志位
输入/输出
noshowbase清除 showbase 标志位
输出
noshowpoint清除 showpoint 标志位
输出
noshowpos清除 showpos 标志位
输出
noskipws清除 skipws 标志位
输入
nounitbuf清除 unitbuf 标志位
输出
nouppercase清除 uppercase标志位
输出
oct设置 oct 标志位
输入/输出
resetiosflags(fmtflags f)清除由 f 指定的标志位
输入/s输出
right设置 right 标志位
输出
scientific设置 scientific 标志位
输出
setbase(int base)
将数值的基数设为 base
输入/输出
setfill(int ch)
将填充字符设为 ch
输出
setiosflags(fmtflags f)
设置由 f 指定的标志位
输入/输出
setprecision(int p)
将数值的精度设为 p
输出
setw(int w)
将输出域的宽度设为w
输出
showbase设置 showbase 标志位
输出
showpos设置 showpos 标志位
输出
skipws设置 skipws 标志位
输入
unitbuf
设置 unitbuf 标志位
输出
uppercase
设置 uppercase 标志位
输出
ws
忽略开头的空白
输入


如果程序中使用这些带参数的操控符函数,那么就要包含头文件 <iomanip>

下面程序使用了操控符函数来控制输出的格式:
[C++] 纯文本查看 复制代码
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    cout <<setprecision(4) << 100.243 << endl;
    cout << setw(20) << "Hello world.";

    cout << endl;
    
    return 0;
}

运行输出:
./ioman
100.2
             Hello world.
注意,上面的操控符函数不带参数时,操控符后面并没有圆括号,如 endl 。

下面程序使用 setiosflags() 来设置标志位 scientific 和 showpos :
[C++] 纯文本查看 复制代码
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    cout << setiosflags(ios::showpos);
    cout << setiosflags(ios::scientific);
    cout << 123 << " " << 123.23;

    return 0;
}

运行输出:
./setios
+123 +1.232300e+02

下面程序使用 ws 来忽略输入 s 中的字符串开头的空白部分:
[C++] 纯文本查看 复制代码
#include <iostream>
using namespace std;

int main()
{
    char s[80];

    cin >> ws >> s;
    cout << s;

    return 0;
}

运行输出:
./ws
    world
world

4917

主题

5879

帖子

3万

积分

GROAD

曲径通幽,安觅芳踪。

Rank: 6Rank: 6

积分
34382
地板
 楼主| 发表于 2011-11-27 13:37:13 | 只看该作者

自定义操控符函数

我们也可以创建自己的操控符函数。创建不带参数的操控符函数要比带参数的要简单。创建无参数的输出操作父函数的代码框架如下:
ostream &manip_name(ostream &stream)
{
   //自己的代码
   return stream;
}
上面,需要重点理解的是:尽管在操控符函数的定义中带有一个指针参数(指向控制符所要控制的流对象),但是在输出表达式中使用操控符函数时不需要指定参数。

下面程序创建一个操控符函数 setup(),该函数设置了左对齐的标志位,将输出域的宽度为 10 ,并将美元符号作为填充字符:
[C++] 纯文本查看 复制代码
#include <iostream>
#include <iomanip>
using namespace std;

ostream &setup(ostream &stream)
{
    stream.setf(ios::left);
    stream << setw(10) << setfill('$');

    return stream;
}

int main()
{
    cout << 10 << " " << setup << 10;

    return 0;
}

运行输出:
./sefman
10 10$$$$$$$$
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|曲径通幽 ( 琼ICP备11001422号-1|公安备案:46900502000207 )

GMT+8, 2024-5-17 23:18 , Processed in 0.066128 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表