|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言
C++作为一种强大的编程语言,其输入输出系统提供了丰富而灵活的功能。无论是简单的控制台输出,还是复杂的格式化报表,C++的输出机制都能满足各种需求。本手册将全面介绍C++输出操作,从基础概念到进阶技巧,帮助读者掌握格式化输出、百分比输出、流控制以及错误排查等关键技能,并通过实战案例提升编程能力。
C++输出基础
cout对象简介
C++中,cout是ostream类的一个对象,它与标准输出设备(通常是控制台)相关联。cout定义在<iostream>头文件中,是C++标准库的一部分。使用cout进行输出操作前,需要包含相应的头文件:
基本输出操作
最基本的输出操作使用插入运算符(<<),它将右侧的数据插入到左侧的输出流中:
- #include <iostream>
- int main() {
- std::cout << "Hello, World!" << std::endl;
- std::cout << 42 << std::endl;
- std::cout << 3.14159 << std::endl;
- return 0;
- }
复制代码
在上面的例子中,std::endl是一个操纵符,它输出换行符并刷新输出缓冲区。
输出运算符(<<)的使用
输出运算符(<<)可以被链式使用,连续输出多个项目:
- #include <iostream>
- #include <string>
- int main() {
- std::string name = "Alice";
- int age = 30;
- double height = 1.75;
-
- std::cout << "Name: " << name << ", Age: " << age << ", Height: " << height << "m" << std::endl;
-
- return 0;
- }
复制代码
这种链式使用方式非常灵活,可以混合输出不同类型的数据,C++会自动处理类型转换。
格式化输出
使用iomanip库
<iomanip>头文件提供了许多用于格式化输出的操纵符。要使用这些功能,需要包含该头文件:
控制输出宽度、精度和填充字符
使用std::setw操纵符可以设置输出的最小宽度:
- #include <iostream>
- #include <iomanip>
- int main() {
- std::cout << "Default width:" << std::endl;
- std::cout << 42 << std::endl;
- std::cout << "Set width to 10:" << std::endl;
- std::cout << std::setw(10) << 42 << std::endl;
- std::cout << "Set width to 10 with '*' as fill character:" << std::endl;
- std::cout << std::setw(10) << std::setfill('*') << 42 << std::endl;
-
- return 0;
- }
复制代码
对于浮点数,可以使用std::setprecision操纵符设置精度:
- #include <iostream>
- #include <iomanip>
- int main() {
- double pi = 3.14159265358979323846;
-
- std::cout << "Default precision:" << std::endl;
- std::cout << pi << std::endl;
-
- std::cout << "Set precision to 5:" << std::endl;
- std::cout << std::setprecision(5) << pi << std::endl;
-
- std::cout << "Set precision to 10:" << std::endl;
- std::cout << std::setprecision(10) << pi << std::endl;
-
- return 0;
- }
复制代码
使用std::setfill操纵符可以设置填充字符:
- #include <iostream>
- #include <iomanip>
- int main() {
- std::cout << "Default fill:" << std::endl;
- std::cout << std::setw(10) << 42 << std::endl;
-
- std::cout << "Set fill to '*':" << std::endl;
- std::cout << std::setw(10) << std::setfill('*') << 42 << std::endl;
-
- return 0;
- }
复制代码
设置进制(十进制、十六进制、八进制)
C++提供了操纵符来控制整数的输出进制:
- #include <iostream>
- #include <iomanip>
- int main() {
- int value = 42;
-
- std::cout << "Decimal: " << std::dec << value << std::endl;
- std::cout << "Hexadecimal: " << std::hex << value << std::endl;
- std::cout << "Octal: " << std::oct << value << std::endl;
-
- // 显示进制前缀
- std::cout << std::showbase;
- std::cout << "Decimal with prefix: " << std::dec << value << std::endl;
- std::cout << "Hexadecimal with prefix: " << std::hex << value << std::endl;
- std::cout << "Octal with prefix: " << std::oct << value << std::endl;
-
- return 0;
- }
复制代码
浮点数格式化(科学计数法、定点表示法)
浮点数可以以不同的格式输出:
- #include <iostream>
- #include <iomanip>
- int main() {
- double value = 12345.6789;
-
- // 默认格式
- std::cout << "Default: " << value << std::endl;
-
- // 科学计数法
- std::cout << "Scientific: " << std::scientific << value << std::endl;
-
- // 定点表示法
- std::cout << "Fixed: " << std::fixed << value << std::endl;
-
- // 设置精度
- std::cout << "Fixed with precision 2: " << std::fixed << std::setprecision(2) << value << std::endl;
- std::cout << "Scientific with precision 3: " << std::scientific << std::setprecision(3) << value << std::endl;
-
- return 0;
- }
复制代码
百分比输出
百分比输出的基本方法
百分比输出在显示比例、增长率等场景中非常常见。基本方法是将小数值乘以100,然后添加百分号:
- #include <iostream>
- int main() {
- double ratio = 0.7532;
-
- std::cout << "Basic percentage: " << ratio * 100 << "%" << std::endl;
-
- return 0;
- }
复制代码
格式化百分比输出
使用格式化操纵符可以使百分比输出更加美观:
- #include <iostream>
- #include <iomanip>
- int main() {
- double ratio = 0.7532;
-
- std::cout << "Formatted percentage: "
- << std::fixed << std::setprecision(2)
- << ratio * 100 << "%" << std::endl;
-
- // 设置宽度和对齐
- std::cout << "Right-aligned: "
- << std::setw(10) << std::right << std::fixed << std::setprecision(2)
- << ratio * 100 << "%" << std::endl;
-
- std::cout << "Left-aligned: "
- << std::setw(10) << std::left << std::fixed << std::setprecision(2)
- << ratio * 100 << "%" << std::endl;
-
- return 0;
- }
复制代码
自定义百分比输出函数
为了更方便地输出百分比,可以创建自定义函数:
- #include <iostream>
- #include <iomanip>
- #include <string>
- // 输出百分比,默认精度为2
- std::ostream& print_percentage(std::ostream& os, double value, int precision = 2) {
- os << std::fixed << std::setprecision(precision)
- << value * 100 << "%";
- return os;
- }
- // 输出带宽度的百分比
- std::ostream& print_percentage(std::ostream& os, double value, int width, int precision = 2) {
- os << std::fixed << std::setprecision(precision)
- << std::setw(width) << value * 100 << "%";
- return os;
- }
- int main() {
- double ratio1 = 0.7532;
- double ratio2 = 0.1234;
-
- std::cout << "Custom percentage function: ";
- print_percentage(std::cout, ratio1) << std::endl;
-
- std::cout << "Custom percentage with width and precision: ";
- print_percentage(std::cout, ratio2, 10, 1) << std::endl;
-
- return 0;
- }
复制代码
更高级的实现是创建一个操纵符:
- #include <iostream>
- #include <iomanip>
- #include <sstream>
- // 百分比操纵符的实现
- struct Percentage {
- double value;
- int precision;
- int width;
-
- Percentage(double v, int p = 2, int w = 0) : value(v), precision(p), width(w) {}
- };
- // 重载<<运算符
- std::ostream& operator<<(std::ostream& os, const Percentage& p) {
- std::ostringstream oss;
- oss << std::fixed << std::setprecision(p.precision);
-
- if (p.width > 0) {
- oss << std::setw(p.width);
- }
-
- oss << p.value * 100 << "%";
- os << oss.str();
- return os;
- }
- // 便捷函数创建Percentage对象
- Percentage percentage(double value, int precision = 2, int width = 0) {
- return Percentage(value, precision, width);
- }
- int main() {
- double ratio1 = 0.7532;
- double ratio2 = 0.1234;
-
- std::cout << "Percentage manipulator: " << percentage(ratio1) << std::endl;
- std::cout << "Percentage with width and precision: " << percentage(ratio2, 1, 10) << std::endl;
-
- return 0;
- }
复制代码
输出流控制
流状态标志
C++输出流有一组状态标志,用于控制格式化行为。这些标志可以通过setf和unsetf成员函数进行设置和清除:
- #include <iostream>
- #include <iomanip>
- int main() {
- int value = 42;
-
- // 设置显示进制前缀
- std::cout.setf(std::ios::showbase);
- std::cout << "With showbase: " << std::hex << value << std::endl;
-
- // 清除显示进制前缀
- std::cout.unsetf(std::ios::showbase);
- std::cout << "Without showbase: " << std::hex << value << std::endl;
-
- // 设置显示正号
- std::cout.setf(std::ios::showpos);
- std::cout << "With showpos: " << value << std::endl;
-
- // 清除显示正号
- std::cout.unsetf(std::ios::showpos);
- std::cout << "Without showpos: " << value << std::endl;
-
- return 0;
- }
复制代码
常用的流状态标志包括:
• 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等。下面是一些常用的流操纵符:
- #include <iostream>
- #include <iomanip>
- int main() {
- // 布尔值输出
- bool flag = true;
- std::cout << "Default bool: " << flag << std::endl;
- std::cout << "Boolalpha: " << std::boolalpha << flag << std::endl;
-
- // 对齐方式
- std::cout << "Default alignment: |" << std::setw(10) << 42 << "|" << std::endl;
- std::cout << "Left alignment: |" << std::left << std::setw(10) << 42 << "|" << std::endl;
- std::cout << "Right alignment: |" << std::right << std::setw(10) << 42 << "|" << std::endl;
- std::cout << "Internal alignment: |" << std::internal << std::setw(10) << -42 << "|" << std::endl;
-
- // 大小写控制
- std::cout << "Default hex: " << std::hex << 255 << std::endl;
- std::cout << "Uppercase hex: " << std::uppercase << 255 << std::endl;
- std::cout << "Lowercase hex: " << std::nouppercase << 255 << std::endl;
-
- return 0;
- }
复制代码
自定义操纵符
除了使用标准库提供的操纵符外,我们还可以创建自己的操纵符。有两种类型的操纵符:不带参数的和带参数的。
- #include <iostream>
- #include <iomanip>
- // 不带参数的操纵符
- std::ostream& add_semicolon(std::ostream& os) {
- os << "; ";
- return os;
- }
- int main() {
- std::cout << "First item" << add_semicolon
- << "Second item" << add_semicolon
- << "Third item" << std::endl;
-
- return 0;
- }
复制代码
创建带参数的操纵符稍微复杂一些,需要使用辅助类和函数:
- #include <iostream>
- #include <iomanip>
- // 辅助类
- struct Repeat {
- char c;
- int n;
- Repeat(char ch, int count) : c(ch), n(count) {}
- };
- // 辅助函数
- Repeat repeat(char c, int n) {
- return Repeat(c, n);
- }
- // 重载<<运算符
- std::ostream& operator<<(std::ostream& os, const Repeat& r) {
- for (int i = 0; i < r.n; ++i) {
- os << r.c;
- }
- return os;
- }
- int main() {
- std::cout << "Header" << std::endl;
- std::cout << repeat('-', 20) << std::endl;
- std::cout << "Content line 1" << std::endl;
- std::cout << "Content line 2" << std::endl;
- std::cout << repeat('=', 20) << std::endl;
-
- return 0;
- }
复制代码
输出缓冲区控制
C++输出流通常使用缓冲区来提高性能。缓冲区在以下情况下会被刷新(即内容被实际输出):
• 缓冲区已满
• 遇到刷新操纵符(如std::endl或std::flush)
• 流被销毁
• 显式调用flush()成员函数
- #include <iostream>
- #include <chrono>
- #include <thread>
- int main() {
- // 不带刷新的输出
- std::cout << "This will be buffered";
- std::this_thread::sleep_for(std::chrono::seconds(2));
- std::cout << " and this will appear together with the previous text" << std::endl;
-
- // 带刷新的输出
- std::cout << "This will appear immediately";
- std::cout.flush(); // 显式刷新缓冲区
- std::this_thread::sleep_for(std::chrono::seconds(2));
- std::cout << " and this will appear after 2 seconds" << std::endl;
-
- // 使用endl操纵符(输出换行并刷新)
- std::cout << "This will appear immediately with a newline" << std::endl;
- std::this_thread::sleep_for(std::chrono::seconds(2));
- std::cout << "And this will appear after 2 seconds" << std::endl;
-
- // 使用unitbuf操纵符(每次输出后自动刷新)
- std::cout << std::unitbuf;
- std::cout << "This will appear immediately";
- std::this_thread::sleep_for(std::chrono::seconds(2));
- std::cout << " and this will also appear immediately" << std::endl;
- std::cout << std::nounitbuf; // 恢复默认缓冲模式
-
- return 0;
- }
复制代码
错误排查
常见输出错误及解决方案
问题:输出格式不符合预期,如浮点数精度不正确,对齐方式错误等。
解决方案:检查是否正确使用了格式化操纵符,并注意操纵符的持久性(一些操纵符会影响后续所有输出)。
- #include <iostream>
- #include <iomanip>
- int main() {
- double value = 3.14159265358979323846;
-
- // 错误示例:精度设置会影响后续所有浮点数输出
- std::cout << "Pi with precision 2: " << std::setprecision(2) << value << std::endl;
- std::cout << "Pi with default precision: " << value << std::endl; // 仍然使用精度2
-
- // 正确示例:保存并恢复流状态
- std::cout << "Pi with precision 2: " << std::setprecision(2) << value << std::endl;
- std::cout << "Pi with restored precision: " << std::setprecision(6) << value << std::endl;
-
- // 更好的方法:使用std::ios_base::precision保存和恢复精度
- std::streamsize original_precision = std::cout.precision();
- std::cout << "Pi with precision 2: " << std::setprecision(2) << value << std::endl;
- std::cout.precision(original_precision);
- std::cout << "Pi with restored precision: " << value << std::endl;
-
- return 0;
- }
复制代码
问题:程序崩溃或异常终止时,部分输出没有显示。
解决方案:确保在关键点刷新输出缓冲区,或使用std::unitbuf操纵符实现自动刷新。
- #include <iostream>
- #include <stdexcept>
- void risky_operation() {
- // 模拟可能抛出异常的操作
- throw std::runtime_error("Something went wrong");
- }
- int main() {
- try {
- std::cout << "Starting risky operation..." << std::flush; // 确保输出显示
- risky_operation();
- std::cout << "Operation completed successfully." << std::endl;
- } catch (const std::exception& e) {
- std::cout << "Error: " << e.what() << std::endl;
- }
-
- return 0;
- }
复制代码
问题:在链式输出中,类型转换可能导致意外结果。
解决方案:使用括号明确表达式的优先级,或避免混合不同类型的输出。
- #include <iostream>
- int main() {
- int a = 5;
- int b = 10;
-
- // 错误示例:输出可能不符合预期
- std::cout << "a + b = " << a + b << std::endl; // 实际上输出的是 b,因为 << a + b 等同于 (cout << a) + b
-
- // 正确示例:使用括号明确优先级
- std::cout << "a + b = " << (a + b) << std::endl;
-
- return 0;
- }
复制代码
流状态检查
输出流有一个状态标志,用于指示流是否处于良好状态。可以使用以下成员函数检查流状态:
• good():流是否处于良好状态
• eof():是否到达输入流的末尾
• fail():上一次操作是否失败
• bad():是否发生严重错误
- #include <iostream>
- #include <fstream>
- int main() {
- std::ofstream outfile("test.txt");
-
- if (!outfile) {
- std::cerr << "Failed to open file for writing" << std::endl;
- return 1;
- }
-
- // 检查流状态
- std::cout << "Initial state - good: " << outfile.good()
- << ", fail: " << outfile.fail()
- << ", bad: " << outfile.bad() << std::endl;
-
- outfile << "Hello, World!" << std::endl;
-
- // 再次检查流状态
- std::cout << "After writing - good: " << outfile.good()
- << ", fail: " << outfile.fail()
- << ", bad: " << outfile.bad() << std::endl;
-
- outfile.close();
-
- return 0;
- }
复制代码
异常处理
默认情况下,C++流不会抛出异常,而是设置错误状态。可以通过exceptions()成员函数启用异常:
- #include <iostream>
- #include <fstream>
- int main() {
- std::ofstream outfile;
-
- // 启用异常
- outfile.exceptions(std::ofstream::failbit | std::ofstream::badbit);
-
- try {
- outfile.open("nonexistent_directory/test.txt");
- outfile << "Hello, World!" << std::endl;
- outfile.close();
- } catch (const std::ofstream::failure& e) {
- std::cerr << "Exception opening/writing/closing file: " << e.what() << std::endl;
- }
-
- return 0;
- }
复制代码
实战案例
案例1:创建格式化报表
假设我们需要创建一个销售报表,包含产品名称、销售数量、单价和总价,并以表格形式输出:
- #include <iostream>
- #include <iomanip>
- #include <vector>
- #include <string>
- struct Product {
- std::string name;
- int quantity;
- double price;
- };
- void print_report_header() {
- std::cout << std::setw(20) << std::left << "Product Name"
- << std::setw(10) << std::right << "Quantity"
- << std::setw(12) << "Unit Price"
- << std::setw(12) << "Total Price" << std::endl;
-
- std::cout << std::setw(20) << std::left << std::setfill('-') << ""
- << std::setw(10) << std::right << ""
- << std::setw(12) << ""
- << std::setw(12) << "" << std::endl;
-
- // 恢复填充字符
- std::cout << std::setfill(' ');
- }
- void print_product(const Product& product) {
- double total = product.quantity * product.price;
-
- std::cout << std::setw(20) << std::left << product.name
- << std::setw(10) << std::right << product.quantity
- << std::setw(12) << std::fixed << std::setprecision(2) << product.price
- << std::setw(12) << std::fixed << std::setprecision(2) << total << std::endl;
- }
- void print_report_footer(const std::vector<Product>& products) {
- double grand_total = 0.0;
- for (const auto& product : products) {
- grand_total += product.quantity * product.price;
- }
-
- std::cout << std::setw(20) << std::left << std::setfill('-') << ""
- << std::setw(10) << std::right << ""
- << std::setw(12) << ""
- << std::setw(12) << "" << std::endl;
-
- // 恢复填充字符
- std::cout << std::setfill(' ');
-
- std::cout << std::setw(42) << std::right << "Grand Total:"
- << std::setw(12) << std::fixed << std::setprecision(2) << grand_total << std::endl;
- }
- int main() {
- std::vector<Product> products = {
- {"Laptop", 5, 999.99},
- {"Smartphone", 10, 499.99},
- {"Tablet", 7, 299.99},
- {"Headphones", 15, 79.99},
- {"Smartwatch", 8, 199.99}
- };
-
- std::cout << "SALES REPORT" << std::endl;
- std::cout << "============" << std::endl << std::endl;
-
- print_report_header();
-
- for (const auto& product : products) {
- print_product(product);
- }
-
- print_report_footer(products);
-
- return 0;
- }
复制代码
案例2:实现进度条显示
进度条是命令行程序中常见的用户界面元素,用于显示长时间运行操作的进度:
- #include <iostream>
- #include <iomanip>
- #include <chrono>
- #include <thread>
- class ProgressBar {
- private:
- int total;
- int width;
- char complete_char;
- char incomplete_char;
-
- public:
- ProgressBar(int total, int width = 50, char complete = '=', char incomplete = ' ')
- : total(total), width(width), complete_char(complete), incomplete_char(incomplete) {}
-
- void display(int progress) {
- float percentage = static_cast<float>(progress) / total;
- int completed_width = static_cast<int>(percentage * width);
-
- std::cout << "\r[";
- std::cout << std::setw(completed_width) << std::setfill(complete_char) << "";
- std::cout << std::setw(width - completed_width) << std::setfill(incomplete_char) << "";
- std::cout << "] " << std::fixed << std::setprecision(1) << percentage * 100 << "%";
- std::cout.flush();
- }
-
- void done() {
- display(total);
- std::cout << std::endl;
- }
- };
- void simulate_long_task(int steps) {
- ProgressBar bar(steps);
-
- for (int i = 0; i <= steps; ++i) {
- // 模拟工作
- std::this_thread::sleep_for(std::chrono::milliseconds(50));
-
- // 更新进度条
- bar.display(i);
- }
-
- bar.done();
- }
- int main() {
- std::cout << "Processing data..." << std::endl;
- simulate_long_task(100);
- std::cout << "Task completed!" << std::endl;
-
- return 0;
- }
复制代码
案例3:日志系统设计
一个简单的日志系统,支持不同级别的日志输出和时间戳:
- #include <iostream>
- #include <iomanip>
- #include <chrono>
- #include <sstream>
- #include <string>
- #include <fstream>
- enum class LogLevel {
- DEBUG,
- INFO,
- WARNING,
- ERROR,
- FATAL
- };
- class Logger {
- private:
- std::ostream& output_stream;
- LogLevel current_level;
- bool show_timestamp;
-
- std::string get_level_string(LogLevel level) {
- switch (level) {
- case LogLevel::DEBUG: return "DEBUG";
- case LogLevel::INFO: return "INFO";
- case LogLevel::WARNING: return "WARNING";
- case LogLevel::ERROR: return "ERROR";
- case LogLevel::FATAL: return "FATAL";
- default: return "UNKNOWN";
- }
- }
-
- std::string get_timestamp() {
- auto now = std::chrono::system_clock::now();
- auto time_t = std::chrono::system_clock::to_time_t(now);
-
- std::stringstream ss;
- ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
- return ss.str();
- }
-
- public:
- Logger(std::ostream& stream = std::cout, LogLevel level = LogLevel::INFO, bool timestamp = true)
- : output_stream(stream), current_level(level), show_timestamp(timestamp) {}
-
- void set_level(LogLevel level) {
- current_level = level;
- }
-
- void log(LogLevel level, const std::string& message) {
- if (level >= current_level) {
- if (show_timestamp) {
- output_stream << "[" << get_timestamp() << "] ";
- }
-
- output_stream << "[" << get_level_string(level) << "] " << message << std::endl;
- }
- }
-
- void debug(const std::string& message) { log(LogLevel::DEBUG, message); }
- void info(const std::string& message) { log(LogLevel::INFO, message); }
- void warning(const std::string& message) { log(LogLevel::WARNING, message); }
- void error(const std::string& message) { log(LogLevel::ERROR, message); }
- void fatal(const std::string& message) { log(LogLevel::FATAL, message); }
- };
- int main() {
- // 控制台日志
- Logger console_logger;
- console_logger.set_level(LogLevel::DEBUG);
-
- console_logger.debug("This is a debug message");
- console_logger.info("This is an info message");
- console_logger.warning("This is a warning message");
- console_logger.error("This is an error message");
- console_logger.fatal("This is a fatal message");
-
- // 文件日志
- std::ofstream log_file("app.log");
- Logger file_logger(log_file, LogLevel::INFO);
-
- file_logger.info("Application started");
- file_logger.debug("This debug message won't appear in the file");
- file_logger.warning("Something unexpected happened");
- file_logger.info("Application finished");
-
- log_file.close();
-
- return 0;
- }
复制代码
进阶技巧
自定义输出运算符重载
通过重载输出运算符(<<),可以为自定义类型提供直接的输出支持:
- #include <iostream>
- #include <string>
- class Person {
- private:
- std::string name;
- int age;
- double height;
-
- public:
- Person(const std::string& n, int a, double h) : name(n), age(a), height(h) {}
-
- // 声明友元函数以便访问私有成员
- friend std::ostream& operator<<(std::ostream& os, const Person& person);
- };
- // 重载输出运算符
- std::ostream& operator<<(std::ostream& os, const Person& person) {
- os << "Person{name: " << person.name
- << ", age: " << person.age
- << ", height: " << person.height << "m}";
- return os;
- }
- int main() {
- Person alice("Alice", 30, 1.75);
- Person bob("Bob", 25, 1.82);
-
- std::cout << "Person details:" << std::endl;
- std::cout << alice << std::endl;
- std::cout << bob << std::endl;
-
- return 0;
- }
复制代码
国际化输出
C++提供了<locale>头文件来支持国际化输出,包括数字、日期、货币等的本地化格式:
- #include <iostream>
- #include <iomanip>
- #include <locale>
- int main() {
- // 设置全局区域
- std::locale::global(std::locale(""));
- std::cout.imbue(std::locale());
-
- double value = 1234567.89;
-
- std::cout << "Default locale: " << value << std::endl;
-
- // 使用特定区域
- std::cout.imbue(std::locale("en_US.UTF-8"));
- std::cout << "US locale: " << value << std::endl;
-
- std::cout.imbue(std::locale("de_DE.UTF-8"));
- std::cout << "German locale: " << value << std::endl;
-
- std::cout.imbue(std::locale("fr_FR.UTF-8"));
- std::cout << "French locale: " << value << std::endl;
-
- // 货币格式化
- std::cout.imbue(std::locale("en_US.UTF-8"));
- std::cout << "US currency: " << std::put_money(value * 100) << std::endl;
-
- std::cout.imbue(std::locale("de_DE.UTF-8"));
- std::cout << "German currency: " << std::put_money(value * 100) << std::endl;
-
- return 0;
- }
复制代码
多线程环境下的输出控制
在多线程程序中,直接使用std::cout可能导致输出混乱。可以使用互斥锁来保护输出操作:
- #include <iostream>
- #include <thread>
- #include <vector>
- #include <mutex>
- // 全局互斥锁保护输出
- std::mutex cout_mutex;
- void thread_safe_print(const std::string& message) {
- std::lock_guard<std::mutex> lock(cout_mutex);
- std::cout << message << std::endl;
- }
- void worker(int id) {
- for (int i = 0; i < 5; ++i) {
- thread_safe_print("Thread " + std::to_string(id) + ": message " + std::to_string(i));
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- }
- }
- int main() {
- std::vector<std::thread> threads;
-
- // 创建多个线程
- for (int i = 0; i < 5; ++i) {
- threads.emplace_back(worker, i);
- }
-
- // 等待所有线程完成
- for (auto& thread : threads) {
- thread.join();
- }
-
- return 0;
- }
复制代码
更高级的实现是创建一个线程安全的日志类:
- #include <iostream>
- #include <string>
- #include <mutex>
- #include <chrono>
- #include <iomanip>
- #include <sstream>
- class ThreadSafeLogger {
- private:
- std::mutex mtx;
- std::ostream& output_stream;
-
- std::string get_timestamp() {
- auto now = std::chrono::system_clock::now();
- auto time_t = std::chrono::system_clock::to_time_t(now);
-
- std::stringstream ss;
- ss << std::put_time(std::localtime(&time_t), "%Y-%m-%d %H:%M:%S");
- return ss.str();
- }
-
- public:
- ThreadSafeLogger(std::ostream& stream = std::cout) : output_stream(stream) {}
-
- void log(const std::string& message) {
- std::lock_guard<std::mutex> lock(mtx);
- output_stream << "[" << get_timestamp() << "] " << message << std::endl;
- }
-
- template<typename T>
- ThreadSafeLogger& operator<<(const T& value) {
- std::lock_guard<std::mutex> lock(mtx);
- output_stream << value;
- return *this;
- }
-
- // 支持操纵符
- ThreadSafeLogger& operator<<(std::ostream& (*manip)(std::ostream&)) {
- std::lock_guard<std::mutex> lock(mtx);
- output_stream << manip;
- return *this;
- }
- };
- // 全局日志实例
- ThreadSafeLogger logger;
- void worker(int id) {
- for (int i = 0; i < 5; ++i) {
- logger << "Thread " << id << ": message " << i << std::endl;
- std::this_thread::sleep_for(std::chrono::milliseconds(100));
- }
- }
- int main() {
- std::vector<std::thread> threads;
-
- // 创建多个线程
- for (int i = 0; i < 5; ++i) {
- threads.emplace_back(worker, i);
- }
-
- // 等待所有线程完成
- for (auto& thread : threads) {
- thread.join();
- }
-
- return 0;
- }
复制代码
总结与最佳实践
本手册全面介绍了C++输出操作,从基础的cout使用到高级的格式化、流控制和错误处理。以下是一些关键要点和最佳实践:
1. 格式化输出:使用<iomanip>头文件中的操纵符来控制输出格式,包括宽度、精度、填充字符等。
2. 状态管理:记住许多格式化设置是持久的,会影响后续所有输出。在需要时保存和恢复流状态。
3. 百分比输出:通过将小数值乘以100并添加百分号来实现百分比输出,使用格式化操纵符来控制显示效果。
4. 流控制:理解流状态标志和操纵符的使用,以及如何创建自定义操纵符来简化输出操作。
5. 缓冲区管理:了解输出缓冲区的工作原理,在需要时使用flush()或std::endl来刷新缓冲区。
6. 错误处理:检查流状态,考虑启用异常处理以简化错误处理代码。
7. 实战应用:将所学知识应用于实际问题,如创建格式化报表、进度条和日志系统。
8. 进阶技巧:通过重载输出运算符支持自定义类型的直接输出,使用国际化功能支持多语言环境,以及在多线程程序中安全地进行输出操作。
格式化输出:使用<iomanip>头文件中的操纵符来控制输出格式,包括宽度、精度、填充字符等。
状态管理:记住许多格式化设置是持久的,会影响后续所有输出。在需要时保存和恢复流状态。
百分比输出:通过将小数值乘以100并添加百分号来实现百分比输出,使用格式化操纵符来控制显示效果。
流控制:理解流状态标志和操纵符的使用,以及如何创建自定义操纵符来简化输出操作。
缓冲区管理:了解输出缓冲区的工作原理,在需要时使用flush()或std::endl来刷新缓冲区。
错误处理:检查流状态,考虑启用异常处理以简化错误处理代码。
实战应用:将所学知识应用于实际问题,如创建格式化报表、进度条和日志系统。
进阶技巧:通过重载输出运算符支持自定义类型的直接输出,使用国际化功能支持多语言环境,以及在多线程程序中安全地进行输出操作。
通过掌握这些技能,你将能够更加灵活和高效地处理C++中的输出操作,提升编程技能并解决实际问题。 |
|