第28章 输入/输出和文件
C++20新特性:输入输出增强
C++20对输入输出系统进行了多项增强,包括同步流(syncstream)等:
同步流(syncstream)
C++20引入了同步流,用于线程安全的输出,避免多线程输出时的交织:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| #include <iostream> #include <syncstream> #include <thread> #include <vector>
void printNumbers(int id) { for (int i = 0; i < 5; i++) { std::osyncstream(std::cout) << "Thread " << id << ": " << i << std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(10)); } }
int main() { std::vector<std::thread> threads; for (int i = 0; i < 5; i++) { threads.emplace_back(printNumbers, i); } for (auto& t : threads) { t.join(); } return 0; }
|
C++20的format库也可以与I/O操作结合使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <format> #include <iostream> #include <fstream>
int main() { std::cout << std::format("Hello, {}!\n", "world"); std::ofstream file("output.txt"); if (file.is_open()) { file << std::format("Name: {}, Age: {}\n", "Alice", 30); file << std::format("Pi is approximately {:.2f}\n", 3.14159); file.close(); } return 0; }
|
C++输入输出流
C++的输入输出系统是基于流(stream)的概念设计的。流是一种抽象,代表了数据的流动。
标准输入输出流
C++提供了三个标准流对象:
- cin:标准输入流,通常关联到键盘
- cout:标准输出流,通常关联到屏幕
- cerr:标准错误流,通常关联到屏幕
- clog:标准日志流,通常关联到屏幕
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| #include <iostream>
int main() { std::cout << "Hello, World!" << std::endl; std::cout << "The answer is " << 42 << std::endl; std::cerr << "Error: Something went wrong!" << std::endl; std::clog << "Log: Operation completed" << std::endl; int x; std::cout << "Enter a number: "; std::cin >> x; std::cout << "You entered: " << x << std::endl; std::string name; std::cout << "Enter your name: "; std::cin >> name; std::cout << "Hello, " << name << "!" << std::endl; std::string line; std::cout << "Enter a line: "; std::cin.ignore(); std::getline(std::cin, line); std::cout << "You entered: " << line << std::endl; return 0; }
|
流操纵符
流操纵符用于修改流的行为,如设置宽度、精度、进制等。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| #include <iostream> #include <iomanip>
int main() { int num = 42; std::cout << "Decimal: " << num << std::endl; std::cout << "Octal: " << std::oct << num << std::endl; std::cout << "Hexadecimal: " << std::hex << num << std::endl; std::cout << "Hexadecimal (uppercase): " << std::uppercase << num << std::endl; double pi = 3.141592653589793; std::cout << "Default: " << pi << std::endl; std::cout << "Fixed: " << std::fixed << pi << std::endl; std::cout << "Scientific: " << std::scientific << pi << std::endl; std::cout << "Precision 2: " << std::setprecision(2) << pi << std::endl; std::cout << "Width 10: " << std::setw(10) << num << std::endl; std::cout << "Width 10 with fill: " << std::setw(10) << std::setfill('*') << num << std::endl; bool flag = true; std::cout << "Boolean: " << flag << std::endl; std::cout << "Boolean (alpha): " << std::boolalpha << flag << std::endl; std::cout << std::resetiosflags(std::ios::scientific | std::ios::fixed); std::cout << "Reset: " << pi << std::endl; return 0; }
|
输入输出错误处理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream>
int main() { int x; std::cout << "Enter a number: "; if (std::cin >> x) { std::cout << "You entered: " << x << std::endl; } else { std::cout << "Invalid input!" << std::endl; std::cin.clear(); std::cin.ignore(1000, '\n'); } return 0; }
|
文件输入输出
C++的文件输入输出是通过文件流类实现的,主要包括:
- ifstream:输入文件流,用于读取文件
- ofstream:输出文件流,用于写入文件
- fstream:文件流,用于读写文件
文件的打开和关闭
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| #include <fstream> #include <iostream>
int main() { std::ofstream outFile("example.txt"); if (outFile.is_open()) { outFile << "Hello, File!" << std::endl; outFile << "The answer is " << 42 << std::endl; outFile.close(); std::cout << "File written successfully!" << std::endl; } else { std::cerr << "Error opening file for writing!" << std::endl; } std::ifstream inFile("example.txt"); if (inFile.is_open()) { std::string line; while (std::getline(inFile, line)) { std::cout << line << std::endl; } inFile.close(); } else { std::cerr << "Error opening file for reading!" << std::endl; } return 0; }
|
文件打开模式
| 模式 | 描述 |
|---|
ios::in | 输入模式,打开文件用于读取 |
ios::out | 输出模式,打开文件用于写入 |
ios::app | 追加模式,在文件末尾写入 |
ios::trunc | 截断模式,打开文件时清空内容 |
ios::binary | 二进制模式,以二进制形式读写 |
ios::ate | 定位模式,打开文件后定位到文件末尾 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| #include <fstream> #include <iostream>
int main() { std::ofstream outFile("example.txt", std::ios::app); if (outFile.is_open()) { outFile << "Appended line" << std::endl; outFile.close(); std::cout << "Line appended successfully!" << std::endl; } else { std::cerr << "Error opening file!" << std::endl; } std::fstream binFile("data.bin", std::ios::binary | std::ios::in | std::ios::out | std::ios::trunc); if (binFile.is_open()) { int num = 42; binFile.write(reinterpret_cast<char*>(&num), sizeof(num)); binFile.seekg(0, std::ios::beg); int readNum; binFile.read(reinterpret_cast<char*>(&readNum), sizeof(readNum)); std::cout << "Read number: " << readNum << std::endl; binFile.close(); } else { std::cerr << "Error opening binary file!" << std::endl; } return 0; }
|
文件位置指针
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| #include <fstream> #include <iostream>
int main() { std::fstream file("example.txt", std::ios::in | std::ios::out); if (file.is_open()) { std::streampos pos = file.tellg(); std::cout << "Current position: " << pos << std::endl; std::string line; std::getline(file, line); std::cout << "Read line: " << line << std::endl; pos = file.tellg(); std::cout << "New position: " << pos << std::endl; file.seekg(0, std::ios::beg); std::cout << "Position after seekg(0): " << file.tellg() << std::endl; file.seekg(0, std::ios::end); std::cout << "Position at end: " << file.tellg() << std::endl; file.close(); } else { std::cerr << "Error opening file!" << std::endl; } return 0; }
|
字符串流
字符串流是一种在内存中进行输入输出操作的流,主要包括:
- istringstream:输入字符串流,用于从字符串读取数据
- ostringstream:输出字符串流,用于向字符串写入数据
- stringstream:字符串流,用于读写字符串
字符串流的使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <sstream> #include <iostream> #include <string>
int main() { std::ostringstream oss; oss << "Hello, " << "String Stream!" << std::endl; oss << "The answer is " << 42 << std::endl; std::string str = oss.str(); std::cout << "Output string: " << str << std::endl; std::string input = "123 456 789"; std::istringstream iss(input); int a, b, c; iss >> a >> b >> c; std::cout << "Read numbers: " << a << ", " << b << ", " << c << std::endl; std::string numStr = "12345"; std::istringstream iss2(numStr); int num; iss2 >> num; std::cout << "String to int: " << num << std::endl; int x = 67890; std::ostringstream oss2; oss2 << x; std::string str2 = oss2.str(); std::cout << "Int to string: " << str2 << std::endl; double pi = 3.1415926535; std::ostringstream oss3; oss3 << std::fixed << std::setprecision(2) << pi; std::string piStr = oss3.str(); std::cout << "Formatted pi: " << piStr << std::endl; return 0; }
|
自定义输入输出操作符
自定义输出操作符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| #include <iostream>
class Point { private: int x; int y;
public: Point(int x = 0, int y = 0) : x(x), y(y) {} friend std::ostream& operator<<(std::ostream& os, const Point& p); friend std::istream& operator>>(std::istream& is, Point& p); };
std::ostream& operator<<(std::ostream& os, const Point& p) { os << "(" << p.x << ", " << p.y << ")"; return os; }
std::istream& operator>>(std::istream& is, Point& p) { char ch1, ch2, ch3; is >> ch1 >> p.x >> ch2 >> p.y >> ch3; if (ch1 != '(' || ch2 != ',' || ch3 != ')') { is.setstate(std::ios::failbit); } return is; }
int main() { Point p1(10, 20); std::cout << "Point p1: " << p1 << std::endl; Point p2; std::cout << "Enter a point (format: (x,y)): "; std::cin >> p2; if (std::cin) { std::cout << "You entered: " << p2 << std::endl; } else { std::cout << "Invalid input!" << std::endl; } return 0; }
|
文件系统操作(C++17+)
C++17引入了<filesystem>库,提供了跨平台的文件系统操作功能。
文件系统基本操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| #include <filesystem> #include <iostream>
namespace fs = std::filesystem;
int main() { fs::path currentPath = fs::current_path(); std::cout << "Current path: " << currentPath << std::endl; fs::path dirPath = currentPath / "test_dir"; if (!fs::exists(dirPath)) { if (fs::create_directory(dirPath)) { std::cout << "Directory created: " << dirPath << std::endl; } } fs::path nestedPath = dirPath / "subdir1" / "subdir2"; if (!fs::exists(nestedPath)) { if (fs::create_directories(nestedPath)) { std::cout << "Nested directories created: " << nestedPath << std::endl; } } std::cout << "\nDirectory contents: " << std::endl; for (const auto& entry : fs::directory_iterator(currentPath)) { std::cout << entry.path() << " " << (fs::is_directory(entry.status()) ? "[dir]" : "[file]") << std::endl; } fs::path filePath = currentPath / "example.txt"; if (fs::exists(filePath)) { std::cout << "\nFile exists: " << filePath << std::endl; std::cout << "File size: " << fs::file_size(filePath) << " bytes" << std::endl; std::cout << "Is regular file: " << fs::is_regular_file(filePath) << std::endl; std::cout << "Is directory: " << fs::is_directory(filePath) << std::endl; } fs::path oldPath = dirPath / "old_name.txt"; fs::path newPath = dirPath / "new_name.txt"; std::ofstream oldFile(oldPath); oldFile << "Test" << std::endl; oldFile.close(); if (fs::exists(oldPath)) { fs::rename(oldPath, newPath); std::cout << "\nFile renamed: " << oldPath << " -> " << newPath << std::endl; } if (fs::exists(newPath)) { fs::remove(newPath); std::cout << "File deleted: " << newPath << std::endl; } if (fs::exists(dirPath)) { fs::remove_all(dirPath); std::cout << "Directory deleted: " << dirPath << std::endl; } return 0; }
|
最佳实践
1. 输入输出流
- 使用命名空间:为了代码简洁,可以使用
using namespace std;,但在头文件中应避免 - 错误处理:总是检查输入操作是否成功
- 流状态:在输入失败后,记得清除错误状态并忽略无效输入
- 格式化:使用流操纵符进行格式化输出,而不是手动拼接字符串
- 性能考虑:对于大量输出,考虑使用
std::ios::sync_with_stdio(false);提高性能
2. 文件操作
- 文件检查:在打开文件后,总是检查是否成功打开
- 资源管理:使用RAII(资源获取即初始化)管理文件流,或确保在使用完毕后关闭文件
- 异常处理:考虑使用异常处理文件操作错误
- 二进制模式:在处理二进制文件时,使用
std::ios::binary模式 - 路径处理:使用C++17的
<filesystem>库处理路径,提高跨平台兼容性
3. 字符串流
- 类型转换:使用字符串流进行类型转换,比
atoi、itoa等函数更安全、更灵活 - 格式化:使用字符串流进行复杂的字符串格式化
- 内存操作:对于需要在内存中进行大量读写操作的场景,使用字符串流
4. 自定义操作符
- 一致性:自定义输入输出操作符时,保持与标准类型的行为一致
- 错误处理:在自定义输入操作符中,正确设置流的错误状态
- 文档:为自定义类型的输入输出格式提供清晰的文档
常见错误和陷阱
1. 输入输出错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| int x; std::cin >> x;
std::cout << "You entered: " << x << std::endl;
int x; if (std::cin >> x) { std::cout << "You entered: " << x << std::endl; } else { std::cout << "Invalid input!" << std::endl; std::cin.clear(); std::cin.ignore(1000, '\n'); }
std::string name; int age; std::cout << "Enter your name: "; std::cin >> name; std::cout << "Enter your age: "; std::cin >> age;
std::string address; std::cout << "Enter your address: "; std::getline(std::cin, address);
std::string name; int age; std::cout << "Enter your name: "; std::cin >> name; std::cout << "Enter your age: "; std::cin >> age; std::cin.ignore();
std::string address; std::cout << "Enter your address: "; std::getline(std::cin, address);
|
2. 文件操作错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| std::ifstream file("nonexistent.txt"); std::string line; std::getline(file, line);
std::ifstream file("nonexistent.txt"); if (file.is_open()) { std::string line; std::getline(file, line); } else { std::cerr << "Error opening file!" << std::endl; }
std::ofstream file("C:\\Users\\name\\file.txt");
std::ofstream file("C:/Users/name/file.txt"); std::ofstream file("C:\\Users\\name\\file.txt");
std::ofstream file("example.txt"); file << "Hello";
std::ofstream file("example.txt"); file << "Hello"; file.close();
{ std::ofstream file("example.txt"); file << "Hello"; }
|
3. 字符串流错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| std::ostringstream oss; oss << "First"; std::string s1 = oss.str();
oss << "Second"; std::string s2 = oss.str();
std::ostringstream oss; oss << "First"; std::string s1 = oss.str();
oss.str(""); oss << "Second"; std::string s2 = oss.str();
|
4. 文件系统操作错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
#include <filesystem> namespace fs = std::filesystem;
fs::create_directory("test");
if (fs::create_directory("test")) { std::cout << "Directory created" << std::endl; } else { std::cout << "Failed to create directory" << std::endl; }
|
小结
本章介绍了C++中的输入输出和文件操作,包括:
- C++输入输出流:标准输入输出流、流操纵符、输入输出错误处理
- 文件输入输出:文件的打开和关闭、文件打开模式、文件位置指针
- 字符串流:输入字符串流、输出字符串流、字符串流的使用
- 自定义输入输出操作符:重载输入输出操作符
- 文件系统操作:C++17的filesystem库
- 最佳实践:输入输出流、文件操作、字符串流、自定义操作符的使用
- 常见错误和陷阱:输入输出错误、文件操作错误、字符串流错误、文件系统操作错误
输入输出是程序与外部世界交互的重要方式,文件操作则是程序持久化数据的关键手段。掌握这些内容对于编写实用的C++程序至关重要。在实际编程中,应根据具体情况选择合适的输入输出方式,遵循最佳实践,避免常见错误。
在后续章节中,我们将学习C++的标准模板库、多线程编程等内容,进一步提高C++编程能力。