活动公告

系统通知
06-22 18:10
系统通知
06-14 00:00
系统通知
通知:本站资源由网友上传分享,如有违规等问题请到版务模块进行投诉,资源失效请在帖子内回复要求补档,会尽快处理!
10-23 09:31

C++输出操作完全手册从基础到进阶学习格式化百分比输出流控制及错误排查解决实际问题提升编程技能实战案例

SunJu_FaceMall

3万

主题

3142

科技点

3万

积分

执行版主

碾压王

积分
32876

塔罗立华奏

执行版主 发表于 2025-9-11 13:10:00 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
引言

C++作为一种强大的编程语言,其输入输出系统提供了丰富而灵活的功能。无论是简单的控制台输出,还是复杂的格式化报表,C++的输出机制都能满足各种需求。本手册将全面介绍C++输出操作,从基础概念到进阶技巧,帮助读者掌握格式化输出、百分比输出、流控制以及错误排查等关键技能,并通过实战案例提升编程能力。

C++输出基础

cout对象简介

C++中,cout是ostream类的一个对象,它与标准输出设备(通常是控制台)相关联。cout定义在<iostream>头文件中,是C++标准库的一部分。使用cout进行输出操作前,需要包含相应的头文件:
  1. #include <iostream>
复制代码

基本输出操作

最基本的输出操作使用插入运算符(<<),它将右侧的数据插入到左侧的输出流中:
  1. #include <iostream>
  2. int main() {
  3.     std::cout << "Hello, World!" << std::endl;
  4.     std::cout << 42 << std::endl;
  5.     std::cout << 3.14159 << std::endl;
  6.     return 0;
  7. }
复制代码

在上面的例子中,std::endl是一个操纵符,它输出换行符并刷新输出缓冲区。

输出运算符(<<)的使用

输出运算符(<<)可以被链式使用,连续输出多个项目:
  1. #include <iostream>
  2. #include <string>
  3. int main() {
  4.     std::string name = "Alice";
  5.     int age = 30;
  6.     double height = 1.75;
  7.    
  8.     std::cout << "Name: " << name << ", Age: " << age << ", Height: " << height << "m" << std::endl;
  9.    
  10.     return 0;
  11. }
复制代码

这种链式使用方式非常灵活,可以混合输出不同类型的数据,C++会自动处理类型转换。

格式化输出

使用iomanip库

<iomanip>头文件提供了许多用于格式化输出的操纵符。要使用这些功能,需要包含该头文件:
  1. #include <iomanip>
复制代码

控制输出宽度、精度和填充字符

使用std::setw操纵符可以设置输出的最小宽度:
  1. #include <iostream>
  2. #include <iomanip>
  3. int main() {
  4.     std::cout << "Default width:" << std::endl;
  5.     std::cout << 42 << std::endl;
  6.     std::cout << "Set width to 10:" << std::endl;
  7.     std::cout << std::setw(10) << 42 << std::endl;
  8.     std::cout << "Set width to 10 with '*' as fill character:" << std::endl;
  9.     std::cout << std::setw(10) << std::setfill('*') << 42 << std::endl;
  10.    
  11.     return 0;
  12. }
复制代码

对于浮点数,可以使用std::setprecision操纵符设置精度:
  1. #include <iostream>
  2. #include <iomanip>
  3. int main() {
  4.     double pi = 3.14159265358979323846;
  5.    
  6.     std::cout << "Default precision:" << std::endl;
  7.     std::cout << pi << std::endl;
  8.    
  9.     std::cout << "Set precision to 5:" << std::endl;
  10.     std::cout << std::setprecision(5) << pi << std::endl;
  11.    
  12.     std::cout << "Set precision to 10:" << std::endl;
  13.     std::cout << std::setprecision(10) << pi << std::endl;
  14.    
  15.     return 0;
  16. }
复制代码

使用std::setfill操纵符可以设置填充字符:
  1. #include <iostream>
  2. #include <iomanip>
  3. int main() {
  4.     std::cout << "Default fill:" << std::endl;
  5.     std::cout << std::setw(10) << 42 << std::endl;
  6.    
  7.     std::cout << "Set fill to '*':" << std::endl;
  8.     std::cout << std::setw(10) << std::setfill('*') << 42 << std::endl;
  9.    
  10.     return 0;
  11. }
复制代码

设置进制(十进制、十六进制、八进制)

C++提供了操纵符来控制整数的输出进制:
  1. #include <iostream>
  2. #include <iomanip>
  3. int main() {
  4.     int value = 42;
  5.    
  6.     std::cout << "Decimal: " << std::dec << value << std::endl;
  7.     std::cout << "Hexadecimal: " << std::hex << value << std::endl;
  8.     std::cout << "Octal: " << std::oct << value << std::endl;
  9.    
  10.     // 显示进制前缀
  11.     std::cout << std::showbase;
  12.     std::cout << "Decimal with prefix: " << std::dec << value << std::endl;
  13.     std::cout << "Hexadecimal with prefix: " << std::hex << value << std::endl;
  14.     std::cout << "Octal with prefix: " << std::oct << value << std::endl;
  15.    
  16.     return 0;
  17. }
复制代码

浮点数格式化(科学计数法、定点表示法)

浮点数可以以不同的格式输出:
  1. #include <iostream>
  2. #include <iomanip>
  3. int main() {
  4.     double value = 12345.6789;
  5.    
  6.     // 默认格式
  7.     std::cout << "Default: " << value << std::endl;
  8.    
  9.     // 科学计数法
  10.     std::cout << "Scientific: " << std::scientific << value << std::endl;
  11.    
  12.     // 定点表示法
  13.     std::cout << "Fixed: " << std::fixed << value << std::endl;
  14.    
  15.     // 设置精度
  16.     std::cout << "Fixed with precision 2: " << std::fixed << std::setprecision(2) << value << std::endl;
  17.     std::cout << "Scientific with precision 3: " << std::scientific << std::setprecision(3) << value << std::endl;
  18.    
  19.     return 0;
  20. }
复制代码

百分比输出

百分比输出的基本方法

百分比输出在显示比例、增长率等场景中非常常见。基本方法是将小数值乘以100,然后添加百分号:
  1. #include <iostream>
  2. int main() {
  3.     double ratio = 0.7532;
  4.    
  5.     std::cout << "Basic percentage: " << ratio * 100 << "%" << std::endl;
  6.    
  7.     return 0;
  8. }
复制代码

格式化百分比输出

使用格式化操纵符可以使百分比输出更加美观:
  1. #include <iostream>
  2. #include <iomanip>
  3. int main() {
  4.     double ratio = 0.7532;
  5.    
  6.     std::cout << "Formatted percentage: "
  7.               << std::fixed << std::setprecision(2)
  8.               << ratio * 100 << "%" << std::endl;
  9.    
  10.     // 设置宽度和对齐
  11.     std::cout << "Right-aligned: "
  12.               << std::setw(10) << std::right << std::fixed << std::setprecision(2)
  13.               << ratio * 100 << "%" << std::endl;
  14.    
  15.     std::cout << "Left-aligned: "
  16.               << std::setw(10) << std::left << std::fixed << std::setprecision(2)
  17.               << ratio * 100 << "%" << std::endl;
  18.    
  19.     return 0;
  20. }
复制代码

自定义百分比输出函数

为了更方便地输出百分比,可以创建自定义函数:
  1. #include <iostream>
  2. #include <iomanip>
  3. #include <string>
  4. // 输出百分比,默认精度为2
  5. std::ostream& print_percentage(std::ostream& os, double value, int precision = 2) {
  6.     os << std::fixed << std::setprecision(precision)
  7.        << value * 100 << "%";
  8.     return os;
  9. }
  10. // 输出带宽度的百分比
  11. std::ostream& print_percentage(std::ostream& os, double value, int width, int precision = 2) {
  12.     os << std::fixed << std::setprecision(precision)
  13.        << std::setw(width) << value * 100 << "%";
  14.     return os;
  15. }
  16. int main() {
  17.     double ratio1 = 0.7532;
  18.     double ratio2 = 0.1234;
  19.    
  20.     std::cout << "Custom percentage function: ";
  21.     print_percentage(std::cout, ratio1) << std::endl;
  22.    
  23.     std::cout << "Custom percentage with width and precision: ";
  24.     print_percentage(std::cout, ratio2, 10, 1) << std::endl;
  25.    
  26.     return 0;
  27. }
复制代码

更高级的实现是创建一个操纵符:
  1. #include <iostream>
  2. #include <iomanip>
  3. #include <sstream>
  4. // 百分比操纵符的实现
  5. struct Percentage {
  6.     double value;
  7.     int precision;
  8.     int width;
  9.    
  10.     Percentage(double v, int p = 2, int w = 0) : value(v), precision(p), width(w) {}
  11. };
  12. // 重载<<运算符
  13. std::ostream& operator<<(std::ostream& os, const Percentage& p) {
  14.     std::ostringstream oss;
  15.     oss << std::fixed << std::setprecision(p.precision);
  16.    
  17.     if (p.width > 0) {
  18.         oss << std::setw(p.width);
  19.     }
  20.    
  21.     oss << p.value * 100 << "%";
  22.     os << oss.str();
  23.     return os;
  24. }
  25. // 便捷函数创建Percentage对象
  26. Percentage percentage(double value, int precision = 2, int width = 0) {
  27.     return Percentage(value, precision, width);
  28. }
  29. int main() {
  30.     double ratio1 = 0.7532;
  31.     double ratio2 = 0.1234;
  32.    
  33.     std::cout << "Percentage manipulator: " << percentage(ratio1) << std::endl;
  34.     std::cout << "Percentage with width and precision: " << percentage(ratio2, 1, 10) << std::endl;
  35.    
  36.     return 0;
  37. }
复制代码

输出流控制

流状态标志

C++输出流有一组状态标志,用于控制格式化行为。这些标志可以通过setf和unsetf成员函数进行设置和清除:
  1. #include <iostream>
  2. #include <iomanip>
  3. int main() {
  4.     int value = 42;
  5.    
  6.     // 设置显示进制前缀
  7.     std::cout.setf(std::ios::showbase);
  8.     std::cout << "With showbase: " << std::hex << value << std::endl;
  9.    
  10.     // 清除显示进制前缀
  11.     std::cout.unsetf(std::ios::showbase);
  12.     std::cout << "Without showbase: " << std::hex << value << std::endl;
  13.    
  14.     // 设置显示正号
  15.     std::cout.setf(std::ios::showpos);
  16.     std::cout << "With showpos: " << value << std::endl;
  17.    
  18.     // 清除显示正号
  19.     std::cout.unsetf(std::ios::showpos);
  20.     std::cout << "Without showpos: " << value << std::endl;
  21.    
  22.     return 0;
  23. }
复制代码

常用的流状态标志包括:

• std::ios::boolalpha:以文本格式输出布尔值(true/false)
• std::ios::showbase:显示进制前缀(0x表示十六进制,0表示八进制)
• std::ios::showpoint:显示小数点
• std::ios::showpos:显示正号
• std::ios::skipws:跳过输入中的空白字符
• std::ios::unitbuf:每次输出后刷新缓冲区
• std::ios::uppercase:大写输出(如十六进制的A-F,科学计数法的E)

流操纵符

流操纵符是用于控制输出格式的特殊函数,可以直接插入到输出流中。我们已经见过一些操纵符,如std::endl、std::setw等。下面是一些常用的流操纵符:
  1. #include <iostream>
  2. #include <iomanip>
  3. int main() {
  4.     // 布尔值输出
  5.     bool flag = true;
  6.     std::cout << "Default bool: " << flag << std::endl;
  7.     std::cout << "Boolalpha: " << std::boolalpha << flag << std::endl;
  8.    
  9.     // 对齐方式
  10.     std::cout << "Default alignment: |" << std::setw(10) << 42 << "|" << std::endl;
  11.     std::cout << "Left alignment: |" << std::left << std::setw(10) << 42 << "|" << std::endl;
  12.     std::cout << "Right alignment: |" << std::right << std::setw(10) << 42 << "|" << std::endl;
  13.     std::cout << "Internal alignment: |" << std::internal << std::setw(10) << -42 << "|" << std::endl;
  14.    
  15.     // 大小写控制
  16.     std::cout << "Default hex: " << std::hex << 255 << std::endl;
  17.     std::cout << "Uppercase hex: " << std::uppercase << 255 << std::endl;
  18.     std::cout << "Lowercase hex: " << std::nouppercase << 255 << std::endl;
  19.    
  20.     return 0;
  21. }
复制代码

自定义操纵符

除了使用标准库提供的操纵符外,我们还可以创建自己的操纵符。有两种类型的操纵符:不带参数的和带参数的。
  1. #include <iostream>
  2. #include <iomanip>
  3. // 不带参数的操纵符
  4. std::ostream& add_semicolon(std::ostream& os) {
  5.     os << "; ";
  6.     return os;
  7. }
  8. int main() {
  9.     std::cout << "First item" << add_semicolon
  10.               << "Second item" << add_semicolon
  11.               << "Third item" << std::endl;
  12.    
  13.     return 0;
  14. }
复制代码

创建带参数的操纵符稍微复杂一些,需要使用辅助类和函数:
  1. #include <iostream>
  2. #include <iomanip>
  3. // 辅助类
  4. struct Repeat {
  5.     char c;
  6.     int n;
  7.     Repeat(char ch, int count) : c(ch), n(count) {}
  8. };
  9. // 辅助函数
  10. Repeat repeat(char c, int n) {
  11.     return Repeat(c, n);
  12. }
  13. // 重载<<运算符
  14. std::ostream& operator<<(std::ostream& os, const Repeat& r) {
  15.     for (int i = 0; i < r.n; ++i) {
  16.         os << r.c;
  17.     }
  18.     return os;
  19. }
  20. int main() {
  21.     std::cout << "Header" << std::endl;
  22.     std::cout << repeat('-', 20) << std::endl;
  23.     std::cout << "Content line 1" << std::endl;
  24.     std::cout << "Content line 2" << std::endl;
  25.     std::cout << repeat('=', 20) << std::endl;
  26.    
  27.     return 0;
  28. }
复制代码

输出缓冲区控制

C++输出流通常使用缓冲区来提高性能。缓冲区在以下情况下会被刷新(即内容被实际输出):

• 缓冲区已满
• 遇到刷新操纵符(如std::endl或std::flush)
• 流被销毁
• 显式调用flush()成员函数
  1. #include <iostream>
  2. #include <chrono>
  3. #include <thread>
  4. int main() {
  5.     // 不带刷新的输出
  6.     std::cout << "This will be buffered";
  7.     std::this_thread::sleep_for(std::chrono::seconds(2));
  8.     std::cout << " and this will appear together with the previous text" << std::endl;
  9.    
  10.     // 带刷新的输出
  11.     std::cout << "This will appear immediately";
  12.     std::cout.flush();  // 显式刷新缓冲区
  13.     std::this_thread::sleep_for(std::chrono::seconds(2));
  14.     std::cout << " and this will appear after 2 seconds" << std::endl;
  15.    
  16.     // 使用endl操纵符(输出换行并刷新)
  17.     std::cout << "This will appear immediately with a newline" << std::endl;
  18.     std::this_thread::sleep_for(std::chrono::seconds(2));
  19.     std::cout << "And this will appear after 2 seconds" << std::endl;
  20.    
  21.     // 使用unitbuf操纵符(每次输出后自动刷新)
  22.     std::cout << std::unitbuf;
  23.     std::cout << "This will appear immediately";
  24.     std::this_thread::sleep_for(std::chrono::seconds(2));
  25.     std::cout << " and this will also appear immediately" << std::endl;
  26.     std::cout << std::nounitbuf;  // 恢复默认缓冲模式
  27.    
  28.     return 0;
  29. }
复制代码

错误排查

常见输出错误及解决方案

问题:输出格式不符合预期,如浮点数精度不正确,对齐方式错误等。

解决方案:检查是否正确使用了格式化操纵符,并注意操纵符的持久性(一些操纵符会影响后续所有输出)。
  1. #include <iostream>
  2. #include <iomanip>
  3. int main() {
  4.     double value = 3.14159265358979323846;
  5.    
  6.     // 错误示例:精度设置会影响后续所有浮点数输出
  7.     std::cout << "Pi with precision 2: " << std::setprecision(2) << value << std::endl;
  8.     std::cout << "Pi with default precision: " << value << std::endl;  // 仍然使用精度2
  9.    
  10.     // 正确示例:保存并恢复流状态
  11.     std::cout << "Pi with precision 2: " << std::setprecision(2) << value << std::endl;
  12.     std::cout << "Pi with restored precision: " << std::setprecision(6) << value << std::endl;
  13.    
  14.     // 更好的方法:使用std::ios_base::precision保存和恢复精度
  15.     std::streamsize original_precision = std::cout.precision();
  16.     std::cout << "Pi with precision 2: " << std::setprecision(2) << value << std::endl;
  17.     std::cout.precision(original_precision);
  18.     std::cout << "Pi with restored precision: " << value << std::endl;
  19.    
  20.     return 0;
  21. }
复制代码

问题:程序崩溃或异常终止时,部分输出没有显示。

解决方案:确保在关键点刷新输出缓冲区,或使用std::unitbuf操纵符实现自动刷新。
  1. #include <iostream>
  2. #include <stdexcept>
  3. void risky_operation() {
  4.     // 模拟可能抛出异常的操作
  5.     throw std::runtime_error("Something went wrong");
  6. }
  7. int main() {
  8.     try {
  9.         std::cout << "Starting risky operation..." << std::flush;  // 确保输出显示
  10.         risky_operation();
  11.         std::cout << "Operation completed successfully." << std::endl;
  12.     } catch (const std::exception& e) {
  13.         std::cout << "Error: " << e.what() << std::endl;
  14.     }
  15.    
  16.     return 0;
  17. }
复制代码

问题:在链式输出中,类型转换可能导致意外结果。

解决方案:使用括号明确表达式的优先级,或避免混合不同类型的输出。
  1. #include <iostream>
  2. int main() {
  3.     int a = 5;
  4.     int b = 10;
  5.    
  6.     // 错误示例:输出可能不符合预期
  7.     std::cout << "a + b = " << a + b << std::endl;  // 实际上输出的是 b,因为 << a + b 等同于 (cout << a) + b
  8.    
  9.     // 正确示例:使用括号明确优先级
  10.     std::cout << "a + b = " << (a + b) << std::endl;
  11.    
  12.     return 0;
  13. }
复制代码

流状态检查

输出流有一个状态标志,用于指示流是否处于良好状态。可以使用以下成员函数检查流状态:

• good():流是否处于良好状态
• eof():是否到达输入流的末尾
• fail():上一次操作是否失败
• bad():是否发生严重错误
  1. #include <iostream>
  2. #include <fstream>
  3. int main() {
  4.     std::ofstream outfile("test.txt");
  5.    
  6.     if (!outfile) {
  7.         std::cerr << "Failed to open file for writing" << std::endl;
  8.         return 1;
  9.     }
  10.    
  11.     // 检查流状态
  12.     std::cout << "Initial state - good: " << outfile.good()
  13.               << ", fail: " << outfile.fail()
  14.               << ", bad: " << outfile.bad() << std::endl;
  15.    
  16.     outfile << "Hello, World!" << std::endl;
  17.    
  18.     // 再次检查流状态
  19.     std::cout << "After writing - good: " << outfile.good()
  20.               << ", fail: " << outfile.fail()
  21.               << ", bad: " << outfile.bad() << std::endl;
  22.    
  23.     outfile.close();
  24.    
  25.     return 0;
  26. }
复制代码

异常处理

默认情况下,C++流不会抛出异常,而是设置错误状态。可以通过exceptions()成员函数启用异常:
  1. #include <iostream>
  2. #include <fstream>
  3. int main() {
  4.     std::ofstream outfile;
  5.    
  6.     // 启用异常
  7.     outfile.exceptions(std::ofstream::failbit | std::ofstream::badbit);
  8.    
  9.     try {
  10.         outfile.open("nonexistent_directory/test.txt");
  11.         outfile << "Hello, World!" << std::endl;
  12.         outfile.close();
  13.     } catch (const std::ofstream::failure& e) {
  14.         std::cerr << "Exception opening/writing/closing file: " << e.what() << std::endl;
  15.     }
  16.    
  17.     return 0;
  18. }
复制代码

实战案例

案例1:创建格式化报表

假设我们需要创建一个销售报表,包含产品名称、销售数量、单价和总价,并以表格形式输出:
  1. #include <iostream>
  2. #include <iomanip>
  3. #include <vector>
  4. #include <string>
  5. struct Product {
  6.     std::string name;
  7.     int quantity;
  8.     double price;
  9. };
  10. void print_report_header() {
  11.     std::cout << std::setw(20) << std::left << "Product Name"
  12.               << std::setw(10) << std::right << "Quantity"
  13.               << std::setw(12) << "Unit Price"
  14.               << std::setw(12) << "Total Price" << std::endl;
  15.    
  16.     std::cout << std::setw(20) << std::left << std::setfill('-') << ""
  17.               << std::setw(10) << std::right << ""
  18.               << std::setw(12) << ""
  19.               << std::setw(12) << "" << std::endl;
  20.    
  21.     // 恢复填充字符
  22.     std::cout << std::setfill(' ');
  23. }
  24. void print_product(const Product& product) {
  25.     double total = product.quantity * product.price;
  26.    
  27.     std::cout << std::setw(20) << std::left << product.name
  28.               << std::setw(10) << std::right << product.quantity
  29.               << std::setw(12) << std::fixed << std::setprecision(2) << product.price
  30.               << std::setw(12) << std::fixed << std::setprecision(2) << total << std::endl;
  31. }
  32. void print_report_footer(const std::vector<Product>& products) {
  33.     double grand_total = 0.0;
  34.     for (const auto& product : products) {
  35.         grand_total += product.quantity * product.price;
  36.     }
  37.    
  38.     std::cout << std::setw(20) << std::left << std::setfill('-') << ""
  39.               << std::setw(10) << std::right << ""
  40.               << std::setw(12) << ""
  41.               << std::setw(12) << "" << std::endl;
  42.    
  43.     // 恢复填充字符
  44.     std::cout << std::setfill(' ');
  45.    
  46.     std::cout << std::setw(42) << std::right << "Grand Total:"
  47.               << std::setw(12) << std::fixed << std::setprecision(2) << grand_total << std::endl;
  48. }
  49. int main() {
  50.     std::vector<Product> products = {
  51.         {"Laptop", 5, 999.99},
  52.         {"Smartphone", 10, 499.99},
  53.         {"Tablet", 7, 299.99},
  54.         {"Headphones", 15, 79.99},
  55.         {"Smartwatch", 8, 199.99}
  56.     };
  57.    
  58.     std::cout << "SALES REPORT" << std::endl;
  59.     std::cout << "============" << std::endl << std::endl;
  60.    
  61.     print_report_header();
  62.    
  63.     for (const auto& product : products) {
  64.         print_product(product);
  65.     }
  66.    
  67.     print_report_footer(products);
  68.    
  69.     return 0;
  70. }
复制代码

案例2:实现进度条显示

进度条是命令行程序中常见的用户界面元素,用于显示长时间运行操作的进度:
  1. #include <iostream>
  2. #include <iomanip>
  3. #include <chrono>
  4. #include <thread>
  5. class ProgressBar {
  6. private:
  7.     int total;
  8.     int width;
  9.     char complete_char;
  10.     char incomplete_char;
  11.    
  12. public:
  13.     ProgressBar(int total, int width = 50, char complete = '=', char incomplete = ' ')
  14.         : total(total), width(width), complete_char(complete), incomplete_char(incomplete) {}
  15.    
  16.     void display(int progress) {
  17.         float percentage = static_cast<float>(progress) / total;
  18.         int completed_width = static_cast<int>(percentage * width);
  19.         
  20.         std::cout << "\r[";
  21.         std::cout << std::setw(completed_width) << std::setfill(complete_char) << "";
  22.         std::cout << std::setw(width - completed_width) << std::setfill(incomplete_char) << "";
  23.         std::cout << "] " << std::fixed << std::setprecision(1) << percentage * 100 << "%";
  24.         std::cout.flush();
  25.     }
  26.    
  27.     void done() {
  28.         display(total);
  29.         std::cout << std::endl;
  30.     }
  31. };
  32. void simulate_long_task(int steps) {
  33.     ProgressBar bar(steps);
  34.    
  35.     for (int i = 0; i <= steps; ++i) {
  36.         // 模拟工作
  37.         std::this_thread::sleep_for(std::chrono::milliseconds(50));
  38.         
  39.         // 更新进度条
  40.         bar.display(i);
  41.     }
  42.    
  43.     bar.done();
  44. }
  45. int main() {
  46.     std::cout << "Processing data..." << std::endl;
  47.     simulate_long_task(100);
  48.     std::cout << "Task completed!" << std::endl;
  49.    
  50.     return 0;
  51. }
复制代码

案例3:日志系统设计

一个简单的日志系统,支持不同级别的日志输出和时间戳:
  1. #include <iostream>
  2. #include <iomanip>
  3. #include <chrono>
  4. #include <sstream>
  5. #include <string>
  6. #include <fstream>
  7. enum class LogLevel {
  8.     DEBUG,
  9.     INFO,
  10.     WARNING,
  11.     ERROR,
  12.     FATAL
  13. };
  14. class Logger {
  15. private:
  16.     std::ostream& output_stream;
  17.     LogLevel current_level;
  18.     bool show_timestamp;
  19.    
  20.     std::string get_level_string(LogLevel level) {
  21.         switch (level) {
  22.             case LogLevel::DEBUG:   return "DEBUG";
  23.             case LogLevel::INFO:    return "INFO";
  24.             case LogLevel::WARNING: return "WARNING";
  25.             case LogLevel::ERROR:   return "ERROR";
  26.             case LogLevel::FATAL:   return "FATAL";
  27.             default:                return "UNKNOWN";
  28.         }
  29.     }
  30.    
  31.     std::string get_timestamp() {
  32.         auto now = std::chrono::system_clock::now();
  33.         auto time_t = std::chrono::system_clock::to_time_t(now);
  34.         
  35.         std::stringstream ss;
  36.         ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
  37.         return ss.str();
  38.     }
  39.    
  40. public:
  41.     Logger(std::ostream& stream = std::cout, LogLevel level = LogLevel::INFO, bool timestamp = true)
  42.         : output_stream(stream), current_level(level), show_timestamp(timestamp) {}
  43.    
  44.     void set_level(LogLevel level) {
  45.         current_level = level;
  46.     }
  47.    
  48.     void log(LogLevel level, const std::string& message) {
  49.         if (level >= current_level) {
  50.             if (show_timestamp) {
  51.                 output_stream << "[" << get_timestamp() << "] ";
  52.             }
  53.             
  54.             output_stream << "[" << get_level_string(level) << "] " << message << std::endl;
  55.         }
  56.     }
  57.    
  58.     void debug(const std::string& message) { log(LogLevel::DEBUG, message); }
  59.     void info(const std::string& message) { log(LogLevel::INFO, message); }
  60.     void warning(const std::string& message) { log(LogLevel::WARNING, message); }
  61.     void error(const std::string& message) { log(LogLevel::ERROR, message); }
  62.     void fatal(const std::string& message) { log(LogLevel::FATAL, message); }
  63. };
  64. int main() {
  65.     // 控制台日志
  66.     Logger console_logger;
  67.     console_logger.set_level(LogLevel::DEBUG);
  68.    
  69.     console_logger.debug("This is a debug message");
  70.     console_logger.info("This is an info message");
  71.     console_logger.warning("This is a warning message");
  72.     console_logger.error("This is an error message");
  73.     console_logger.fatal("This is a fatal message");
  74.    
  75.     // 文件日志
  76.     std::ofstream log_file("app.log");
  77.     Logger file_logger(log_file, LogLevel::INFO);
  78.    
  79.     file_logger.info("Application started");
  80.     file_logger.debug("This debug message won't appear in the file");
  81.     file_logger.warning("Something unexpected happened");
  82.     file_logger.info("Application finished");
  83.    
  84.     log_file.close();
  85.    
  86.     return 0;
  87. }
复制代码

进阶技巧

自定义输出运算符重载

通过重载输出运算符(<<),可以为自定义类型提供直接的输出支持:
  1. #include <iostream>
  2. #include <string>
  3. class Person {
  4. private:
  5.     std::string name;
  6.     int age;
  7.     double height;
  8.    
  9. public:
  10.     Person(const std::string& n, int a, double h) : name(n), age(a), height(h) {}
  11.    
  12.     // 声明友元函数以便访问私有成员
  13.     friend std::ostream& operator<<(std::ostream& os, const Person& person);
  14. };
  15. // 重载输出运算符
  16. std::ostream& operator<<(std::ostream& os, const Person& person) {
  17.     os << "Person{name: " << person.name
  18.        << ", age: " << person.age
  19.        << ", height: " << person.height << "m}";
  20.     return os;
  21. }
  22. int main() {
  23.     Person alice("Alice", 30, 1.75);
  24.     Person bob("Bob", 25, 1.82);
  25.    
  26.     std::cout << "Person details:" << std::endl;
  27.     std::cout << alice << std::endl;
  28.     std::cout << bob << std::endl;
  29.    
  30.     return 0;
  31. }
复制代码

国际化输出

C++提供了<locale>头文件来支持国际化输出,包括数字、日期、货币等的本地化格式:
  1. #include <iostream>
  2. #include <iomanip>
  3. #include <locale>
  4. int main() {
  5.     // 设置全局区域
  6.     std::locale::global(std::locale(""));
  7.     std::cout.imbue(std::locale());
  8.    
  9.     double value = 1234567.89;
  10.    
  11.     std::cout << "Default locale: " << value << std::endl;
  12.    
  13.     // 使用特定区域
  14.     std::cout.imbue(std::locale("en_US.UTF-8"));
  15.     std::cout << "US locale: " << value << std::endl;
  16.    
  17.     std::cout.imbue(std::locale("de_DE.UTF-8"));
  18.     std::cout << "German locale: " << value << std::endl;
  19.    
  20.     std::cout.imbue(std::locale("fr_FR.UTF-8"));
  21.     std::cout << "French locale: " << value << std::endl;
  22.    
  23.     // 货币格式化
  24.     std::cout.imbue(std::locale("en_US.UTF-8"));
  25.     std::cout << "US currency: " << std::put_money(value * 100) << std::endl;
  26.    
  27.     std::cout.imbue(std::locale("de_DE.UTF-8"));
  28.     std::cout << "German currency: " << std::put_money(value * 100) << std::endl;
  29.    
  30.     return 0;
  31. }
复制代码

多线程环境下的输出控制

在多线程程序中,直接使用std::cout可能导致输出混乱。可以使用互斥锁来保护输出操作:
  1. #include <iostream>
  2. #include <thread>
  3. #include <vector>
  4. #include <mutex>
  5. // 全局互斥锁保护输出
  6. std::mutex cout_mutex;
  7. void thread_safe_print(const std::string& message) {
  8.     std::lock_guard<std::mutex> lock(cout_mutex);
  9.     std::cout << message << std::endl;
  10. }
  11. void worker(int id) {
  12.     for (int i = 0; i < 5; ++i) {
  13.         thread_safe_print("Thread " + std::to_string(id) + ": message " + std::to_string(i));
  14.         std::this_thread::sleep_for(std::chrono::milliseconds(100));
  15.     }
  16. }
  17. int main() {
  18.     std::vector<std::thread> threads;
  19.    
  20.     // 创建多个线程
  21.     for (int i = 0; i < 5; ++i) {
  22.         threads.emplace_back(worker, i);
  23.     }
  24.    
  25.     // 等待所有线程完成
  26.     for (auto& thread : threads) {
  27.         thread.join();
  28.     }
  29.    
  30.     return 0;
  31. }
复制代码

更高级的实现是创建一个线程安全的日志类:
  1. #include <iostream>
  2. #include <string>
  3. #include <mutex>
  4. #include <chrono>
  5. #include <iomanip>
  6. #include <sstream>
  7. class ThreadSafeLogger {
  8. private:
  9.     std::mutex mtx;
  10.     std::ostream& output_stream;
  11.    
  12.     std::string get_timestamp() {
  13.         auto now = std::chrono::system_clock::now();
  14.         auto time_t = std::chrono::system_clock::to_time_t(now);
  15.         
  16.         std::stringstream ss;
  17.         ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
  18.         return ss.str();
  19.     }
  20.    
  21. public:
  22.     ThreadSafeLogger(std::ostream& stream = std::cout) : output_stream(stream) {}
  23.    
  24.     void log(const std::string& message) {
  25.         std::lock_guard<std::mutex> lock(mtx);
  26.         output_stream << "[" << get_timestamp() << "] " << message << std::endl;
  27.     }
  28.    
  29.     template<typename T>
  30.     ThreadSafeLogger& operator<<(const T& value) {
  31.         std::lock_guard<std::mutex> lock(mtx);
  32.         output_stream << value;
  33.         return *this;
  34.     }
  35.    
  36.     // 支持操纵符
  37.     ThreadSafeLogger& operator<<(std::ostream& (*manip)(std::ostream&)) {
  38.         std::lock_guard<std::mutex> lock(mtx);
  39.         output_stream << manip;
  40.         return *this;
  41.     }
  42. };
  43. // 全局日志实例
  44. ThreadSafeLogger logger;
  45. void worker(int id) {
  46.     for (int i = 0; i < 5; ++i) {
  47.         logger << "Thread " << id << ": message " << i << std::endl;
  48.         std::this_thread::sleep_for(std::chrono::milliseconds(100));
  49.     }
  50. }
  51. int main() {
  52.     std::vector<std::thread> threads;
  53.    
  54.     // 创建多个线程
  55.     for (int i = 0; i < 5; ++i) {
  56.         threads.emplace_back(worker, i);
  57.     }
  58.    
  59.     // 等待所有线程完成
  60.     for (auto& thread : threads) {
  61.         thread.join();
  62.     }
  63.    
  64.     return 0;
  65. }
复制代码

总结与最佳实践

本手册全面介绍了C++输出操作,从基础的cout使用到高级的格式化、流控制和错误处理。以下是一些关键要点和最佳实践:

1. 格式化输出:使用<iomanip>头文件中的操纵符来控制输出格式,包括宽度、精度、填充字符等。
2. 状态管理:记住许多格式化设置是持久的,会影响后续所有输出。在需要时保存和恢复流状态。
3. 百分比输出:通过将小数值乘以100并添加百分号来实现百分比输出,使用格式化操纵符来控制显示效果。
4. 流控制:理解流状态标志和操纵符的使用,以及如何创建自定义操纵符来简化输出操作。
5. 缓冲区管理:了解输出缓冲区的工作原理,在需要时使用flush()或std::endl来刷新缓冲区。
6. 错误处理:检查流状态,考虑启用异常处理以简化错误处理代码。
7. 实战应用:将所学知识应用于实际问题,如创建格式化报表、进度条和日志系统。
8. 进阶技巧:通过重载输出运算符支持自定义类型的直接输出,使用国际化功能支持多语言环境,以及在多线程程序中安全地进行输出操作。

格式化输出:使用<iomanip>头文件中的操纵符来控制输出格式,包括宽度、精度、填充字符等。

状态管理:记住许多格式化设置是持久的,会影响后续所有输出。在需要时保存和恢复流状态。

百分比输出:通过将小数值乘以100并添加百分号来实现百分比输出,使用格式化操纵符来控制显示效果。

流控制:理解流状态标志和操纵符的使用,以及如何创建自定义操纵符来简化输出操作。

缓冲区管理:了解输出缓冲区的工作原理,在需要时使用flush()或std::endl来刷新缓冲区。

错误处理:检查流状态,考虑启用异常处理以简化错误处理代码。

实战应用:将所学知识应用于实际问题,如创建格式化报表、进度条和日志系统。

进阶技巧:通过重载输出运算符支持自定义类型的直接输出,使用国际化功能支持多语言环境,以及在多线程程序中安全地进行输出操作。

通过掌握这些技能,你将能够更加灵活和高效地处理C++中的输出操作,提升编程技能并解决实际问题。
「七転び八起き(ななころびやおき)」
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则