文章目录
异常
软件开发中常见错误
- 语法错误
- 逻辑错误
- 功能错误
- 设计缺陷
- 需求不符
- 环境异常
- 操作不当
传统C中的错误处理机制
- 通过返回值判断错误
- 优点:函数调用路径中的栈对象可以得到正确的析构函数,不会有内存泄漏风险。
- 缺点:错误处理流程麻烦,需要逐层判断,代码臃肿。
- 通过远程跳转处理错误
- 优点:不需要逐层反悔判断,一步到位的错误处理,代码精炼。
- 缺点:函数调用路径中的栈对象失去被析构的机会,哟内存泄漏的风险。
代码示例
- error.cpp
#include <iostream>
#include <cstdio>
using namespace std;
class A{
public:
A(void){ cout << "A的构造" << endl; }
~A(void){ cout << "A的析构" << endl; }
};
int func3(){
A a;
FILE* fp = fopen("xx.txt","r");
if(fp == NULL){
cout << "文件打开失败" << endl;
return -1;
}
cout << "func3正常运行" << endl;
return 0;
}
int func2(){
A a;
if(func3()==-1){
return -1;
}
cout << "func2正常运行" << endl;
return 0;
}
int func1(){
A a;
if(func2()==-1){
return -1;
}
cout << "func1正常运行" << endl;
return 0;
}
int main(void)
{
if(func1()==-1){
return -1;
}
cout << "main正常运行" << endl;
return 0;
}
- 02error.cpp
#include <iostream>
#include <cstdio>
#include <csetjmp>
using namespace std;
jmp_buf g_env;
class A{
public:
A(void){ cout << "A的构造" << endl; }
~A(void){ cout << "A的析构" << endl; }
};
int func3(){
A a;
FILE* fp = fopen("xx.txt","r");
if(fp == NULL){
longjmp(g_env,-1);
}
cout << "func3正常运行" << endl;
return 0;
}
int func2(){
A a;
func3();
cout << "func2正常运行" << endl;
return 0;
}
int func1(){
A a;
func2();
cout << "func1正常运行" << endl;
return 0;
}
int main(void)
{
if(setjmp(g_env)==-1){
cout << "文件打开失败" << endl;
return -1;
}
func1();
cout << "main正常运行" << endl;
return 0;
}
C++ 异常机制
结合两种传统错误处理的优点,同时避免它们的缺点,在形式上实现一步到位的错误处理,同时保证栈对象得到正确的析构。
异常语法
- 异常抛出(异常对象可以基本类型的数据,也可以是类类型的对象。)
throw 异常对象;
- 异常的检测和捕获(catch子句根据throw所抛出的异常对象类型,自上而下顺序匹配,而不是最优匹配;因此对子类类型的异常捕获要写在前面,否则它会被基类类型的异常捕获语句提前截获。)
try{
可能发生异常的语句;
}
catch(异常类型1){
针对异常类型1的处理..
}
catch(异常类型2){
针对异常类型2的处理..
}
...
catch(...){
针对其它异常的处理..
}
代码示例(基础抛出异常方式)
- exception.cpp
#include <iostream>
#include <cstdio>
using namespace std;
class FileError{
public:
FileError(const string& file,int line)
:m_file(file),m_line(line){
cout << "出错位置:" << m_file <<
"," << m_line << endl;
}
private:
string m_file;
int m_line;
};
class A{
public:
A(void){ cout << "A的构造" << endl; }
~A(void){ cout << "A的析构" << endl; }
};
int func3(){
A a;
FILE* fp = fopen("xx.txt","r");
if(fp == NULL){
throw FileError(__FILE__,__LINE__);
//throw -1;//抛出异常
}
cout << "func3正常运行" << endl;
return 0;
}
int func2(){
A a;
func3();
cout << "func2正常运行" << endl;
return 0;
}
int func1(){
A a;
func2();
cout << "func1正常运行" << endl;
return 0;
}
int main(void)
{
try{//检测异常
func1();
cout << "main正常运行" << endl;
}
catch(int ex){//捕获和处理异常
if(ex==-1)
cout << "文件打开失败" << endl;
return -1;
}
catch(FileError& ex){
cout << "文件打开失败!" << endl;
return -1;
}
return 0;
}
- 执行结果
函数的异常说明
- 定义:说明该函数可能抛出的异常类型
返回类型 函数名(形参表)throw(异常类型表){...}
- 函数的异常说明是一种承诺,表示该函数所抛出的异常不会超出说明的范围。如果抛出了异常说明以外的类型,该异常无法被正常捕获,而会被系统捕获到,导致进程终止。
- 两种极端形式
- 不写异常说明:表示可以抛出任何异常。
- 空异常说明:throw(),表示不会抛出任何异常。
- 如果函数的声明和定义分开书写,要保证异常说明的类型一致,但是顺序无所谓。
- 如果基类中的虚函数带有异常说明,那么该函数在子类中覆盖版本不能说明比基类抛出更多的异常,否则将会因为“放松throw限定”而导致编译失败。
代码示例(抛出子类异常)
- exception.cpp
#include <iostream>
using namespace std;
class ErrorA{};
class ErrorB:public ErrorA{};
void func(void){
throw ErrorA();
//throw ErrorB();
}
int main(void)
{
try{
func();
}
//对子类类型的异常捕获要写在前面
catch(ErrorB& ex){
cout << "针对ErrorB处理" << endl;
return -1;
}
catch(ErrorA& ex){
cout << "针对ErrorA处理" << endl;
return -1;
}
return 0;
}
-
执行结果
-
02exception.cpp
#include <iostream>
using namespace std;
class FileError{};
class MemoryError{};
//函数声明
void func(void)
throw(FileError,MemoryError,int);
//函数定义
void func(void)
throw(int,FileError,MemoryError){
throw MemoryError();
throw -1;
//throw FileError();
}
int main(void)
{
try{
func();
}
catch(FileError& ex){
cout << "文件错误的处理" << endl;
return -1;
}
catch(MemoryError& ex){
cout << "内存错误的处理" << endl;
return -1;
}
catch(int ex){
cout << "int错误的处理" << endl;
return -1;
}
}
- 执行结果
标准异常类(exception)
class exception{
public:
exception() throw();
virtual ~exception() throw();
/* Returns a C-style character string
* describing the general cause
* of the current error. */
virtual const char* what() const throw();
};
构造和析构函数中的异常
- 构造函数可以抛出异常,但对象会被不完整构造,这样的对象的析构函数不会被正常执行;因此在构造函数抛出异常之前需要手动销毁异常产生之前所分配的动态资源。
- 析构函数不要抛出异常。
代码示例
- 带有异常的构造函数和析构函数,exception.cpp
#include <iostream>
using namespace std;
class Base{
public:
virtual void func(void)throw(int){}
virtual ~Base(void)throw(){}
};
class Derived:public Base{
public:
void func(void)throw(){}
~Derived(void)throw(){}
};
int main(void){
return 0;
}
-
执行结果
-
02exception.cpp
#include <iostream>
using namespace std;
class FileError:public exception{
public:
~FileError(void) throw() {}
const char* what(void) const throw(){
cout << "文件错误处理" << endl;
return "FileError";
}
};
class MemoryError:public exception{
public:
~MemoryError(void) throw() {}
const char* what(void) const throw(){
cout << "内存错误处理" << endl;
return "MemoryError";
}
};
int main(void)
{
try{
char* p = new char[0xffffffff];
//throw FileError();
//throw MemoryError();
//...
}
catch(exception& ex){
cout << ex.what() << endl;
return -1;
}
return 0;
}
-
执行结果
-
正确使用,03exception.cpp
#include <iostream>
using namespace std;
class A{
public:
A(void){ cout << "A的构造" << endl; }
~A(void){ cout << "A的析构" << endl; }
};
class B{
public:
B(void):m_a(new A){
if("error"){
delete m_a;
throw -1;
}
//...
}
~B(void){
delete m_a;
}
A* m_a;
};
int main(void)
{
try{
B b;
}
catch(int ex){
cout << "捕获到异常:" << ex << endl;
return 0;
}
}
- 执行结果
I/O流
主要 I/O 类
ios
/ \
istream ostream
/ | \ / | \
istrstream ifstream iostream ofstream ostrstream
格式化 I/O
- 格式化函数(成员函数)
cout << 100/3.0 << endl;//33.3333
cout.presicion(8);
cout << 100/3.0 << endl;//33.333333
- 流控制符(全局函数)
cout << 100/3.0 << endl;//33.3333
cout << setprecision(8) << 100/3.0
<< endl;//33.333333
代码示例
- format.cpp
#include <iostream>
using namespace std;
int main(void)
{
cout << 100/3.0 << endl;//33.3333
cout.precision(10);
cout << 100/3.0 << endl;//33.33333333
cout << '[';
cout.width(10);//设置域宽
cout.fill('-');//设置空白位置填充的字符
cout.setf(ios::showpos);//显示符号位
cout.setf(ios::internal);//内插对齐
cout << 12345 << "]" << endl;
return 0;
}
-
执行结果
-
02format.cpp
#include <iostream>
#include <iomanip>
using namespace std;
int main(void)
{
cout << 100/3.0 << endl;//33.3333
//cout.precision(10);
//cout << 100/3.0 << endl;//33.33333333
cout << setprecision(10) << 100/3.0
<< endl;
cout << '[';
/*cout.width(10);//设置域宽
cout.fill('-');//设置空白位置填充的字符
cout.setf(ios::showpos);//显示符号位
cout.setf(ios::internal);//内插对齐
cout << 12345 << "]" << endl;*/
cout << setw(10) << setfill('-') <<
showpos << internal << 12345 <<
']' << endl;
return 0;
}
- 执行结果
字符串流
#include <strstream> //过时
istrstream
ostrstream
------------------------
#include <sstream> //推荐
istringstream //类似sscanf
ostringstream //类似sprintf
代码示例
- string.cpp
#include <iostream>
#include <sstream>
using namespace std;
int main(void)
{
int i = 12345;
double d = 6.78;
string s = "hello";
/* char buf[100] = {0}
* sprintf(buf,"%d %g %s",i,d,s);
* */
ostringstream oss;
oss << i << ' ' << d << ' ' << s;
cout << oss.str() << endl;
istringstream iss;
iss.str("54321 8.76 world");
int i2;
double d2;
string s2;
//sscanf(buf,"%d %g %s",&i2,&d2,s)
iss >> i2 >> d2 >> s2;
cout << i2 << ',' << d2 << ',' << s2
<< endl;
return 0;
}
- 执行结果
文件流
#include <fstream>
ifstream //类似fscanf
ofstream //类似fprintf
代码示例
- file.cpp
#include <fstream>
#include <iostream>
using namespace std;
int main(void)
{
ofstream ofs("file.txt");
ofs << 12345 << ' ' << 6.78 << ' '
<< "hello" << endl;
ofs.close();
ifstream ifs("file.txt");
int i;
double d;
string s;
ifs >> i >> d >> s;
cout << i << ',' << d << ','
<< s << endl;
ifs.close();
return 0;
}
- 执行结果
二进制 I/O
//fwrite
ostream& ostream::write(
const char* buf,size_t num);
//fread
istream& istream::read(
char* buf,streamsize num);
代码示例
- last.cpp
#include <fstream>
#include <iostream>
using namespace std;
int main(void)
{
ofstream ofs("last.txt");
char wbuf[] = "C++ 流格式化";
ofs.write(wbuf,sizeof(wbuf));
ofs.close();
ifstream ifs("last.txt");
char rbuf[50] = {0};
ifs.read(rbuf,sizeof(rbuf));
cout << "读到数据:" << rbuf << endl;
ifs.close();
return 0;
}
- 执行结果