IO对象无拷贝或赋值
由于不能拷贝IO对象,因此我们也不能将形参或者返回类型设置为流类型。读写一个IO对象会改变其状态,因此传递和返回的引用是不能const的。
流的状态的查询
C++ 标准库中的 IO 流库提供了多种方法来帮助开发者查询流(stream)的状态。这些状态可以帮助确定流操作是否成功,以及在失败的情况下了解失败的原因。
- good()
如果流未遇到任何错误,即流状态良好(没有设置 failbit、badbit 或 eofbit),则 good() 返回 true。
通常用来检查一个序列的流操作是否都成功执行。 - eof()
如果在读取过程中达到了文件的末尾(设置了 eofbit),则 eof() 返回 true。常用于循环中,以确定是否已经读取到文件的末尾。 - fail()
如果流的一个操作因为逻辑错误而失败(例如,期望读取一个整数,但读取到了一个字母),或因为到达文件末尾而无法执行预期的读取操作,则 fail() 返回 true。这会设置 failbit 或 eofbit。用于检查一个读取或写入操作是否未能按预期执行。 - bad()
如果在读写过程中遇到严重的错误(如无法读取磁盘文件),导致流的一部分或全部无效,则 bad() 返回 true。这会设置 badbit。用来检查由于硬件故障或其他严重问题导致的流错误。
流的条件状态的管理
管理流的条件状态的方法有三个:
- rdstate()
返回一个表示当前流状态的位掩码。这个掩码可以与 ios_base::failbit, ios_base::eofbit, ios_base::badbit 等进行比较。用于获取流的详细状态,可以用于复杂的错误处理逻辑。 - setstate()
手动设置流的状态位。可以用来设置 failbit, badbit 或 eofbit。在某些特定情况下,需要人为改变流的状态时使用。 - clear()
用于清除所有错误状态位(默认情况下),或者将流的状态位设置为特定的状态。clear()是一个重载的成员,有两个版本,一个版本参数为空,另外一个版本接受一个iostate类型的参数。用于重置流的状态,例如在错误处理后继续使用流。
auto old_state=cin.rdstate();//记住cin当前的状态
cin.clear();//清除所有状态位
process_input(cin);
cin.setstate(old_state);//复位就状态
//复位failbit和badbit
cin.clear(cin.rdstate()&~cin.failbit&~cinbadbit);
流的关联
在 C++ 标准库中,可以使用 std::ios::tie() 方法来关联两个流。tie() 是 std::ios 类的一个成员函数,它的工作原理是:
std::ostream* tie() const;:返回当前流关联的输出流。
std::ostream* tie(std::ostream* tied_stream);:将当前输入流关联到指定的输出流。如果 tied_stream 是 nullptr,则解除任何现有的关联。
在 C++ 中,当我们讨论两个关联的流时,通常指的是一个输入流(如 std::istream)被关联到一个输出流(如 std::ostream)。这种关联的主要目的是在从输入流读取数据之前自动刷新输出流的缓冲区,以确保所有之前的输出都已经处理,使得程序的输出与用户的输入操作保持同步。
在大多数情况下,std::cin 默认与 std::cout 关联。这意味着在每次尝试从 std::cin 读取数据之前,std::cout 的缓冲区都会自动刷新。
#include <iostream>
int main() {
std::cout << "Enter a number: "; // 不立即刷新
int num;
std::cin >> num; // 在读取之前,cout 的内容被刷新
std::cout << "You entered: " << num << std::endl;
return 0;
}
unitbuf操作符
如果想在每次输出操作后都刷新缓冲区,我们可以使用unitbuf操作符。它告诉流在接下来的每次写操作之后都进行一次刷新操作。而nounitbuf操作符则重置流,使其恢复使用正常的缓冲区刷新机制
cout<<unitbuf;
cout<<nounitbuff;
文件操作
文件模式
每一个流都有一个关联的文件模式,它用来表示打开文件的方式。
打开方式 | 含义 |
---|---|
in | 以读方式打开 |
out | 以写方式打开 |
app | 每次写操作前均定位到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行IO |
out和trunc两种方式有相似之处,但是有很大的不同。out方式打开文件,如果文件不存在会创建一个文件,如果文件存在,会将指针指向文件的开头处进行写操作。而trunc方式打开文件会将文件中原有的内容清空。
ofstream out("file1");//使用输出流的时候默认使用out方式
ifstream in("file1");//使用输入流的时候默认使用in方式
fstream fs("file1",std::ios::out|std::ios::in);//使用文件流的时候没有默认方式,必须显式指明打开的方式
打开文件
当我们想要读写一个文件的时候,通常要定义一个文件流对象,然后将其与文件关联起来。
ifstream in(ifile);//使用文件名来创建一个文件流对象,这个过程会调用open系统调用
ofstream out;//没有提供文件名,但是可以在这时候显式的使用open()
读取文件内容
1. >> 运算符
这种方式类似于使用 std::cin。它适用于已知数据格式和数据类型的情况,可以直接读取基本数据类型(如整数、浮点数、字符等),那些定义了>>操作的类型也可以使用。
#include <fstream>
#include <iostream>
int main() {
std::ifstream file("data.txt");
int a;
double b;
char c;
if (file >> a >> b >> c) {
std::cout << "Read values: " << a << ", " << b << ", " << c << std::endl;
}
file.close();
return 0;
}
2. std::getline()
std::getline() 用于读取一行文本,直到遇到换行符。这对于读取一整行文本非常有用,特别是处理不规则数据或只需逐行处理的情况。
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream file("data.txt");
std::string line;
while (std::getline(file, line)) {
std::cout << line << std::endl;
}
file.close();
return 0;
}
3. read()
对于二进制文件或需要按字节读取的情况,可以使用 read() 方法。gcount() 方法返回最后一次非格式化输入操作读取的字节数。
#include <fstream>
#include <iostream>
#include <vector>
int main() {
std::ifstream file("data.bin", std::ios::binary);
std::vector<char> buffer(1024);
while (file.read(buffer.data(), buffer.size())) {
std::cout << "Read " << file.gcount() << " bytes." << std::endl;
}
file.close();
return 0;
}
对于 std::vector,data() 方法返回一个指向向量内存中第一个元素的指针。这使得你可以直接通过指针操作向量中的数据,而不是通过向量的标准接口。
4. 使用迭代器
istreambuf_iterator 是一个输入流缓冲区迭代器,它可以用于从文件中读取内容,并将其作为字符序列处理。
向文件中写入
1.使用插入运算符<<
这是最常见的方式,适用于写入格式化的文本数据。它类似于如和使用cout来向控制台输出数据。
#include<fstream>
#include<string>
int main(){
{
std::ofstream file("example.txt");
if(file){
file<<"Hello World!\n;
file<<"Number: "<<123<<'\n';
}
file.close();
}
}
2.使用成员函数write()
当需要写入二进制数据或者非格式数据的时候,使用write()是最好的选择。这个方法接受一个指向数据的指针和数据的字节数。第一个参数必须是一个执行字节数据的char指针。char类型的大小为1字节,这意味这char*能指向任意类型数据的字节流。
#include<fstream>
int main(){
std::ofstream file("data.bin",std::ios::binary);
if(file){
double data=3.1415926;
file.write(reinterpret_cast<char*>(&data), sizeof(data));
file.write()
}
}
在C++中,reinterpret_cast<char*>(&data) 是一个类型转换操作,用于将一个类型的指针转换为另一个类型的指针。这种转换并不改变指针本身的值(即内存地址),而是改变了编译器对该内存地址所指向数据的解释方式。
3. 使用流缓冲区迭代器
这种方法可以用于从容器或其他数据源直接复制数据到文件流。
#include <fstream>
#include <iterator>
#include <vector>
int main() {
std::vector<char> data = {'H', 'e', 'l', 'l', 'o'};
std::ofstream file("output.txt");
std::copy(data.begin(), data.end(), std::ostreambuf_iterator<char>(file));
file.close();
}
在C++中,进行文件写操作主要可以通过使用 ofstream 类(输出文件流),这是 库的一部分。通过不同的方式和技术,你可以以多种方法写入文件。下面是一些常用的文件写操作方式:
1. 使用插入运算符 (<<)
这是最常见的方法,适用于写入格式化的文本数据。它类似于如何使用 cout 来向控制台输出数据。
#include <fstream>
#include <string>
int main() {
std::ofstream file("example.txt");
if (file) {
file << "Hello, World!\n";
file << "Number: " << 123 << "\n";
}
file.close();
}
2. 使用成员函数 write()
当你需要写入二进制数据或非格式化数据时,write() 函数是最合适的选择。这个方法接受一个指向数据的指针和数据的字节数。
#include <fstream>
int main() {
std::ofstream file("data.bin", std::ios::binary);
if (file) {
double data = 3.14159;
file.write(reinterpret_cast<char*>(&data), sizeof(data));
}
file.close();
}
4. 使用std::copy
这种方法适用于从容器或数组中复制数据到文件流。通常与 std::ostream_iterator 结合使用,适用于文本数据。
#include <fstream>
#include <algorithm>
#include <vector>
int main() {
std::vector<std::string> lines = {"Line 1", "Line 2", "Line 3"};
std::ofstream file("example.txt");
std::copy(lines.begin(), lines.end(), std::ostream_iterator<std::string>(file, "\n"));
file.close();
}
5.使用格式化输出
首先在内存中构建整个输出内容,然后一次性写入文件。这在处理复杂的数据格式化时特别有用。
#include <fstream>
#include <sstream>
int main() {
std::ostringstream oss;
oss << "Hello, " << "World!" << std::endl;
oss << "Number: " << 123;
std::ofstream file("example.txt");
file << oss.str();
file.close();
}
string流
std::istringstream
std::istringstream 类似于输入文件流 std::ifstream,但它是从一个字符串读取数据。
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::string input = "123 45.67 Hello";
std::istringstream iss(input);
int intValue;
double doubleValue;
std::string stringValue;
iss >> intValue >> doubleValue >> stringValue;
std::cout << "Int: " << intValue << "\n"
<< "Double: " << doubleValue << "\n"
<< "String: " << stringValue << std::endl;
}
std::ostringstream
std::ostringstream 类似于输出文件流 std::ofstream,但它用于向一个字符串写入数据。
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::ostringstream oss;
oss << "Integer: " << 123 << "\n"
<< "Double: " << 3.14159 << "\n"
<< "String: " << "Hello World";
std::string output = oss.str();
std::cout << output << std::endl;
}
std::stringstream
std::stringstream 同时支持输入和输出操作,非常适合同时需要读取和写入的场景。
#include <iostream>
#include <sstream>
#include <string>
int main() {
std::stringstream ss;
ss << "Example 123 45.67";
std::string str;
int intValue;
double doubleValue;
ss >> str >> intValue >> doubleValue;
std::cout << "String: " << str << "\n"
<< "Integer: " << intValue << "\n"
<< "Double: " << doubleValue << std::endl;
// 再写入新的数据
ss << " New Data";
std::string completeString = ss.str();
std::cout << "Updated Stream: " << completeString << std::endl;
}
io流迭代器
istream_iterator
istream_iterator 用于从输入流中读取数据。它可以与任何标准输入流(如 std::cin、std::istringstream 等)一起使用。
这种迭代器将输入流转换为一个迭代器,通过使用它,可以用迭代器的方式访问流中的数据。通常与标准算法如 std::copy 结合使用,从流中读取数据直至达到EOF。
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec;
std::copy(std::istream_iterator<int>(std::cin),
std::istream_iterator<int>(),
std::back_inserter(vec));
}
std::istream_iterator 在C++中是一种非常强大的迭代器,它将输入流(如 std::cin 或其他 std::istream 对象)转换为一个可迭代的对象。
ostream_iterator
ostream_iterator 用于向输出流写入数据。它适用于任何标准输出流,例如 std::cout、std::ostringstream 等。
这种迭代器将输出流转换为一个迭代器接口,使得可以将数据直接输出到关联的流中。常见用法是与 std::copy 等算法结合,将容器中的数据输出到标准输出或其他输出流。
#include <iostream>
#include <iterator>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::copy(vec.begin(), vec.end(), std::ostream_iterator<int>(std::cout, " "));
}
std::ostream_iterator(std::cout, " ")是将一个可迭代对象处理成一个输出流,其中使用空格当作间隔符