C++文件操作与异常处理
1、文件操作
1.1 文件的概念
1.1.1 文件的定义
文件是数据的集合,是存储在计算机外部介质(如硬盘、U盘、光盘等)上的一组相关数据的集合。从用户的角度来看,文件是一个单独命名的、具有特定属性的数据集合。
在C++中,我们通过文件来实现数据的持久化存储和跨程序的数据共享。文件可以存储各种类型的数据,如文本、图像、音频、视频等。
从操作系统的角度来看,文件是文件系统中的一个基本单位,它由文件名、文件属性(如文件类型、大小、创建时间等)和实际存储的数据三部分组成。
总的来说,文件是一种结构化的数据组织形式,它独立于程序而存在,可以被多个程序访问和操作。在C++程序设计中,我们通过文件输入/输出操作来读取或写入文件中的数据,实现数据的持久化存储和共享。
1.1.2 文件的分类
根据文件内容的组织形式和存储方式,可以将文件分为以下几类:
-
文本文件
- 以ASCII码或Unicode等字符编码形式存储数据
- 每一行数据以换行符(如"\n")结尾
- 可以使用文本编辑器直接打开和编辑
- 常见的文本文件扩展名有.txt、.csv、.html等
-
二进制文件
- 以二进制形式存储数据,不可直接阅读
- 数据以字节为单位进行组织和存储
- 需要专门的程序来解析和处理其内容
- 常见的二进制文件扩展名有.bin、.dat、.exe等
-
格式化文件
- 具有特定的文件格式和数据组织结构
- 通常包含文件头、元数据和实际数据等部分
- 需要按照规定的格式进行读写和解析
- 常见的格式化文件有.doc、.pdf、.jpg、.mp3等
-
设备文件
- 操作系统中用于表示硬件设备的特殊文件
- 通过读写设备文件来与硬件设备进行通信
- 在Unix/Linux系统中,设备文件位于/dev目录下
-
目录文件
- 用于组织和管理其他文件的特殊文件
- 记录了目录下各个文件的属性信息
- 操作系统使用目录文件来实现文件系统的树形结构
在C++文件操作中,我们主要处理的是文本文件和二进制文件。根据实际需求选择合适的文件类型,并使用相应的读写方式进行操作。
1.1.3 文件的组织形式
文件的组织形式是指文件内部数据的存储和安排方式。合理的文件组织形式可以提高文件的读写效率和存储利用率。以下是几种常见的文件组织形式:
-
顺序组织
- 文件中的记录按照某种顺序依次存储
- 记录之间的顺序由其键值或其他属性决定
- 适合于对文件进行顺序访问和批量处理
- 插入、删除记录需要移动大量数据,效率较低
-
索引组织
- 在顺序文件的基础上,为文件建立一个或多个索引表
- 索引表中存储记录的键值和对应的文件指针
- 通过索引表可以快速定位和访问文件中的记录
- 适合于对文件进行随机访问和按键值查找记录
-
散列组织
- 根据记录的键值计算出其在文件中的存储位置
- 通过散列函数将键值映射到文件的某个块或桶中
- 支持按键值快速查找和访问记录
- 适合于对文件进行频繁的随机访问和查找操作
-
多级索引组织
- 在索引组织的基础上,对索引表再建立一个或多个高级索引
- 形成了多级索引结构,提高了索引的查找效率
- 适合于对大型文件进行快速的随机访问和查找
-
B树和B+树组织
- 使用平衡多路查找树(如B树、B+树)作为文件的组织形式
- 树中的节点包含多个键值和指向子节点的指针
- 支持高效的插入、删除、查找等操作
- 适合于对大型文件进行动态管理和维护
在C++文件操作中,我们通常采用顺序组织或者索引组织的方式来存储和访问文件数据。对于一些特定的应用场景,如数据库系统,还会使用散列组织、B树等高级的文件组织形式。
选择合适的文件组织形式需要考虑文件的大小、访问方式、数据特点等因素。合理的文件组织可以显著提高文件操作的效率和性能。
1.2 文件的打开与关闭
1.2.1 文件流对象
在C++中,文件操作是通过文件流对象来实现的。文件流对象提供了一组方便的接口,用于对文件进行读写操作。C++标准库中定义了三个文件流类,分别用于不同的文件操作:
1.2.1.1. ifstream
(Input File Stream)
ifstream
是 C++ 标准库中的一个类,用于从文件中读取数据。它提供了一种方便的方式来打开文件并进行读取操作。
以下是 ifstream
的一些关键特性和用法:
-
包含头文件:要使用
ifstream
,需要包含<fstream>
头文件。 -
创建
ifstream
对象:可以通过两种方式创建ifstream
对象:- 默认构造函数:
ifstream inFile;
- 构造函数接受文件名:
ifstream inFile("data.txt");
- 默认构造函数:
-
打开文件:如果在创建
ifstream
对象时没有指定文件名,可以使用open()
函数打开文件:ifstream inFile; inFile.open("data.txt");
-
检查文件是否成功打开:可以使用
is_open()
函数检查文件是否成功打开:if (inFile.is_open()) { // 文件打开成功,可以进行读取操作 } else { // 文件打开失败,进行错误处理 }
-
读取数据:
ifstream
提供了多种方式来读取文件中的数据:- 使用
>>
运算符读取格式化的数据:int number; inFile >> number;
- 使用
getline()
函数读取一行数据:string line; getline(inFile, line);
- 使用
get()
函数读取单个字符:char ch; inFile.get(ch);
- 使用
-
检查文件读取状态:可以使用
eof()
函数检查是否达到文件末尾,使用good()
函数检查读取操作是否成功。 -
关闭文件:读取完数据后,使用
close()
函数关闭文件:inFile.close();
下面是一个完整的示例,演示了如何使用 ifstream
读取文件中的数据:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
ifstream inFile("data.txt");
if (inFile.is_open()) {
string line;
while (getline(inFile, line)) {
cout << line << endl;
}
inFile.close();
} else {
cout << "Unable to open file" << endl;
}
return 0;
}
在上述示例中,我们创建了一个 ifstream
对象 inFile
,并指定要读取的文件名 "data.txt"
。然后,我们检查文件是否成功打开。如果打开成功,使用 getline()
函数逐行读取文件内容,并将每一行输出到控制台。最后,关闭文件。
总之,ifstream
是 C++ 中用于从文件读取数据的重要工具。它提供了方便的函数和运算符来打开文件、读取数据和关闭文件。合理使用 ifstream
,可以轻松地实现文件读取功能。
- 用于从文件中读取数据
- 默认以文本模式打开文件
- 常用的成员函数有
open()
,close()
,read()
,seekg()
,tellg()
,eof()
,fail()
等
2 . ofstream
(Output File Stream)
- 用于向文件中写入数据
- 默认以文本模式打开文件
- 常用的成员函数有
open()
,close()
,write()
,seekp()
,tellp()
,flush()
等
3 . fstream
(File Stream)
- 可以同时对文件进行读写操作
- 继承了
ifstream
和ofstream
的功能 - 常用的成员函数有
open()
,close()
,read()
,write()
,seekg()
,seekp()
,tellg()
,tellp()
等
下面是一个使用文件流对象读写文件的简单示例:
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 写入数据到文件
ofstream outFile("data.txt");
if (!outFile) {
cerr << "打开文件失败!" << endl;
return 1;
}
outFile << "Hello, world!" << endl;
outFile.close();
// 从文件读取数据
ifstream inFile("data.txt");
if (!inFile) {
cerr << "打开文件失败!" << endl;
return 1;
}
string str;
getline(inFile, str);
cout << "读取到的内容: " << str << endl;
inFile.close();
return 0;
}
在使用文件流对象时,我们需要包含<fstream>
头文件。创建文件流对象后,使用open()
函数打开文件,并检查是否打开成功。然后就可以使用<<
和>>
运算符或者read()
和write()
函数进行文件的读写操作了。最后,记得使用close()
函数关闭文件。
文件流对象为C++文件操作提供了一种简洁、灵活的方式,使得文件的读写变得更加方便和高效。
1.2.2 打开文件的模式
在C++中,使用文件流对象的open()
函数打开文件时,可以通过指定打开模式来控制文件的访问方式。打开模式决定了文件的读写权限和操作行为。
以下是常用的文件打开模式:
-
ios::in
(读取模式):- 以读取模式打开文件,即只允许从文件中读取数据。
- 文件必须已经存在,否则打开失败。
- 文件指针初始位置在文件开头。
-
ios::out
(写入模式):- 以写入模式打开文件,即只允许向文件中写入数据。
- 如果文件不存在,则创建一个新文件;如果文件已存在,则其内容会被清空。
- 文件指针初始位置在文件开头。
-
ios::app
(追加模式):- 以追加模式打开文件,即只允许在文件末尾添加新的数据。
- 如果文件不存在,则创建一个新文件;如果文件已存在,则原有内容会被保留。
- 文件指针初始位置在文件末尾,新写入的数据会追加到文件末尾。
-
ios::ate
(文件尾模式):- 打开文件时,文件指针初始位置在文件末尾。
- 可以与其他模式结合使用,如
ios::in | ios::ate
表示以读取模式打开文件,并将文件指针定位到文件末尾。
-
ios::trunc
(截断模式):- 如果文件已存在,则清空文件内容,文件长度变为0。
- 通常与
ios::out
模式结合使用,如ios::out | ios::trunc
表示以写入模式打开文件,并清空文件内容。
-
ios::binary
(二进制模式):- 以二进制模式打开文件,不对数据进行任何转换。
- 与其他模式结合使用,如
ios::in | ios::binary
表示以二进制读取模式打开文件。 - 二进制模式下,文件以字节为单位进行读写,不对换行符等特殊字符进行转换。
打开模式可以使用按位或运算符(|
)进行组合,以实现多个模式的组合效果。例如:
ofstream file("data.txt", ios::out | ios::app);
上述代码以写入和追加模式打开文件"data.txt",如果文件不存在则创建新文件,如果文件已存在则在文件末尾追加新的数据。
需要注意的是,打开模式必须与文件流对象的类型匹配。例如,ifstream
对象只能使用ios::in
模式,而ofstream
对象只能使用ios::out
、ios::app
等写入模式。
选择合适的打开模式可以确保文件的正确访问和操作,避免出现文件损坏或数据丢失等问题。在实际应用中,根据具体的需求和文件的用途来选择适当的打开模式。
1.2.3 文件的打开函数open()
在C++中,文件流对象(如ifstream
、ofstream
和fstream
)提供了open()
函数,用于打开文件并将文件与文件流对象关联起来。open()
函数允许我们指定文件名、打开模式等参数,以控制文件的打开方式。
open()
函数有以下两种常用的形式:
-
void open(const char* filename, ios::openmode mode = ios::in | ios::out);
filename
: 要打开的文件名,可以是相对路径或绝对路径。mode
: 打开文件的模式,默认为ios::in | ios::out
,即可读可写。
-
void open(const string& filename, ios::openmode mode = ios::in | ios::out);
filename
: 要打开的文件名,使用string
类型表示。mode
: 打开文件的模式,默认为ios::in | ios::out
,即可读可写。
下面是一些使用open()
函数打开文件的示例:
// 以读取模式打开文件
ifstream inputFile;
inputFile.open("data.txt", ios::in);
// 以写入模式打开文件
ofstream outputFile;
outputFile.open("result.txt", ios::out);
// 以读写模式打开文件
fstream ioFile;
ioFile.open("config.ini", ios::in | ios::out);
// 以二进制写入模式打开文件
ofstream binaryFile;
binaryFile.open("data.bin", ios::out | ios::binary);
使用open()
函数打开文件时,需要注意以下几点:
-
在打开文件之前,需要先创建相应的文件流对象,如
ifstream
、ofstream
或fstream
。 -
open()
函数的第一个参数指定要打开的文件名,可以是相对路径或绝对路径。文件名可以使用C风格字符串(const char*
)或C++的string
类型。 -
open()
函数的第二个参数指定打开文件的模式,可以使用ios
命名空间中的枚举值,如ios::in
表示读取模式,ios::out
表示写入模式,ios::app
表示追加模式等。多个模式可以使用按位或运算符(|
)进行组合。 -
打开文件后,可以使用文件流对象的成员函数(如
is_open()
)来检查文件是否成功打开。 -
对于写入操作,如果指定的文件不存在,
open()
函数会自动创建该文件。 -
在使用完文件后,需要使用文件流对象的
close()
函数关闭文件,以释放系统资源。
通过open()
函数,我们可以灵活地打开不同类型的文件,并指定适当的打开模式,以满足文件读写的需求。在实际应用中,根据具体的文件操作场景选择合适的文件流对象和打开模式,确保文件的正确打开和访问。
1.2.4 文件的关闭函数close()
在C++中,当我们完成对文件的读写操作后,需要使用文件流对象的close()
函数来关闭文件。关闭文件可以释放与文件关联的系统资源,确保文件的完整性和数据的持久性。
close()
函数的基本语法如下:
void close();
close()
函数没有参数,它直接作用于文件流对象,将与文件流对象关联的文件关闭。
下面是一些使用close()
函数关闭文件的示例:
// 打开文件进行读取
ifstream inputFile("data.txt");
// 读取文件内容
// ...
// 关闭文件
inputFile.close();
// 打开文件进行写入
ofstream outputFile("result.txt");
// 向文件写入数据
// ...
// 关闭文件
outputFile.close();
// 打开文件进行读写
fstream ioFile("config.ini", ios::in | ios::out);
// 读取和修改文件内容
// ...
// 关闭文件
ioFile.close();
使用close()
函数关闭文件时,需要注意以下几点:
-
在调用
close()
函数之前,确保已经完成了对文件的所有读写操作。一旦文件被关闭,就无法再对其进行读写。 -
如果文件流对象在析构时还没有关闭文件,析构函数会自动调用
close()
函数关闭文件。但是,为了确保文件的及时关闭和资源的释放,建议显式调用close()
函数。 -
可以使用文件流对象的
is_open()
函数来检查文件是否成功关闭。如果文件已关闭,is_open()
函数将返回false
。 -
对已经关闭的文件流对象调用
close()
函数不会产生错误,但也不会执行任何操作。 -
如果在文件关闭时发生错误(如文件写入失败、磁盘空间不足等),
close()
函数会设置文件流对象的错误状态。可以使用文件流对象的fail()
或bad()
函数来检查错误状态。
通过及时关闭文件,我们可以确保文件的完整性,避免数据丢失或损坏。同时,关闭文件也可以释放系统资源,提高程序的效率和可靠性。
在实际应用中,建议养成良好的编程习惯,在不再需要访问文件时,显式调用close()
函数关闭文件。可以将文件的打开和关闭操作放在同一个代码块中,以确保文件的正确关闭。
1.3 文件的读写操作
1.3.1 文件的写入操作
1.3.1.1 写入单个字符函数put()
在C++中,文件输出流对象提供了put()
函数,用于向文件中写入单个字符。put()
函数是一个非格式化的写入函数,它直接将字符写入文件,不进行任何格式转换。
put()
函数的语法如下:
ostream& put(char ch);
其中,ch
是要写入文件的字符。put()
函数返回一个ostream
对象的引用,因此可以将多个put()
函数的调用连接起来。
下面是一个示例,演示了如何使用put()
函数向文件中写入单个字符:
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 打开文件用于写入
ofstream outputFile("output.txt");
if (outputFile.is_open()) {
// 使用put()函数写入单个字符
outputFile.put('H');
outputFile.put('e');
outputFile.put('l');
outputFile.put('l');
outputFile.put('o');
outputFile.put('\n');
// 写入字符数组
char arr[] = {'W', 'o', 'r', 'l', 'd', '!', '\n'};
for (char ch : arr) {
outputFile.put(ch);
}
// 关闭文件
outputFile.close();
cout << "字符已写入文件 output.txt" << endl;
}
else {
cout << "无法打开文件 output.txt" << endl;
}
return 0;
}
在上述示例中,我们首先打开一个名为"output.txt"的文件用于写入。然后,使用put()
函数依次写入字符’H’、‘e’、‘l’、‘l’、‘o’和换行符’\n’,组成单词"Hello"。
接下来,我们定义了一个字符数组arr
,其中包含字符’W’、‘o’、‘r’、‘l’、‘d’、‘!‘和换行符’\n’。通过循环遍历数组,使用put()
函数将每个字符写入文件。
最后,关闭文件并输出提示信息。
使用put()
函数写入单个字符时,需要注意以下几点:
put()
函数每次只能写入一个字符,如果需要写入多个字符,需要多次调用put()
函数。put()
函数不会自动添加换行符,如果需要换行,需要显式写入换行符’\n’。put()
函数返回一个ostream
对象的引用,可以将多个put()
函数的调用连接起来,以实现连续写入多个字符。
通过put()
函数,我们可以方便地向文件中写入单个字符,它提供了一种简单的方式来构建字符串或逐个字符地写入数据。在实际应用中,可以根据需要选择使用put()
函数或其他写入函数来实现文件的写入操作。
1.3.1.2 写入字符串函数write()
在C++中,文件输出流对象提供了write()
函数,用于向文件中写入一个字符串或字符数组。write()
函数是一个非格式化的写入函数,它直接将字符串的内容写入文件,不进行任何格式转换。
write()
函数有两种常用的重载形式:
-
ostream& write(const char* s, streamsize n);
s
是要写入文件的字符串指针。n
是要写入的字符数。- 该函数将字符串
s
的前n
个字符写入文件。
-
ostream& write(const char* s, int n);
s
是要写入文件的字符串指针。n
是要写入的字符数。- 该函数将字符串
s
的前n
个字符写入文件。
下面是一个示例,演示了如何使用write()
函数向文件中写入字符串:
#include <iostream>
#include <fstream>
#include <cstring>
using namespace std;
int main() {
// 打开文件用于写入
ofstream outputFile("output.txt");
if (outputFile.is_open()) {
// 使用write()函数写入字符串
const char* str1 = "Hello, ";
outputFile.write(str1, strlen(str1));
const char* str2 = "world!";
outputFile.write(str2, strlen(str2));
outputFile.write("\n", 1);
// 写入字符数组
char arr[] = "This is a test.\n";
outputFile.write(arr, sizeof(arr) - 1);
// 关闭文件
outputFile.close();
cout << "字符串已写入文件 output.txt" << endl;
}
else {
cout << "无法打开文件 output.txt" << endl;
}
return 0;
}
在上述示例中,我们首先打开一个名为"output.txt"的文件用于写入。然后,使用write()
函数分别写入两个字符串"Hello, “和"world!”,并写入一个换行符。
接下来,我们定义了一个字符数组arr
,其中包含字符串"This is a test."和换行符。使用write()
函数将字符数组的内容写入文件,注意使用sizeof(arr) - 1
来指定写入的字符数,以去掉字符数组末尾的空字符。
最后,关闭文件并输出提示信息。
使用write()
函数写入字符串时,需要注意以下几点:
write()
函数需要指定要写入的字符串指针和写入的字符数。- 字符串指针指向的内存区域应该包含至少指定字符数的有效字符。
write()
函数不会自动添加空字符或换行符,需要根据需要显式写入。- 对于字符数组,可以使用
sizeof()
操作符计算数组的大小,但需要减去末尾的空字符。
通过write()
函数,我们可以高效地向文件中写入字符串或字符数组,特别适用于写入大量的文本数据。在实际应用中,可以根据需要选择使用write()
函数或其他写入函数来实现文件的写入操作。
1.3.1.3 格式化写入函数<<
操作符
在C++中,文件输出流对象重载了<<
操作符,用于向文件中进行格式化写入。<<
操作符可以将各种类型的数据格式化为字符串并写入文件,提供了一种方便、灵活的写入方式。
使用<<
操作符进行格式化写入的语法如下:
ofstream& operator<<(ofstream& ofs, T val);
其中,ofs
是文件输出流对象,val
是要写入文件的数据,可以是各种类型,如整数、浮点数、字符、字符串等。<<
操作符返回文件输出流对象的引用,因此可以将多个<<
操作符连接起来,实现连续写入。
下面是一个示例,演示了如何使用<<
操作符向文件中进行格式化写入:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// 打开文件用于写入
ofstream outputFile("output.txt");
if (outputFile.is_open()) {
// 使用<<操作符进行格式化写入
string name = "John";
int age = 25;
double height = 1.75;
outputFile << "Name: " << name << endl;
outputFile << "Age: " << age << endl;
outputFile << "Height: " << height << " meters" << endl;
outputFile << "This is a line of text." << endl;
outputFile << 42 << " is the answer." << endl;
// 关闭文件
outputFile.close();
cout << "数据已写入文件 output.txt" << endl;
}
else {
cout << "无法打开文件 output.txt" << endl;
}
return 0;
}
在上述示例中,我们首先打开一个名为"output.txt"的文件用于写入。然后,使用<<
操作符进行格式化写入。
我们定义了一个字符串变量name
、一个整数变量age
和一个双精度浮点数变量height
,分别表示姓名、年龄和身高。使用<<
操作符将这些变量的值与相应的标签一起写入文件,每个数据占一行。
接下来,我们直接使用<<
操作符写入一行文本"This is a line of text.“和一个包含整数的字符串"42 is the answer.”。
最后,关闭文件并输出提示信息。
使用<<
操作符进行格式化写入时,需要注意以下几点:
<<
操作符可以连续使用,将多个数据依次写入文件。<<
操作符会自动进行类型转换,将数据转换为对应的字符串表示形式。- 可以使用
endl
操作符或'\n'
字符来插入换行符,实现数据的分行写入。 <<
操作符的使用方式与cout
类似,提供了一致的格式化输出方式。
通过<<
操作符,我们可以方便地进行格式化写入,将各种类型的数据写入文件,并可以灵活地组合和排版数据。在实际应用中,可以根据需要选择使用<<
操作符或其他写入函数来实现文件的写入操作。
1.3.2 文件的读取操作
1.3.2.1 读取单个字符函数get()
在C++中,文件输入流对象提供了get()
函数,用于从文件中读取单个字符。get()
函数是一个非格式化的读取函数,它从文件中读取一个字符,并将文件指针移动到下一个字符的位置。
get()
函数有以下常用的重载形式:
-
int get();
- 该函数从文件中读取一个字符,并返回其ASCII码值。
- 如果读取失败(如达到文件末尾),函数返回
EOF
(文件结束标志)。
-
istream& get(char& ch);
- 该函数从文件中读取一个字符,并将其存储在引用参数
ch
中。 - 函数返回文件输入流对象的引用,因此可以将多个
get()
函数的调用连接起来。 - 如果读取失败(如达到文件末尾),函数将设置文件输入流对象的状态标志。
- 该函数从文件中读取一个字符,并将其存储在引用参数
下面是一个示例,演示了如何使用get()
函数从文件中读取单个字符:
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 打开文件用于读取
ifstream inputFile("input.txt");
if (inputFile.is_open()) {
char ch;
// 使用get()函数读取单个字符
while (inputFile.get(ch)) {
cout << ch;
}
cout << endl;
// 关闭文件
inputFile.close();
}
else {
cout << "无法打开文件 input.txt" << endl;
}
return 0;
}
在上述示例中,我们首先打开一个名为"input.txt"的文件用于读取。然后,使用get()
函数在一个循环中逐个读取文件中的字符。
我们定义了一个字符变量ch
用于存储读取到的字符。使用inputFile.get(ch)
从文件中读取一个字符,并将其存储在ch
中。循环条件inputFile.get(ch)
确保只要读取成功就继续循环。
在循环内部,我们将读取到的字符输出到控制台,以便查看读取的内容。
最后,关闭文件。
使用get()
函数读取单个字符时,需要注意以下几点:
get()
函数每次只读取一个字符,如果需要读取多个字符,需要在循环中多次调用get()
函数。get()
函数读取字符时,会将文件指针移动到下一个字符的位置。- 可以通过文件输入流对象的状态标志(如
eof()
,fail()
)来判断读取是否成功。 - 读取到文件末尾时,
get()
函数会返回EOF
或设置文件输入流对象的状态标志。
通过get()
函数,我们可以方便地从文件中读取单个字符,并根据需要对字符进行处理。在实际应用中,可以根据需要选择使用get()
函数或其他读取函数来实现文件的读取操作。
1.3.2.2 读取字符串函数read()
在C++中,文件输入流对象提供了read()
函数,用于从文件中读取指定数量的字符到字符数组中。read()
函数是一个非格式化的读取函数,它从文件中读取指定数量的字符,并将其存储在字符数组中。
read()
函数的语法如下:
istream& read(char* s, streamsize n);
其中,s
是要存储读取字符的字符数组指针,n
是要读取的字符数。read()
函数返回文件输入流对象的引用,因此可以将多个read()
函数的调用连接起来。
下面是一个示例,演示了如何使用read()
函数从文件中读取字符串:
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 打开文件用于读取
ifstream inputFile("input.txt");
if (inputFile.is_open()) {
char buffer[256];
// 使用read()函数读取字符串
inputFile.read(buffer, sizeof(buffer) - 1);
// 添加字符串终止符
buffer[inputFile.gcount()] = '\0';
cout << "读取到的字符串: " << buffer << endl;
// 关闭文件
inputFile.close();
}
else {
cout << "无法打开文件 input.txt" << endl;
}
return 0;
}
在上述示例中,我们首先打开一个名为"input.txt"的文件用于读取。然后,使用read()
函数从文件中读取字符串。
我们定义了一个字符数组buffer
用于存储读取到的字符串,数组大小为256。使用inputFile.read(buffer, sizeof(buffer) - 1)
从文件中读取字符串,其中sizeof(buffer) - 1
表示要读取的字符数,减1是为了留出空间存储字符串终止符。
读取完成后,我们使用inputFile.gcount()
获取实际读取到的字符数,并在字符数组的相应位置添加字符串终止符'\0'
,以确保字符数组表示一个有效的C风格字符串。
最后,将读取到的字符串输出到控制台,并关闭文件。
使用read()
函数读取字符串时,需要注意以下几点:
read()
函数从文件中读取指定数量的字符,并将其存储在字符数组中。- 字符数组的大小应该足够大,以容纳读取到的字符串以及字符串终止符。
- 读取完成后,需要在字符数组的适当位置添加字符串终止符,以便将其视为一个有效的C风格字符串。
- 可以使用文件输入流对象的
gcount()
函数获取实际读取到的字符数。 - 如果读取到文件末尾,
read()
函数会设置文件输入流对象的状态标志(如eof()
)。
通过read()
函数,我们可以从文件中读取指定数量的字符,并将其存储在字符数组中。在实际应用中,可以根据需要选择使用read()
函数或其他读取函数来实现文件的读取操作。
1.3.2.3 格式化读取函数>>
操作符
在C++中,文件输入流对象重载了>>
操作符,用于从文件中进行格式化读取。>>
操作符可以从文件中读取各种类型的数据,并将其存储在相应的变量中。
使用>>
操作符进行格式化读取的语法如下:
ifstream& operator>>(ifstream& ifs, T& val);
其中,ifs
是文件输入流对象,val
是要读取的数据变量,可以是各种类型,如整数、浮点数、字符串等。>>
操作符返回文件输入流对象的引用,因此可以将多个>>
操作符连接起来,实现连续读取。
下面是一个示例,演示了如何使用>>
操作符从文件中进行格式化读取:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// 打开文件用于读取
ifstream inputFile("input.txt");
if (inputFile.is_open()) {
string name;
int age;
double height;
// 使用>>操作符进行格式化读取
inputFile >> name;
inputFile >> age;
inputFile >> height;
cout << "Name: " << name << endl;
cout << "Age: " << age << endl;
cout << "Height: " << height << endl;
// 关闭文件
inputFile.close();
}
else {
cout << "无法打开文件 input.txt" << endl;
}
return 0;
}
在上述示例中,我们假设文件"input.txt"的内容如下:
John
25
1.75
我们首先打开文件"input.txt"用于读取。然后,使用>>
操作符从文件中进行格式化读取。
我们定义了一个字符串变量name
、一个整数变量age
和一个双精度浮点数变量height
,用于存储读取到的数据。使用>>
操作符依次读取文件中的数据,并将其存储到相应的变量中。
读取完成后,将读取到的数据输出到控制台,以便查看读取的结果。
最后,关闭文件。
使用>>
操作符进行格式化读取时,需要注意以下几点:
>>
操作符根据数据的类型自动进行格式化读取,将文件中的数据转换为相应的类型。- 文件中的数据应该按照与变量类型相匹配的格式进行组织,以便正确读取。
>>
操作符会自动跳过文件中的空格和换行符,因此数据之间可以使用空格或换行符进行分隔。- 如果读取到文件末尾或发生读取错误,
>>
操作符会设置文件输入流对象的状态标志(如eof()
,fail()
)。
通过>>
操作符,我们可以方便地从文件中进行格式化读取,将文件中的数据读取到相应的变量中。在实际应用中,可以根据需要选择使用>>
操作符或其他读取函数来实现文件的读取操作。
1.3.3 文件指针的操作
1.3.3.1 文件指针的定位函数seekp()
和seekg()
在C++中,文件流对象提供了seekp()
和seekg()
函数,用于移动文件指针的位置,实现对文件的随机访问。
-
seekp()
函数:- 用于设置写入位置指针(put pointer)的位置。
- 语法:
ostream& seekp(streampos pos);
或ostream& seekp(streamoff off, ios_base::seekdir dir);
pos
表示要设置的绝对位置。off
表示相对于dir
的偏移量。dir
表示偏移的起始位置,可以是ios_base::beg
(文件起始位置)、ios_base::cur
(当前位置)或ios_base::end
(文件末尾位置)。
-
seekg()
函数:- 用于设置读取位置指针(get pointer)的位置。
- 语法:
istream& seekg(streampos pos);
或istream& seekg(streamoff off, ios_base::seekdir dir);
- 参数含义与
seekp()
函数相同。
下面是一个示例,演示了如何使用seekp()
和seekg()
函数进行文件指针定位:
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 打开文件用于读写
fstream file("data.txt", ios::in | ios::out);
if (file.is_open()) {
// 写入数据
file << "Hello, World!";
// 使用seekp()函数将写入位置指针移动到文件起始位置
file.seekp(0, ios::beg);
// 修改数据
file << "Hi";
// 使用seekg()函数将读取位置指针移动到文件末尾
file.seekg(0, ios::end);
// 获取文件大小
streampos fileSize = file.tellg();
cout << "文件大小: " << fileSize << " 字节" << endl;
// 使用seekg()函数将读取位置指针移动到文件起始位置
file.seekg(0, ios::beg);
// 读取数据
char buffer[256];
file.read(buffer, fileSize);
buffer[fileSize] = '\0';
cout << "文件内容: " << buffer << endl;
// 关闭文件
file.close();
}
else {
cout << "无法打开文件 data.txt" << endl;
}
return 0;
}
在上述示例中,我们打开文件"data.txt"用于读写操作。
首先,我们向文件中写入字符串"Hello, World!“。然后,使用seekp()
函数将写入位置指针移动到文件的起始位置,并修改文件内容为"Hi”。
接下来,我们使用seekg()
函数将读取位置指针移动到文件的末尾位置,并使用tellg()
函数获取文件的大小。
然后,我们使用seekg()
函数将读取位置指针移动到文件的起始位置,并使用read()
函数读取文件的内容到字符数组中。
最后,关闭文件并输出文件的大小和内容。
通过seekp()
和seekg()
函数,我们可以灵活地移动文件指针的位置,实现对文件的随机访问和修改。这在某些场景下非常有用,例如在文件中查找特定位置、修改文件的特定部分等。
需要注意的是,使用seekp()
和seekg()
函数时,要确保文件是以二进制模式打开的,否则可能会出现意外的行为。
1.3.3.2 获取文件指针位置函数tellp()
和tellg()
在C++中,文件流对象提供了tellp()
和tellg()
函数,用于获取文件指针的当前位置。这些函数可以帮助我们确定文件指针在文件中的位置,以便进行文件的随机访问和定位。
-
tellp()
函数:- 用于获取写入位置指针(put pointer)的当前位置。
- 语法:
streampos tellp();
- 返回值类型为
streampos
,表示文件指针的当前位置。
-
tellg()
函数:- 用于获取读取位置指针(get pointer)的当前位置。
- 语法:
streampos tellg();
- 返回值类型为
streampos
,表示文件指针的当前位置。
下面是一个示例,演示了如何使用tellp()
和tellg()
函数获取文件指针的位置:
#include <iostream>
#include <fstream>
using namespace std;
int main() {
// 打开文件用于读写
fstream file("data.txt", ios::in | ios::out);
if (file.is_open()) {
// 写入数据
file << "Hello, World!";
// 获取写入位置指针的当前位置
streampos putPos = file.tellp();
cout << "写入位置指针的当前位置: " << putPos << endl;
// 移动读取位置指针到文件起始位置
file.seekg(0, ios::beg);
// 读取数据
char buffer[256];
file.read(buffer, putPos);
buffer[putPos] = '\0';
cout << "文件内容: " << buffer << endl;
// 获取读取位置指针的当前位置
streampos getPos = file.tellg();
cout << "读取位置指针的当前位置: " << getPos << endl;
// 关闭文件
file.close();
}
else {
cout << "无法打开文件 data.txt" << endl;
}
return 0;
}
在上述示例中,我们打开文件"data.txt"用于读写操作。
首先,我们向文件中写入字符串"Hello, World!"。然后,使用tellp()
函数获取写入位置指针的当前位置,并将其存储在变量putPos
中。
接下来,我们使用seekg()
函数将读取位置指针移动到文件的起始位置,并使用read()
函数读取文件内容到字符数组中。读取的字节数为putPos
,即写入位置指针的当前位置。
然后,我们使用tellg()
函数获取读取位置指针的当前位置,并将其存储在变量getPos
中。
最后,关闭文件并输出写入位置指针和读取位置指针的当前位置。
通过tellp()
和tellg()
函数,我们可以获取文件指针的当前位置,了解文件读写的进度和位置。这在某些场景下非常有用,例如记录文件的读写位置、计算文件的大小等。
需要注意的是,文件指针的位置是以字节为单位进行计算的,因此返回的位置值表示从文件起始位置到当前位置的字节数。
结合seekp()
、seekg()
、tellp()
和tellg()
函数,我们可以灵活地定位文件指针,实现文件的随机访问和定位操作。
1.4 文本文件与二进制文件
1.4.1 文本文件的读写
在C++中,文本文件的读写可以通过文件流对象来实现。文本文件是以字符为单位进行读写的,每行文本以换行符结尾。下面我们将详细介绍文本文件的读写操作。
- 写入文本文件:
- 使用
ofstream
对象打开文件,指定打开模式为ios::out
。 - 使用
<<
操作符或write()
函数将文本数据写入文件。 - 写入完成后,关闭文件。
- 使用
示例代码:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// 打开文件用于写入
ofstream outputFile("output.txt");
if (outputFile.is_open()) {
// 写入文本数据
outputFile << "Hello, World!" << endl;
outputFile << "This is a text file." << endl;
string text = "C++ is awesome!";
outputFile.write(text.c_str(), text.length());
outputFile << endl;
// 关闭文件
outputFile.close();
cout << "文本已写入文件 output.txt" << endl;
}
else {
cout << "无法打开文件 output.txt" << endl;
}
return 0;
}
- 读取文本文件:
- 使用
ifstream
对象打开文件,指定打开模式为ios::in
。 - 使用
>>
操作符或getline()
函数从文件中读取文本数据。 - 读取完成后,关闭文件。
- 使用
示例代码:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
// 打开文件用于读取
ifstream inputFile("input.txt");
if (inputFile.is_open()) {
string line;
// 逐行读取文本数据
while (getline(inputFile, line)) {
cout << line << endl;
}
// 关闭文件
inputFile.close();
}
else {
cout << "无法打开文件 input.txt" << endl;
}
return 0;
}
在读取文本文件时,我们通常使用getline()
函数逐行读取文本数据,每次读取一行直到遇到换行符为止。这样可以方便地处理文本文件中的每一行数据。
文本文件的读写操作相对简单,使用文件流对象提供的操作符和函数即可轻松实现。在实际应用中,根据具体需求选择合适的读写方式,并注意对文件的打开、关闭和异常处理。
通过文本文件的读写,我们可以将程序的输出保存到文件中,或者从文件中读取数据进行处理和分析。这在许多场景下都非常有用,如日志记录、数据存储、配置文件解析等。
1.4.2 二进制文件的读写
在C++中,二进制文件的读写可以通过文件流对象来实现。二进制文件是以字节为单位进行读写的,不会进行任何格式转换。下面我们将详细介绍二进制文件的读写操作。
- 写入二进制文件:
- 使用
ofstream
对象打开文件,指定打开模式为ios::out | ios::binary
。 - 使用
write()
函数将二进制数据写入文件。 - 写入完成后,关闭文件。
- 使用
示例代码:
#include <iostream>
#include <fstream>
using namespace std;
struct Person {
char name[50];
int age;
double height;
};
int main() {
// 创建Person对象
Person person = {"John", 25, 1.75};
// 打开文件用于二进制写入
ofstream outputFile("data.bin", ios::out | ios::binary);
if (outputFile.is_open()) {
// 写入二进制数据
outputFile.write(reinterpret_cast<char*>(&person), sizeof(Person));
// 关闭文件
outputFile.close();
cout << "二进制数据已写入文件 data.bin" << endl;
}
else {
cout << "无法打开文件 data.bin" << endl;
}
return 0;
}
在上述示例中,我们定义了一个Person
结构体,包含姓名、年龄和身高信息。然后,我们创建了一个Person
对象,并使用write()
函数将其以二进制形式写入文件。注意,我们使用reinterpret_cast
将Person
对象的地址转换为char*
类型,以便写入文件。
- 读取二进制文件:
- 使用
ifstream
对象打开文件,指定打开模式为ios::in | ios::binary
。 - 使用
read()
函数从文件中读取二进制数据。 - 读取完成后,关闭文件。
- 使用
示例代码:
#include <iostream>
#include <fstream>
using namespace std;
struct Person {
char name[50];
int age;
double height;
};
int main() {
// 打开文件用于二进制读取
ifstream inputFile("data.bin", ios::in | ios::binary);
if (inputFile.is_open()) {
// 创建Person对象
Person person;
// 读取二进制数据
inputFile.read(reinterpret_cast<char*>(&person), sizeof(Person));
// 关闭文件
inputFile.close();
// 输出读取的数据
cout << "Name: " << person.name << endl;
cout << "Age: " << person.age << endl;
cout << "Height: " << person.height << endl;
}
else {
cout << "无法打开文件 data.bin" << endl;
}
return 0;
}
在读取二进制文件时,我们使用read()
函数从文件中读取二进制数据,并将其存储到Person
对象中。同样,我们使用reinterpret_cast
将Person
对象的地址转换为char*
类型,以便读取文件。
二进制文件的读写操作与文本文件略有不同,主要在于使用ios::binary
标志指定二进制模式,并使用write()
和read()
函数进行数据的写入和读取。二进制文件通常用于存储结构化的数据、图像、音频等非文本格式的数据。
在实际应用中,根据具体需求选择合适的文件格式和读写方式。对于需要保持数据结构和内存布局的情况,二进制文件是一个很好的选择。但是,二进制文件的可读性较差,不易于人工查看和编辑。
通过二进制文件的读写,我们可以高效地存储和读取复杂的数据结构,实现数据的持久化和传输。在一些性能敏感的应用中,二进制文件的读写可以提供更高的效率和更紧凑的存储。
1.5 文件重定向
1.5.1 标准输入输出流
在C++中,标准输入输出流是一组预定义的输入输出流对象,用于与标准输入设备(通常是键盘)和标准输出设备(通常是显示器)进行交互。C++提供了三个标准输入输出流对象:
- 标准输入流(cin):
- 对应于标准输入设备,通常是键盘。
- 使用
cin
对象从标准输入读取数据。 - 常用的输入操作符有
>>
和getline()
函数。
示例代码:
#include <iostream>
#include <string>
using namespace std;
int main() {
int age;
string name;
// 从标准输入读取数据
cout << "请输入您的姓名: ";
getline(cin, name);
cout << "请输入您的年龄: ";
cin >> age;
// 输出读取的数据
cout << "您的姓名是: " << name << endl;
cout << "您的年龄是: " << age << endl;
return 0;
}
- 标准输出流(cout):
- 对应于标准输出设备,通常是显示器。
- 使用
cout
对象将数据输出到标准输出。 - 常用的输出操作符有
<<
和endl
(换行符)。
示例代码:
#include <iostream>
using namespace std;
int main() {
int age = 25;
string name = "John";
// 输出数据到标准输出
cout << "姓名: " << name << endl;
cout << "年龄: " << age << endl;
cout << "C++ 是一门强大的编程语言!" << endl;
return 0;
}
- 标准错误流(cerr):
- 对应于标准错误输出设备,通常也是显示器。
- 使用
cerr
对象将错误信息输出到标准错误输出。 - 与
cout
类似,使用<<
操作符进行输出。
示例代码:
#include <iostream>
using namespace std;
int main() {
int number;
cout << "请输入一个正整数: ";
cin >> number;
if (number <= 0) {
cerr << "错误: 输入的数字必须是正整数!" << endl;
return 1;
}
cout << "您输入的数字是: " << number << endl;
return 0;
}
标准输入输出流提供了方便的方式来与用户进行交互和输出信息。通过使用cin
、cout
和cerr
对象,我们可以轻松地读取用户输入、显示程序输出以及报告错误信息。
在实际应用中,我们经常使用标准输入输出流来实现用户界面、日志记录、调试信息输出等功能。通过灵活运用标准输入输出流,可以使程序更加友好和交互性更强。
需要注意的是,标准输入输出流是全局的,在整个程序中都可以访问和使用。同时,它们也与文件流有着相似的接口和操作方式,使得在不同的输入输出场景下可以方便地切换和适应。
1.5.2 重定向运算符<<
和>>
在C++中,重定向运算符<<
和>>
用于在标准输入输出流和文件流之间进行重定向操作。通过重定向运算符,我们可以将标准输入输出重定向到文件,或者将文件内容重定向到标准输入输出。
- 输出重定向运算符
<<
:- 将标准输出重定向到文件。
- 语法:
cout << 数据 >> 文件对象
- 将原本输出到标准输出的数据重定向输出到指定的文件对象中。
示例代码:
#include <iostream>
#include <fstream>
using namespace std;
int main() {
ofstream outputFile("output.txt");
if (outputFile.is_open()) {
// 将标准输出重定向到文件
cout << "这条消息将被重定向到文件" << endl;
cout << "Hello, world!" << endl;
// 恢复标准输出
cout.rdbuf(clog.rdbuf());
cout << "这条消息将输出到标准输出" << endl;
outputFile.close();
}
else {
cout << "无法打开文件 output.txt" << endl;
}
return 0;
}
在上述示例中,我们首先创建了一个文件输出流对象outputFile
,并将其关联到文件"output.txt"。然后,使用cout << 数据 >> outputFile
将标准输出重定向到文件。之后的输出操作将写入到文件中,而不是输出到标准输出设备。
通过调用cout.rdbuf(clog.rdbuf())
可以恢复标准输出,将输出重定向回标准输出设备。
- 输入重定向运算符
>>
:- 将文件内容重定向到标准输入。
- 语法:
文件对象 >> 变量
- 将文件中的内容读取到指定的变量中,作为标准输入。
示例代码:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main() {
ifstream inputFile("input.txt");
if (inputFile.is_open()) {
string line;
// 将文件内容重定向到标准输入
while (inputFile >> line) {
cout << line << endl;
}
inputFile.close();
}
else {
cout << "无法打开文件 input.txt" << endl;
}
return 0;
}
在上述示例中,我们创建了一个文件输入流对象inputFile
,并将其关联到文件"input.txt"。然后,使用inputFile >> line
将文件中的内容读取到字符串变量line
中,相当于从标准输入读取数据。通过循环读取,我们可以将文件中的每一行内容输出到标准输出设备。
重定向运算符提供了一种方便的方式来改变标准输入输出的来源和目标。通过重定向,我们可以将程序的输入输出与文件进行关联,实现数据的持久化存储和读取。
需要注意的是,重定向运算符并不改变标准输入输出流对象本身,而是通过操作流的缓冲区来实现重定向功能。在使用重定向运算符时,要确保正确关联文件流对象,并在不需要重定向时恢复标准输入输出。
通过灵活运用重定向运算符,我们可以方便地将程序的输入输出与文件进行交互,实现数据的存储、加载和处理。这在日志记录、数据分析、配置文件读取等场景中非常有用。