13.IO流与文件
目录介绍
- 13.1 输入输出介绍
- 13.1.1 输入输出流
- 13.1.2 I/O库头文件
- 13.1.3 标准输出流cout
- 13.1.4 标准输入流cin
- 13.1.5 标准错误流cerr
- 13.1.6 标准日志流clog
- 13.2 文件操作实践
- 13.2.1 文件是什么
- 13.2.2 文件类型分类
- 13.2.3 操作文件类
- 13.2.4 文本文件写
- 13.2.5 文本文件读
- 13.2.6 二进制文件写
- 13.2.7 二进制文件读
- 13.3 文件读写指针
- 13.3.1 文件错误与状态
- 13.3.2 文件的追加
- 13.3.3 文件结尾的判断
- 13.3.4 在指定位置读/写文件
- 13.4 IO综合案例
- 13.4.1 键盘输入写文本文件
- 13.4.2 实现文件复制
- 13.4.3 合并两个文件信息
- 13.4.4 文件加密
- 13.4.5 在文件中查找关键词
13.1 输入输出介绍
13.1.1 输入输出流
流是字节序列。如果字节流是从设备流向内存,这叫做输入操作。如果字节流是从内存流向设备,这叫做输出操作。
学过 C 语言的读者应该知道,它有一整套完成数据读写(I/O)的解决方案:
- 使用 scanf()、gets() 等函数从键盘读取数据,使用 printf()、puts() 等函数向屏幕上输出数据;
- 使用 fscanf()、fgets() 等函数读取文件中的数据,使用 fprintf()、fputs() 等函数向文件中写入数据。
C 语言的这套 I/O 解决方案也适用于 C++ 程序,但 C++ 并没有“偷懒”,它自己独立开发了一套全新的 I/O 解决方案,其中就包含大家一直使用的 cin 和 cout。
- 用 cin 接收从键盘输入的数据,用 cout 向屏幕上输出数据(这 2 个过程又统称为“标准 I/O”)。
- 除此之外,C++ 也对从文件中读取数据和向文件中写入数据做了支持(统称为“文件 I/O”)。
13.1.2 I/O库头文件
ios 是所有流类的基类,它派生出 istream 和 ostream。
特别需要指出的是,为了避免多继承的二义性,从 ios 派生出 istream 和 ostream 时,均使用了 virtual 关键字(虚继承)。
这些流类各自的功能分别为:
- istream:常用于接收从键盘输入的数据;
- ostream:常用于将数据输出到屏幕上;
- ifstream:用于读取文件中的数据;
- ofstream:用于向文件中写入数据;
- iostream:继承自 istream 和 ostream 类,因为该类的功能兼两者于一身,既能用于输入,也能用于输出;
- fstream:兼 ifstream 和 ofstream 类功能于一身,既能读取文件中的数据,又能向文件中写入数据。
<iostream>
分别对应于标准输入流、标准输出流。<iomanip>
声明对执行标准化 I/O 有用的服务。<fstream>
为用户控制的文件处理声明服务。
13.1.3 标准输出流cout
在C++中,cout是标准输出流对象,用于向控制台或终端输出数据。它是iostream库中的一部分,可以通过包含头文件<iostream>
来使用。
使用cout进行输出非常简单,只需使用<<
运算符将要输出的数据插入到cout对象中即可。以下是一些示例用法:
#include <iostream>
int main() {
int number = 42;
double pi = 3.14159;
std::string message = "Hello, world!";
std::cout << "This is a number: " << number << std::endl;
std::cout << "This is pi: " << pi << std::endl;
std::cout << "This is a message: " << message << std::endl;
return 0;
}
cout对象可以与各种数据类型一起使用,包括基本数据类型(如整数、浮点数、字符等)和自定义类型(如字符串、数组、对象等)。
需要注意的是,cout是一个全局对象,属于std命名空间。因此,在使用cout之前,我们需要使用std::前缀来指定命名空间,或者使用using namespace std;
语句来引入整个std命名空间。
13.1.4 标准输入流cin
在C++中,cin是标准输入流对象,用于从控制台或终端读取用户输入的数据。它也是iostream库的一部分,可以通过包含头文件<iostream>
来使用。
使用cin进行输入非常简单,只需使用>>
运算符将输入的数据存储到相应的变量中。以下是一些示例用法:
#include <iostream>
int main() {
int number;
double pi;
std::string message;
std::cout << "Enter a number: ";
std::cin >> number;
std::cout << "Enter the value of pi: ";
std::cin >> pi;
std::cout << "Enter a message: ";
std::cin.ignore(); // 忽略之前的换行符
std::getline(std::cin, message);
std::cout << "You entered: " << number << ", " << pi << ", " << message << std::endl;
return 0;
}
使用cin对象从用户那里读取了一个整数、一个浮点数和一行字符串。每个输入都使用>>运算符将输入的数据存储到相应的变量中。std::getline函数用于读取一行字符串,而std::cin.ignore()函数用于忽略之前的换行符。
13.1.5 标准错误流cerr
cerr是标准错误流对象,用于向控制台或终端输出错误信息。它也是iostream库的一部分,可以通过包含头文件<iostream>
来使用。
使用cerr进行输出也非常简单,只需使用<<
运算符将要输出的错误信息插入到cerr对象中即可。以下是一个示例用法:
#include <iostream>
int main() {
int dividend = 10;
int divisor = 0;
if (divisor == 0) {
std::cerr << "Error: Division by zero!" << std::endl;
} else {
int result = dividend / divisor;
std::cout << "Result: " << result << std::endl;
}
return 0;
}
进行了一个除法运算,但是当除数为零时,会输出一个错误信息到cerr对象中。这样可以将错误信息与正常输出分开,使其更易于识别和处理。
与cout不同的是,cerr对象的输出通常会直接显示在控制台或终端的错误输出流中,而不会被重定向或缓冲。这使得cerr适用于输出紧急或重要的错误信息,以便及时通知用户。
13.1.6 标准日志流clog
clog是标准日志流对象,用于向控制台或终端输出日志信息。它也是iostream库的一部分,可以通过包含头文件<iostream>
来使用。
#include <iostream>
int main() {
int value = 42;
std::clog << "This is a log message: " << value << std::endl;
return 0;
}
使用clog对象输出了一个日志信息,其中包含一个整数值。与cout和cerr不同,clog对象的输出通常会被缓冲,这意味着日志信息可能不会立即显示在控制台或终端上,而是在适当的时机进行刷新。
clog对象通常用于输出一般的日志信息,既不像cout那样用于正常输出,也不像cerr那样用于错误信息。它可以用于记录程序的运行状态、调试信息或其他重要的日志记录。
13.2 文件操作实践
13.2.1 文件是什么
内存中存放的数据在计算机关机后就会消失。要长久保存数据,就要使用硬盘、光盘、U 盘等设备。为了便于数据的管理和检索,引入了“文件”的概念。
成千上万个文件如果不加分类放在一起,用户使用起来显然非常不便,因此又引入了树形目录(目录也叫文件夹)的机制,可以把文件放在不同的文件夹中,文件夹中还可以嵌套文件夹,这就便于用户对文件进行管理和使用。
所谓“格式”,就是关于文件中每一部分的内容代表什么含义的一种约定。 例如,常见的纯文本文件(也叫文本文件,扩展名通常是“.txt”),指的是能够在 Windows 的“记事本”程序中打开,并且能看出是一段有意义的文字的文件。文本文件的格式可以用一句话来描述:文件中的每个字节都是一个可见字符的 ASCII 码。
所谓“文本文件”和“二进制文件”,只是约定俗成的、从计算机用户角度出发进行的分类,并不是计算机科学的分类。因为从计算机科学的角度来看,所有的文件都是由二进制位组成的,都是二进制文件。文本文件和其他二进制文件只是格式不同而已。
13.2.2 文件类型分类
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放。
通过文件可以将数据持久化
C++中对文件操作需要包含头文件 ==< fstream >==
文件类型分为两种:
- 文本文件 —— 文件以文本的ASCII码形式存储在计算机中
- 二进制文件 —— 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们
13.2.3 操作文件类
C++ 标准库中还专门提供了 3 个类用于实现文件操作,它们统称为文件流类,这 3 个类分别为:
- ifstream:专用于从文件中读取数据;
- ofstream:专用于向文件中写入数据;
- fstream:既可用于从文件中读取数据,又可用于向文件中写入数据。
13.2.4 文本文件写
写文件步骤如下:
- 包含头文件 :#include <fstream>
- 创建流对象 :ofstream ofs;
- 打开文件 :ofs.open("文件路径",打开方式);
- 写数据 :ofs << "写入的数据";
- 关闭文件 :ofs.close();
文件打开方式:
打开方式 | 解释 |
---|---|
ios::in | 为读文件而打开文件 |
ios::out | 为写文件而打开文件 |
ios::ate | 初始位置:文件尾 |
ios::app | 追加方式写文件 |
ios::trunc | 如果文件存在先删除,再创建 |
ios::binary | 二进制方式 |
注意: 文件打开方式可以配合使用,利用|操作符
**例如:**用二进制方式写文件 ios::binary | ios:: out
示例:
#include <fstream>
void test1() {
ofstream ofs;
ofs.open("2_4.txt" ,ios::out);
ofs << "姓名:张三" << endl;
ofs << "性别:男" << endl;
ofs << "年龄:18" << endl;
ofs.close();
}
int main() {
test1();
return 0;
}
总结:
- 文件操作必须包含头文件 fstream
- 读文件可以利用 ofstream ,或者fstream类
- 打开文件时候需要指定操作文件的路径,以及打开方式
- 利用<<可以向文件中写数据
- 操作完毕,要关闭文件
13.2.5 文本文件读
读文件与写文件步骤相似,但是读取方式相对于比较多。读文件步骤如下:
- 包含头文件 :#include <fstream>
- 创建流对象 :ifstream ifs;
- 打开文件并判断文件是否打开成功 :ifs.open("文件路径",打开方式);
- 读数据 :四种方式读取
- 关闭文件 :ifs.close();
示例:
#include "fstream"
#include "string"
#include "iostream"
using namespace std;
void test() {
ifstream ifs;
ifs.open("text.txt", ios::in);
if (!ifs.is_open()) {
cout << "文件打开失败" << endl;
return;
}
//第一种方式
char buf[1024] = {0};
while (ifs >> buf) {
cout << buf << endl;
}
//第二种
// char buf[1024] = {0};
// while (ifs.getline(buf, sizeof(buf))) {
// cout << buf << endl;
// }
//第三种
// string buf;
// while (getline(ifs, buf)) {
// cout << buf << endl;
// }
// char c;
// while ((c = ifs.get()) != EOF) {
// cout << c;
// }
ifs.close();
}
int main() {
test();
return 0;
}
总结:
- 读文件可以利用 ifstream ,或者fstream类
- 利用is_open函数可以判断文件是否打开成功
- close 关闭文件
13.2.6 二进制文件写
以二进制的方式对文件进行读写操作。打开方式要指定为 ==ios::binary==
二进制方式写文件主要利用流对象调用成员函数write
函数原型 :ostream& write(const char * buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
示例:
#include "fstream"
#include "string"
using namespace std;
class Person {
public:
char name[64];
int age;
};
void test() {
//1、包含头文件
//2、创建输出流对象
ofstream ofs("person.txt", ios::out | ios::binary);
//3、打开文件
//ofs.open("person.txt", ios::out | ios::binary);
Person p = {"张三" , 18};
//4、写文件
ofs.write((const char *)&p, sizeof(p));
//5、关闭文件
ofs.close();
}
int main() {
test();
return 0;
}
总结:
- 文件输出流对象 可以通过write函数,以二进制方式写数据
13.2.7 二进制文件读
二进制方式读文件主要利用流对象调用成员函数read
函数原型:istream& read(char *buffer,int len);
参数解释:字符指针buffer指向内存中一段存储空间。len是读写的字节数
示例:
#include "fstream"
#include "string"
#include "iostream"
using namespace std;
class Person {
public:
char name[64];
int age;
};
void test() {
//2、创建输入流对象
ifstream ifs("person.txt", ios::in | ios::binary);
if (!ifs.is_open()) {
cout << "文件打开失败" << endl;
return;
}
//3、打开文件
//ofs.open("person.txt", ios::in | ios::binary);
Person p;
//4、读文件
ifs.read((char *)&p, sizeof(p));
//5、关闭文件
ifs.close();
cout << "姓名: " << p.name << " 年龄: " << p.age << endl;
}
int main() {
test();
return 0;
}
文件输入流对象 可以通过read函数,以二进制方式读数据
13.3 文件读写指针
13.3.1 文件错误与状态
std::fstream类中定义了一些成员函数和状态标志,用于检测和处理文件错误和状态。以下是一些常用的状态标志和相关的成员函数:
- fail():检测文件操作是否失败。返回true表示失败,返回false表示成功。
- bad():检测文件流是否处于错误状态。返回true表示错误,返回false表示正常。
- eof():检测文件流是否到达文件末尾。返回true表示到达末尾,返回false表示未到达末尾。
- good():检测文件流是否处于正常状态。返回true表示正常,返回false表示出现错误。
- clear():清除文件流的错误状态标志。
- rdstate():返回当前文件流的状态标志。
- setstate():设置文件流的状态标志。
通过使用这些成员函数和状态标志,可以检测和处理文件操作中的错误和状态。例如,可以使用fail()函数来检测文件读取操作是否失败,然后使用clear()函数来清除错误状态标志。
void test() {
cout << "文件错误与状态" << endl;
std::ifstream file("example.txt");
//std::ifstream file("yc.txt");
if (!file) {
//我们检测文件是否成功打开,如果打开失败,则输出错误信息并返回。
std::cerr << "Failed to open the file." << std::endl;
return;
}
int num;
//然后,我们使用while循环从文件中读取整数,并检测读取操作是否失败。如果失败,则输出错误信息并清除错误状态标志。
while (file >> num) {
if (file.fail()) {
std::cerr << "Error reading the file." << std::endl;
file.clear(); // 清除错误状态标志
break;
}
std::cout << num << " ";
}
if (file.eof()) {
//最后,我们检测是否到达文件末尾,并关闭文件。
std::cout << std::endl << "End of file reached." << std::endl;
}
file.close();
std::cerr << "file to close" << std::endl;
//在上述示例中,我们打开了一个名为example.txt的文件,并使用std::ifstream对象file进行读取操作。
}
int main() {
test();
return 0;
}
13.3.2 文件的追加
在C++中,可以使用文件流对象的std::ofstream类来实现文件的追加操作。可以创建新文件或打开已存在的文件进行写入。
要实现文件的追加,需要在打开文件时指定追加模式。可以使用std::ios::app标志来指定追加模式,它会将数据追加到文件的末尾而不是覆盖原有内容。
void test() {
cout << "文件的追加" << endl;
std::ofstream file("yc.txt" , std::ios::app);
if (!file) {
std::cerr << "Failed to open the file." << std::endl;
return;
}
file << "This is a new line." << std::endl;
file << "This is another line." << std::endl;
file.close();
std::ifstream inFile("yc.txt" , std::ios::binary | std::ios::in);
if (inFile.is_open()) {
int data[5];
inFile.read(reinterpret_cast<char*>(data), sizeof(data));
for (int i = 0; i < 5; i++) {
std::cout << data[i] << " " <<endl;
}
inFile.close();
std::cout << std::endl << "Binary file read successfully." << std::endl;
} else {
std::cout << "Failed to open the file for reading." << std::endl;
}
}
int main() {
test();
return 0;
}
13.3.3 文件结尾的判断
在C++中,可以使用文件流对象的eof()函数来判断文件是否已经到达结尾。eof()函数是std::istream和std::ostream类的成员函数,用于检测文件流是否已经到达文件末尾。
eof()函数返回一个bool值,如果文件流已经到达文件末尾,则返回true,否则返回false。
以下是一个示例,演示了如何使用eof()函数来判断文件是否已经到达结尾:
void test() {
cout << "文件结尾的判断" << endl;
std::ifstream file("yc.txt");
if (!file) {
//首先,我们检测文件是否成功打开,如果打开失败,则输出错误信息并返回。
std::cerr << "Failed to open the file." << std::endl;
return;
}
std::string line;
//使用std::getline()函数从文件中逐行读取数据,并将每行数据输出到控制台。
while (std::getline(file,line)) {
std::cout << line << std::endl;
}
if (file.eof()) {
//使用eof()函数检测文件是否已经到达结尾。如果到达结尾,则输出相应的提示信息。
std::cout << "End of file reached." << std::endl;
}
file.close();
}
int main() {
test();
return 0;
}
13.3.4 在指定位置读/写文件
在C++中,可以使用文件流对象的seekg()和seekp()函数来在指定位置进行文件的读取和写入操作。这两个函数用于设置文件流的读取和写入位置。
seekg()函数用于设置输入文件流的读取位置,而seekp()函数用于设置输出文件流的写入位置。这两个函数都接受一个参数,表示要设置的位置。
以下是一个示例,演示了如何在指定位置读取和写入文件:
void test() {
cout << "在指定位置读/写文件" << endl;
std::fstream file("yc.txt",std::ios::in | std::ios::out);
if (!file) {
std::cerr << "Failed to open the file." << std::endl;
}
cout << "修改前的文件内容" << endl;
string buf;
while (getline(file, buf)) {
cout << buf << endl;
}
// 在指定位置读取文件
file.seekg(5, std::ios::beg); // 从文件开头偏移5个字节
char ch;
file >> ch;
std::cout << "Character at position 5: " << ch << std::endl;
// 在指定位置写入文件
file.seekp(10, std::ios::beg); // 从文件开头偏移10个字节
file << "XYZ";
file.close();
}
int main() {
test();
return 0;
}
13.4 IO综合案例
13.4.1 键盘输入写文本文件
从控制面板输入信息,把这些内容写到指定的文本文件。第一步:获取键盘输入内容;第二步:将内容写入到对应文本文件。
int main() {
ofstream ofs;
ofs.open("yc.txt",ios::out);
//第一步,从键盘获取数据
//string data;
char data[64];
cout << "Writing to the file" << endl;
cout << "Enter your name: ";
//键盘输入数据
cin.getline(data, 100);
ofs << data << endl;
cout << "Enter your age: ";
cin >> data;
cin.ignore();
// 再次向文件写入用户输入的数据
ofs << data << endl;
cout << "Enter your book: ";
cin >> data;
cin.ignore();
ofs << data << endl;
ofs.close();
return 0;
}
13.4.2 实现文件复制
需求:读取一个文件的内容,然后复制到另一个文件中。
void test() {
//使用std::ifstream类打开源文件,并使用std::ofstream类创建目标文件。
//我们使用std::ios::binary标志来以二进制模式打开文件,以确保正确复制文件的内容。
std::ifstream source_file("yc.txt",std::ios::binary);
std::ofstream destination_file("ycdoubi.txt",std::ios::binary);
if (!source_file) {
std::cout << "无法打开源文件" << std::endl;
return;
}
if (!destination_file) {
std::cout << "无法创建目标文件" << std::endl;
return;
}
char ch;
//使用get函数从源文件逐个字节读取内容,并使用put函数将字节写入目标文件,从而实现文件的复制。这个过程会一直进行,直到源文件的末尾。
while (source_file.get(ch)) {
destination_file.put(ch);
}
//请注意,这个示例是逐个字节复制文件的简单实现。对于大型文件,逐个字节的复制可能效率较低。
//在实际应用中,您可以使用更高效的方法,如缓冲区复制或使用std::copy函数。
source_file.close();
destination_file.close();
std::cout << "文件复制完成" << std::endl;
}
int main() {
test();
return 0;
}