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

发布于:2024-09-19 ⋅ 阅读:(11) ⋅ 点赞:(0)

一、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标志 X X
dec 启用dec标志 X X
endl 输出换行标示,并清空缓冲区 X
ends 输出空字符 X
fixed 启用fixed标志 X
flush 清空流 X
hex 启用 hex 标志 X X
internal 启用 internal 标志 X
left 启用 left 标志 X
noboolalpha 关闭boolalpha 标志 X X
noshowbase 关闭showbase 标志 X
noshowpoint 关闭showpoint 标志 X
noshowpos 关闭showpos 标志 X
noskipws 关闭skipws 标志 X
nounitbuf 关闭unitbuf 标志 X
nouppercase 关闭uppercase 标志 X
oct 启用 oct 标志 X X
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返回的数据可以一层一层向上返回,直到被捕获处理。


网站公告

今日签到

点亮在社区的每一天
去签到