【C++探索之旅】第一部分第十课:文件读写,海阔凭鱼跃



内容简介

1、第一部分第十课:文件读写,海阔凭鱼跃

2、第一部分第十一课预告:小练习,猜单词


文件读写,海阔凭鱼跃

上一课《【C++探索之旅】第一部分第九课:数组威武,动静合一》中,我们学习了动态数组和静态数组,也看到其实字符串很类似字符数组(到了之后的第二部分,学习面向对象,我们会知道其实string是一个类)。

到目前为止,我们写的程序还比较简单,当然了,因为我们刚开始学习C++嘛。但只要加以训练,我们就慢慢地能够写一些真正的应用了。我们也开始逐渐了解C++的基础知识了,不过缺了很重要的一环:与文件交互。

我们已经学会如何将信息输出到控制台(console)以及如何提取用户在控制台中输入的数据(使用cin和cout)。但是,我们岂能就此罢休。想想我们
之前介绍过的一些程序,例如:记事本,一些IDE(VS, CodeBlocks, xCode, Eclipse,
etc),绘图软件,等等,都能够读写文件。

在游戏领域就更是如此啦(我知道一帮宅男已经激动了):游戏里的数据要保存,游戏的图片,音乐,道具,等等。都需要存档。

总之,如果一个软件不会与文件交互,那么它的功能是比较有限的。

因此,一起来学习如何读写文件吧。你会发现,如果你掌握了cin和cout的用法,那其实你已经知道大半啦。



写入文件


我们要读写文件,首先需要打开文件。就好像平时我们要记笔记一样,你总得先打开笔记本吧,才能阅读内容,或者往里面写东西。

一旦文件被打开之后,接下来的操作就很类似之前用cin和cout来进行标准输入和输出了。我们又会与老朋友<<和>>见面。

用术语来说,我们会将一个程序和外界的通信方式用"流"来描述。流,英语是stream。记得吗?我们要使用cin和cout,需要用

#include <iostream>

因为cin和cout定义在iostream这个C++的标准库中。而这里的iostream就是input output stream的缩写,表示"输入输出流"。所以,其实我们早就在不知不觉地接触流的概念了。

在这一章中,我们要和文件交互,那么就需要文件流来帮忙了。聪明如你一定想到了,是的,文件的英语是file,那么文件流就是file stream。是不是很简单呢?

因此,我们需要用到fstream这个标准库,fstream就是file stream的缩写。

当然了,如果你不是参加一个程序员的派对,也不需要显得很专业,那么说"读写文件"就可以了。

fstream头文件

在C++中,我们要使用一个功能,需要引入合适的头文件。因此,我们在程序一开始须要这样做:

#include <fstream>

接下来,我们就学习如何创建一个文件流,以便我们能读写文件。

以写模式打开文件

流其实是对象,还记得我们说C++是一门面向对象的语言吗?当然我们现在还不深究,要到第二部分讲面向对象编程时才会畅聊类和对象。暂时只需要知道这些流其实都是C++的对象(不是找对象的对象,少年你想多了)。

也完全无需害怕,因为我们之后还会不断提到流。暂时,只需把其看作比较高级的变量就可以了。这些文件流包含了文件的很多信息,提供给我们很多功能,例如可以关闭文件,在文件中移动,等等。


会看到,声明一个流的对象,其实就和我们声明变量一样简单。首先,我们来看看如何创建用于写文件的流,须要用到ofstream,也就是output
file stream,因为是从程序向文件输入数据,因此对于程序来说是"出去"的流,因此是output(输出),而不是input(输入)。

话休絮烦。翠花,上"栗子":

#include <iostream>
#include <fstream>
using namespace std;

int main()
{   
    ofstream myStream("C:/Cpp/Files/scores.txt");   
    //声明用于写入文件的流,
    //此文件是在C盘下的Cpp文件夹的子文件夹Files中的scores.txt文件
    
    return 0;
}

在上面程序中,我在myStream后面的括号中指定了文件的路径.这个路径可以有两种形式:

  • 绝对路径:就是文件的所在,不过是从根文件夹开始的路径。例如:C:/Cpp/Files/scores.txt
  • 相对路径:也是文件的所在,不过是相对于你的程序的路径。例如,你的程序位于C:/Cpp/,那么如果你的文件是在C:/Cpp/Files/scores.txt,你在程序里指定文件的相对路径时就要写 Files/scores.txt

自此,我们就可以使用这个文件流来写文件啦。

如果文件不存在,那么会被自动创建。不过,至少指定的目录要存在,不然会出现"目录不存在"的错误。在我们上面的例子中,至少目录C:/Cpp/Files必须事先存在。

在打开文件的时候,也会有其他问题。例如文件不属于你,或者磁盘已满,等等,总之,打开失败。因此,我们为了保险起见,总要测试文件是否顺利被打开。我们使用 if (myStream) 的方法来测试。

ofstream myStream("C:/Cpp/Files/scores.txt");  //试着打开这个文件

if(myStream)  //测试打开文件是否成功
{
    //一切顺利,我们可以使用此文件了
}
else
{
    cout << "出错: 无法打开此文件." << endl;
}

至此,我们已经做好了写文件的准备工作。你会看到,接下来的操作还是有点眼熟的。

向流中写入数据

前面我们说过写入文件的操作就和以前我们使用cout类似。因此当我对你说要使用<<运算符来进行操作的时候,你应该不会太惊讶。

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main()
{
    string const fileName("C:/Cpp/Files/scores.txt");
    ofstream myStream(fileName.c_str());
    
    if(myStream)    
    {
        myStream << "大家好,我是被写入文件的一句话." << endl;
        myStream << 54.26 << endl;
        int age(23);
        myStream << "我" << age << "岁了." << endl;
    }
    else
    {
        cout << "出错: 无法打开此文件." << endl;
    }
    
    return 0;
}

上面的程序中,可以看到我们首先声明了一个string的变量,里面存放了C:/Cpp/Files/scores.txt这个字符串,不过之后在将其赋给ofstream的对象myStream时,我们却用了c_str()这个函数,这是为什么呢?

其实,ofstream接受的参数是char *(暂时不需要知道是什么,马上我们会学习指针的知识,到时就清楚了),c_str()函数就是用于将string转换成char *

运行此程序,不出意外的话,你的电脑的C:/Cpp/Files/目录下就会多出一个文件 scores.txt, 里面的内容如下所示:

你也来试试

你也可以写一个程序,请求用户输入自己的名字和年龄,然后你的程序将这些信息写入文件。

文件的不同打开模式

我们只需要再处理一个小问题:

假如文件已经存在,那怎么办呢?

如果运行上面的已有程序,那么文件的内容会被删除,然后替换为你写入的内容。但是假如我们想要保留文件本来的内容,只是想在文件末尾追加我们的新内容呢?

不用怕,肯定有办法的。只需要在打开文件的时候添加第二个参数,用于指明文件的打开模式,如下所示:

ofstream myStream("C:/Cpp/Files/scores.txt", ios::app);

app是英语append的缩写,表示"追加",也就是说写入的内容不会覆盖原本文件里的内容,而是追加到文件末尾。



读取文件

我们学习了如何写文件,现在来学习如何读取文件内容吧。你会看到,两种操作是很类似的。

以读的形式打开文件

之前我们用了ofstream的对象,那么这次就要用到ifstream的对象了,ifstream是input file stream的缩写。当然也需要测试文件是否顺利被打开。

ifstream myStream("C:/Cpp/Files/scores.txt");  //试着打开文件

if(myStream)
{
    //可以读取文件
}
else
{
    cout << "出错: 无法以读的形式打开此文件." << endl;
}

没有什么新的难点不是吗?

接下来我们就可以读取文件内容了。

要读取文件内容,有三种不同的方式:

  1. 一行一行地读取,用getline()函数
  2. 一个词一个词地读取,用>>
  3. 一个字符一个字符地读取,用get()函数

我们分别来学习这三种方式:

一行一行地读取

第一种方式可以一次读取整一行的内容,将其存储在一个字符串里。举例如下:

string line; // 储存整行内容的字符串变量
getline(myStream, line); //读取整一行,存储到line中

此函数的原理和cin是类似的。

一个词一个词地读取

第二种方式,其实你也早就知道了,毕竟聪慧如你嘛。举例如下:

double number;
myStream >> number; //从文件中读取一个浮点数
string word;
myStream >> word;    //从文件中读取一个单词


个方法会读取当前所在的文件位置处的内容和之后的一个空格("词"并不是我们平时说的一个单词,而是以空格来分隔的,假如中间没有空格,那么就是一个词,
例如heusyg3这是一个词,但是heu
syg3却被认为是两个词,因为中间存在空格)。读取的内容根据变量的类型会被转换成double,int,string,等等。

一个字符一个字符地读取

第三种方式,我们之前没学过,不过也很简单就是了。举例如下:

char a;
myStream.get(a);

上面的代码读取一个字符,将其存储在char型变量a中。

这个方法可以读取所有字符,不管是字母,空格,回车符,制表符,等等。

还记得在【C++探索之旅】第一部分第五课:简易计算器中,
我们学习过cin的用法吗?还记得我们说过在cin>>和getline之间需要使用cin.ignore()吗?因此,这里我们从一个词一
个词地读取(用cin>>)转换到一行一行地读取(用getline()),也需要在之间加入ignore()。不过,因为我们这里是在读取
文件,所以不能用cin.ignore(),而要使用ifstream的ignore方法,如下所示:

ifstream myStream("C:/Cpp/Files/scores.txt");

string word;
myStream >> word;          //读取一个词

myStream.ignore();        //改变读取方式

string line;
getline(myStream, line); //读取一整行

一次读取整个文件

很多时候,我们会希望读取整个文件。我们已经学习了如何读取文件,但是还没学习当到达文件结尾时,如何停止。


了获知我们是否还可以继续读取,可以用getline函数的返回值。getline函数的返回值是一个bool(布尔值),如果等于true,还可以继续
读,说明还没到文件末尾;如果等于false,那么说明已经读取了文件的最后一行或者出错了。在false的情况下,就不能再继续读取了。

还记得我们学过的循环吗?只要还没到达文件末尾(getline函数返回是true),我们就继续读取文件。while循环就是最好的选择啦。看如下例子:

#include <iostream>
#include <fstream>
#include <string>
using namespace std; 

int main()
{
   ifstream file("C:/Cpp/Files/scores.txt"); // 尝试打开文件
   
   if(file)
   {
      //文件顺利打开,可以读取了
      
      string line;  //存储读取的一整行的变量
      
      while(getline(file, line))  //只要没到达文件末尾,我们就一直一行一行地读取
      {
         cout << line << endl;
         //在控制台显示读取的行
         //或者随便你拿这一行干什么,由你决定
      }
   }
   else
   {
      cout << "出错: 无法以读的形式打开此文件." << endl;
   }
   
   return 0;
}

一旦我们读取了这些行,我们就可以非常方便地操作它们了。在上面的例子中,我们只是把读取的每一行显示在控制台中,但是你可以随便怎么用。



一些小技巧

这一课的最后,我们来学习几个小技巧,这样文件读写我们就学习得差不多了。

提前关闭文件

我们已经知道怎么打开一个文件,但还没演示如何关闭文件。倒不是因为我忘记了,而是之前关闭文件显得没有那么必要。一旦我们跳出了文件流声明的区块,打开的文件就会被自动关闭。例如:

void f()
{
   ofstream myStream("C:/Cpp/Files/scores.txt"); //打开文件
   
   // 操作文件
   
}  //当我们跳出这个函数,文件就自动被关闭了

因此,并不需要做任何操作来显式地关闭文件。

但是,有时候我们想要提前关闭文件,在它被自动关闭前。为了达到这个目的,我们必须"不择手段"... 哦,不是,是使用close函数。例如:

void f()
{   
    ofstream myStream("C:/Cpp/Files/scores.txt");  
    //打开文件C:/Cpp/Files/scores.txt
    
    //使用文件   
    
    myStream.close();  //关闭文件
    //自此,我们将不能再往文件里写东西了
}

同样地,我们也可以推迟打开文件。用open函数。例如:

void f(){   
    ofstream myStream;  //声明文件流,但没有绑定文件
    myStream.open("C:/Cpp/Files/scores.txt");  
    //打开文件C:/Cpp/Files/scores.txt

    //使用文件   

   myStream.close();  //关闭文件   
   //自此,我们将不能再往文件里写东西了
}

正如你所见,以上的操作都很简单。然而,在大部分时候,没必要使用open和close函数来显示地打开和关闭文件。

文件里的游标

我们再来深入一些技术细节,"研究"一下文件的读取是怎么运作的。

你还记得平时用文本编辑器的时候,我们在编辑文本时总会有一个一闪一闪的光标(cursor),指示了我们当前编辑的位置吗?如下图所示:

可以看到,目前光标位于Oscar的后面。

在C++中操作文件时,也是同样的原理。有一个游标(cursor)一直指示当前在文件中的位置。

例如,当我们运行这一行的时候:

ifstream file("C:/Cpp/Files/scores.txt");

文件C:/Cpp/Files/scores.txt会被打开,游标会定位于文件最开始处。

如果之后我们读取第一个词,就会读取到Oscar这个词。读取完之后,我们的游标就会位于下一个单词的开始处了,如下图所示:

可以看到,现在游标位于is这第二个词的开始处了。然后我们可以接着读取第二个词,第三个,... 一直到文件结束。

但如果这样的话,我们只能按顺序读取文件,这可太束缚了。我们需要自由,需要飞翔,"在你的心上,自由地飞翔~" (小编,你的药已经准备好了...)

幸好,我们能够在文件中移动,说到移动,那就是移动那个cursor(游标)了。例如,我们可以说"我要移动到距离文件开始处20个字符的地方",或者"我要从当前位置前进32个字符"。这样,我们就可以很方便地读取我们真正想要的内容了。

首先,我们要了解游标目前位于哪里。然后才能正确地移动。

获得在文件中的位置

有一个方法可以获知当前我们的游标位于文件的第几个字符处(从文件开始处算起)。不过,对于输入文件流(ifstream)和输出文件流(ofstream),所用的函数不一样,而且名字也有点古怪,我们列在下面:


针对ifstream


针对ofstream


tellg()


tellp()

然而,这两个函数的使用方法完全一样。因此只介绍其中一个就可以了。举例如下:

ofstream file("C:/Cpp/Files/scores.txt");
int position = file.tellp(); //获取当前位置
cout << "目前位于文件中的第" << position << "个字符处." << endl;

在文件中移动

用于在文件中移动的函数也有两个,成对的,每一个对应一种流的形式:


针对ifstream


针对ofstream


seekg()


seekp()

用法和之前的两个函数类似。

这两个函数接受两个参数:一个是在文件中的位置,另一个是相对文件中的位置的距离数(字符数/字节数)。

myStream.seekp(numberOfCharacters, position);

对于此函数的position参数,有三种可能的位置:

  • 文件开始处 : ios::beg ;
  • 文件末尾处 : ios::end ;
  • 当前位置 : ios::cur.

例如,我想要移动到距离文件开始处10个字符的地方,我会这么做:

myStream.seekp(10, ios::beg);

假如我想要移动到距离当前游标所在位置的20个字符处,我会这么做:

myStream.seekp(20, ios::cur);

相信你已经理解啦。

获知文件大小(所包含字节数)

这第三个小技巧需要用到前两个。为了获知文件的大小,我们首先移动到文件末尾,然后询问我们所在的位置。你知道怎么做了吗?一起来看看吧:

#include <iostream>
#include <fstream>
using namespace std;

int main()
{
    ifstream file("C:/Cpp/Files/scores.txt");  //打开文件
    
    file.seekg(0, ios::end);  //移动到文件末尾
    
    int size;
    size = file.tellg();
    //在文件结尾处调用tellg这个函数,以获得目前位于第几个字符处,因此也就知道了文件的大小
    
    cout << "文件的大小是: " << size << "个字节." << endl;
    
    return 0;
}

好了,我们学完了文件读写的大致概念。不过肯定不只于此,还有很多知识点需要慢慢在实践中去探索。



总结

  1. 在C++中,为了能读写文件,需要引入fstream头文件。
  2. 为了写入文件,我们需要创建一个ofstream对象;为了读取文件,我们需要创建一个ifstream对象。
  3. 写入文件的操作其实很类似 cout :  myStream << "文本";  读取文件的操作其实很类似 cout :  myStream >> variable;
  4. 可以用getline()函数一行一行地读取文件。
  5. 游标(cursor)指示了写入操作或读取操作时,在文件中的位置。如果需要,可以移动这个游标。

第一部分第十一课预告

今天的课就到这里,一起加油吧!

下一课我们学习:小练习,猜单词

时间: 2024-10-10 15:06:27

【C++探索之旅】第一部分第十课:文件读写,海阔凭鱼跃的相关文章

【C语言探索之旅】 第一部分第十课:练习题+习作

内容简介 1.课程大纲 2.第一部分第十课: 练习题+习作 3.第二部分第一课预告: 模块化编程 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个游戏. C语言编程基础知识 什么是编程? 工欲善其事,必先利其器 你的第一个程序 变量的世界 运算那点事 条件表达式 循环语句 实战:第一个C语言小游戏 函数 练习题 习作:完善第一个C语言小游戏 C语言高级技术 模块化编程 进击的指针,C语言王牌 数组 字符串 预处理 创建你自己的变量类型 文件

Linux探索之旅 | 第五部分第二课:一入Shell深似海,酷炫外壳惹人爱

-- 简书作者 谢恩铭 转载请注明出处 内容简介 前言 Shell是什么? 我们的第一个Shell脚本 运行Shell脚本 总结 第五部分第三课预告:变量在手,Shell不愁 1. 前言 上一课是 Linux探索之旅 | 第五部分第一课:Vim岂是池中物,宝剑锋从磨砺出 . 现在,我们已经学习了 Vim 这样强大的文本编辑器.相信我,Vim 对我们之后的课程会非常有用. 这一课我们可以进入第五部分的重心了:Shell 编程. 什么是Shell呢? 首先,shell 是英语"壳,外壳"的

【C语言探索之旅】 第二部分第二课:进击的指针,C语言的王牌!

内容简介 1.课程大纲 2.第二部分第二课: 进击的指针,C语言的王牌 3.第二部分第三课预告: 数组 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个游戏. C语言编程基础知识 什么是编程? 工欲善其事,必先利其器 你的第一个程序 变量的世界 运算那点事 条件表达式 循环语句 实战:第一个C语言小游戏 函数 练习题 习作:完善第一个C语言小游戏 C语言高级技术 模块化编程 进击的指针,C语言王牌 数组 字符串 预处理 创建你自己的变量类型

【Linux探索之旅】第二部分第二课:命令行,世界尽在掌握

内容简介 1.第二部分第二课:命令行,世界尽在掌握 2.第二部分第三课预告:文件和目录,组织不会亏待你 命令行,世界尽在掌握 今天的标题是不是有点霸气侧漏呢? 读者:"小编,你为什么每次都要起这么非主流的标题呢?不能愉快地玩耍么?" 小编:"那我问你,老子他为什么要写<道德经>咧?" 读者:"为什么咧?" 小编:"因为老子愿意!" 开个小玩笑轻松一下 O(∩_∩)O~ 没办法,不能不激动,因为我们终于来到了这一刻,

【C语言探索之旅】 第二部分第九课: 实战&quot;悬挂小人&quot;游戏

内容简介 1.课程大纲 2.第二部分第九课: 实战"悬挂小人"游戏 3.第二部分第十课预告: 安全的文本输入 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个游戏. C语言编程基础知识 什么是编程? 工欲善其事,必先利其器 你的第一个程序 变量的世界 运算那点事 条件表达式 循环语句 实战:第一个C语言小游戏 函数 练习题 习作:完善第一个C语言小游戏 C语言高级技术 模块化编程 进击的指针,C语言王牌 数组 字符串 预处理 创建

【C语言探索之旅】 第二部分第九课: 实战[悬挂小人]游戏

内容简介 1.课程大纲 2.第二部分第九课: 实战"悬挂小人"游戏 3.第二部分第十课预告: 安全的文本输入 课程大纲 我们的课程分为四大部分,每一个部分结束后都会有练习题,并会公布答案.还会带大家用C语言编写三个游戏. C语言编程基础知识 什么是编程? 工欲善其事,必先利其器 你的第一个程序 变量的世界 运算那点事 条件表达式 循环语句 实战:第一个C语言小游戏 函数 练习题 习作:完善第一个C语言小游戏 C语言高级技术 模块化编程 进击的指针,C语言王牌 数组 字符串 预处理 创建

【Web探索之旅】第二部分第二课:服务器语言

内容简介 1.第二部分第二课:服务器语言 2.第二部分第三课预告:框架和内容管理系统 第二部分第二课:服务器语言 介绍了Web的客户端,我们来谈谈Web的服务器端. 既然客户端有客户端的编程语言(HTML,CSS和JS),那么我们服务器端岂能逊色呢,对吧. 服务器端也有不少种编程语言.这些编程语言写成的程序会在服务器端的电脑上被执行. 如果说客户端的语言编写的程序决定了我们的网页的外观,那么服务器端的语言编写的程序决定了网页的功能和如何与用户交互. 你也许会问:"既然我们可以用HTML,CSS和

【Linux探索之旅】第二部分第九课:查找文件,无所遁形

内容简介 1.第二部分第九课:查找文件,无所遁形 2.第二部分测试题 查找文件,无所遁形 这一课不难,但挺重要的. 之前的课程我们见识过了Linux下文件的组织形式是很特别的,跟Windows不一样. 我们也用ls / 这个命令来列出根目录下的所有目录,有/bin,/etc,/var,/home,等等.而这些目录下又有子目录和文件,错综复杂. 这些目录中有一部分是历史遗留的,从Unix时代就有了.问题是:我们如何在这"茫茫文海"中查找我们需要的文件. "人潮人海中,有你有我.

三十二 文件读写

读写文件是最常见的IO操作.Python内置了读写文件的函数,用法和C是兼容的. 读写文件前,我们先必须了解一下,在磁盘上读写文件的功能都是由操作系统提供的,现代操作系统不允许普通的程序直接操作磁盘,所以,读写文件就是请求操作系统打开一个文件对象(通常称为文件描述符),然后,通过操作系统提供的接口从这个文件对象中读取数据(读文件),或者把数据写入这个文件对象(写文件). 读文件 要以读文件的模式打开一个文件对象,使用Python内置的open()函数,传入文件名和标示符: >>> f =