文件输入输出操作全解析
1. 文件流的关闭与重新打开
文件流对象销毁时,文件会自动关闭,但也能手动关闭输入流,方式与关闭输出流相同,示例代码如下:
inFile().close(); // 关闭输入流
调用
close()
后,文件将无法再被读取。若显式关闭文件流,可通过调用
open()
以不同的打开模式重新打开,
open()
接受文件名和打开模式两个参数,第二个参数有默认值,示例如下:
outFile.close();
outFile.open(filename); // 重新打开文件,覆盖原内容
outFile.open(filename, std::ios::out|std::ios::app); // 重新打开文件,追加数据
2. 设置流的打开模式
ifstream
或
ofstream
对象的打开模式决定了对文件的操作方式。打开模式由
ios_base
中定义并通过
ios
类继承到流类的
openmode
类型的位掩码值组合而成。
ifstream
和
ofstream
对象的构造函数的第二个参数为
openmode
类型,有默认值。以下是常见的打开模式值及其含义:
| 值 | 含义 |
| ---- | ---- |
|
ios::app
| 每次写入前移动到文件末尾(追加操作),确保只能在文件现有内容后添加数据,不能覆盖 |
|
ios::ate
| 打开文件后移动到文件末尾,之后可将当前位置移动到文件其他位置 |
|
ios::binary
| 设置为二进制模式,二进制模式下,字符在文件传输时保持不变,未设置则为文本模式 |
|
ios::in
| 打开文件用于读取 |
|
ios::out
| 打开文件用于写入 |
|
ios::trunc
| 将现有文件截断为零长度 |
打开模式规范由一个或多个
openmode
值组成,通过按位或运算组合这些值。例如,以二进制模式打开文件并只能在末尾追加数据,可指定模式为
ios::out|ios::app|ios::binary
。若要同时以读写模式打开文件,需使用
fstream
对象并指定
ios::in
和
ios::out
。
ifstream
对象的默认打开模式是
ios::in
,
ofstream
对象的默认打开模式是
ios::out
。可在文件流构造函数的第二个参数中指定打开模式,示例如下:
std::string filename {"D:\\Example_Data\\primes.txt"};
std::ofstream outFile {filename, std::ios::out|std::ios::app};
// 等同于
std::ofstream outFile {filename, std::ios::app};
3. 生成并处理素数文件的示例程序
以下是一个示例程序,用于生成并显示所需数量的素数,若
primes.txt
文件已存在,会读取并显示文件中的素数,同时将新找到的素数添加到文件中:
// Ex17_03.cpp
// Reading and writing the primes file
#include <fstream>
#include <iostream>
#include <iomanip>
#include <cmath>
#include <string>
using std::ios;
using std::string;
using ulong = unsigned long long;
ulong nextprime(ulong aprime, const string filename); // Find the prime following aprime
int main()
{
string filename {"D:\\Example_Data\\more_primes.txt"};
size_t nprimes {}; // Number of primes required
size_t count {}; // Count of primes found
ulong lastprime {}; // Last prime found
size_t perline {5}; // Number output per line
// Get number of primes required
std::cout << "How many primes would you like (at least 4)?: ";
std::cin >> nprimes;
if (nprimes < 4) nprimes = 4;
std::ifstream inFile; // Create input file stream
inFile.open(filename); // Open the filename file for input
if (!inFile.fail()) // If there is a file...
{ // ...read primes from it
while (true)
{
inFile >> lastprime;
if (inFile.eof()) break;
std::cout << std::setw(10) << lastprime << (++count % perline == 0 ? "\n" : "");
if (count == nprimes) break;
}
inFile.close(); // Reading is finished
if (count == nprimes) // Check if we found them all
{
inFile.close(); // We are done with the file
std::cout << std::endl << count << "primes found in file." << std::endl;
return 0;
}
}
// If we get to here, we need to find more primes
inFile.clear(); // Clear EOF flag
inFile.close(); // Reading is finished
try
{
size_t oldCount {count}; // The number that were in the file
std::ofstream outFile; // Create an output stream object
if (oldCount == 0)
{ // The file is empty
outFile.open(filename); // Open file to create it
if (!outFile.is_open())
throw ios::failure {string {"Error opening output file "} + filename + " in main()"};
outFile << "2\n3\n5\n"; // Write 1st three primes to file
outFile.close();
std::cout << std::setw(10) << 2 << std::setw(10) << 3 << std::setw(10) << 5;
lastprime = 5;
count = 3;
}
while (count < nprimes)
{
lastprime = nextprime(lastprime, filename);
outFile.open(filename, ios::out | ios::app); // Open file to append data
if (!outFile.is_open())
throw ios::failure {string {"Error opening output file "} + filename + " in main()"};
outFile << lastprime << '\n';
outFile.close();
std::cout << std::setw(10) << lastprime << (++count % perline == 0 ? "\n" : "");
}
std::cout << std::endl << nprimes << " primes found. "
<< nprimes-oldCount << " added to file." << std::endl;
}
catch (std::exception& ex)
{
std::cout << typeid(ex).name() << ": " << ex.what() << std::endl;
return 1;
}
}
ulong nextprime(ulong last, const string filename)
{
bool isprime {false}; // true when we have a prime
ulong aprime {}; // Stores a prime from the file
std::ifstream inFile; // Local file input stream object
// Find the next prime
ulong limit {};
while (true)
{
last += 2ULL; // Next value for checking
limit = static_cast<ulong>(std::sqrt(last));
// Try dividing the candidate by all the primes up to limit
inFile.open(filename); // Open the primes file
if (!inFile.is_open())
throw ios::failure {string {"Error opening input file "} +filename + " in nextprime()"};
do
{
inFile >> aprime;
} while (aprime <= limit && !inFile.eof() && (isprime = last % aprime > 0));
inFile.close();
if (isprime) // We got one...
return last; // ...so return it
}
}
该程序的执行流程如下:
1. 获取用户所需的素数数量。
2. 尝试打开
more_primes.txt
文件进行读取,若文件存在且包含足够的素数,读取并显示这些素数,然后结束程序。
3. 若文件不存在或文件中的素数数量不足,清除文件流的
EOF
标志并关闭文件。
4. 若文件为空,创建文件并写入前三个素数(2、3、5)。
5. 在
while
循环中,调用
nextprime()
函数找到下一个素数,以追加模式打开文件并将新素数写入文件,同时显示该素数。
4. 管理当前流位置
可通过
istream
和
ostream
类的成员函数访问和更改当前流位置,这些函数不适用于标准流,适用于所有文件流类对象。
istream
中的
tellg()
函数用于获取输入流对象的当前位置,
ostream
中的
tellp()
函数用于获取输出流对象的当前位置,二者返回
pos_type
类型的值,表示流中的绝对位置。示例如下:
std::ifstream::pos_type here {inFile.tellg()}; // 记录当前文件位置
// 可使用 auto 简化
auto here = inFile.tellg();
通过
seekg()
函数可将输入流对象的位置设置为指定位置,
seekp()
函数用于输出流对象。例如,将
inFile
的流位置重置为
here
:
inFile.seekg(here);
也可相对于流中的三个特定位置(开头、末尾、当前位置)以偏移量指定新位置,偏移量可为正或负。例如:
graph LR
A[输入文件流] --> B[ios::beg]
A --> C[ios::cur]
A --> D[ios::end]
B --> E[正向偏移]
C --> F[正向偏移]
C --> G[负向偏移]
D --> H[负向偏移]
E --> I[inFile.seekg(2, std::ios::beg)]
F --> J[inFile.seekg(-2, std::ios::cur)]
H --> K[inFile.seekg(-4, std::ios::end)]
在
nextprime()
函数中,可通过将文件位置重置为开头来避免每次循环都关闭和重新打开文件,优化后的代码如下:
ulong nextprime(ulong last, const string filename)
{
bool isprime {false}; // true when we have a prime
ulong aprime {}; // Stores a prime from the file
std::ifstream inFile(filename); // Local file input stream object
if (!inFile.is_open())
throw ios::failure {string {"Error opening input file "} + filename + " in nextprime()"};
// Find the next prime
ulong limit {};
while (true)
{
last += 2ULL; // Next value for checking
limit = static_cast<ulong>(std::sqrt(last));
// Try dividing the candidate by all the primes up to limit
do
{
inFile >> aprime;
} while (aprime <= limit && !inFile.eof() && (isprime = last % aprime > 0));
inFile.seekg(0);
if (isprime) // We got one...
{
inFile.close(); // ...close the file...
return last; // ...and return the prime
}
}
}
通过优化,每次循环只需将文件位置重置为开头,找到素数后再关闭文件,提高了程序效率。
文件输入输出操作全解析
5. 流位置操作的详细解释
流位置操作对于文件输入输出至关重要,下面详细解释获取和更改流位置的操作。
- 获取当前流位置 :
-
tellg()用于输入流,tellp()用于输出流,返回pos_type类型的值,代表流中的绝对位置。例如:
std::ifstream inFile("example.txt");
std::ifstream::pos_type currentPos = inFile.tellg();
// 或者使用 auto 简化
auto currentPosAuto = inFile.tellg();
这里
currentPos
和
currentPosAuto
都记录了
inFile
的当前文件位置。
- 更改当前流位置 :
-
seekg()用于输入流,seekp()用于输出流。可以使用绝对位置或相对位置来设置。- 绝对位置 :
std::ifstream inFile("example.txt");
std::ifstream::pos_type targetPos = 10; // 假设要移动到位置 10
inFile.seekg(targetPos);
- **相对位置**:可以相对于流的开头(`ios::beg`)、当前位置(`ios::cur`)、末尾(`ios::end`)进行偏移。偏移量可以是正或负。
std::ifstream inFile("example.txt");
// 从开头偏移 5 个位置
inFile.seekg(5, std::ios::beg);
// 从当前位置向后偏移 3 个位置
inFile.seekg(3, std::ios::cur);
// 从末尾向前偏移 2 个位置
inFile.seekg(-2, std::ios::end);
以下是一个表格总结流位置操作函数:
| 函数 | 适用流类型 | 作用 |
| ---- | ---- | ---- |
|
tellg()
| 输入流 | 获取当前输入流位置 |
|
tellp()
| 输出流 | 获取当前输出流位置 |
|
seekg()
| 输入流 | 设置输入流的位置 |
|
seekp()
| 输出流 | 设置输出流的位置 |
6. 流位置操作在素数程序中的优化效果
在之前的素数程序中,
nextprime()
函数每次循环都打开和关闭文件,效率较低。通过使用流位置操作进行优化后,效果显著。
优化前的
nextprime()
函数:
ulong nextprime(ulong last, const string filename)
{
bool isprime {false};
ulong aprime {};
std::ifstream inFile;
ulong limit {};
while (true)
{
last += 2ULL;
limit = static_cast<ulong>(std::sqrt(last));
inFile.open(filename);
if (!inFile.is_open())
throw ios::failure {string {"Error opening input file "} +filename + " in nextprime()"};
do
{
inFile >> aprime;
} while (aprime <= limit && !inFile.eof() && (isprime = last % aprime > 0));
inFile.close();
if (isprime)
return last;
}
}
优化后的
nextprime()
函数:
ulong nextprime(ulong last, const string filename)
{
bool isprime {false};
ulong aprime {};
std::ifstream inFile(filename);
if (!inFile.is_open())
throw ios::failure {string {"Error opening input file "} + filename + " in nextprime()"};
ulong limit {};
while (true)
{
last += 2ULL;
limit = static_cast<ulong>(std::sqrt(last));
do
{
inFile >> aprime;
} while (aprime <= limit && !inFile.eof() && (isprime = last % aprime > 0));
inFile.seekg(0);
if (isprime)
{
inFile.close();
return last;
}
}
}
优化后的流程如下:
1. 函数开始时打开文件。
2. 在循环中,每次检查新的素数候选值。
3. 循环结束后,使用
seekg(0)
将文件位置重置为开头,避免了频繁打开和关闭文件。
4. 找到素数后关闭文件并返回结果。
通过这种优化,减少了文件打开和关闭的开销,提高了程序的执行效率。
7. 异常处理在文件操作中的重要性
在文件输入输出操作中,异常处理是必不可少的。如在素数程序中,使用
try-catch
块来捕获可能出现的异常。
try
{
size_t oldCount {count};
std::ofstream outFile;
if (oldCount == 0)
{
outFile.open(filename);
if (!outFile.is_open())
throw ios::failure {string {"Error opening output file "} + filename + " in main()"};
outFile << "2\n3\n5\n";
outFile.close();
std::cout << std::setw(10) << 2 << std::setw(10) << 3 << std::setw(10) << 5;
lastprime = 5;
count = 3;
}
while (count < nprimes)
{
lastprime = nextprime(lastprime, filename);
outFile.open(filename, ios::out | ios::app);
if (!outFile.is_open())
throw ios::failure {string {"Error opening output file "} + filename + " in main()"};
outFile << lastprime << '\n';
outFile.close();
std::cout << std::setw(10) << lastprime << (++count % perline == 0 ? "\n" : "");
}
std::cout << std::endl << nprimes << " primes found. "
<< nprimes-oldCount << " added to file." << std::endl;
}
catch (std::exception& ex)
{
std::cout << typeid(ex).name() << ": " << ex.what() << std::endl;
return 1;
}
在上述代码中:
- 当打开文件失败时,会抛出
ios::failure
异常。
-
catch
块捕获异常并输出异常信息,避免程序崩溃。
异常处理的步骤如下:
1. 在
try
块中执行可能抛出异常的操作,如打开文件、写入数据等。
2. 当出现异常时,抛出相应的异常对象。
3.
catch
块捕获异常并进行处理,如输出错误信息、进行恢复操作等。
通过异常处理,可以增强程序的健壮性,提高程序在面对错误时的处理能力。
8. 总结
文件输入输出操作涉及多个方面,包括文件流的关闭与重新打开、流打开模式的设置、流位置的管理以及异常处理等。
- 文件流的关闭和重新打开可以灵活控制文件的访问。
- 合理设置流打开模式可以满足不同的文件操作需求,如读写、追加等。
- 流位置操作可以提高文件操作的效率,避免不必要的文件打开和关闭。
- 异常处理可以增强程序的健壮性,提高程序的稳定性。
在实际编程中,需要综合运用这些知识,根据具体需求进行合理的文件输入输出操作,以实现高效、稳定的程序。
下面是一个简单的 mermaid 流程图,总结整个素数程序的执行流程:
graph TD
A[获取用户所需素数数量] --> B[尝试打开文件读取素数]
B --> C{文件存在且素数足够?}
C -- 是 --> D[显示素数并结束程序]
C -- 否 --> E[清除 EOF 标志并关闭文件]
E --> F{文件为空?}
F -- 是 --> G[创建文件并写入前三个素数]
F -- 否 --> H[继续寻找新素数]
G --> H
H --> I[调用 nextprime() 找下一个素数]
I --> J[以追加模式打开文件写入新素数]
J --> K[显示新素数]
K --> L{素数数量达到要求?}
L -- 否 --> I
L -- 是 --> M[结束程序]
通过这个流程图,可以清晰地看到素数程序的整体执行逻辑,有助于理解和优化程序。
超级会员免费看
2263

被折叠的 条评论
为什么被折叠?



