C++ 文件操作与输出格式化全解析
1. 文件操作基础
在 C++ 中, ios_base 类是 ofstream 和 ifstream 的基类。对于较新的编译器, ofstream 是一个模板类,它派生自 basic_ofstream ,而 basic_ofstream 又派生自 basic_ios , basic_ios 最终派生自 ios_base 。你可以在 这里 查看这些类的简化图。
读取现有文件的操作与使用 cin 对象类似。以下是一个示例代码,用于打开文件并读取其中的字符串:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
int main()
{
string word;
ifstream infile("../MyFile.txt");
infile >> word;
cout << word << endl;
infile.close();
return 0;
}
运行此程序时,之前写入文件的字符串 “Hi,” 会显示在屏幕上。
2. 打开文件时的错误处理
打开文件时可能会遇到各种问题,因为文件存储在物理设备上,如硬盘、闪存驱动器或 SD 卡。以下是一些可能出现的问题:
- 磁盘部分损坏,导致现有文件损坏。
- 磁盘空间不足。
- 尝试打开不存在的目录中的文件。
如果指定完整路径和文件名来打开文件进行写入,但目录不存在,计算机的响应会因操作系统而异。你可以编写一个简单的测试程序来尝试创建和打开一个不存在的目录中的文件,例如 /abc/def/ghi/jkl/abc.txt 。
在 Windows 系统中,尝试在不存在的目录中创建文件时,系统不会创建该目录,因为底层调用的操作系统函数 CreateFile() 不会为你创建目录。
为了确定 ostream 类是否无法创建文件,可以调用其 fail() 成员函数。如果对象无法创建文件,该函数将返回 true 。以下是一个示例代码:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ofstream outfile("/abc/def/ghi/MyFile.txt");
if (outfile.fail()) {
cout << "Couldn't open the file!" << endl;
return 0;
}
outfile << "Hi" << endl;
outfile.close();
return 0;
}
运行此代码时,如果系统中不存在 /abc/def/ghi 目录,你将看到 “Couldn’t open the file!” 消息。
除了调用 fail() 成员函数外,还可以使用各种流类中可用的 “!” 运算符。以下是使用该运算符的示例代码:
if (!outfile)
{
cout << "Couldn't open the file!" << endl;
return 0;
}
虽然很多人更喜欢使用 !outfile 而不是 outfile.fail() ,但 !outfile 可能会让初学者感到困惑,因此建议使用 outfile.fail() 。
文件创建失败的原因可能有以下几种:
- 目录不存在。
- 磁盘空间不足。
- 应用程序没有创建文件的权限。
- 文件名无效,包含操作系统不允许的字符,如 * 或 ? 。
一个好的应用程序应该做到以下两点:
1. 检查文件创建是否成功。
2. 如果文件创建失败,进行适当的处理,例如向用户显示友好的错误消息,并建议他们释放更多磁盘空间。
3. 使用 ios 标志
在创建 ofstream 或 ifstream 实例来打开文件时,可以通过提供标志来修改文件的打开方式。标志是一种小的指示符,用于告诉函数如何执行操作。
以下是一些常见的标志:
| 标志 | 描述 |
| ---- | ---- |
| ios::app 或 ios_base::app | 以追加模式打开文件,将数据追加到现有文件的末尾。 |
| ios::in 或 ios_base::in | 以读取模式打开文件。 |
| ios::out 或 ios_base::out | 以写入模式打开文件。 |
| ios::trunc 或 ios_base::trunc | 在写入文件之前清空文件内容。 |
| ios::nocreate | 仅在文件已存在时打开文件,若文件不存在则不创建。 |
| ios::noreplace | 仅创建新文件,若文件已存在则不打开。 |
以下是使用标志的示例代码:
ofstream outfile("AppendableFile.txt", ios::app);
你可以使用按位或运算符 | 组合多个标志。例如, ios::app 和 ios::nocreate 一起使用时,表示仅打开已存在的文件并追加数据:
ofstream outfile("../MyFile.txt", ios::app | ios::nocreate);
if (outfile.fail()) {
cout << "Couldn't open the file!" << endl;
return 0;
}
outfile << "Hi" << endl;
outfile.close();
需要注意的是, nocreate 标志在新的标准库中不可用。如果你的编译器不支持该标志,可以使用以下替代方法:
ifstream infile("../MyFile.txt");
if (infile.fail())
{
cout << "Couldn't open the file!" << endl;
return 0;
}
infile.close();
ofstream outfile("../MyFile.txt", ios::app);
outfile << "Hi" << endl;
outfile.close();
4. 写入文件的基本操作
在 C++ 中,使用插入运算符 << 写入文件非常简单。就像使用 cout 对象向控制台写入数据一样,你可以使用相同的方式向文件写入数据。以下是一个示例代码:
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
ofstream outfile("outfile.txt");
outfile << "Lookit me! I'm in a file!" << endl;
int x = 200;
outfile << x << endl;
outfile.close();
return 0;
}
此代码创建一个名为 outfile.txt 的文件,并向其中写入字符串和整数。
插入运算符 << 是一个重载的运算符函数,在 basic_ostream 类(对于 100% 符合 ANSI 标准的库)或 ostream 类(对于不完全符合 ANSI 标准的库)中,有多个重载形式,可用于处理基本类型和一些标准 C++ 类。
5. 输出格式化
当保存数字列表到文件时,对数字进行格式化可以使输出更加整齐。输出格式化主要包括以下三个方面:
- 格式标志 :指定输出的一般样式,例如以科学计数法显示浮点数,或用 true 和 false 表示布尔值。
- 精度 :指定浮点数小数点右侧的位数。
- 字段宽度 :指定数字占用的空间,用于对齐数据。
5.1 格式标志
ISO C++ 标准提供了许多格式标志。对于不完全符合 ANSI 标准的编译器,使用 ios 类的标志;对于完全符合 ANSI 标准的编译器,使用 ios_base 类的标志。
以下是一些常见的格式标志及其描述:
| 标志 | 描述 |
| ---- | ---- |
| boolalpha (仅 ANSI 标准) | 设置该标志后,布尔变量将以 true 或 false 输出;清除该标志后,布尔变量将以 0 或 1 输出。 |
| fixed | 指定浮点数输出不以科学计数法显示(对于大数可能仍会以科学计数法显示)。 |
| scientific | 指定浮点数始终以科学计数法显示。 |
| dec | 设置整数以十进制输出。 |
| hex | 设置整数以十六进制输出。 |
| oct | 设置整数以八进制输出。 |
| left | 使数字在宽度字段中左对齐。 |
| right | 使数字在宽度字段中右对齐。 |
| showbase | 打印整数时,在前面加上表示基数的特殊字符。 |
| showpoint | 使浮点数即使是整数也显示小数点。 |
| showpos | 使正数前面显示加号。 |
| unitbuf | 每次输出操作后刷新输出。 |
| uppercase | 使十六进制或科学计数法中的字母以大写显示。 |
以下是使用格式标志的示例代码:
cout.setf(ios_base::scientific);
cout << 987654.321 << endl;
要关闭某个标志,可以调用 unsetf() 成员函数,或者设置另一个相反的标志。例如,要关闭科学计数法模式,可以使用以下代码:
cout.unsetf(ios_base::scientific);
cout.setf(ios_base::fixed);
部分格式标志也有对应的操纵符形式,例如 boolalpha 可以使用以下两种方式设置:
cout.setf(ios_base::boolalpha);
cout << boolalpha;
以下是一些标志的操纵符和反操纵符表格:
| 标志 | 操纵符(开启) | 操纵符(关闭) |
| ---- | ---- | ---- |
| boolalpha | boolalpha | noboolalpha |
| showbase | showbase | noshowbase |
| showpoint | showpoint | noshowpoint |
| showpos | showpos | noshowpos |
| skipws | skipws | noskipws |
| uppercase | uppercase | nouppercase |
| fixed | fixed | scientific |
| scientific | scientific | fixed |
还有一些操纵符没有反操纵符,它们是三态的,例如 dec 、 hex 和 oct 用于设置整数的基数, internal 、 left 和 right 用于设置对齐方式,同一时间只能激活一个。
5.2 指定精度
在写入浮点数时,指定小数点右侧的位数(即精度)通常很有用。可以通过调用流的 precision() 函数来设置或读取精度。
以下是设置精度的示例代码:
cout.precision(4);
如果不设置精度,流通常会有一个默认精度,可能是 6 位,具体取决于编译器。
精度与 showpoint 格式标志结合使用时会有有趣的效果。在科学领域,精度指的是不计算小数点左侧最左边 0 的总位数。例如, 3.567 、 8432. 和 0.1853 被认为具有相同的精度,因为它们都有四位有效数字。
以下是一个结合 showpoint 和 precision 的示例代码:
#include <iostream>
using namespace std;
int main()
{
int i;
cout.setf(ios_base::showpoint);
cout.precision(4);
for (i=1; i<=10; i++) {
cout << 1.0 / i << endl;
}
cout << 2.0 << endl;
cout << 12.0 << endl;
cout << 12.5 << endl;
cout << 123.5 << endl;
cout << 1234.9 << endl;
cout << 12348.8 << endl;
cout << 123411.5 << endl;
cout << 1234111.5 << endl;
return 0;
}
precision() 函数还有一个对应的操纵符 setprecision ,使用时需要包含 <iomanip> 头文件。以下两行代码的效果相同:
cout.precision(4);
cout << setprecision(4);
5.3 设置字段宽度
使用流的 width() 成员函数或 setw() 操纵符可以设置字段宽度,使数据对齐。
以下是使用 width() 函数的示例代码:
cout.width(10);
cout << 20 << endl;
输出结果为:
20
注意, width() 函数设置的宽度只对下一个输出操作有效。为了避免这个问题,建议使用 setw() 操纵符:
cout << setw(10) << 20 << setw(10) << 30 << endl;
输出结果为:
20 30
以下是一个使用 setw() 操纵符创建整齐表格的示例代码:
#include <iostream>
#include <iomanip>
#include <fstream>
using namespace std;
int main()
{
ofstream sals("salaries.txt");
sals << setprecision(2);
sals << fixed;
sals << left;
sals << setw(20) << "Name" << setw(10) << "Salary";
sals << endl;
sals << "------------------- "; // 19 hyphens, one space
sals << "----------" << endl; // 10 hyphens
sals << setw(20) << "Hank Williams";
sals << setw(10) << 28422.82 << endl;
sals << setw(20) << "Buddy Holly";
sals << setw(10) << 39292.22 << endl;
sals << setw(20) << "Otis Redding";
sals << setw(10) << 43838.55 << endl;
sals.close();
return 0;
}
运行此代码后,会生成一个名为 salaries.txt 的文件,内容如下:
Name Salary
------------------- ----------
Hank Williams 28422.82
Buddy Holly 39292.22
Otis Redding 43838.55
需要注意的是,指定的字段宽度是最小值。如果输出字符数小于字段宽度,运行时库会用空格填充;如果输出字符数大于字段宽度,库不会截断数据。
6. 确保数据安全与获取工作目录
有时候,我们希望将数据放置在特定的公共文件夹中,比如当前工作目录(应用程序使用的目录)。C++ 提供了 getcwd() 方法来获取此信息,该方法在 <direct.h> 头文件中。使用 getcwd() 方法的步骤如下:
1. 创建一个用于存放信息的缓冲区。
2. 调用 getcwd() 方法让 C++ 提供当前工作目录信息。
以下是一个示例代码:
#include <iostream>
#include <direct.h>
#include <stdlib.h>
using namespace std;
int main()
{
char CurrentPath[_MAX_PATH];
getcwd(CurrentPath, _MAX_PATH);
cout << CurrentPath << endl;
return 0;
}
运行此代码,输出将是包含应用程序的目录名称,例如 C:\CPP_AIO\BookV\Chapter02\GetWorkingDirectory 。 _MAX_PATH 常量是路径的最大长度,这里创建了一个 char 数组来存储当前工作目录信息。
7. 总结与建议
在进行文件操作和输出格式化时,我们需要注意以下几点:
- 错误处理 :在打开和创建文件时,要检查操作是否成功,并对失败情况进行适当处理,避免给用户带来糟糕的体验。可以使用 fail() 函数或 ! 运算符进行检查,但建议使用 fail() 以避免初学者产生困惑。
- 标志使用 :根据编译器的兼容性选择合适的标志( ios 或 ios_base ),并了解不同标志的作用和组合方式。对于不支持的标志,要掌握替代方法。
- 格式化输出 :合理使用格式标志、精度和字段宽度来使输出更加整齐和易读。使用操纵符时要注意其使用规则和头文件的包含。
以下是一个简单的流程图,展示了文件操作的基本流程:
graph TD;
A[开始] --> B[打开文件];
B --> C{文件打开成功?};
C -- 是 --> D[进行读写操作];
C -- 否 --> E[输出错误信息];
D --> F[关闭文件];
E --> F;
F --> G[结束];
总之,通过掌握文件操作和输出格式化的技巧,我们可以编写出更加健壮和易读的 C++ 程序,为数据的存储和展示提供更好的支持。希望这些内容能帮助你在 C++ 编程中更加得心应手。
在实际应用中,你可以根据具体需求灵活运用这些知识。例如,在处理大量数据时,合理的输出格式化可以提高数据的可读性;在多用户环境下,确保文件操作的安全性和错误处理的完善性可以避免程序崩溃和数据丢失。不断实践和探索,你将能够更好地掌握这些技术。
超级会员免费看
1818

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



