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