第一部分:C++流类库概述与体系结构
C++没有内置的输入/输出语句,而是通过一个强大的I/O流类库 来实现。这个库的核心思想是"流" ------ 一种在数据的生产者(如键盘、文件)和消费者(如程序、屏幕、文件)之间建立的、管理数据流动的抽象。
1. 核心流类体系
整个流类体系是一个派生类体系。下面是主要的类及其关系:
ios_base (抽象基类,包含格式状态和操作)
|
basic_ios<> (模板基类,管理流缓冲区)
/ \
/ \
/ \
basic_ostream<> (输出流) basic_istream<> (输入流)
| |
(插入操作 <<) (提取操作 >>)
| |
------------------- -------------------
| |
basic_ofstream<> basic_ifstream<>
(文件输出流) (文件输入流)
| |
------------------- -------------------
| |
basic_ostringstream<> basic_istringstream<>
(字符串输出流) (字符串输入流)
| |
------------------------------------
|
basic_stringstream<> / basic_fstream<>
(字符串输入输出流) / (文件输入输出流)
重要具体类解释(对应常用类型别名):
-
ios: 通常指ios_base或basic_ios,是所有流类的抽象根基,定义了格式控制、状态标志等公共接口。 -
ostream(std::ostream) : 通用输出流类。cout、clog就是它的对象。 -
istream(std::istream) : 通用输入流类。cin就是它的对象。 -
iostream: 多重继承自istream和ostream,用于需要同时进行输入输出的场景。 -
ofstream: 专用于文件输出的类。 -
ifstream: 专用于文件输入的类。 -
fstream: 专用于文件输入输出的类。 -
ostringstream: 用于向内存中的字符串写入数据。 -
istringstream: 用于从内存中的字符串读取数据。 -
stringstream: 用于对内存中的字符串进行读写。 -
streambuf: 流缓冲区类 。这是一个关键但常被忽略的类。ios类内部包含一个指向streambuf的指针。它负责底层字符序列的实际读写、缓冲管理。文件操作对应filebuf,字符串操作对应stringbuf。
四个预定义的全局流对象:
cpp
#include <iostream>
std::cin; // 标准输入,对应键盘,带缓冲的istream对象
std::cout; // 标准输出,对应屏幕,带缓冲的ostream对象
std::cerr; // 标准错误输出,对应屏幕,无缓冲(立即输出)
std::clog; // 标准日志输出,对应屏幕,带缓冲
运算符重载:
-
operator <<: 插入运算符 ,用于输出。cout << data; -
operator >>: 提取运算符 ,用于输入。cin >> data;
第二部分:流的格式化输入/输出控制
控制数据如何显示(如进制、宽度、精度、对齐)是I/O操作的核心。主要有三种方式。
2.1 方式一:使用 ios 基类的成员函数
ios类内部通过一组标志位(flags) 和成员变量来控制格式。
主要格式标志位(ios::fmtflags类型):
这些标志在ios_base中定义为静态常量或枚举值。
cpp
// 进制控制
ios::dec // 十进制 (默认)
ios::oct // 八进制
ios::hex // 十六进制
ios::showbase // 显示进制前缀(0, 0x)
// 浮点数显示
ios::fixed // 固定小数格式
ios::scientific // 科学计数法格式
ios::showpoint // 总是显示小数点
ios::showpos // 非负数显示 + 号
// 对齐与填充
ios::left // 左对齐
ios::right // 右对齐 (默认)
ios::internal // 符号左对齐,数值右对齐(用于数字)
// 其他
ios::skipws // 输入时跳过空白字符 (默认)
ios::uppercase // 十六进制输出使用大写字母,科学计数法输出'E'
ios::boolalpha // 将bool值输出为 true/false 而非 1/0
// 刷新控制
ios::unitbuf // 每次输出后立即刷新缓冲区
相关成员函数:
cpp
// 1. 获取/设置全部标志字
fmtflags flags() const; // 获取当前所有格式标志
fmtflags flags(fmtflags fmtfl); // 设置新标志,返回旧标志
// 2. 设置特定标志位
fmtflags setf(fmtflags fmtfl); // 添加(开启)标志位
fmtflags setf(fmtflags fmtfl, fmtflags mask); // 在mask指定的区域中设置fmtfl
// mask 常用值: ios::basefield (dec|oct|hex), ios::adjustfield (left|right|internal), ios::floatfield (scientific|fixed)
// 3. 清除特定标志位
void unsetf(fmtflags fmtfl);
// 4. 控制域宽、填充字符、浮点精度
streamsize width() const; // 获取当前域宽
streamsize width(streamsize wide); // 设置域宽,仅对下一次IO有效
char fill() const; // 获取当前填充字符
char fill(char fillch); // 设置填充字符
streamsize precision() const; // 获取当前浮点数精度
streamsize precision(streamsize prec); // 设置浮点数精度
示例:使用成员函数控制格式
cpp
#include <iostream>
int main() {
int num = 255;
double pi = 3.1415926535;
// 设置十六进制并显示前缀
std::cout.setf(std::ios::hex | std::ios::showbase | std::ios::uppercase);
std::cout << num << std::endl; // 输出: 0xFF
// 清除十六进制,恢复十进制
std::cout.unsetf(std::ios::hex | std::ios::showbase | std::ios::uppercase);
std::cout.setf(std::ios::dec);
std::cout << num << std::endl; // 输出: 255
// 设置域宽、填充、左对齐
std::cout.width(10);
std::cout.fill('*');
std::cout.setf(std::ios::left);
std::cout << num << std::endl; // 输出: 255*******
// 设置浮点数精度和格式
std::cout.precision(5);
std::cout.setf(std::ios::fixed);
std::cout << pi << std::endl; // 输出: 3.14159
return 0;
}
2.2 方式二:使用标准流操纵符(Manipulators)
这是更简洁、更常用的方式。操纵符是特殊的函数或对象,可以直接用在<<或>>运算符链中。
非参数化操纵符(定义在<ios>或<iostream>中):
cpp
// 进制
std::dec, std::oct, std::hex
// 显示控制
std::showbase, std::noshowbase
std::uppercase, std::nouppercase
std::showpoint, std::noshowpoint
std::showpos, std::noshowpos
std::boolalpha, std::noboolalpha
// 对齐
std::left, std::right, std::internal
// 浮点格式
std::fixed, std::scientific, std::defaultfloat (恢复默认)
// 刷新与跳过空白
std::endl // 插入换行并刷新缓冲区
std::flush // 仅刷新缓冲区
std::ws // 输入时跳过空白字符 (用于cin >> std::ws;)
示例:使用非参数化操纵符
cpp
#include <iostream>
int main() {
int num = 255;
bool flag = true;
std::cout << std::hex << std::showbase << std::uppercase << num << std::endl; // 0xFF
std::cout << std::boolalpha << flag << std::noboolalpha << std::endl; // true
std::cout << std::dec << num << std::endl; // 255
return 0;
}
2.3 方式三:使用参数化流操纵符(需包含<iomanip>)
这些操纵符可以接受参数,功能更强大。
cpp
#include <iomanip>
std::setw(int n) // 设置域宽 (类似width(),只影响下一次IO)
std::setfill(char c) // 设置填充字符
std::setprecision(int n)// 设置浮点数精度
std::setbase(int b) // 设置整数进制 (b=8,10,16)
// C++98 之后还有 setiosflags, resetiosflags 等,但不如直接使用标志方便
示例:综合使用参数化操纵符
cpp
#include <iostream>
#include <iomanip>
int main() {
int num = 12345;
double money = 1234.567;
std::cout << std::setw(15) << std::setfill('_') << std::left << num << std::endl; // 12345__________
std::cout << std::setw(12) << std::setfill(' ') << std::fixed << std::setprecision(2) << money << std::endl; // 1234.57
std::cout << std::setbase(16) << std::showbase << 100 << std::endl; // 0x64
return 0;
}
这部分我们涵盖了C++流类库的基本框架和格式化控制。核心要点:
-
理解流类体系的层次关系 ,特别是
istream、ostream及其文件/字符串特化版本。 -
掌握格式控制的三种方式 :成员函数、非参数操纵符、参数化操纵符。在代码中,最推荐使用操纵符(尤其是
<iomanip>中的),因为它们更清晰、更容易嵌入到输出链中。