15 友元、异常和其他
2020-02-24 10:28:24 4 举报
AI智能生成
C++ Plus 笔记
作者其他创作
大纲/内容
其他
return Text();//Text是类,是否会创建临时对象问题
1
#include<iostream>
using namespace std;
class Text{
public:
Text(){cout<<"T created\n";}
Text(const Text & t1){cout<<"T copystructor created\n";}
Text(const Text & t1){cout<<"T copystructor created\n";}
~Text(){cout<<"T destoryed\n";}
};
Text haha()
{
return Text();
}
int main()
{
Text t1=haha();
return 0;
}
结果
T created
T destoryed
只有一对,表示return Text()不产生临时对象。
2
int main()
{
Text t1=haha();
return 0;
}
改成
改成
int main()
{
Text t1;
t1=haha();
t1=haha();
return 0;
}
结果
T created
T created
T destoryed
T destoryed
结论
说明在haha()函数中,return Text()创建对象,只会在main()视情况而产出。
都没有用到复制构造函数。
友元
友元类
作用
友元类的所有方法都可以访问原始类的私有成员和保护成员
虽然友元被授予从外部访问类的私有部分的权限,但它们不与面向对象的编程思想相矛盾,
提高了公有接口的灵活性
提高了公有接口的灵活性
(例子)
电视和遥控
电视和遥控
关系
不是has-a,不是is-a
遥控器可以改变电视机的状态(其私有数据决定其状态),
表明应将Remote类作为TV类的一个友元。
表明应将Remote类作为TV类的一个友元。
格式
class Tv{
public:
firend class Remote;//
.....
};
........
class Remote{
........
};
public:
firend class Remote;//
.....
};
........
class Remote{
........
};
说明
将Remote类声明为Tv的友元,
Remote成员函数可访问Tv的私有数据.
Remote成员函数可访问Tv的私有数据.
即:class Tv{
....
private: int channel;
.......};
class Remote:{
public:
.....
void set_chan(Tv & t, int c) {t.channel = c;}
};
....
private: int channel;
.......};
class Remote:{
public:
.....
void set_chan(Tv & t, int c) {t.channel = c;}
};
Remote类中的成员函数一般用Tv作为参数,说明遥控独立于电视机,
一个遥控器可用于多台电视机。
如果创建一个大型类来包含电视机和遥控器,无法实现一个遥控器可用
于多台电视机。
一个遥控器可用于多台电视机。
如果创建一个大型类来包含电视机和遥控器,无法实现一个遥控器可用
于多台电视机。
代码
头文件
// tv.h -- Tv and Remote classes
#ifndef TV_H_
#define TV_H_
class Tv
{
public:
friend class Remote; // Remote can access Tv private parts
enum {Off, On};
enum {MinVal,MaxVal = 20};
enum {Antenna, Cable};
enum {TV, DVD};
Tv(int s = Off, int mc = 125) : state(s), volume(5),
maxchannel(mc), channel(2), mode(Cable), input(TV) {}
void onoff() {state = (state == On)? Off : On;}
bool ison() const {return state == On;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_mode() {mode = (mode == Antenna)? Cable : Antenna;}
void set_input() {input = (input == TV)? DVD : TV;}
void settings() const; // display all settings
private:
int state; // on or off
int volume; // assumed to be digitized
int maxchannel; // maximum number of channels
int channel; // current channel setting
int mode; // broadcast or cable
int input; // TV or DVD
};
class Remote
{
private:
int mode; // controls TV or DVD
public:
Remote(int m = Tv::TV) : mode(m) {}
bool volup(Tv & t) { return t.volup();}
bool voldown(Tv & t) { return t.voldown();}
void onoff(Tv & t) { t.onoff(); }
void chanup(Tv & t) {t.chanup();}
void chandown(Tv & t) {t.chandown();}
void set_chan(Tv & t, int c) {t.channel = c;}
void set_mode(Tv & t) {t.set_mode();}
void set_input(Tv & t) {t.set_input();}
};
#endif
定义
// tv.cpp -- methods for the Tv class (Remote methods are inline)
#include <iostream>
#include "tv.h"
bool Tv::volup()
{
if (volume < MaxVal)
{
volume++;
return true;
}
else
return false;
}
bool Tv::voldown()
{
if (volume > MinVal)
{
volume--;
return true;
}
else
return false;
}
void Tv::chanup()
{
if (channel < maxchannel)
channel++;
else
channel = 1;
}
void Tv::chandown()
{
if (channel > 1)
channel--;
else
channel = maxchannel;
}
void Tv::settings() const
{
using std::cout;
using std::endl;
cout << "TV is " << (state == Off? "Off" : "On") << endl;
if (state == On)
{
cout << "Volume setting = " << volume << endl;
cout << "Channel setting = " << channel << endl;
cout << "Mode = "
<< (mode == Antenna? "antenna" : "cable") << endl;
cout << "Input = "
<< (input == TV? "TV" : "DVD") << endl;
}
}
使用
//use_tv.cpp -- using the Tv and Remote classes
#include <iostream>
#include "tv.h"
int main()
{
using std::cout;
Tv s42;
cout << "Initial settings for 42\" TV:\n";
s42.settings();
s42.onoff();
s42.chanup();
cout << "\nAdjusted settings for 42\" TV:\n";
s42.settings();
Remote grey;
grey.set_chan(s42, 10);
grey.volup(s42);
grey.volup(s42);
cout << "\n42\" settings after using remote:\n";
s42.settings();
Tv s58(Tv::On);
s58.set_mode();
grey.set_chan(s58,28);
cout << "\n58\" settings:\n";
s58.settings();
// std::cin.get();
return 0;
}
结果
Initial settings for 42" TV:
TV is Off
Adjusted settings for 42" TV:
TV is On
Volume setting = 5
Channel setting = 3
Mode = cable
Input = TV
42" settings after using remote:
TV is On
Volume setting = 7
Channel setting = 10
Mode = cable
Input = TV
58" settings:
TV is On
Volume setting = 5
Channel setting = 28
Mode = antenna
Input = TV
说明
class Tv{
public:
enum {Off , On};
....
private:
int state;
....
};
void onoff() { state = (state == On)? off : On; }
public:
enum {Off , On};
....
private:
int state;
....
};
void onoff() { state = (state == On)? off : On; }
void onoff() { state = (state == On)? off : On; }
state相当于只取0和1,可用下面代码代替
void onoff() { state^=1;};
state相当于只取0和1,可用下面代码代替
void onoff() { state^=1;};
^=表示按位异或和赋值
按位异或,表示
与1异或取相反,与0异或不变。(主要是0和1)
按位异或,表示
与1异或取相反,与0异或不变。(主要是0和1)
友元成员函数
前言
上例中,大多数Remote方法使用Tv类的公有接口实现的。
不是真正需要作为友元的,唯一直接访问Tv成员的Remote
方法是Remote::set_chan();
不是真正需要作为友元的,唯一直接访问Tv成员的Remote
方法是Remote::set_chan();
作用
让Remote类的set_chan()成为Tv的友元,仅这个函数可以访问
Tv的私有数据成员。
Tv的私有数据成员。
格式
class Tv{
public:
friend void Remote::set_chan( Tv & t , int c );
......
};
public:
friend void Remote::set_chan( Tv & t , int c );
......
};
说明
声明的过程中还要注意一下两点
/顺序
原因
要编译器能够处理这条语句,它必须知道Remote的定义(类声明)。
否则,它无法知道Remote是一个类,而set_chan是这个类的方法。
否则,它无法知道Remote是一个类,而set_chan是这个类的方法。
Remote的方法提到了Tv对象,意味着应将Remote的定义放到Tv的定义前面。
解决互相依赖的问题,应该使用前向声明。
eg:
class Tv;
class Remote;
eg:
class Tv;
class Remote;
正确顺序
class Tv; // forward declaration
class Remote{....};
class Tv{....};
class Remote{....};
class Tv{....};
错误顺序
class Remote;
class Tv{....};
class Remote{.....};
class Tv{....};
class Remote{.....};
原因
编译器在Tv类的声明看到Remote的一个方法被声明为Tv类的
友元之前,应该先看到Remote类的声明和set_chan()方法的声明;
友元之前,应该先看到Remote类的声明和set_chan()方法的声明;
改进
问题
在Remote类中,包含了很多内联代码
void onoff( Tv & t){ t.onoff(); }
t.onoff()将调用Tv的一个方法,所以编译器此时必须已经看到了Tv类的声明,
这样才能知道Tv有哪些方法,但是Tv的声明在Remote 的后方。
void onoff( Tv & t){ t.onoff(); }
t.onoff()将调用Tv的一个方法,所以编译器此时必须已经看到了Tv类的声明,
这样才能知道Tv有哪些方法,但是Tv的声明在Remote 的后方。
解决
使Remote之包含方法声明,并将实际的定义放在Tv类之后。
排列顺序
class Tv;
class Remote{ ... };
class Tv{ ... };
// put Remote method definitions here;
eg:
inline bool Remote::volup( Tv & t){ return t.voiup;}
class Remote{ ... };
class Tv{ ... };
// put Remote method definitions here;
eg:
inline bool Remote::volup( Tv & t){ return t.voiup;}
注意
内联函数的链接性是内部的,函数的定义必须在使用函数的文件中。(这里是头文件中)
不是内联的函数定义可放在实现文件中,这样函数链接性是外部的。
不是内联的函数定义可放在实现文件中,这样函数链接性是外部的。
让整个Romate类成为友元并不需要前向声明,因为友元语句本身已经
指出Remote是一个类。
指出Remote是一个类。
代码
类声明
// tvfm.h -- Tv and Remote classes using a friend member
#ifndef TVFM_H_
#define TVFM_H_
class Tv; // forward declaration
class Remote
{
public:
enum State{Off, On};
enum {MinVal,MaxVal = 20};
enum {Antenna, Cable};
enum {TV, DVD};
private:
int mode;
public:
Remote(int m = TV) : mode(m) {}
bool volup(Tv & t); // prototype only
bool voldown(Tv & t);
void onoff(Tv & t);
void chanup(Tv & t);
void chandown(Tv & t);
void set_mode(Tv & t);
void set_input(Tv & t);
void set_chan(Tv & t, int c);
};
class Tv
{
public:
friend void Remote::set_chan(Tv & t, int c);
enum State{Off, On};
enum {MinVal,MaxVal = 20};
enum {Antenna, Cable};
enum {TV, DVD};
Tv(int s = Off, int mc = 125) : state(s), volume(5),
maxchannel(mc), channel(2), mode(Cable), input(TV) {}
void onoff() {state = (state == On)? Off : On;}
bool ison() const {return state == On;}
bool volup();
bool voldown();
void chanup();
void chandown();
void set_mode() {mode = (mode == Antenna)? Cable : Antenna;}
void set_input() {input = (input == TV)? DVD : TV;}
void settings() const;
private:
int state;
int volume;
int maxchannel;
int channel;
int mode;
int input;
};
// Remote methods as inline functions
inline bool Remote::volup(Tv & t) { return t.volup();}
inline bool Remote::voldown(Tv & t) { return t.voldown();}
inline void Remote::onoff(Tv & t) { t.onoff(); }
inline void Remote::chanup(Tv & t) {t.chanup();}
inline void Remote::chandown(Tv & t) {t.chandown();}
inline void Remote::set_mode(Tv & t) {t.set_mode();}
inline void Remote::set_input(Tv & t) {t.set_input();}
inline void Remote::set_chan(Tv & t, int c) {t.channel = c;}
#endif
其他友元关系
Remote和Tv互为友元
class Tv
{
friend class Remote;
public:
void buzz( Remote & r);
.....
};
class Remote{
friend class Tv;
public:
void Bool volup(Tv & t) { t.volup();}
};
{
friend class Remote;
public:
void buzz( Remote & r);
.....
};
class Remote{
friend class Tv;
public:
void Bool volup(Tv & t) { t.volup();}
};
说明
对于Remote对象的Tv方法,其原型可在Remote类声明之前
声明,但必须在Remote类声明之后定义。
声明,但必须在Remote类声明之后定义。
共同的友元
作用
函数成为两个类的友元,可以访问两个类的私有数据。
格式
class Analyzer;
class Probe{
friend void sync( Analyzer &a , const Probe &p);
friend void sync(Probe &p,const Analyzer &a);
....
};
class Anlyzer{
friend void sync( Analyzer &a , const Probe &p);
friend void sync(Probe &p,const Analyzer &a);
};
class Probe{
friend void sync( Analyzer &a , const Probe &p);
friend void sync(Probe &p,const Analyzer &a);
....
};
class Anlyzer{
friend void sync( Analyzer &a , const Probe &p);
friend void sync(Probe &p,const Analyzer &a);
};
inline void sync(Analyzer &a , const Probe &p)
{
}
inline void sync( Probe &p,const Analyzer &a )
{
}
{
}
inline void sync( Probe &p,const Analyzer &a )
{
}
没有限定域
嵌套类
作用
帮助实现另一个类,并避免名称冲突
格式
类声明
class Queue
{
private:
.....
class Node
{
public:
Item item;
Node * next;
Node(const Item & i):item(i), next(0){ } //内联实现
};
Node * front; // pointer to front of Queue
Node * rear; // pointer to rear of Queue
......
}
......
}
如果想在方法文件中
定义Node(嵌套类)
构造函数
定义Node(嵌套类)
构造函数
Queue::Node::Node( const Item & i ) :item( i ),next( 0 ) { }
Queue类中函数对
Node对象的创建
Node对象的创建
bool Queue::enqueue(const Item & item)
{
......
......
Node * add = new Node(item); // create node
......
......
}
说明
对类进行嵌套不创建类成员,而定义一种类型。
在Queue类中创建Node对象才创建类成员
在Queue类中创建Node对象才创建类成员
嵌套类和访问权限
作用域
含义
嵌套类的声明位置,即Node在Queue的
私有部分/公有部分/保护部分声明
私有部分/公有部分/保护部分声明
嵌套位置
//下见表,更清晰
//下见表,更清晰
嵌套类在另一个类的私有
部分声明
部分声明
eg:Queue和Node,只有Queue中的成员可以使用Node对象
和指向Node对象的指针
和指向Node对象的指针
嵌套类在另一个类的保护
部分声明
部分声明
上+
Queue的派生类可以使用
Queue的派生类可以使用
嵌套类在另一个类的公有
部分声明
部分声明
上+
外部世界可见
外部世界可见
在外部世界使用它格式
class Team{
public:
class Coach{ ... };
};
....
Team::Coach forhire;
public:
class Coach{ ... };
};
....
Team::Coach forhire;
说明
嵌套结构和枚举的作用域与此相同
嵌套类、结构和枚举的作用域特征
声明位置 | 包含它的类是否可以使用它 | 从包含它的类派生而来的类是否可以使用它 | 在外部是否可以使用 |
私有部分 | 是 | 否 | 否 |
保护部分 | 是 | 是 | 否 |
公有部分 | 是 | 是 | 是 |
访问控制
含义
嵌套类的公有部分、私有部分、保护部分控制了
对类成员的访问。
对类成员的访问。
访问控制
访问控制规则和常规类相同
说明
因此Queue类智能访问Node的公有部分,这样有悖于数据成员
声明为私有的惯例,但Node类时Queue类内部实现的一项特性,
对外部世界不可见。
声明为私有的惯例,但Node类时Queue类内部实现的一项特性,
对外部世界不可见。
模板中的嵌套
代码
类声明
// queuetp.h -- queue template with a nested class
#ifndef QUEUETP_H_
#define QUEUETP_H_
template <class Item>
class QueueTP
{
private:
enum {Q_SIZE = 10};
// Node is a nested class definition
class Node
{
public:
Item item;
Node * next;
Node(const Item & i):item(i), next(0){ }
};
Node * front; // pointer to front of Queue
Node * rear; // pointer to rear of Queue
int items; // current number of items in Queue
const int qsize; // maximum number of items in Queue
QueueTP(const QueueTP & q) : qsize(0) {}
QueueTP & operator=(const QueueTP & q) { return *this; }
public:
QueueTP(int qs = Q_SIZE);
~QueueTP();
bool isempty() const
{
return items == 0;
}
bool isfull() const
{
return items == qsize;
}
int queuecount() const
{
return items;
}
bool enqueue(const Item &item); // add item to end
bool dequeue(Item &item); // remove item from front
};
// QueueTP methods
template <class Item>
QueueTP<Item>::QueueTP(int qs) : qsize(qs)
{
front = rear = 0;
items = 0;
}
template <class Item>
QueueTP<Item>::~QueueTP()
{
Node * temp;
while (front != 0) // while queue is not yet empty
{
temp = front; // save address of front item
front = front->next;// reset pointer to next item
delete temp; // delete former front
}
}
// Add item to queue
template <class Item>
bool QueueTP<Item>::enqueue(const Item & item)
{
if (isfull())
return false;
Node * add = new Node(item); // create node
// on failure, new throws std::bad_alloc exception
items++;
if (front == 0) // if queue is empty,
front = add; // place item at front
else
rear->next = add; // else place at rear
rear = add; // have rear point to new node
return true;
}
// Place front item into item variable and remove from queue
template <class Item>
bool QueueTP<Item>::dequeue(Item & item)
{
if (front == 0)
return false;
item = front->item; // set item to first item in queue
items--;
Node * temp = front; // save location of first item
front = front->next; // reset front to next item
delete temp; // delete former first item
if (items == 0)
rear = 0;
return true;
}
#endif
使用
#include <iostream>
#include <string>
#include "queuetp.h"
int main()
{
using std::string;
using std::cin;
using std::cout;
QueueTP<string> cs(5);
string temp;
while(!cs.isfull())
{
cout << "Please enter your name. You will be "
"served in the order of arrival.\n"
"name: ";
getline(cin, temp);
cs.enqueue(temp);
}
cout << "The queue is full. Processing begins!\n";
while (!cs.isempty())
{
cs.dequeue(temp);
cout << "Now processing " << temp << "...\n";
}
// cin.get();
return 0;
}
结果
Please enter your name. You will be served in the order of arrival.
name: Tom
Please enter your name. You will be served in the order of arrival.
name: Sally
Please enter your name. You will be served in the order of arrival.
name: Alice
Please enter your name. You will be served in the order of arrival.
name: Lily
Please enter your name. You will be served in the order of arrival.
name: Lucy
The queue is full. Processing begins!
Now processing Tom...
Now processing Sally...
Now processing Alice...
Now processing Lily...
Now processing Lucy...
异常
前言
C++异常处理的是对程序运行阶段发生的异常情况的一种响应
异常是相对较新的C++功能,有些老式编译器可能没有实现.
另外,有些编译器默认关闭这种特性,您可能需要使用编译器选项来启用它.
另外,有些编译器默认关闭这种特性,您可能需要使用编译器选项来启用它.
调用abort()
作用
典型实现是向标准错误流(cerr使用的错误流)发送消息
abnormal program termination(程序终止),然后终止程序。
返回一个随实现而异的值,告诉操作系统(如果程序是由另一个程序
调用的,告诉父进程),处理失败。
abnormal program termination(程序终止),然后终止程序。
返回一个随实现而异的值,告诉操作系统(如果程序是由另一个程序
调用的,告诉父进程),处理失败。
也可以使用exit()该函数刷新文件缓冲区,但不显示消息。
条件
头文件
cstdlib(stdlib.h)
名称空间
std
代码
//error1.cpp -- using the abort() function
#include <iostream>
#include <cstdlib>
double hmean(double a, double b);
int main()
{
double x, y, z;
std::cout << "Enter two numbers: ";
while (std::cin >> x >> y)
{
z = hmean(x,y);
std::cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << std::endl;
std::cout << "Enter next set of numbers <q to quit>: ";
}
std::cout << "Bye!\n";
return 0;
}
double hmean(double a, double b)
{
if (a == -b)
{
std::cout << "untenable arguments to hmean()\n";
std::abort();
}
return 2.0 * a * b / (a + b);
}
结果
Enter two numbers: 3 6
Harmonic mean of 3 and 6 is 4
Enter next set of numbers <q to quit>: 10 -10
untenable arguments to hmean()
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
//也可能是 abnormal program termination,因编译器而异
//也可能是 abnormal program termination,因编译器而异
返回错误码
原理
一种比异常终止更灵活的方法是,使用函数的返回值来指出问题.
使用指针参数或引用参数来将值(这里指的是Z)返回给调用函数,
并使用函数的返回值指出成功还是失败
并使用函数的返回值指出成功还是失败
代码
代码
//error2.cpp -- returning an error code
#include <iostream>
#include <cfloat> // (or float.h) for DBL_MAX
bool hmean(double a, double b, double * ans);
int main()
{
double x, y, z;
std::cout << "Enter two numbers: ";
while (std::cin >> x >> y)
{
if (hmean(x,y,&z))
std::cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << std::endl;
else
std::cout << "One value should not be the negative "
<< "of the other - try again.\n";
std::cout << "Enter next set of numbers <q to quit>: ";
}
std::cout << "Bye!\n";
return 0;
}
bool hmean(double a, double b, double * ans)
{
if (a == -b)
{
*ans = DBL_MAX;
return false;
}
else
{
*ans = 2.0 * a * b / (a + b);
return true;
}
}
结果
Enter two numbers: 3 6
Harmonic mean of 3 and 6 is 4
Enter next set of numbers <q to quit>: 10 -10
One value should not be the negative of the other - try again.
Enter next set of numbers <q to quit>: 1 19
Harmonic mean of 1 and 19 is 1.9
Enter next set of numbers <q to quit>: q
Bye!
说明
bool hmean(double a, double b, double * ans);
第三个参数可以是指针或引用。对内置类型的参数,很多程序员都倾向于使用
指针,因为这样可以明显看出是哪个参数用于提供答案。
第三个参数可以是指针或引用。对内置类型的参数,很多程序员都倾向于使用
指针,因为这样可以明显看出是哪个参数用于提供答案。
另一种在某个地方存储返回条件(这里指返回z)的方法是使用一个全局变量。
异常机制
C++异常作用
C++异常处理的是对程序运行阶段发生的异常情况的一种响应
异常提供了将控制权从程序的一个部分传递到另以部分的途径
异常处理的
组成部分
组成部分
引发异常
throw语句
throw "bad hmean() arguments: a = -b not allowed";
throw关键字表示引发异常
紧随throw关键字其后的值(字符串/对象)指出异常的特征。
throw实际是跳转,即命令程序跳到另一条语句。
使用处理程序捕获异常
catch块
catch (const char * s) // start of exception handler
{
std::cout << s << std::endl;
std::cout << "Enter a new pair of numbers: ";
continue;
}
catch关键字表示捕获异常
catch(...)括号中的类型声明,指出异常处理程序要响应的异常类型。
{}代码块,指出要采取的措施
{}代码块,指出要采取的措施
catch关键字和异常类型用作标签,指出当异常被引发时,
程序应跳到这个位置的执行。
程序应跳到这个位置的执行。
使用try
try标识块
try { // start of try block
z = hmean(x,y);
}
try块标识其中特定的异常可能被激活的代码块,
花括号括起的代码块,表明需要著名这些代码引发的异常。
花括号括起的代码块,表明需要著名这些代码引发的异常。
它后面跟一个或者多个catch。
代码
#include <iostream>
double hmean(double a, double b);
int main()
{
double x, y, z;
std::cout << "Enter two numbers: ";
while (std::cin >> x >> y)
{
try { // start of try block
z = hmean(x,y);
} // end of try block
catch (const char * s) // start of exception handler
{
std::cout << s << std::endl;
std::cout << "Enter a new pair of numbers: ";
continue;
} // end of handler
std::cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << std::endl;
std::cout << "Enter next set of numbers <q to quit>: ";
}
std::cout << "Bye!\n";
return 0;
}
double hmean(double a, double b)
{
return 2.0 * a * b / (a + b);
}
结果
Enter two numbers: 3 6
Harmonic mean of 3 and 6 is 4
Enter next set of numbers <q to quit>: 10 -10
bad hmean() arguments: a = -b not allowed
Enter a new pair of numbers: 1 19
Harmonic mean of 1 and 19 is 1.9
Enter next set of numbers <q to quit>: q
Bye!
原理
//try块
try { // start of try block
try { // start of try block
z = hmean(x,y);
}
如果其中的某条语句导致异常被引发,后面的catch块将对异常进行
处理。如果程序在try块的外面调用hmean(),将无法处理异常。
处理。如果程序在try块的外面调用hmean(),将无法处理异常。
//引发异常代码
if (a == -b)
if (a == -b)
throw "bad hmean() arguments: a = -b not allowed";
被引发异常的字符串是"bad hmean() arguments: a = -b not allowed"。
异常类型可以是字符串,也可是是其他C++类型;通常为类类型。
异常类型可以是字符串,也可是是其他C++类型;通常为类类型。
执行throw语句类似执行返回语句,因为它将终止函数的执行。
但throw不是将控制权返回给调用程序,而是导致程序沿函数调用序列后退,
直到找到包含try块的函数.(不再执行try块的后续语句)
执行完try块中的语句后,如果没有引发任何异常,则程序跳过try块后面的catch块,
直接执行处理程序后面的第一条语句.
但throw不是将控制权返回给调用程序,而是导致程序沿函数调用序列后退,
直到找到包含try块的函数.(不再执行try块的后续语句)
执行完try块中的语句后,如果没有引发任何异常,则程序跳过try块后面的catch块,
直接执行处理程序后面的第一条语句.
//处理程序
catch (const char * s) // start of exception handler
{
std::cout << s << std::endl;
std::cout << "Enter a new pair of numbers: ";
continue;
}
char*s表明改处理程序与字符串异常匹配。
当异常与该处理程序匹配时,程序将执行括号中的代码。
当异常与该处理程序匹配时,程序将执行括号中的代码。
其他
如果函数引发了异常,但没有try块或没有匹配的处理程序时,
在默认情况下,程序最终将调用absort()函数,但可以修改这种
行为。
在默认情况下,程序最终将调用absort()函数,但可以修改这种
行为。
将对象用作异常类型
好处
可以使用不同的异常类型来区分不同的函数在不同情况下引发的异常
对象可以携带信息,程序员可以根据这些信息来确定引发异常的
原因。catch块可以根据这些信息决定采取什么样的措施。
原因。catch块可以根据这些信息决定采取什么样的措施。
代码
头文件
// exc_mean.h -- exception classes for hmean(), gmean()
#include <iostream>
class bad_hmean
{
private:
double v1;
double v2;
public:
bad_hmean(double a = 0, double b = 0) : v1(a), v2(b){}
void mesg();
};
inline void bad_hmean::mesg()
{
std::cout << "hmean(" << v1 << ", " << v2 <<"): "
<< "invalid arguments: a = -b\n";
}
class bad_gmean
{
public:
double v1;
double v2;
bad_gmean(double a = 0, double b = 0) : v1(a), v2(b){}
const char * mesg();
};
inline const char * bad_gmean::mesg()
{
return "gmean() arguments should be >= 0\n";
}
使用
//error4.cpp ?using exception classes
#include <iostream>
#include <cmath> // or math.h, unix users may need -lm flag
#include "exc_mean.h"
// function prototypes
double hmean(double a, double b);
double gmean(double a, double b);
int main()
{
using std::cout;
using std::cin;
using std::endl;
double x, y, z;
cout << "Enter two numbers: ";
while (cin >> x >> y)
{
try { // start of try block
z = hmean(x,y);
cout << "Harmonic mean of " << x << " and " << y
<< " is " << z << endl;
cout << "Geometric mean of " << x << " and " << y
<< " is " << gmean(x,y) << endl;
cout << "Enter next set of numbers <q to quit>: ";
}// end of try block
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
cout << "Try again.\n";
continue;
}
catch (bad_gmean & hg)
{
cout << hg.mesg();
cout << "Values used: " << hg.v1 << ", "
<< hg.v2 << endl;
cout << "Sorry, you don't get to play any more.\n";
break;
} // end of catch block
}
cout << "Bye!\n";
// cin.get();
// cin.get();
return 0;
}
double hmean(double a, double b)
{
if (a == -b)
throw bad_hmean(a,b);
return 2.0 * a * b / (a + b);
}
double gmean(double a, double b)
{
if (a < 0 || b < 0)
throw bad_gmean(a,b);
return std::sqrt(a * b);
}
结果
//都不会引发异常
Enter two numbers: 4 12
Enter two numbers: 4 12
Harmonic mean of 4 and 12 is 6
Geometric mean of 4 and 12 is 6.9282
//第一个引发
//第一个引发
Enter next set of numbers <q to quit>: 5 -5
hmean(5, -5): invalid arguments: a = -b
Try again.
//第二个引发
//第二个引发
5 -2
Harmonic mean of 5 and -2 is -6.66667
gmean() arguments should be >= 0
Values used: 5, -2
Sorry, you don't get to play any more.
Bye!
说明
class bad_hmean{ ... } // a==-b时引发
class bad_gmean{....}// a<0||b<0时引发
class bad_gmean{....}// a<0||b<0时引发
try{
z=hmean(x,y);
....
cout<<gmean(x,y);
}
z=hmean(x,y);
....
cout<<gmean(x,y);
}
如果执行z=hmean时候引发异常,将直接跳到catch后面。
//执行完catch是否会继续执行try剩下的语句??不会。。
//执行完catch是否会继续执行try剩下的语句??不会。。
throw bad_hmean(a,b);
throw 对象的格式
catch (bad_hmean & bg)
{
........
continue;
}
catch (bad_gmean & hg)
{
..........
break;
}
bad_hmean异常处理跳出本次循环。
bad_gmean异常处理跳出循环。
bad_gmean异常处理跳出循环。
异常规范和C++11
异常规范
前言
C++98的内容,C++11摒弃
内容
throw()部分就是异常规范.
可能出现在函数原型和函数定义中,
可包含类型列表,也可不包含。
可能出现在函数原型和函数定义中,
可包含类型列表,也可不包含。
double harm( double a ) throw( bad_thing);
double marm( double ) throw();
作用
告诉用户可能使用try块
不足:可以使用注释完成
让编译器添加执行运行阶段检查的代码,
检查是否违反了异常规范。
检查是否违反了异常规范。
不足:很难检查
结论
最好不要使用这项功能
C++11支持的特殊
的异常规范
的异常规范
内容
double marm() noexcept;
指出函数不会发生异常
运算符noexcept( ) 判断操作数是否会引发异常
争议
不使用
使用
知道函数不会引发异常,有助于编译器优化代码
栈解退
函数调用和返回原理
栈解退原理
- 假设try块没有直接调用引发异常的函数,而是调用了对引发异常的函数进行调用的函数,则程序流程将从引发异常的函数调到包含try块和处理程序的函数.C++通常通过将信息放在栈中来处理函数调用.
- 异常极其重要的一点:程序进行栈解退以回到能够捕获异常的地方时,将释放栈中的自动存储型变量.如果变量是类对象,将为该对象调用析构函数.
图片
注意"throw"的位置
代码理解原理
父节点
// exc_mean.h -- exception classes for hmean(), gmean()
#include <iostream>
class bad_hmean
{
private:
double v1;
double v2;
public:
bad_hmean(double a = 0, double b = 0) : v1(a), v2(b){}
void mesg();
};
inline void bad_hmean::mesg()
{
std::cout << "hmean(" << v1 << ", " << v2 <<"): "
<< "invalid arguments: a = -b\n";
}
class bad_gmean
{
public:
double v1;
double v2;
bad_gmean(double a = 0, double b = 0) : v1(a), v2(b){}
const char * mesg();
};
inline const char * bad_gmean::mesg()
{
return "gmean() arguments should be >= 0\n";
}
父节点
//error5.cpp -- unwinding the stack
#include <iostream>
#include <cmath> // or math.h, unix users may need -lm flag
#include <string>
#include "exc_mean.h"
class demo
{
private:
std::string word;
public:
demo (const std::string & str)
{
word = str;
std::cout << "demo " << word << " created\n";
}
~demo()
{
std::cout << "demo " << word << " destroyed\n";
}
void show() const
{
std::cout << "demo " << word << " lives!\n";
}
};
// function prototypes
double hmean(double a, double b);
double gmean(double a, double b);
double means(double a, double b);
int main()
{
using std::cout;
using std::cin;
using std::endl;
double x, y, z;
{
demo d1("found in block in main()");
cout << "Enter two numbers: ";
while (cin >> x >> y)
{
try { // start of try block
z = means(x,y);
cout << "The mean mean of " << x << " and " << y
<< " is " << z << endl;
cout << "Enter next pair: ";
} // end of try block
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
cout << "Try again.\n";
continue;
}
catch (bad_gmean & hg)
{
cout << hg.mesg();
cout << "Values used: " << hg.v1 << ", "
<< hg.v2 << endl;
cout << "Sorry, you don't get to play any more.\n";
break;
} // end of catch block
}
d1.show();
}
cout << "Bye!\n";
// cin.get();
// cin.get();
return 0;
}
double hmean(double a, double b)
{
if (a == -b)
throw bad_hmean(a,b);
return 2.0 * a * b / (a + b);
}
double gmean(double a, double b)
{
if (a < 0 || b < 0)
throw bad_gmean(a,b);
return std::sqrt(a * b);
}
double means(double a, double b)
{
double am, hm, gm;
demo d2("found in means()");
am = (a + b) / 2.0; // arithmetic mean
try
{
hm = hmean(a,b);
gm = gmean(a,b);
}
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
std::cout << "Caught in means()\n";
throw; // rethrows the exception
}
d2.show();
return (am + hm + gm) / 3.0;
}
结果
demo found in block in main() created
Enter two numbers: 4 12
demo found in means() created
demo found in means() lives!
demo found in means() destroyed
The mean mean of 4 and 12 is 6.97607
//
//
Enter next pair: 5 -5
demo found in means() created
hmean(5, -5): invalid arguments: a = -b
Caught in means()
demo found in means() destroyed
hmean(5, -5): invalid arguments: a = -b
Try again.
//
//
5 -2
demo found in means() created
demo found in means() destroyed
gmean() arguments should be >= 0
Values used: 5, -2
Sorry, you don't get to play any more.
demo found in block in main() lives!
demo found in block in main() destroyed
Bye!
说明
demo对象作用
知道发生异常时,自动变量/对象如何被处理
demo对象有在main中,有在means函数中
在means()函数中的try块只能捕抓bad_hmean异常,
如果发生bad_gmean异常将返回main中的try_catch中
如果发生bad_gmean异常将返回main中的try_catch中
main中的try-catch
try { // start of try block
z = means(x,y);
cout << "The mean mean of " << x << " and " << y
<< " is " << z << endl;
cout << "Enter next pair: ";
} // end of try block
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
cout << "Try again.\n";
continue;
}
catch (bad_gmean & hg)
{
cout << hg.mesg();
cout << "Values used: " << hg.v1 << ", "
<< hg.v2 << endl;
cout << "Sorry, you don't get to play any more.\n";
break;
} // end of catch block
means中的try-catch
try
{
hm = hmean(a,b);
gm = gmean(a,b);
}
catch (bad_hmean & bg) // start of catch block
{
bg.mesg();
std::cout << "Caught in means()\n";
throw; // rethrows the exception
}
注意means 中的throw将返回main中的try-catch,并throw的异常类型是
bad_hmean
bad_hmean
结果分析
输入5 -5时候
跳转位置
demo对象的执行情况
总是被destroy
输入5 -2时候
其他异常特性
try-catch机制和
函数返回机制区别
函数返回机制区别
返回
函数
函数fun()中的返回语句将控制权返回到调用fun()的函数
try-catch
throw语句将控制权向上返回到第一个这样的函数:
包含能够捕获相应异常的try-catch组合。
包含能够捕获相应异常的try-catch组合。
try不同
引发异常时编译器总是创建一个临时拷贝,即使异常规范和catch块中
指定的是引用
指定的是引用
class problem { ... }
....
try{
super();
}
catch( problem & p)
{
//statement
}
void super() throw ( problem)
{
...
if ( oh_no)
{
problem opps;
throw oops;
......
}
}
....
try{
super();
}
catch( problem & p)
{
//statement
}
void super() throw ( problem)
{
...
if ( oh_no)
{
problem opps;
throw oops;
......
}
}
p指向的是opps的副本,不是opps本身。
同时函数super()执行完后,opps不存在。
同时函数super()执行完后,opps不存在。
将引发异常和创建对象组合在一起更简单
problem opps;
throw oops;
→
throw problem() ;
throw oops;
→
throw problem() ;
所以throw一个在被调用函数中的对象,也可以运行。
catch块中为何使用引用?
原因
基类引用将能够捕捉派生类对象。
例子
class bad_1{ ... }
class bad_2:public bad_1{ ... }
class bad_3:public bad_2{ ... }
...
void duper()
{
...
if (oh_no)
throw bad_1();
if (rats)
throw bad_2();
if (drat)
throw bad_3();
}
...
try{
duper();
}
catch(bad_3 &be)
{//statements }
catch(bad_2 &be)
{//statements}
catch(bad_1 &be)
{//statements}
注意
注意排列catc块的顺序,将捕获位于层析结构最下面的异常类的catch语句放在最前面,
将捕获基类异常的catch语句放在后面.
//不然基类会捕捉派生类对象,不管后面如何。
将捕获基类异常的catch语句放在后面.
//不然基类会捕捉派生类对象,不管后面如何。
捕捉其他异常
格式
catch( ... ){ // statement }
相当于switch的default
例子
try{
duper();
}
catch(bad_3 &be)
{//statements }
catch(bad_2 &be)
{//statements}
catch(bad_1 &be)
{//statements}
catch ( ... )
{ //statements }
exception类
C++异常主要目的
为设计容错程序提供语言级支持,即异常使得在程序设计中包含错误处理功能更容易,
以免事后操去一些严格的错误处理方式
以免事后操去一些严格的错误处理方式
exception类
特点
C++编译器将异常合并到语言中。
是一种与异常相关的类。
是一种与异常相关的类。
条件
头文件
exception
//之前是
exception.h 或except.h
//之前是
exception.h 或except.h
名称空间?/
作用
代码可以引发exception异常,
也可将exception类用作基类。
//像前面的bad_gmean和bad_hmean 一样,主要用在throw 后面
//用作基类就可以只用一个基类接收不同派生类。
代码可以引发exception异常,
也可将exception类用作基类。
//像前面的bad_gmean和bad_hmean 一样,主要用在throw 后面
//用作基类就可以只用一个基类接收不同派生类。
应用
exception类中有一个名为what()的虚拟成员函数,它返回一个字符串,内容随实现而异。
可以从exception派生而来的类中重新定义它。
//字符串的内容可以说明异常的内容
可以从exception派生而来的类中重新定义它。
//字符串的内容可以说明异常的内容
代码
#include<exception>
class bad_hmean :public std exception
{
public:
const char *what(){ return "bad arguments to hmean()";}
...
};
class bad_gmean : public std::exception
{
public:
const char *what(){ return "bad arguments to gmean()";}
...
};
try{
...
}
catch(std::exception & e)
{
cout << e.what()<<endl;
....
}
说明
e.what()根据不同的基类显示。
基于exception的异常类型
stdexcept异常t类
//头文件stdexcept
//头文件stdexcept
内容
定义了logic_error和runtime_error两个类
logic_error类
定义
class logic_error:public exception {
public:
explict logic_error( const string& what_arg );
...
};
public:
explict logic_error( const string& what_arg );
...
};
构造函数接受一个string对象作为参数,
参数提供了方法what()以c-风格字符串方式
返回的字符数据
参数提供了方法what()以c-风格字符串方式
返回的字符数据
作用
描述典型的逻辑错误,可以通过合理的编程避免这种错误,但实际上这些错误还是
可能发生。
可能发生。
每个类的名称指出它用于报告的错误类型:如下
派生类
domain_error;
指出函数参数的范围不在定义域内
数学函数有定义域(domian),设写一个函数sin(),模仿arcsinx 功能,
该函数将一个参数传递给函数std::sin(),则可以让该函数在参数不在定义域-1到
+1之间时引发domain_error异常。
该函数将一个参数传递给函数std::sin(),则可以让该函数在参数不在定义域-1到
+1之间时引发domain_error异常。
invalid_argument
指出给函数传递了一个意料之外的值
eg:希望函数接受一个这样的字符串:每个字符要么“1”要么“0“
如果传递的字符中包含其他字符时,引发invalid_argument异常
如果传递的字符中包含其他字符时,引发invalid_argument异常
length_error
指出没有足够的空间来执行所需的操作
out_of_bounds
out_of_bounds
指示索引错误
说明
每个类都有一个类似于logic_error的构造函数,能够提供一个
供方法what() 返回的字符串
供方法what() 返回的字符串
eg:class domain_error:public logic_error {
public:
explict domain_errorr( const string& what_arg );
...
};
public:
explict domain_errorr( const string& what_arg );
...
};
runtime_error类
定义
class runtime_error:public exception {
public:
explict runtime_error( const string& what_arg );
...
};
public:
explict runtime_error( const string& what_arg );
...
};
作用
描述可能在运行期间发生但难以预计和防范的错误。
每个类的名称指出它用于报告的错误类型:如下
派生类
range_error
计算结果可能不在范围之内,没有发生上溢和下溢错误。
overflow_error
整型或浮点型计算,计算结果比类型的最大数量级导致上溢。
underflow_error
( 下溢 )
( 下溢 )
错误在浮点数计算,计算结果比浮点类型的最小非0值还小导致下溢。
说明
logic_error系列存在可以通过编程修复的问题
rumtime_error系列存在无法避免的问题。
所有这些错误类有相同的常规特征,他们之间的区别在于:
不通的类名让您能够分别处理每种异常
不通的类名让您能够分别处理每种异常
继承关系能够一起处理每种异常
try{
.....
}
catch( out_of_bounds & oe ) // catch out_of_bounds error
{ ... }
catch( logic_error & oe ) // catch remaining logic_error family
{ ... }
catch( exception & oe )
{ ... }
.....
}
catch( out_of_bounds & oe ) // catch out_of_bounds error
{ ... }
catch( logic_error & oe ) // catch remaining logic_error family
{ ... }
catch( exception & oe )
{ ... }
bad_alloc异常和new
作用
使用new导致的内存分配问题,C++的最新处理方式是让 new 引发 bad_alloc 异常。
以前处理无法分配请求的内存量时,
new返回一个空指针。
new返回一个空指针。
ball_alloc
头文件
new头文件包含bad_allco类的声明
派生类
它是从exception 类公有派生而来的
应用
说明
try {
.....
.....
pb = new Big[10000]; // 1,600,000,000 bytes
.....
}
catch (bad_alloc & ba)
{
cout << "Caught the exception!\n";
cout << ba.what() << endl;
exit(EXIT_FAILURE);
}
如果pd=new Big[10000]发生错误时,会throw 出bad_alloc 异常?
catch会扑抓
catch会扑抓
bad_alloc的what返回的语句通常是std::bad_alloc
代码
// newexcp.cpp -- the bad_alloc exception
#include <iostream>
#include <new>
#include <cstdlib> // for exit(), EXIT_FAILURE
using namespace std;
struct Big
{
double stuff[20000];
};
int main()
{
Big * pb;
try {
cout << "Trying to get a big block of memory:\n";
pb = new Big[10000]; // 1,600,000,000 bytes
cout << "Got past the new request:\n";
}
catch (bad_alloc & ba)
{
cout << "Caught the exception!\n";
cout << ba.what() << endl;
exit(EXIT_FAILURE);
}
cout << "Memory successfully allocated\n";
pb[0].stuff[0] = 4;
cout << pb[0].stuff[0] << endl;
delete [] pb;
// cin.get();
return 0;
}
结果
Trying to get a big block of memory:
Caught the exception!
std::bad_alloc;
Caught the exception!
std::bad_alloc;
空指针和new
作用
在new在分配失败时返回空指针
方法/格式
int *pi = new ( std::nothrow ) int;/或者
int *pa = new (std::nowthrow) int[50];
int *pa = new (std::nowthrow) int[50];
就是说nothrow/nowthrow都可以
异常、类和继承
三者可能关系
从一个异常类派生出另一个
在类定义中嵌套异常类声明来组合异常
这种嵌套声明本身可被继承,还可用作基类
应用
类声明
// sales.h -- exceptions and inheritance
#include <stdexcept>
#include <string>
class Sales
{
public:
enum {MONTHS = 12}; // could be a static const
class bad_index : public std::logic_error
{
private:
int bi; // bad index value
public:
explicit bad_index(int ix,
const std::string & s = "Index error in Sales object\n");
int bi_val() const {return bi;}
virtual ~bad_index() throw() {}
};
explicit Sales(int yy = 0);
Sales(int yy, const double * gr, int n);
virtual ~Sales() { }
int Year() const { return year; }
virtual double operator[](int i) const;
virtual double & operator[](int i);
private:
double gross[MONTHS];
int year;
};
class LabeledSales : public Sales
{
public:
class nbad_index : public Sales::bad_index
{
private:
std::string lbl;
public:
nbad_index(const std::string & lb, int ix,
const std::string & s = "Index error in LabeledSales object\n");
const std::string & label_val() const {return lbl;}
virtual ~nbad_index() throw() {}
};
explicit LabeledSales(const std::string & lb = "none", int yy = 0);
LabeledSales(const std::string & lb, int yy, const double * gr, int n);
virtual ~LabeledSales() { }
const std::string & Label() const {return label;}
virtual double operator[](int i) const;
virtual double & operator[](int i);
private:
std::string label;
};
实现
// sales.cpp -- Sales implementation
#include "sales.h"
using std::string;
Sales::bad_index::bad_index(int ix, const string & s )
: std::logic_error(s), bi(ix)
{
}
Sales::Sales(int yy)
{
year = yy;
for (int i = 0; i < MONTHS; ++i)
gross[i] = 0;
}
Sales::Sales(int yy, const double * gr, int n)
{
year = yy;
int lim = (n < MONTHS)? n : MONTHS;
int i;
for (i = 0; i < lim; ++i)
gross[i] = gr[i];
// for i > n and i < MONTHS
for ( ; i < MONTHS; ++i)
gross[i] = 0;
}
double Sales::operator[](int i) const
{
if(i < 0 || i >= MONTHS)
throw bad_index(i);
return gross[i];
}
double & Sales::operator[](int i)
{
if(i < 0 || i >= MONTHS)
throw bad_index(i);
return gross[i];
}
LabeledSales::nbad_index::nbad_index(const string & lb, int ix,
const string & s ) : Sales::bad_index(ix, s)
{
lbl = lb;
}
LabeledSales::LabeledSales(const string & lb, int yy)
: Sales(yy)
{
label = lb;
}
LabeledSales::LabeledSales(const string & lb, int yy, const double * gr, int n)
: Sales(yy, gr, n)
{
label = lb;
}
double LabeledSales::operator[](int i) const
{ if(i < 0 || i >= MONTHS)
throw nbad_index(Label(), i);
return Sales::operator[](i);
}
double & LabeledSales::operator[](int i)
{
if(i < 0 || i >= MONTHS)
throw nbad_index(Label(), i);
return Sales::operator[](i);
}
用法
// use_sales.cpp -- nested exceptions
#include <iostream>
#include "sales.h"
int main()
{
using std::cout;
using std::cin;
using std::endl;
double vals1[12] =
{
1220, 1100, 1122, 2212, 1232, 2334,
2884, 2393, 3302, 2922, 3002, 3544
};
double vals2[12] =
{
12, 11, 22, 21, 32, 34,
28, 29, 33, 29, 32, 35
};
Sales sales1(2011, vals1, 12);
LabeledSales sales2("Blogstar",2012, vals2, 12 );
cout << "First try block:\n";
try
{
int i;
cout << "Year = " << sales1.Year() << endl;
for (i = 0; i < 12; ++i)
{
cout << sales1[i] << ' ';
if (i % 6 == 5)
cout << endl;
}
cout << "Year = " << sales2.Year() << endl;
cout << "Label = " << sales2.Label() << endl;
for (i = 0; i <= 12; ++i)
{
cout << sales2[i] << ' ';
if (i % 6 == 5)
cout << endl;
}
cout << "End of try block 1.\n";
}
catch(LabeledSales::nbad_index & bad)
{
cout << bad.what();
cout << "Company: " << bad.label_val() << endl;
cout << "bad index: " << bad.bi_val() << endl;
}
catch(Sales::bad_index & bad)
{
cout << bad.what();
cout << "bad index: " << bad.bi_val() << endl;
}
cout << "\nNext try block:\n";
try
{
sales2[2] = 37.5;
sales1[20] = 23345;
cout << "End of try block 2.\n";
}
catch(LabeledSales::nbad_index & bad)
{
cout << bad.what();
cout << "Company: " << bad.label_val() << endl;
cout << "bad index: " << bad.bi_val() << endl;
}
catch(Sales::bad_index & bad)
{
cout << bad.what();
cout << "bad index: " << bad.bi_val() << endl;
}
cout << "done\n";
// std::cin.get();
return 0;
}
结果
First try block:
Year = 2011
1220 1100 1122 2212 1232 2334
2884 2393 3302 2922 3002 3544
Year = 2012
Label = Blogstar
12 11 22 21 32 34
28 29 33 29 32 35
Index error in LabeledSales object
Company: Blogstar
bad index: 12
Next try block:
Index error in Sales object
bad index: 20
done
说明
类声明
( 关系主要说明)
( 关系主要说明)
Sales 类,用于存储一个年份和包含12个月的销售数据的数组
LabeledSales类是从Sales派生而来的,新增了一个用于存储数据的标签成员
Sales 中的内嵌异常类bad_index用于“检查”Sales中的operator[]( int i ) 的 i 是否
超出范围,超界时引发异常。
超出范围,超界时引发异常。
Sales中的bad_index类是由logic_error公有派生来的,
所以要包括头文件#include<stdexcept>,
bad_index集成logic_error的what()成员数据。
所以要包括头文件#include<stdexcept>,
bad_index集成logic_error的what()成员数据。
LabelSales中的嵌套异常类nbad_index 新增了显示label 的值,
它从bad_index公有继承而来,实际上也是logic_error的公有继承。
它从bad_index公有继承而来,实际上也是logic_error的公有继承。
在bad_index异常类中有析构函数
virtual ~bad_index() throw() {},
后throw() 的作用?含义?
void fun() throw() 表示fun不允许抛出任何异常,即fun是异常安全的。
void fun() throw(...) 表示fun可以抛出任何形式的异常。
void fun() throw(exceptionType) 表示fun只能抛出exceptionType类型的异常。
比如:
void GetTag() throw(int); 表示只抛出int类型异常
void GetTag() throw(int,char); 表示抛出in,char类型异常
void GetTag() throw(); 表示不会抛出任何类型异常
void GetTag() throw(...); 表示抛出任何类型异常
那么,void GetTag() throw(int); 表示只抛出int类型异常 这句解释怎么理解呢?并不表示一定会抛出异常,但是一旦抛出异常只会抛出int类型。如果抛出非int类型异常,调用unexsetpion()函数,退出程序。
virtual ~bad_index() throw() {},
后throw() 的作用?含义?
1、对throw的说明
C++函数后面后加关键字throw(something)限制,是对这个函数的异常安全性作出限制。
举例及解释如下:void fun() throw() 表示fun不允许抛出任何异常,即fun是异常安全的。
void fun() throw(...) 表示fun可以抛出任何形式的异常。
void fun() throw(exceptionType) 表示fun只能抛出exceptionType类型的异常。
比如:
void GetTag() throw(int); 表示只抛出int类型异常
void GetTag() throw(int,char); 表示抛出in,char类型异常
void GetTag() throw(); 表示不会抛出任何类型异常
void GetTag() throw(...); 表示抛出任何类型异常
那么,void GetTag() throw(int); 表示只抛出int类型异常 这句解释怎么理解呢?并不表示一定会抛出异常,但是一旦抛出异常只会抛出int类型。如果抛出非int类型异常,调用unexsetpion()函数,退出程序。
实现
double Sales::operator[](int i) const
{
if(i < 0 || i >= MONTHS)
throw bad_index(i);
return gross[i];
}
throw后返回调用它的try块的后catch()部分。
throw bad_index(....)不产生bad_index()对象,
catch( Sales::bad_index & bad)后bad才产生bad_index对象bad
throw bad_index(....)不产生bad_index()对象,
catch( Sales::bad_index & bad)后bad才产生bad_index对象bad
异常何时会迷失方向
意外异常
出现条件
异常是在带异常规范的函数中引发的,必须与规范列表的某种异常匹配。
否则,则称为意外异常。
否则,则称为意外异常。
异常规范应包含函数调用的其他函数引发的异常,不然也会出现意外异常
很难知道,所以这也是C++11将其摒弃的原因。
默认导致情况
将导致程序异常终止。
具体
程序先调用unexcepted()函数。这个函数将调用terminate(0函数,后者默认调用abort()函数、
可修改导致情况
方法1
修改terminate()
方法2
修改unexcepted()调用的函数,而不是terminate()
unexcepted()函数
头文件
exception
原型
typedef void (*unexpexted_handler) ();
unexpected_handler set_unexpected( unexpected_handler f ) throw( );//C++98
unexpected_handler set_unexpected( unexpected_handler f ) noexcept;//C++11
unexpected_handler set_unexpected( unexpected_handler f ) throw( );//C++98
unexpected_handler set_unexpected( unexpected_handler f ) noexcept;//C++11
unexpected()函数
void unexpected();
void unexpected() noexcept;
void unexpected() noexcept;
说明
unexpected_handler函数
可以作以下行为
可以作以下行为
通过调用terminate() (默认行为)、abort()或exit()来终止程序
引发异常
引发的异常与原来的异常规范匹配
结果
用预期异常取代意外异常
新引发的异常与原来的异常规范不匹配,
且异常规范中没有包括std::bad_exception类型
且异常规范中没有包括std::bad_exception类型
结果
程序将调用terminate()函数
bad_exception是从exception派生而来的,声明位于头文件exception中
如果新引发的异常与原来的异常规范不匹配,且原来的异常规范中包含了
std::bad_exception类型
std::bad_exception类型
结果
不匹配的异常将被std::bad_exception异常取代
例子
(如果要捕获所有异常)
(如果要捕获所有异常)
#include<exception>
using namespace std;
using namespace std;
void myUnexpected()
{
throw std::bad_exception(); // or just throw
}
{
throw std::bad_exception(); // or just throw
}
set_unexpected( myUnexpected );
double Argh( double ,double ) throw( out_of_bounds , bad_exception )
...
try{
x= Argh(a,b);
}
catch( out_of_bounds &ex)
{ ...... }
catch( bad_exception & ex)
{ ..... }
...
try{
x= Argh(a,b);
}
catch( out_of_bounds &ex)
{ ...... }
catch( bad_exception & ex)
{ ..... }
修改unexcepted()调用的函数,而不是terminate()
未捕获异常
出现条件
不是在函数中引发的(或函数没有异常规范),则必须捕获它,没有被捕获
(没有try块或没有匹配的catch块),会出现未捕获异常。
(没有try块或没有匹配的catch块),会出现未捕获异常。
默认导致情况
导致程序异常终止。
具体
首先调用函数terminate()。terminate()函数在默认情况下,调用abort()函数。
修改导致情况
指定terminate()应调用的函数(而不是abort())来修改terminate()的这种行为。
方法
调用set_terminate()函数修改。
set_terminate()函数
头文件
exception
terminate()函数也是这个头文件
原型
typedef void (*terminate_handler) ();
terminate_handler set_terminate( terminate_handler f ) throw(); // C++98
terminate_handler ser_terminate( terminate_handler f ) noexcept ; // C++11
terminate_handler set_terminate( terminate_handler f ) throw(); // C++98
terminate_handler ser_terminate( terminate_handler f ) noexcept ; // C++11
terminate()函数
void terminate();//C++98
void terminate() noexcept;//C++11
void terminate() noexcept;//C++11
说明
terminate_handler是这样的一种类型
指向没有参数和返回值得函数的指针
set_terminate()函数将不带任何参数且返回类型为void 的函数的名称(地址)作为参数,
并返回该函数的地址。
并返回该函数的地址。
如果调用了set_terminate() 函数多次,则terminate()将调用最后一次set_terminate()调用
设置的函数
设置的函数
例子
void myQuit()
{
cout<<"Termingating due to uncaught exception\n";
exit(5); //将退出状态值设置为5?
}
set_terminate( myQuit );
{
cout<<"Termingating due to uncaught exception\n";
exit(5); //将退出状态值设置为5?
}
set_terminate( myQuit );
现在,如果引发一个异常且没有捕获,程序将调用terminate(),后者调用MyQuit()
有关异常注意事项
动态内存分配和异常
情况1
void test1( int n)
{
string mesg("I am ..."):
.....
if (oh_no)
throw exception();
.....
return;
}
{
string mesg("I am ..."):
.....
if (oh_no)
throw exception();
.....
return;
}
说明
当函数结束时,将为mesg调用string 的析构函数
情况2
void test2( int n)
{
double *ar = new double[n];
.......
if (oh_no)
throw exception():
.......
delete [] ar;
return;
}
{
double *ar = new double[n];
.......
if (oh_no)
throw exception():
.......
delete [] ar;
return;
}
说明
函数过早终止会将函数末尾的delete[] 语句被忽略,内存被泄露
解决
void test3 (int n)
{
double *ar = new double[n];
....
try{
if ( oh_no )
throw exception();
}
catch(exception & ex)
{
delete [] ar;
return;
}
.....
delete [] ar;
return ;
}
{
double *ar = new double[n];
....
try{
if ( oh_no )
throw exception();
}
catch(exception & ex)
{
delete [] ar;
return;
}
.....
delete [] ar;
return ;
}
但会产生其他错误机会
0 条评论
下一页