43、C++ 文件操作与输出格式化全解析

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++ 编程中更加得心应手。

在实际应用中,你可以根据具体需求灵活运用这些知识。例如,在处理大量数据时,合理的输出格式化可以提高数据的可读性;在多用户环境下,确保文件操作的安全性和错误处理的完善性可以避免程序崩溃和数据丢失。不断实践和探索,你将能够更好地掌握这些技术。

【轴承故障诊断】加权多尺度字典学习模型(WMSDL)及其在轴承故障诊断上的应用(Matlab代码实现)内容概要:本文介绍了加权多尺度字典学习模型(WMSDL)在轴承故障诊断中的应用,并提供了基于Matlab的代码实现。该模型结合多尺度分析字典学习技术,能够有效提取轴承振动信号中的故障特征,提升故障识别精度。文档重点阐述了WMSDL模型的理论基础、算法流程及其在实际故障诊断中的实施步骤,展示了其相较于传统方法在特征表达能力和诊断准确性方面的优势。同时,文中还提及该资源属于一个涵盖多个科研方向的技术合集,包括智能优化算法、机器学习、信号处理、电力系统等多个领域的Matlab仿真案例。; 适合人群:具备一定信号处理和机器学习基础,从事机械故障诊断、工业自动化、智能制造等相关领域的研究生、科研人员及工程技术人员。; 使用场景及目标:①学习并掌握加权多尺度字典学习模型的基本原理实现方法;②将其应用于旋转机械的轴承故障特征提取智能诊断;③结合实际工程数据复现算法,提升故障诊断系统的准确性和鲁棒性。; 阅读建议:建议读者结合提供的Matlab代码进行实践操作,重点关注字典学习的训练过程多尺度分解的实现细节,同时可参考文中提到的其他相关技术(如VMD、CNN、BILSTM等)进行对比实验算法优化。
【硕士论文复现】可再生能源发电电动汽车的协同调度策略研究(Matlab代码实现)内容概要:本文档围绕“可再生能源发电电动汽车的协同调度策略研究”展开,旨在通过Matlab代码复现硕士论文中的核心模型算法,探讨可再生能源(如风电、光伏)大规模电动汽车接入电网后的协同优化调度方法。研究重点包括考虑需求侧响应的多时间尺度调度、电动汽车集群有序充电优化、源荷不确定性建模及鲁棒优化方法的应用。文中提供了完整的Matlab实现代码仿真模型,涵盖从场景生成、数学建模到求解算法(如NSGA-III、粒子群优化、ADMM等)的过程,帮助读者深入理解微电网智能电网中的能量管理机制。; 适合人群:具备一定电力系统基础知识和Matlab编程能力的研究生、科研人员及从事新能源、智能电网、电动汽车等领域技术研发的工程人员。; 使用场景及目标:①用于复现和验证硕士论文中的协同调度模型;②支撑科研工作中关于可再生能源消纳、电动汽车V2G调度、需求响应机制等课题的算法开发仿真验证;③作为教学案例辅助讲授能源互联网中的优化调度理论实践。; 阅读建议:建议结合文档提供的网盘资源下载完整代码,按照目录顺序逐步学习各模块实现,重点关注模型构建逻辑优化算法的Matlab实现细节,并通过修改参数进行仿真实验以加深理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值