1. C++ IO流概述
在C++编程中,IO流(Input/Output Stream)是处理数据输入输出的核心机制。作为一门系统级语言,C++通过流的概念抽象了底层硬件差异,为开发者提供了统一的接口来处理各种I/O操作。IO流库是C++标准库中最基础且使用频率最高的组件之一,它封装了字符序列的读写操作,使得从键盘输入、向屏幕输出、文件读写等操作变得简单高效。
C++的IO流库主要包含以下几个关键部分:
- 标准输入输出流(cin/cout/cerr/clog)
- 文件流(ifstream/ofstream/fstream)
- 字符串流(istringstream/ostringstream/stringstream)
- 流操纵符(endl、flush、setw等)
这些组件共同构成了C++强大的IO处理能力,是每个C++开发者必须掌握的基础知识。理解IO流的工作机制不仅能帮助开发者编写更健壮的代码,还能在处理复杂IO场景时游刃有余。
2. 标准IO流详解
2.1 基本流对象
C++标准库预定义了四个标准流对象:
cin:标准输入流,通常关联键盘cout:标准输出流,通常关联显示器cerr:非缓冲的标准错误流clog:带缓冲的标准日志流
这些对象在<iostream>头文件中声明,使用时无需额外创建。它们的典型用法如下:
cpp复制#include <iostream>
int main() {
int age;
std::cout << "请输入您的年龄: "; // 输出提示
std::cin >> age; // 读取输入
std::cout << "您输入的年龄是: " << age << std::endl;
if(age < 0) {
std::cerr << "错误:年龄不能为负数!" << std::endl;
}
return 0;
}
2.2 流的状态与错误处理
每个流对象都维护着一个状态标志,可以通过以下成员函数检查:
good():流处于正常状态eof():到达文件末尾fail():发生了非致命错误(如类型不匹配)bad():发生了致命错误(如磁盘读写错误)
正确处理流状态是编写健壮程序的关键。一个完整的输入处理应该包含错误检查:
cpp复制int value;
std::cin >> value;
if(std::cin.fail()) {
std::cin.clear(); // 清除错误状态
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 忽略错误输入
std::cerr << "输入无效,请重新输入数字" << std::endl;
}
3. 文件流操作
3.1 文件流的基本使用
C++通过<fstream>头文件提供了文件流支持,主要包含三个类:
ifstream:输入文件流(读文件)ofstream:输出文件流(写文件)fstream:输入输出文件流(读写文件)
文件操作的基本流程包括:打开文件、检查是否成功打开、进行读写操作、关闭文件。下面是一个完整的文件复制示例:
cpp复制#include <fstream>
#include <iostream>
int main() {
std::ifstream inFile("source.txt", std::ios::binary);
if(!inFile) {
std::cerr << "无法打开源文件" << std::endl;
return 1;
}
std::ofstream outFile("destination.txt", std::ios::binary);
if(!outFile) {
std::cerr << "无法创建目标文件" << std::endl;
return 1;
}
outFile << inFile.rdbuf(); // 高效的文件复制方法
inFile.close();
outFile.close();
return 0;
}
3.2 文件打开模式
文件流构造函数或open()方法的第二个参数指定文件打开模式,常用选项包括:
ios::in:以读取方式打开ios::out:以写入方式打开ios::app:追加模式ios::ate:打开后定位到文件末尾ios::binary:二进制模式ios::trunc:如果文件存在则清空
这些模式可以通过位或操作|组合使用。例如,以读写方式打开二进制文件并保留原有内容:
cpp复制std::fstream file("data.bin",
std::ios::in | std::ios::out | std::ios::binary);
4. 字符串流应用
4.1 字符串流的基本用法
<sstream>头文件提供的字符串流类允许将字符串作为流来处理,这在数据格式转换和字符串处理中非常有用。主要类包括:
istringstream:输入字符串流ostringstream:输出字符串流stringstream:输入输出字符串流
典型应用场景包括类型转换和字符串拼接:
cpp复制#include <sstream>
#include <string>
// 字符串转换为数值
std::string str = "123.45";
std::istringstream iss(str);
double value;
iss >> value; // value现在为123.45
// 数值转换为字符串
std::ostringstream oss;
oss << "结果是:" << value;
std::string result = oss.str(); // "结果是:123.45"
4.2 高级字符串处理技巧
字符串流特别适合处理结构化文本数据。例如,解析CSV格式的数据:
cpp复制std::string csvLine = "John,Doe,35,New York";
std::istringstream ss(csvLine);
std::string token;
while(std::getline(ss, token, ',')) {
std::cout << "字段: " << token << std::endl;
}
5. 流操纵符与格式化
5.1 常用流操纵符
C++提供了多种流操纵符(定义在<iomanip>中)来控制输出格式:
setw(n):设置字段宽度setprecision(n):设置浮点数精度setfill(c):设置填充字符left/right:设置对齐方式fixed/scientific:设置浮点数表示法
示例用法:
cpp复制#include <iomanip>
std::cout << std::setw(10) << std::setfill('*') << std::left << 123
<< std::endl; // 输出"123*******"
std::cout << std::fixed << std::setprecision(2) << 3.14159; // 输出"3.14"
5.2 自定义流操纵符
开发者可以创建自定义的流操纵符来实现特定的格式化需求。例如,创建一个输出时间格式的操纵符:
cpp复制#include <ctime>
std::ostream& currentTime(std::ostream& os) {
std::time_t t = std::time(nullptr);
return os << std::ctime(&t);
}
int main() {
std::cout << "当前时间是: " << currentTime;
return 0;
}
6. 高级IO技巧与性能优化
6.1 缓冲机制与同步
C++流默认使用缓冲机制来提高IO效率。理解缓冲行为对性能调优很重要:
endl会刷新缓冲区,而'\n'不会flush强制刷新缓冲区unitbuf设置流在每次操作后自动刷新tie()可以将输入输出流绑定,保证在输入前输出缓冲区被刷新
在性能敏感的场景中,减少不必要的刷新可以显著提高IO效率:
cpp复制// 低效写法
for(int i=0; i<10000; ++i) {
std::cout << i << std::endl; // 每次循环都刷新缓冲区
}
// 高效写法
for(int i=0; i<10000; ++i) {
std::cout << i << '\n'; // 只在缓冲区满时刷新
}
std::cout.flush(); // 最后手动刷新
6.2 二进制IO与内存映射
对于大量数据的读写,二进制模式通常比文本模式更高效。二进制读写示例:
cpp复制struct Data {
int id;
double value;
char name[32];
};
Data d = {1, 3.14, "sample"};
std::ofstream out("data.bin", std::ios::binary);
out.write(reinterpret_cast<char*>(&d), sizeof(Data));
out.close();
Data readData;
std::ifstream in("data.bin", std::ios::binary);
in.read(reinterpret_cast<char*>(&readData), sizeof(Data));
对于超大型文件,可以考虑使用操作系统特定的内存映射文件技术来进一步提高性能。
7. 常见问题与解决方案
7.1 输入处理中的陷阱
处理用户输入时常见的错误包括:
- 混合使用
>>和getline()导致换行符残留 - 未检查输入有效性
- 缓冲区溢出风险
解决方案示例:
cpp复制int age;
std::string name;
std::cout << "请输入年龄: ";
std::cin >> age;
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n'); // 清除换行符
std::cout << "请输入姓名: ";
std::getline(std::cin, name); // 现在可以正确读取整行
7.2 文件操作最佳实践
文件操作时应注意:
- 总是检查文件是否成功打开
- 使用RAII技术确保文件会被关闭
- 处理可能的中途失败情况
使用现代C++技术改进文件处理:
cpp复制#include <memory>
void processFile(const std::string& filename) {
auto fileDeleter = [](std::FILE* f) { if(f) fclose(f); };
std::unique_ptr<std::FILE, decltype(fileDeleter)> file(
std::fopen(filename.c_str(), "rb"), fileDeleter);
if(!file) {
throw std::runtime_error("无法打开文件");
}
// 使用file.get()操作文件
// 退出作用域时文件会自动关闭
}
7.3 跨平台兼容性问题
不同平台在文本文件处理上的差异:
- 换行符:Windows使用"\r\n",Unix使用"\n"
- 文件路径分隔符:Windows用"",Unix用"/"
- 文本模式与二进制模式的行为差异
编写跨平台代码的建议:
cpp复制#ifdef _WIN32
const char PATH_SEP = '\\';
#else
const char PATH_SEP = '/';
#endif
std::string buildPath(const std::string& dir, const std::string& file) {
return dir + PATH_SEP + file;
}
// 处理文本文件时明确指定打开模式
std::ofstream textFile("data.txt", std::ios::out | std::ios::text);
8. C++17/20中的IO改进
8.1 文件系统库
C++17引入了<filesystem>库,大大简化了文件操作:
cpp复制#include <filesystem>
namespace fs = std::filesystem;
void demoFilesystem() {
// 创建目录
fs::create_directory("test_dir");
// 复制文件
fs::copy("source.txt", "test_dir/destination.txt");
// 遍历目录
for(const auto& entry : fs::directory_iterator(".")) {
std::cout << entry.path() << std::endl;
}
}
8.2 格式化库(C++20)
C++20的<format>库提供了更强大的格式化能力:
cpp复制#include <format>
std::cout << std::format("Hello, {}! The answer is {:.2f}\n",
"World", 3.14159);
8.3 范围适配器与视图
C++20的范围库可以与IO流结合,创建强大的数据处理管道:
cpp复制#include <ranges>
#include <algorithm>
// 读取文件,过滤空行,转换为大写
std::ifstream file("data.txt");
auto lines = std::ranges::istream_view<std::string>(file)
| std::views::filter([](const auto& s) { return !s.empty(); })
| std::views::transform([](std::string s) {
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
return s;
});
for(const auto& line : lines) {
std::cout << line << '\n';
}
9. 性能测试与比较
9.1 不同IO方式的性能对比
下表比较了不同IO方法的性能特点:
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 标准流操作符(<<, >>) | 常规文本IO | 类型安全,易用 | 性能较低 |
| C风格printf/scanf | 高性能文本IO | 执行速度快 | 类型不安全 |
| 二进制读写 | 大数据量 | 最高效 | 无格式检查 |
| 内存映射文件 | 超大文件 | 零拷贝 | 实现复杂 |
9.2 IO优化实践
在实际项目中优化IO性能的经验:
- 减少小量多次的IO操作,改为批量处理
- 使用
std::ios::sync_with_stdio(false)关闭与C流的同步 - 对于大量数据,考虑使用内存映射文件
- 在多线程环境中,为每个线程使用独立的流对象
cpp复制// 关闭同步提升性能(代价是不能混合使用C和C++ IO)
std::ios::sync_with_stdio(false);
// 解除cin与cout的绑定提升性能
std::cin.tie(nullptr);
10. 实战案例:日志系统设计
10.1 基本日志类实现
结合所学知识,我们可以实现一个简单的日志系统:
cpp复制#include <fstream>
#include <memory>
#include <mutex>
class Logger {
public:
enum class Level { Debug, Info, Warning, Error };
Logger(const std::string& filename)
: file_(filename, std::ios::app), minLevel_(Level::Info) {}
void setMinLevel(Level level) { minLevel_ = level; }
template<typename... Args>
void log(Level level, Args&&... args) {
if(level < minLevel_) return;
std::lock_guard<std::mutex> lock(mutex_);
file_ << getLevelStr(level) << ": ";
(file_ << ... << std::forward<Args>(args)) << std::endl;
}
private:
std::string getLevelStr(Level level) {
switch(level) {
case Level::Debug: return "DEBUG";
case Level::Info: return "INFO";
case Level::Warning: return "WARNING";
case Level::Error: return "ERROR";
default: return "UNKNOWN";
}
}
std::ofstream file_;
Level minLevel_;
std::mutex mutex_;
};
// 使用示例
Logger logger("app.log");
logger.log(Logger::Level::Info, "Application started, version ", 1.0);
10.2 高级日志功能扩展
可以进一步扩展日志系统功能:
- 添加时间戳
- 支持日志文件滚动
- 添加线程ID信息
- 支持网络日志传输
cpp复制#include <chrono>
#include <iomanip>
// 在log方法中添加时间戳
auto now = std::chrono::system_clock::now();
auto now_time = std::chrono::system_clock::to_time_t(now);
file_ << std::put_time(std::localtime(&now_time), "%Y-%m-%d %H:%M:%S") << " ";
11. 自定义流实现
11.1 继承标准流类
通过继承标准流类可以实现自定义的流行为。例如,创建一个统计行数的输出流:
cpp复制class LineCountingStream : public std::ostream {
class LineCountingBuffer : public std::streambuf {
public:
LineCountingBuffer(std::streambuf* sbuf) : sbuf_(sbuf), lineCount_(0) {}
int_type overflow(int_type c) override {
if(c == '\n') ++lineCount_;
return sbuf_->sputc(c);
}
int getLineCount() const { return lineCount_; }
private:
std::streambuf* sbuf_;
int lineCount_;
};
public:
LineCountingStream(std::ostream& os)
: std::ostream(new LineCountingBuffer(os.rdbuf())) {}
~LineCountingStream() { delete rdbuf(); }
int getLineCount() const {
return static_cast<LineCountingBuffer*>(rdbuf())->getLineCount();
}
};
// 使用示例
LineCountingStream lcs(std::cout);
lcs << "Hello\nWorld\n";
std::cout << "Lines written: " << lcs.getLineCount() << std::endl;
11.2 实现自定义streambuf
更底层的自定义可以通过继承std::streambuf实现。例如,创建一个将输出同时写入多个流的缓冲器:
cpp复制class TeeBuffer : public std::streambuf {
public:
TeeBuffer(std::streambuf* sbuf1, std::streambuf* sbuf2)
: sbuf1_(sbuf1), sbuf2_(sbuf2) {}
protected:
virtual int overflow(int c) override {
if(c != EOF) {
sbuf1_->sputc(c);
sbuf2_->sputc(c);
}
return c;
}
virtual int sync() override {
sbuf1_->pubsync();
sbuf2_->pubsync();
return 0;
}
private:
std::streambuf* sbuf1_;
std::streambuf* sbuf2_;
};
class TeeStream : public std::ostream {
public:
TeeStream(std::ostream& o1, std::ostream& o2)
: std::ostream(&buf_), buf_(o1.rdbuf(), o2.rdbuf()) {}
private:
TeeBuffer buf_;
};
// 使用示例
std::ofstream logFile("output.log");
TeeStream tee(std::cout, logFile);
tee << "这条消息会同时显示在屏幕和日志文件中\n";
12. 国际化与本地化支持
12.1 使用locale处理本地化
C++的locale机制可以处理不同地区的数字、日期、货币等格式:
cpp复制#include <locale>
void demoLocale() {
// 使用系统默认locale
std::cout.imbue(std::locale(""));
double value = 1234567.89;
std::cout << "本地化数字格式: " << value << std::endl;
// 特定地区的locale
std::cout.imbue(std::locale("de_DE"));
std::cout << "德国数字格式: " << value << std::endl;
std::cout.imbue(std::locale("en_US"));
std::cout << "美国数字格式: " << value << std::endl;
}
12.2 宽字符流支持
对于需要处理Unicode或多字节字符的场景,C++提供了宽字符流:
cpp复制#include <locale>
#include <codecvt>
void demoWideChar() {
std::locale::global(std::locale(""));
std::wcout.imbue(std::locale());
std::wstring ws = L"中文测试";
std::wcout << L"宽字符输出: " << ws << std::endl;
// 宽窄字符转换
std::wstring_convert<std::codecvt_utf8<wchar_t>> converter;
std::string narrow = converter.to_bytes(ws);
std::wstring wide = converter.from_bytes("更多测试");
}
13. 异常安全与资源管理
13.1 RAII在IO中的应用
使用RAII(Resource Acquisition Is Initialization)模式确保资源正确释放:
cpp复制class FileHandle {
public:
explicit FileHandle(const std::string& filename,
std::ios::openmode mode = std::ios::in)
: file_(filename, mode) {
if(!file_) {
throw std::runtime_error("无法打开文件: " + filename);
}
}
~FileHandle() {
if(file_.is_open()) {
file_.close();
}
}
// 禁止拷贝
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// 允许移动
FileHandle(FileHandle&&) = default;
FileHandle& operator=(FileHandle&&) = default;
std::fstream& get() { return file_; }
private:
std::fstream file_;
};
// 使用示例
try {
FileHandle fh("important.dat", std::ios::out | std::ios::binary);
fh.get() << "关键数据";
// 文件会在fh离开作用域时自动关闭
} catch(const std::exception& e) {
std::cerr << "错误: " << e.what() << std::endl;
}
13.2 事务性文件操作
对于需要原子性保证的操作,可以采用临时文件+重命名的方式:
cpp复制bool atomicWrite(const std::string& filename, const std::string& content) {
std::string tempName = filename + ".tmp";
try {
{
std::ofstream temp(tempName, std::ios::binary);
if(!temp) return false;
temp << content;
if(!temp) return false;
} // 确保文件被关闭
// 在Windows上需要先删除目标文件
std::remove(filename.c_str());
if(std::rename(tempName.c_str(), filename.c_str()) != 0) {
std::remove(tempName.c_str());
return false;
}
return true;
} catch(...) {
std::remove(tempName.c_str());
return false;
}
}
14. 多线程环境下的IO处理
14.1 线程安全的IO策略
在多线程环境中使用IO流需要注意:
- 标准流对象本身不是线程安全的
- 每个线程使用独立的流对象是最简单的解决方案
- 对于共享流,必须使用互斥锁保护
线程安全的日志类示例:
cpp复制class ThreadSafeLogger {
public:
ThreadSafeLogger(std::ostream& os) : os_(os) {}
template<typename T>
ThreadSafeLogger& operator<<(const T& msg) {
std::lock_guard<std::mutex> lock(mutex_);
os_ << msg;
return *this;
}
// 处理endl等特殊操纵符
ThreadSafeLogger& operator<<(std::ostream& (*manip)(std::ostream&)) {
std::lock_guard<std::mutex> lock(mutex_);
manip(os_);
return *this;
}
private:
std::ostream& os_;
std::mutex mutex_;
};
// 使用示例
ThreadSafeLogger safeCout(std::cout);
void worker(int id) {
safeCout << "线程" << id << "开始工作\n";
}
std::vector<std::thread> threads;
for(int i=0; i<5; ++i) {
threads.emplace_back(worker, i);
}
for(auto& t : threads) t.join();
14.2 异步IO技术
对于高性能应用,可以考虑使用异步IO技术:
cpp复制#include <future>
#include <vector>
std::future<void> asyncWrite(const std::string& filename,
const std::vector<char>& data) {
return std::async(std::launch::async, [=] {
std::ofstream file(filename, std::ios::binary);
file.write(data.data(), data.size());
});
}
// 使用示例
std::vector<char> largeData(1024*1024, 'A'); // 1MB数据
auto future = asyncWrite("largefile.bin", largeData);
// 可以继续做其他工作...
future.wait(); // 等待写入完成
15. 性能敏感场景的IO优化
15.1 减少系统调用
系统调用是IO操作的主要性能瓶颈。减少系统调用的策略包括:
- 使用大缓冲区
- 批量读写代替多次小量IO
- 使用
std::ios::sync_with_stdio(false)关闭同步
设置大缓冲区示例:
cpp复制std::ofstream file("large.dat");
char buffer[1024*1024]; // 1MB缓冲区
file.rdbuf()->pubsetbuf(buffer, sizeof(buffer));
15.2 内存映射文件
对于超大文件处理,内存映射文件通常是最快的方式。虽然C++标准库没有直接支持,但可以使用平台特定API:
cpp复制#ifdef _WIN32
#include <windows.h>
#else
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#endif
class MappedFile {
public:
MappedFile(const std::string& filename) {
#ifdef _WIN32
hFile_ = CreateFileA(filename.c_str(), GENERIC_READ,
FILE_SHARE_READ, NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, NULL);
if(hFile_ == INVALID_HANDLE_VALUE) throw std::runtime_error("无法打开文件");
hMapping_ = CreateFileMapping(hFile_, NULL, PAGE_READONLY, 0, 0, NULL);
if(!hMapping_) {
CloseHandle(hFile_);
throw std::runtime_error("无法创建文件映射");
}
data_ = static_cast<const char*>(MapViewOfFile(hMapping_, FILE_MAP_READ, 0, 0, 0));
if(!data_) {
CloseHandle(hMapping_);
CloseHandle(hFile_);
throw std::runtime_error("无法映射文件视图");
}
// 获取文件大小
DWORD high;
size_ = GetFileSize(hFile_, &high);
size_ |= (static_cast<size_t>(high) << 32);
#else
fd_ = open(filename.c_str(), O_RDONLY);
if(fd_ == -1) throw std::runtime_error("无法打开文件");
struct stat sb;
if(fstat(fd_, &sb) == -1) {
close(fd_);
throw std::runtime_error("无法获取文件状态");
}
size_ = sb.st_size;
data_ = static_cast<const char*>(
mmap(NULL, size_, PROT_READ, MAP_PRIVATE, fd_, 0));
if(data_ == MAP_FAILED) {
close(fd_);
throw std::runtime_error("无法映射文件");
}
#endif
}
~MappedFile() {
#ifdef _WIN32
UnmapViewOfFile(data_);
CloseHandle(hMapping_);
CloseHandle(hFile_);
#else
munmap(const_cast<char*>(data_), size_);
close(fd_);
#endif
}
const char* data() const { return data_; }
size_t size() const { return size_; }
private:
const char* data_ = nullptr;
size_t size_ = 0;
#ifdef _WIN32
HANDLE hFile_ = INVALID_HANDLE_VALUE;
HANDLE hMapping_ = NULL;
#else
int fd_ = -1;
#endif
};
// 使用示例
MappedFile mf("largefile.bin");
std::cout << "文件大小: " << mf.size() << "字节\n";
// 可以直接访问mf.data()作为只读内存区域
16. 错误处理与调试技巧
16.1 流状态深度解析
理解流状态机对于调试IO问题至关重要。流状态由以下标志位组成:
goodbit:无错误 (0)eofbit:到达文件末尾 (1)failbit:逻辑错误 (2)badbit:不可恢复错误 (4)
检查状态标志的底层方法:
cpp复制std::ifstream file("data.txt");
file >> someVar;
if(file.rdstate() & std::ios::failbit) {
std::cerr << "逻辑错误发生\n";
}
// 重置状态
file.clear(file.rdstate() & ~std::ios::failbit);
16.2 调试IO问题的工具与技术
调试IO问题的有效方法:
- 检查流状态和错误信息
- 使用
tellg()/tellp()获取当前位置 - 验证文件权限和路径
- 检查系统错误代码
获取系统错误信息的跨平台方法:
cpp复制#include <system_error>
std::string getLastError() {
#ifdef _WIN32
DWORD err = GetLastError();
LPSTR msg = nullptr;
FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
(LPSTR)&msg, 0, NULL);
std::string result(msg ? msg : "未知错误");
LocalFree(msg);
return result;
#else
return std::system_category().message(errno);
#endif
}
// 使用示例
std::ofstream file("/nonexistent/path/file.txt");
if(!file) {
std::cerr << "无法打开文件: " << getLastError() << std::endl;
}
17. C++23中的IO新特性展望
17.1 格式化输出改进
C++23将进一步增强格式化能力,包括:
- 更灵活的格式字符串
- 更好的类型支持
- 性能优化
cpp复制// C++23预览(可能变化)
std::cout << std::format("{:?}", someObject); // 调试格式
std::cout << std::format("{:%F %T}", system_clock::now()); // 日期时间
17.2 协程与异步IO
C++20引入的协程特性为异步IO提供了新的可能性:
cpp复制#include <coroutine>
#include <future>
async_io_task readFileAsync(const std::string& filename) {
// 使用协程实现异步文件读取
co_await async_read_operation(filename);
// ...
}
18. 跨语言IO交互
18.1 与C标准IO交互
C++流可以与C标准IO函数混合使用,但需要注意同步问题:
cpp复制#include <cstdio>
void demoCMixing() {
std::ios::sync_with_stdio(true); // 启用同步
printf("使用printf输出\n");
std::cout << "使用cout输出\n";
FILE* f = fopen("data.txt", "r");
if(f) {
char buffer[256];
while(fgets(buffer, sizeof(buffer), f)) {
std::cout << "读取到: " << buffer;
}
fclose(f);
}
}
18.2 与其他语言的数据交换
常见的数据交换格式:
- 文本格式:CSV、JSON、XML
- 二进制格式:Protocol Buffers、FlatBuffers
- 序列化框架:Boost.Serialization、cereal
JSON处理示例(使用第三方库):
cpp复制#include <nlohmann/json.hpp>
void demoJSON() {
nlohmann::json j;
j["name"] = "John";
j["age"] = 30;
j["scores"] = {95, 87, 92};
// 写入文件
std::ofstream("data.json") << j.dump(2);
// 从文件读取
std::ifstream in("data.json");
nlohmann::json j2;
in >> j2;
std::cout << j2["name"] << std::endl;
}
19. 设计模式在IO中的应用
19.1 装饰器模式扩展流功能
使用装饰器模式可以灵活地扩展流功能:
cpp复制class LoggingStream : public std::ostream {
class LoggingBuffer : public std::streambuf {
public:
LoggingBuffer(std::ostream& stream, std::ostream& log)
: stream_(stream), log_(log) {}
protected:
int overflow(int c) override {
if(c != EOF) {
stream_.put(c);
log_.put(c);
}
return c;
}
int sync() override {
stream_.flush();
log_.flush();
return 0;
}
private:
std::ostream& stream_;
std::ostream& log_;
};
public:
LoggingStream(std::ostream& stream, std::ostream& log)
: std::ostream(new LoggingBuffer(stream, log)) {}
~LoggingStream() { delete rdbuf(); }
};
// 使用示例
std::ofstream logFile("command.log");
LoggingStream lcout(std::cout, logFile);
lcout << "这条消息会同时显示在屏幕和日志中\n";
19.2 策略模式实现多种IO策略
使用策略模式可以动态切换IO实现:
cpp复制class IOWriter {
public:
virtual ~IOWriter() = default;
virtual void write(const std::string& data) = 0;
};
class FileWriter : public IOWriter {
public:
explicit FileWriter(const std::string& filename)
: file_(filename) {}
void write(const std::string& data) override {
file_ << data;
}
private:
std::ofstream file_;
};
class NetworkWriter : public IOWriter {
// 实现网络写入...
};
class Application {
public:
void setWriter(std::unique_ptr<IOWriter> writer) {
writer_ = std::move(writer);
}
void doWork() {
writer_->write("应用程序数据...");
}
private:
std::unique_ptr<IOWriter> writer_;
};
// 使用示例
Application app;
app.setWriter(std::make_unique<FileWriter>("output.txt"));
app.doWork();
20. 安全注意事项
20.1 防止路径注入攻击
处理文件路径时的安全注意事项:
- 验证用户提供的路径
- 规范化路径防止目录遍历攻击
- 限制文件权限
路径规范化示例:
cpp复制#include <filesystem>
std::string safePath(const std::string& baseDir, const std::string& userInput) {
namespace fs = std::filesystem;
try {
// 组合路径并规范化
fs::path fullPath = fs::canonical(baseDir) / userInput;
// 验证结果路径仍在基目录下
if(fullPath.string().find(fs::canonical(baseDir).string()) != 0) {
throw std::runtime_error("非法路径访问");
}
return fullPath.string();
} catch(const fs::filesystem_error& e) {
throw std::runtime_error("路径处理错误: " + std::string(e.what()));
}
}
20.2 安全的临时文件处理
创建临时文件的最佳实践:
- 使用系统提供的临时目录
- 生成不可预测的文件名
- 设置适当的文件权限
cpp复制#include <random>
#include <filesystem>
std::string createTempFile() {
namespace fs = std::filesystem;
//
