【STL】iostream 编程:使用插入运算符并控制格式


本文介绍如何使用插入运算符、如何控制输出格式,以及如何为自定义类重载插入运算符。


目录

  • [1 概述](#1 概述)
  • [2 输出宽度](#2 输出宽度)
    • [2.1 width 示例(默认空格填充)](#2.1 width 示例(默认空格填充))
    • [2.2 width 示例(自定义填充字符)](#2.2 width 示例(自定义填充字符))
    • [2.3 setw 示例](#2.3 setw 示例)
    • [2.4 规则](#2.4 规则)
    • [2.5 综述](#2.5 综述)
  • [3 对齐方式](#3 对齐方式)
    • [3.1 示例](#3.1 示例)
    • [3.2 常用对齐标识](#3.2 常用对齐标识)
    • [3.3 简化写法(C++标准操纵符,无需 setiosflags)](#3.3 简化写法(C++标准操纵符,无需 setiosflags))
  • [4 精度控制](#4 精度控制)
    • [4.1 示例1(无标记)](#4.1 示例1(无标记))
    • [4.2 示例2(定点标记)](#4.2 示例2(定点标记))
    • [4.3 示例3(科学计数法)](#4.3 示例3(科学计数法))
    • [4.4 规则](#4.4 规则)
    • [4.5 简化写法](#4.5 简化写法)
  • [5 进制基数](#5 进制基数)
    • [5.1 示例(大小写控制)](#5.1 示例(大小写控制))
    • [5.2 规则](#5.2 规则)
  • [6 带引号字符串操纵符(C++14)](#6 带引号字符串操纵符(C++14))

1 概述

插入运算符 << 已预先为所有 C++标准数据类型实现,作用是将字节数据写入输出流对象 。插入运算符可配合预定义的流操纵符使用,操纵符能够修改整数的默认输出格式。

你可以通过以下四类配置控制输出样式:

  1. 输出宽度

    • setw(n),设置单次输出占位字符长度;
  2. 对齐方式

    • left / right / internal,左对齐、右对齐、符号内对齐;
  3. 浮点数精度

    • setprecision(n),控制小数保留位数或有效数字;
  4. 数值进制基数

    • dec(十进制)、hex(十六进制)、oct(八进制);
      说明
  5. 插入运算符 <<

    • 内置支持 int、double、string、bool 等基础类型;自定义结构体 / 类需要自己重载 operator<< 才能直接用 cout << obj。
  6. 操纵符 manipulator

    • 如 setw、left、fixed、hex 这类,写在 << 中间,全局修改流的打印规则。

2 输出宽度

想要对齐输出内容,可以通过两种方式为每个输出项指定字段宽度:

  • 在流中插入 setw 操纵符;
  • 调用流的 width 成员函数;

2.1 width 示例(默认空格填充)

下面示例将所有数值右对齐,每个字段至少占用 10 个字符宽度:

cpp 复制代码
// output_width.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;

int main( )
{
   double values[] = { 1.23, 35.36, 653.7, 4358.24 };
   for( int i = 0; i < 4; i++ )
   {
      cout.width(10);
      cout << values[i] << '\n';
   }
}

运行结果:

可以看到,在输出结果中,长度不足10个字符的数值,左侧会自动填充空格

2.2 width 示例(自定义填充字符)

如果需要自定义填充字符,使用 fill 成员函数设置填充符号,默认填充字符为空格。

修改上面循环,使用星号填充空白区域:

cpp 复制代码
// output_width.cpp
// compile with: /EHsc
#include <iostream>
using namespace std;

int main()
{
    double values[] = { 1.23, 35.36, 653.7, 4358.24 };
    for (int i = 0; i < 4; i++)
    {
        cout.width(10);
        cout.fill('*');
        cout << values[i] << endl;
    }
}

运行结果:

注:操纵符 endl 等价于换行符 '\n' 并刷新缓冲区。

2.3 setw 示例

同一行打印多组数据时,推荐使用 setw 操纵符连续设置宽度

下述示例中名字占用7字符宽度,数值占用10字符宽度:

cpp 复制代码
// setw.cpp
// compile with: /EHsc
#include <iostream>
#include <iomanip>
using namespace std;

int main( )
{
   double values[] = { 1.23, 35.36, 653.7, 4358.24 };
   const char *names[] = { "Zoot", "Jimmy", "Al", "Stan" };
   for( int i = 0; i < 4; i++ )
      cout << setw( 7 )  << names[i]
           << setw( 10 ) << values[i] << endl;
}

运行结果:

width 成员函数定义在头文件<iostream>;带参数的操纵符 setw 必须引入 <iomanip>。

2.4 规则

  1. setw 和 width 不会截断输出内容。如果数据实际长度超过设定宽度,完整数值照常输出,仅受精度设置约束。
  2. setw、width 仅对紧随其后的下一个输出字段生效,打印完一项后宽度自动恢复默认自适应模式。
  3. 其余格式化配置(填充字符、精度、进制等)一旦设置会持续生效,必须手动修改才会改变

2.5 综述

  1. width() 与 setw() 区别
    • cout.width(n):成员函数,单独调用,一次只控制后一个输出;仅需 <iostream>;
    • setw(n):链式操纵符,写在 << 中间,适合一行多列排版;必须引入 <iomanip>;
  2. fill() 持久生效
    • cout.fill('*') 设置后永久有效,不会像 width 一样一次性失效,直到再次调用 fill 修改;
  3. 不会截断是核心特性
    • 宽度仅用来预留空位对齐,不是限制最大输出长度;
    • 数字、字符串过长会完整打印,不会被截断;

3 对齐方式

  • 通过操纵符 setiosflags 搭配枚举常量 ios::left 开启左对齐。该枚举定义在 ios 类中,使用时必须加上 ios:: 作用域前缀;
  • 操纵符 resetiosflags 用于关闭左对齐标记;
  • 和 width、setw 只生效一次不同,setiosflags / resetiosflags 设置的格式标记会永久保持,直到手动修改。

3.1 示例

输出流默认采用右对齐。如果想要把上一示例中的名称左对齐、数字保持右对齐,可以修改循环代码如下:

cpp 复制代码
// setw.cpp
// compile with: /EHsc
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    double values[] = { 1.23, 35.36, 653.7, 4358.24 };
    const char* names[] = { "Zoot", "Jimmy", "Al", "Stan" };
    for (int i = 0; i < 4; i++)
        cout << setiosflags(ios::left)
        << setw(6) << names[i]
        << resetiosflags(ios::left)
        << setw(10) << values[i] << endl;
}

运行结果:

3.2 常用对齐标识

  • ios::left:左对齐,空白填充在右侧;
  • ios::right:默认右对齐,空白填充在左侧;
  • ios::internal:符号左对齐、数字右对齐(仅对数值生效)。

3.3 简化写法(C++标准操纵符,无需 setiosflags)

left / right 是简化版操纵符,底层等价于 setiosflags,同样持久生效。

cpp 复制代码
// setw.cpp
// compile with: /EHsc
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    double values[] = { 1.23, -35.36, +653.7, -4358.24 };
    const char* names[] = { "Zoot", "Jimmy", "Al", "Stan" };
    for (int i = 0; i < 4; i++)
        cout << left << setw(6) << names[i]
        << right << setw(10) << values[i] << endl;
}

4 精度控制

浮点数默认精度为6位有效数字。例如数值3466.9768默认输出为3466.98。

可以通过 setprecision 操纵符修改浮点数打印规则,搭配两个关键格式标记:

  • fixed:定点模式,示例数字输出为 3466.976800;
  • scientific:科学计数法,示例数字输出为 3466977e+003;

4.1 示例1(无标记)

沿用对齐小节的案例,要求浮点数只保留1位有效数字,修改循环代码:

cpp 复制代码
// setw.cpp
// compile with: /EHsc
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    double values[] = { 1.23, 35.36, 653.7, 4358.24 };
    const char* names[] = { "Zoot", "Jimmy", "Al", "Stan" };
    for (int i = 0; i < 4; i++)
        cout << setiosflags(ios::left)
        << setw(6)
        << names[i]
        << resetiosflags(ios::left)
        << setw(10)
        << setprecision(1)
        << values[i]
        << endl;
}

运行结果:

setprecision(1)代表小数点后保留1位

4.2 示例2(定点标记)

如果不想自动启用科学计数法,在循环前开启定点标记:

cpp 复制代码
// setw.cpp
// compile with: /EHsc
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    double values[] = { 1.23, 35.36, 653.7, 4358.24 };
    const char* names[] = { "Zoot", "Jimmy", "Al", "Stan" };
    cout << setiosflags(ios::fixed);     //开启定点标记
    for (int i = 0; i < 4; i++)
        cout << setiosflags(ios::left)
        << setw(6)
        << names[i]
        << resetiosflags(ios::left)
        << setw(10)
        << setprecision(1)
        << values[i]
        << endl;
}

运行结果:

4.3 示例3(科学计数法)

把标记换成 ios::scientific 则强制使用科学计数法,同样保留1位小数:

cpp 复制代码
// setw.cpp
// compile with: /EHsc
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
    double values[] = { 1.23, 35.36, 653.7, 4358.24 };
    const char* names[] = { "Zoot", "Jimmy", "Al", "Stan" };
    cout << setiosflags(ios::scientific);   //开启科学计数法
    for (int i = 0; i < 4; i++)
        cout << setiosflags(ios::left)
        << setw(6)
        << names[i]
        << resetiosflags(ios::left)
        << setw(10)
        << setprecision(1)
        << values[i]
        << endl;
}

运行结果:

4.4 规则

  1. 开启 ios::fixed / ios::scientific 任意一种:setprecision(n) = 小数点后保留 n 位数字
  2. 两种标记都不开启(默认浮点模式):setprecision(n) = 整个数值的有效数字总位数
  3. setiosflags 开启的定点 / 科学计数法标记会持续生效,需要用 resetiosflags 清除
  4. 自动切换科学计数:默认模式下,如果数值量级过大/过小,流会自动切换成科学计数法来满足有效数字要求,定点模式则始终保留小数点,不会自动切换

4.5 简化写法

无需 setiosflags,可直接使用标准操纵符 fixed / scientific:

cpp 复制代码
cout << fixed << setprecision(2);
cout << scientific << setprecision(3);

5 进制基数

操纵符 dec(十进制)、oct(八进制)、hex(十六进制) 用于设置输入、输出整数时默认的进制。

  • 例如向输出流插入 hex 操纵符后,流会自动把整数的内存二进制值转换成十六进制文本输出。
  • 若未开启 uppercase 格式标记(默认状态),十六进制里的字母 a~f 会以小写展示
  • 如果开启 uppercase,则字母变为大写 A~F

流默认的进制为 dec,也就是十进制。

5.1 示例(大小写控制)

cpp 复制代码
// setw.cpp
// compile with: /EHsc
#include <iostream>
#include <iomanip>
using namespace std;

int main()
{
	int num = 15;
	cout << hex << num << endl;      // 输出 f
	cout << hex << uppercase << num << endl; // 输出 F
}

运行结果

5.2 规则

  1. 三个进制操纵符作用:
    • dec:十进制(默认)
    • oct:八进制
    • hex:十六进制
      三者均为持久生效,设置后一直保持,直到切换为其他进制
  2. 进制标记、uppercase 标记全局持久

6 带引号字符串操纵符(C++14)

将字符串写入流后,直接调用 stringstream::str() 可以完整取回原始字符串。但如果后续想用提取运算符 >> 把流内容读取到新字符串,结果会不符合预期 ------因为 >> 默认遇到第一个空白字符就停止读取。

示例代码如下:

cpp 复制代码
std::stringstream ss;
std::string inserted = "This is a sentence.";
std::string extracted;

ss << inserted;
ss >> extracted;

std::cout << inserted;     //  This is a sentence.
std::cout << extracted;    //  This

虽然可以手动处理该问题,但为了更方便地实现字符串"写入-读出"无损往返,C++14在头文件 <iomanip> 中新增了 std::quoted 流操纵符。

  • 写入流时:quoted() 会用分隔符(默认双引号")包裹整个字符串,内部自带的引号会用转义符 \ 转义;

  • 读取流时:会持续读取所有字符,直到匹配到末尾的分隔符为止;
    分隔符仅存在于流缓冲区中:

  • 使用 >> 提取字符串时,首尾引号会被自动剔除,内部转义符还原;

  • 调用 basic_stringstream::str() 获取流内部完整文本时,引号与转义符都会保留;
    插入、提取操作对空白字符的处理逻辑和代码里字符串的书写形式无关,因此无论输入是普通字符串还是原始字符串字面量,quoted 都适用。原始字符串内自带的引号、换行、制表符等内容,都能被 quoted() 完整保留。

相关推荐
aaPIXa6222 小时前
C++ TensorRT Edge-LLM 边缘推理框架:从原理到实战
开发语言·c++·edge
nianniannnn4 小时前
c++复习自存--标准模板库STL
开发语言·c++·windows
努力努力再努力wz4 小时前
【高性能网络库与HTTP Server系列】:基于主从 Reactor 模型实现高性能 C++ 网络库与 HTTP Server
开发语言·网络·数据结构·数据库·c++·网络协议·http
Robot_Nav4 小时前
C++ 面试常问问题汇总:基础语法、面向对象、STL、多线程与设计模式
c++·设计模式·面试
盐焗鹌鹑蛋5 小时前
【C++】二叉搜索树
c++
哎呦,帅小伙哦5 小时前
C++工程实战: 预处理宏、宏重定义避坑、LOG_TAG业务实践与__COUNTER__深度解析
c++
IVVi0jToe5 小时前
高效C++线程池设计与实现
java·c++·安全
^yi5 小时前
【C++】内存管理
开发语言·c++
云栖梦泽6 小时前
从 IMX415 摄像头驱动理解 Media Controller 框架
linux·开发语言·c++·嵌入式硬件