C++(C++的文件I/O)

一、C++的文件IO

在C++中把文件的读写操作都封装在标准库中,ifstream类主要用于读取文件内容,ofstream主要用于写入文件内容,fstream类可读可写。

打开文件操作:
1、使用构造函数打开文件
fstream(const char *filename, openmode mode);
功能:创建操作文件的类对象,并顺便打开文件
filename:文件的路径
mode:打开的方式或权限
    默认参数是:O_RDWR
    ios::app 添加输出,O_WRONLY|O_CREAT|O_APPEND 
    ios::in 为读取打开文件 O_RDONLY
    ios::out 为写入打开文件 O_WRONLY|O_CREAT|O_TRUNC
    ios::binary 以二进制模式打开文件,相当于C语言中fopen函数的带b的打开方式
    ios::ate 当已打开时寻找到EOF,打开文件后顺序设置文件位置指针
    ios::trunc 文件存在则清空文件 O_TRUNC
​
ifstream( const char *filename, openmode mode);
mode
    默认参数是:O_RDONLY
    
ofstream( const char *filename, openmode mode);
mode
    默认参数是:O_WRONLY|O_CREAT|O_TRUNC
2、使用open成员函数打开文件
void open( const char *filename);
void open( const char *filename, openmode mode);
功能:与构造函数参数相同
3、如何判断文件打开成功或失败
方法1:直接使用类对象进行逻辑判断,因它们重载逻辑运算符。
方法2:调用good函数,该函数用于判断对象的上一次操作是否成成功,所以也可判断文件打开是否成功。
文本格式读写:

注意:像使用cout一样写入数据,使用cin一样读取数据。

注意:如果需要对一个结构、类进行文本格式读写,最好给它重载输入、输出运算符,不光cin、cout可以使用,ofstream、ifsteam也可以使用。

练习1:设计一个员工类,写入若干个员工信息到emp.txt文件,然后再读取出来测试是否写入成功。

二进入格式读写:

注意:如果是在Windows系统下读写二进制文件,mode参数中要有ios::binary,就像在C语言中要加b。

istream& read( char *buffer, streamsize num );
功能:读取一块数据到内存
buffer:一般情况下需要强制类型转换,特别是结构对象或类对象。
注意:返回值与标准C和Linux系统读取函数不同,需要调用gcount函数获取读取了多少个字节数据。
​
ostream& write(const char *buffer, streamsize num);
功能:把一块内存中的数据写入文件
注意:返回值与标准C和Linux系统读取函数不同,需要调用good函数判断写入是否成功。

注意:如果结构、类成员中有指针成员或string类的成员变量,不能以二进制格式直接把对象保存到文件中,最好以文本格式保存。

练习:使用C++语言实现cp命令。

随机读写:
istream &seekg( off_type offset, ios::seekdir origin );
ostream &seekp( off_type offset, ios::seekdir origin );
功能:以偏移值+基础位置设置文件的位置指针,之所以这样设计是为了兼容那些有两个文件位置(读写分开)操作系统,使用方法与lseek、fseek类型。
ios::seekdir origin
    ios::beg SEEK_SET
    ios::cur SEEK_CUR
    ios::end SEEK_END
​
istream &seekg( pos_type position );
ostream &seekp( pos_type position );
功能:以绝对位置设计文件的位置指针
pos_type:
    把文件的位置指针移动到文件的第几个字节。
​
pos_type tellg();
pos_type tellp();
功能:获取文件的位置指针,与ftell函数的功能相同。
注意:由于操作系统的文件位置指针是两个,读取各一个,所有C++语言提供了两g和p两套位置指针函数,但在Linux系统和Windows系统下,读写操作共用一个位置指针,所以使用p、g没有区别。

特殊格式的读写:
fmtflags flags();
fmtflags flags( fmtflags f );
功能:获取当前流的格式标志
​
fmtflags setf( fmtflags flags );
fmtflags setf( fmtflags flags, fmtflags needed );
功能:设置当前流的格式化标志为flags
​
void unsetf( fmtflags flags );
清除与当前流相关的给定的标志flags 
操作符描述输入输出
boolalpha启用boolalpha标志XX
dec启用dec标志XX
endl输出换行标示,并清空缓冲区X
ends输出空字符X
fixed启用fixed标志X
flush清空流X
hex启用 hex 标志XX
internal启用 internal 标志X
left启用 left 标志X
noboolalpha关闭boolalpha 标志XX
noshowbase关闭showbase 标志X
noshowpoint关闭showpoint 标志X
noshowpos关闭showpos 标志X
noskipws关闭skipws 标志X
nounitbuf关闭unitbuf 标志X
nouppercase关闭uppercase 标志X
oct启用 oct 标志XX
right启用 right 标志X
scientific启用 scientific 标志X
showbase启用 showbase 标志X
showpoint启用 showpoint 标志X
showpos启用 showpos 标志X
skipws启用 skipws 标志X
unitbuf启用 unitbuf 标志X
uppercase启用 uppercase 标志X
ws跳过所有前导空白字符X
#include <iostream>
using namespace std;
​
int main(int argc,const char* argv[])
{
    /*
    printf("|%4d|\n",1);    
    cout << "|";
    cout.width(4);
    cout << 1 << "|" <<  endl;
    */
    int num = 0x01020304;
    printf("%x\n",num);
    cout << hex << num << endl;
    bool flags = false;
    cout << boolalpha << flags << endl;
    return 0;
}

二、 异常处理

什么是异常处理:

从宏观角度来说,异常处理就是当程序执行过程中出现了错误,以及对错误的处理方案。

C语言的异常处理:

C语言一般通过函数返回值、信号,来表示程序在运行过程中出现的错误。

例如:

文件打开失败,fopen、open函数的返回值来判断文件打开是否成功。

缺点:返回的类型单一,返回的数据很难跨作用域,还需要考虑它们的成功情况,必须使用if、switch对返回值进行判断。

断错误、非法硬件指令、总线错误、浮点异常等代码执行过程中出现错误信息。

缺点:错误信息过于简单,捕获处理完后进行依然需要结束。

C++语言的异常处理:
1、如何抛异常

throw 数据;

类似return语句返回一个数据,但不同时它可以返回任何类型的数据,并且可以不需要预告声明。

注意:throw与return最大区别是,throw返回的数据,上层必须处理,否则程序会立即结束(核心已转储)。

2、声明异常

1、所谓的异常声明,就是函数的实现者对调用者的一种承诺,我会抛哪些类型的导常

返回值类型 函数名(参数列表) throw(类型,...)
{
​
}

2、如果函数不进行异常声明,则表示可能会抛出任何类型的异常。

3、如果抛出了声明以外的异常,编译不会出错,但无法捕获,即使你写的准确捕获语句,也无法捕获,也就是说如果函数的实现者不遵守承诺,调用它的程序只有死路一条。

4、throw() 表示不会抛出任何异常,请放心调用。

size_t file_size(const char* filename)
{
    throw 1234;
    ifstream ifs(filename);
    if(!ifs)
    {
        throw string("文件打开失败!");
    }
​
    ifs.seekg(0,ios::end);
    return ifs.tellg();
}
​
int main(int argc,const char* argv[])
{
    try{
        cout << file_size("eheheheheh") << endl;
    }
    catch (int num)
    {
        cout << "我就知道你不靠谱" << num << endl;
    }
    catch (string str)
    {
        cout << str << endl;
    }
    return 0;
}

5、类成员函数的异常声明列表如果不同,会影响函数覆盖,如果其它条件都符,只有异常声明列表不同,编译会出错误。

#include <iostream>
using namespace std;
​
class A
{
public:
    virtual void func(void) throw()
    {   
        cout << "我是A类的func函数" << endl;
    }   
};
​
class B : public A
{
public:
    void func(void) throw()
    {   
        cout << "我是B类的func函数" << endl;
    }   
};
​
int main(int argc,const char* argv[])
{
    A* a = new B;
    a->func();
    return 0;
}
3、捕获异常
try{
    可能产生异常的函数调用、代码。
}
catch(类型1 变量名){
    1、处理异常
    2、继续往上抛
}
catch(类型2 变量名){
    1、处理异常
    2、继续往上抛
}
...
int main(int argc,const char* argv[])
{ 
    int* p;
    try{
        p = new int[0xffffffff];
    }
    catch(bad_array_new_length error){
        cout << "申请内在失败" << endl;
        cout << error.what() << endl;
    }
}

注意:如果继续往上抛的异常,没有被处理,那么程序将停止执行(我个人习惯,只在main函数内进行异常捕获)。

4、抛异常和捕获异常要注意的问题

1、捕获异常时要先尝试捕获子类异常变量,再捕获父类异常变量,因为catch不会挑选最合适的,而从上到下选择一个可以捕获的类型,或者只写捕获父类异常变量,这样返回父类异常和子类异常都可以兼容。

2、不要在异常类的构造函数的析构函数中抛出异常,如果该类对象就是异常数据,那么会在抛异常的过程中产生新的异常(指的设计异常类,暂时不需要掌握)。

3、不要抛指针类型的异常,因为我们的异常是跨作用域的,当捕获者获得异常后,指针指向的内存可能已经释放,那么捕获的指针就可能是野指针(异常会一层层往上抛,要么被捕获,要么是抛到main函数中,程序死掉)。

4、尽量使用类名创建临时的类对象进行抛异常,使用引用来捕获异常,因为这样既避免调用拷贝构造函数,也避免对象出了作用域后被释放产生悬空引用。

5、不需要抛基本类型的异常数据,如果想抛自定义的异常,建议封装成异常类,并且该类继承exception类,这样我们只需要在main函数中写一份异常捕获即可。

C++标准异常:

所谓的C++标准异常就是在使用C++标准库中的函数、类、类对象、new、delete时可能抛出的异常,简称C++标准异常。

异常描述
std::exception该异常是所有标准 C++ 异常的父类。
std::bad_alloc该异常可以通过 new 抛出。
std::bad_cast该异常可以通过 dynamic_cast 抛出。
std::bad_exception这在处理 C++ 程序中无法预期的异常时非常有用。
std::bad_typeid该异常可以通过 typeid 抛出。
std::logic_error理论上可以通过读取代码来检测到的异常。
std::domain_error当使用了一个无效的数学域时,会抛出该异常。
std::invalid_argument当使用了无效的参数时,会抛出该异常。
std::length_error当创建了太长的 std::string 时,会抛出该异常。
std::out_of_range该异常可以通过方法抛出,例如 std::vector 和 std::bitset<>::operator。
std::runtime_error理论上不可以通过读取代码来检测到的异常。
std::overflow_error当发生数学上溢时,会抛出该异常。
std::range_error当尝试存储超出范围的值时,会抛出该异常。
std::underflow_error当发生数学下溢时,会抛出该异常。
#include <exception>
using namespace std;
​
int main(int argc,const char* argv[])
{
    // 只要是C++标准异常,该方法都可以捕获
    try{
        // int* p = new int[0xffffffff];
        // string str(0xffffffff,'x');
        int a = 1234 , b = 0;
        int c = a / b;
    }   
    catch (exception& ex) 
    {   
        cout << ex.what() << endl;
    }   
    
    return 0;
} 
自定义的通用异常类
#ifndef MY_ERROR_H
#define MY_ERROR_H
#include <iostream>
using namespace std;
​
class MyError:public exception
{
    string whatInfo;
public:
    MyError(const char* file,const char* func,size_t line,const char* info)
    {
        whatInfo = file;
        whatInfo += " ";
        whatInfo += func;
        whatInfo += " ";
        
        char buf[21];
        sprintf(buf,"%u",line);
        whatInfo += buf;
​
        whatInfo += ":";
        whatInfo += info;
    }
​
    ~MyError(void) throw() {}
​
    const char* what(void)const throw()
    {
        return whatInfo.c_str();
    }
};
​
#define Error(info) MyError(__FILE__,__func__,__LINE__,info)
​
#endif//MY_ERROR_H

C++中的异常处理与C语言的错误处理的区别?

throw是在return语句的基础上实现了,都是向调用返回一个数据。

1、throw可以返回多种类型数据,而return只能返回一种。

2、return返回的数据可以不处理,throw返回的数据必须处理,否则程序停止运行。

3、return返回的数据给调用者,throw返回的数据可以一层一层向上返回,直到被捕获处理。

 一、ASCII 输出   为了使用下面的方法, 你必须包含头文件(译者注:在标准C++中,已经使用取 代,所有的C++标准头文件都是无后缀的。)。这是 的一个扩展集, 提供有缓 冲的文件输入输出操作. 事实上, 已经被包含了, 所以你不必包含所有这两个 文件, 如果你想显式包含他们,那随便你。我们从文件操作类的设计开始, 我会讲解如何进行ASCII I/O 操作。如果你猜是"fstream," 恭喜你答对了! 但这篇文章介绍的方法,我们分别使用"ifstream"?和 "ofstream" 来作输入输出。   如果你用过标准控制台流"cin"?和 "cout," 那现在的事情对你来说很简单。 我们现在开始讲输出部 分,首先声明一个类对象。 ofstream fout;   这就可以了,不过你要打开一个文件的话, 必须像这样调用ofstream::open()。 fout.open("output.txt");   你也可以把文件名作为构造参数来打开一个文件. ofstream fout("output.txt");   这是我们使用的方法, 因为这样创建和打开一个文件看起来更简单. 顺便说一句, 如果你要打开的文 件不存在,它会为你创建一个, 所以不用担心文件创建的问题. 现在就输出到文件,看起来和"cout"的操 作很像。 对不了解控制台输出"cout"的人, 这里有个例子。 int num = 150; char name[] = "John Doe"; fout << "Here is a number: " << num << " "; fout << "Now here is a string: " << name << " ";   现在保存文件,你必须关闭文件,或者回写文件缓冲. 文件关闭之后就不能再操作了, 所以只有在你 不再操作这个文件的时候才调用它,它会自动保存文件。 回写缓冲区会在保持文件打开的情况下保存文 件, 所以只要有必要就使用它。回写看起来像另一次输出, 然后调用方法关闭。像这样: fout << flush; fout.close();    现在你用文本编辑器打开文件,内容看起来是这样:   Here is a number: 150 Now here is a string: John Doe   很简单吧! 现在继续文件输入, 需要一点技巧, 所以先确认你已经明白了流操作,对 "<>" 比较熟悉了, 因为你接下来还要用到他们。继续…   二、ASCII 输入   输入和"cin" 流很像. 和刚刚讨论的输出流很像, 但你要考虑几件事情。在我们开始复杂的内容之前 , 先看一个文本:   12 GameDev 15.45 L This is really awesome!   为了打开这个文件,你必须创建一个in-stream对象,?像这样。 ifstream fin("input.txt");   现在读入前四行. 你还记得怎么用"<<" 操作符往流里插入变量和符号吧?好,?在 "<>" (提取) 操作符. 使用方法是一样的. 看这个代码片段.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值