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标志 | 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返回的数据可以一层一层向上返回,直到被捕获处理。