出处http://blog.csdn.net/btooth/article/details/995097简介本教程将以C++最基本的文件I/O(输出/输出)开始。此后,我将从更深入的方面,为你展示一些技巧,并分析给出一些有用的函数。你需要对C++有一个较好的理解,否则这个教程于你而言将是陌生而毫无用处。你的第一个程序首先我将给出一段代码,接着再逐行进行解释。我们的第一个程序将建立一个文件,并写入一些字符:#include
{ofstream SaveFile(“cpp-home.txt”);SaveFile << “Hello World, from www.cpp-home.com and Loobian!”;SaveFile.close();
}仅仅如此吗?没错!这个程序将在当前运行目录下建立一个名为cpp-home.txt的文件,并向它写入“Hello World, from www.cpp-home.com and Loobian!”。下面给出各行的含义:#include
名称 | 描述 |
打开一个可读取文件 | |
ios::out | 打开一个可写入文件 |
你写入的所有数据将被追加到文件的末尾,此方式使用ios::out | |
ios::ate | 你写入的所有数据将被追加到文件的末尾,此方式不使用ios::out |
删除文件原来已存在的内容(清空文件) | |
ios::nocreate | 如果要打开的文件并不存在,那么以此参数调用open()函数将无法进行。 |
如果要打开的文件已存在,试图用open()函数打开时将返回一个错误。 | |
ios::binary | 以二进制的形式打开一个文件。 |
实际上,以上的值都属于一个枚举类型的int常量。但为了让你的编程生涯不至于太痛苦,你可以像上表所见的那样使用那些名称。下面是一个关于如何使用打开模式的例子:#include
{fstream File("test.txt",ios::in | ios::out);File << "Hi!";//将“Hi!”写入文件static char str[10];//当使用static时,数组会自动被初始化//即是被清空为零
File.seekg(ios::beg);// 回到文件首部// 此函数将在后面解释File >> str;cout << str << endl;File.close();
}OK,这儿又有一些新东西,所以我将逐行进行解释:fstream File(“test.txt”, ios::in | ios::out); —— 此行创建一个fstream对象,执行时将会以读/写方式打开test.txt文件。这意味着你可以同时读取文件并写入数据。File << “Hi!”; —— 我打赌你已经知道它的意思了。static char str[10]; —— 这将创建一个容量为10的字符数组。我猜static对你而言或者有些陌生,如果这样就忽略它。这只不过会在创建数组的同时对其进行初始化。File.seekg(ios::beg); —— OK,我要让你明白它究竟会做些什么,因此我将以一些有点儿离题、但挺重要的内容开始我的解释。还记得它么:while(!OpenFile.eof()){OpenFile.get(ch);cout << ch;}你是不是曾经很想知道那背后真正执行了什么操作?不管是 或不是,我都将为你解释。这是一个while型循环,它会一直反复,直至程序的操作到达文件的尾端。但这个循环如何知道是否已经到了文件末尾?嗯,当你读 文件的时候,会有一个类似于“内置指针(inside-pointer)”的东西,它表明你读取(写入也一样)已经到了文件的哪个位置,就像记事本中的光 标。而每当你调用OpenFile.get(ch)的时候,它会返回当前位置的字符,存储在ch变量中,并将这一内置指针向前移动一个字符。因此下次该函数再被调用时,它将会返回下一个字符。而这一过程将不断反复,直到读取到达文件尾。所以,让我们回到那行代码:函数seekg()将把内置指针定位到指定的位置(依你决定)。你可以使用:ios::beg —— 可将它移动到文件首端ios::end —— 可将它移动到文件末端或者,你可以设定向前或向后跳转的字符数。例如,如果你要向定位到当前位置的5个字符以前,你应当写:File.seekg(-5);如果你想向后跳过40个字符,则应当写:File.seekg(40);同时,我必须指出,函数seekg()是被重载的,它也可以带两个参数。另一个版本是这样子的:File.seekg(-5,ios::end);在这个例子中,你将能够读到文件文本的最后4个字符,因为:1)你先到达了末尾(ios::end)2)你接着到达了末尾的前五个字符的位置(-5)为什么你会读到4个字符而不是5个?噢,只须把最后一个看成是“丢掉了”,因为文件最末端的“东西”既不是字符也不是空白符,那只是一个位置(译注:或许ios::end所“指”的根本已经超出了文件本身的范围,确切的说它是指向文件最后一个字符的下一个位置,有点类似STL中的各个容器的end迭代点是指向最后一个元素的下一位置。这样设计可能是便于在循环中实现遍历)。你现在可能想知道为什么我要使用到这个函数。呃,当我把“Hi”写进文件之后,内置指针将被设为指向其后面……也就是文件的末尾。因此我必须将内置指针设回文件起始处。这就是这个函数在此处的确切用途。File >> str; —— 这也是新鲜的玩意儿!噢,我确信这行代码让你想起了cin >> .实际上,它们之间有着相当的关联。此行会从文件中读取一个单词,然后将它存入指定的数组变量中。例如,如果文件中有这样的文本片断:Hi! Do you know me?使用File >> str,则只会将“Hi!”输出到str数组中。你应当已经注意到了,它实际上是将空格作为单词的分隔符进行读取的。由于我存入文件中的只是单独一个“Hi!”,我不需要写一个while循环,那会花费更多的时间来写代码。这就是我使用此方法的原因。顺便说一下,到目前为止,我所使用的读取文件的while循环中,程序读文件的方式是一个字符一个字符进行读取的。然而你也可以一个单词一个单词地进行读取,像这样:char str[30];//每个单词的长度不能超过30个字符while(!OpenFile.eof()){OpenFile >> str;cout << str;}你也可以一行一行地进行读取,像这样:char line[100];//每个整行将会陆续被存储在这里
while(!OpenFile.eof())
{OpenFile.getline(line,100);//100是数组的大小cout << line << endl;
}你现在可能想知道应当使用哪种方法。嗯,我 建议你使用逐行读取的方式,或者是最初我提及的逐字符读取的方式。而逐词读取的方式并非一个好的方案,因为它不会读出新起一行这样的信息,所以如果你的文 件中新起一行时,它将不会将那些内容新起一行进行显示,而是加在已经打印的文本后面。而使用getline()或者get()都将会向你展现出文件的本来面目!现在,我将向你介绍如何检测文件打开操作是否成功。实现上,好的方法少之又少,我将都会涉及它们。需要注意的是,出现“X”的时候,它实际可以以“o”、 “i”来代替,或者也可以什么都不是(那将是一个fstream对象)。例1:最通常的作法Xfstream File(“cpp-home.txt”);if (!File)
{cout << “Error opening the file! Aborting…/n”;exit(1);
}例2:如果文件已经被创建,返回一个错误ofstream File("unexisting.txt", ios::nocreate);if(!File){cout << “Error opening the file! Aborting…/n”;exit(1);}例3:使用fail()函数ofstream File("filer.txt", ios::nocreate);if(File.fail()){cout << “Error opening the file! Aborting…/n”;exit(1);}例3中的新出现的东西,是fail()函数。如果有任何输入/输出错误(不是在文件末尾)发生,它将返回非零值。我也要讲一些我认为非常重要的内容!例如,如果你已经创建一个流文件对象,但你没有进行打开文件操作,像这样:ifstream File;//也可以是一个ofstream这样,我们就拥有一个文件句柄,但我们仍然没有打开文件。如果你打算迟些打开它,那么可以用open()函数来实现,我已经在本教程中将它介绍了。但如果在你的程序的某处,你可能需要知道当前的句柄是否关联了一个已经打开的文件,那么你可以用is_open()来进行检测。如果文件没有打开,它将返回0 (false);如果文件已经打开,它将返回1 (true)。例如:ofstream File1;File1.open("file1.txt");cout << File1.is_open() << endl;上面的代码将会返回1(译注:指File1.is_open()函数,下句同),因为我们已经打开了一个文件(在第二行)。而下面的代码则会返回0,这是由于我们没有打开文件,而只是创建了一个流文件句柄:ofstream File1;cout << File1.is_open() << endl;检测输入/输出的状态标志在此我不打算解释“标志(flags)”一词的含义,不 过假如你真的完全不理解关于这方面的概念,那么将本章读过一遍之后也许你对此会得到一些认识,我也相信你同样能理解这部分的理论。尽管如此,如果你还是不 明白标志在C++中的含义,我推荐你阅读一些关于这个主题的资料。好,让我们开始吧。C++中负责的输入/输出的系统包括了关于每一个输入/输出操作的结果的记录信息。这些当前的状态信息被包含在io_state类型的对象中。io_state是一个枚举类型(就像open_mode一样),以下便是它包含的值(译注:表中第一列为枚举值的名称,第二列为该值相应含义的描述):
godbit | 无错误 |
Eofbit | 已到达文件尾 |
failbit | 非致命的输入/输出错误 |
badbit | 致使的输入/输出错误 |
有两种方法可以获得输入/输出的状态信息。一种方法是通过调用rdstate()函数,它将返回当前状态的错误标记(上表中提到的)。例如,假如没有任何错误,则rdstate()会返回goodbit.另一种方法则是使用下面任何一个函数来检测相应的输入/输出状态:bool bad();bool eof();//还记得它么?“不断读取文件内容直到到达文件末尾!”bool fail();//噢,这也是老朋友……检测一个打开操作是否成功bool good();假如badbit标志被标设(译注:原文为“If thebadbitflag is up”,这里将“is up”译为“标设”,意即出现了badbit对应的错误,badbit状态被置为当前的错误状态,下同),则bad()函数返回true;假如failbit标志被标设,则fail()函数返回true;假如没有错误发生(goodbit标志被标设),则good()函数返回true;假如操作已经到达了文件末尾(eofbit被标设),则eof()函数返回true.如果错误发生,你必须清除这些错误状态,以使你的程序能正确适当地继续运行——如果你这么打算的话。要清除错误状态,需使用clear()函数。此函数带一个参数,它是你将要设为当前状态的标志值。假使你想让你的程序“清清爽爽”地运行下去,只要将ios::goodbit作为实参。你将在以下内容中看到示例代码。我将向你展示示例代码,以巩固你所学到的理论知识。示例1:简单的状态检测//实际应用中可将FileStream替换成你相应在使用的文件流句柄if(FileStream.rdstate() == ios::eofbit)cout << "End of file!/n";if(FileStream.rdstate() == ios::badbit)cout << "Fatal I/O error!/n";if(FileStream.rdstate() == ios::failbit)cout << "Non-fatal I/O error!/n";if(FileStream.rdstate() == ios::goodbit)cout << "No errors!/n";示例2:clear()函数#include
#include
void main()
{
//假如我们已经在test_file.txt中存有了“Hello”的内容
ifstream File("test_file.txt");
char arr[10];
File.read(arr,10);
//由于Hello占5个字符,因此这里将返回5
cout << File.tellg() << endl;
File.close();
}
tellp() —— 与tellg()有同样的功能,但它用于写文件时。总而言之:当我们读取一个文件,并要知道内置指针的当前位置时,应该使用tellg();当我们写入一个文件,并要知道内置指针的当前位置时,应该使用tellp(). 由于此函数的用法与tellg()完全一样,我就不给出示例代码了。
seekp()——还记得seekg()么?当我在读取一个文件,并想到达文件中某个特定位置时,就曾使用过它。seekp()亦如此,只不过它用于写入一个文件的时候。例如,假如我在进行文件读写,而要定位到当前位置的三个字符之前,则需调用FileHandle.seekg(-3). 但如果我是在写入一个文件,并且比如我要重写后5个字符的内容,我就必须往回跳转5个字符,因而,我应该使用FileHandle.seekp(-5).
ignore()—— 使用于读取文件之时。如果你想略过一定数量的字符,只需使用此函数。实际上,你也可以使用seekg()来代替,然而使用ignore()有一个优点——你可以指定一个特定“界限规则(delimiter rule)”,同样使得ignore()在指定的位置停下。函数原型如下:
istream& ignore( int nCount, delimiter );
nCount表示要略过的字符数量,而delimiter—— 与它的名称有着同样的含义:假如你想在文件末尾停下,则可使用EOF值传入,这样一来此函数就等同于seekg();但该参数还可以使用其他值,例如‘/n’这样可以在换行的同时定位在新行处。下面是示例:
#include
void main()
{
//假设test_file.txt中已经存有"Hello World"这一内容
ifstream File("test_file.txt");
static char arr[10];
//假如一直没有遇到字符"l",则向前定位直到跳过6个字符
//而如果期间遇到"l",则停止向前,定位在该处
File.ignore(6,'l');
File.read(arr,10);
cout << arr << endl;//它将显示"lo World!"
File.close();
}
getline()——虽然前面的章节中我曾提到过这个函数,但还有一些内容我们未曾涉及:此函数不但可用于逐行读取,而且它还可以设为遇到某个特定字符后停止读取。下面给出传递这一参数的方法:
getline(array,array_size,delim);
以下为示例代码:
#include
void main()
{
//假设test_file.txt中已经存有"Hello World"这一内容
ifstream File("test_file.txt");
static char arr[10];
/读取,直到满足下面的条件之一:
1)已经读取10个字符
2)遇到字母"o"
3)出现新一行
/
File.getline(arr,10,'o');
cout << arr << endl;//将显示"Hell"
File.close();
}
peek()——此函数将返回输入流文件的下一个字符,但它不移动内置指针。我想你该记得,像get()这样的函数也返回输入流文件的下一个字符,而与此同时它将移动内置指针。所以当你再次调用get()函数的时候,它会返回再下一个字符,而非前面那个。哦,使用peek()也会返回字符,但它不会移动“光标”。所以,假如你连续两次调用peek()函数,它会返回同一个字符。考虑以下代码:
#include
void main()
{
//假设test_file.txt中已经存有"Hello World"这一内容
ifstream File("test_file.txt");
char ch;
File.get(ch);
cout << ch << endl;//将显示"H"
cout << char(File.peek()) << endl;//将显示"e"
cout << char(File.peek()) << endl;//将再次显示"e"
File.get(ch);
cout << ch << endl;//还是显示"e"
File.close();
}
顺便说一下,我忘了讲——peek()函数实质上返回的是字符的ASCII码,而非字符本身。因此,假如你想看到字符本身,你得像我在示例中做的那样进行调用(译注:即要转为char类型)。
_unlink()——删除一个文件。假如你要使用此函数,需要在你的程序中包含io.h头文件。下面是示例代码:
#include
#include
void main()
{
ofstream File;
File.open("delete_test.txt");//创建一个文件
File.close();
_unlink("delete_test.txt");//删除这个文件
//试图打开此文件,但假如它已不存在
//函数将返回一个ios::failbit错误值
File.open("delete_test.txt",ios::nocreate);
//验证它是否返回该值
if(File.rdstate() == ios::failbit)
cout << "Error...!/n";//耶,成功了
File.close();
}
putback()——此函数将返回最后一个所读取字符,同时将内置指针移动-1个字符。换言之,如果你使用get()来读取一个字符后再使用putback(),它将为你返回同一个字符,然而同时会将内置指针移动-1个字符,所以你再次使用get()时,它还是会为你返回同样的字符。下面是示例代码:
#include
void main()
{
//test_file.txt应包含内容
ifstream File("test_file.txt");
char ch;
File.get(ch);
cout << ch << endl;//将显示"H"
File.putback(ch);
cout << ch << endl;//仍将显示"H"
File.get(ch);
cout << ch << endl;//再一次显示"H"
File.close();
}
flush() —— 在处理输出流文件的时候,你所存入的数据实际上并非立刻写入文件,而是先放入一个缓冲区中,直到该缓冲区放满数据之后,这些数据才被存入真正的文件中(在你的磁盘上)。旋即缓冲区会被清空,再重新进行下一轮写入。
但假如你想在缓冲区写满之前就将其中的数据写入磁盘,则使用flush()函数。只须像这样进行调用:FileHandle.flush(),这样缓冲区内的数据将会写入实际的物理文件,而后缓冲区被清空。
再补充一点(高阶的)内容:flush()函数会调用与相应流缓冲(streambuf)相联系的sync()函数(出自MSDN)。
结语
*
嗯, 我希望你现在可以实现你的文件输入/输出程序了。我已经将自己所知的内容都涉及到,我想它们比你所需要的还要多。即使如此,还是会有一些内容我没有提及 的……不过我想你或许都不会使用到那些方面的内容。因此,如果你还需要了解更多关于某些特定主题的高阶的理论,可以在互联网上搜索,例如,可以尝试一下google.com,但你可别来问我!我不对任何询问我关于如何实现某个程序此类问题的邮件作答复。
假如你喜欢这个系列的教程,或者相反,我都会非常高兴能看到你的意见。所以请随意地在网上联系我:loobian@cpp-home.com
想获取更多的C++教程,请访问:www.cpp-home.com原文链接: https://www.cnblogs.com/justtofun/p/5487704.html
欢迎关注
微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍
原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/233303
非原创文章文中已经注明原地址,如有侵权,联系删除
关注公众号【高性能架构探索】,第一时间获取最新文章
转载文章受原作者版权保护。转载请注明原作者出处!