-cpp友元异常和其他
Posted itzyjr
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了-cpp友元异常和其他相关的知识,希望对你有一定的参考价值。
本章内容包括:
- 友元类。
- 友元类方法。
- 嵌套类。
- 引发异常、try块和catch块。
- 异常类。
- 运行阶段类型识别(RTTI)。
- dynamic_cast和typeid。
- static_cast、const_cast和reiterpret_cast。
一、友元
前面我们将友元函数用于类的扩展接口中,类并非只能拥有友元函数,也可以将类作为友元。在这种情况下,友元类的所有方法都可以访问原始类的私有成员和保护成员。另外,也可以做更严格的限制,只将特定的成员函数指定为另一个类的友元。哪些函数、成员函数或类为友元是由类定义的,而不能从外部强加友情。因此,尽管友元被授予从外部访问类的私有部分的权限,但它们并不与面向对象的编程思想相悖;相反,它们提高了公有接口的灵活性。
友元类:
什么时候希望一个类成为另一个类的友元呢?我们来看一个例子。假定需要编写一个模拟电视机和遥控器的简单程序。决定定义一个Tv类和一个Remote类,来分别表示电视机和遥控器。很明显,这两个类之间应当存在某种关系,但是什么样的关系呢?遥控器并非电视机,反之亦然,所以公有继承的is-a关系并不适用。遥控器也非电视机的一部分,反之亦然,因此包含或私有继承和保护继承的has-a关系也不适用。事实上,遥控器可以改变电视机的状态,这表明应将Romote类作为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();
return 0;
这个示例的主要目的在于表明,类友元是一种自然用语,用于表示一些关系。如果不使用某些形式的友元关系,则必须将Tv类的私有部分设置为公有的,或者创建一个笨拙的、大型类来包含电视机和遥控器。这种解决方法无法反应这样的事实,即同一个遥控器可用于多台电视机。
友元成员函数:
让Remote类的set_chan()函数成为Tv类的友元的方法是,在Tv类声明中将其声明为友元:
class Tv
friend void Remote::set_chan(Tv& t, int c);
...
然而,要使编译器能够处理这条语句,它必须知道Remote的定义。否则,它无法知道Remote是一个类,而set_chan是这个类的方法。这意味着应将Remote的定义放到Tv的定义前面。Remote的方法提到了Tv对象,而这意味着Tv定义应当位于Remote定义之前。避开这种循环依赖的方法是,使用前向声明(forward declaration)。为此,需要在Remote定义的前面插入下面的语句:
class Tv;// forward declaration
正确的排列:
class Tv;// 前向声明
class Remote ...;
class Tv ...;
————————————————————————
不能像下面这样排列:
class Remote;// 前向声明
class Tv ...;
class Remote ...;
不能用以上的第二种排列方式,原因在于:在编译器在Tv类的声明中看到Remote的一个方法被声明为Tv类的友元之前,应该先看到Remote类的声明和set_chan( )方法的声明。
还有一个麻烦。程序清单中的Remote声明包含了内联代码,例如:
void onoff(Tv& t) t.onoff();
由于这将调用Tv的一个方法,所以编译器此时必须已经看到了Tv类的声明,这样才能知道Tv有哪些方法,但正如看到的,该声明位于Remote声明的后面。这种问题的解决方法是,使Remote声明中只包含方法声明,并将实际的定义放在Tv类之后。这样,排列顺序将如下:
class Tv;// 前向声明
class Remote ...;// 这里只定义Tv要使用的方法的原型
class Tv ...;
把Remote的实现方法放到这里
顺便说一句,让整个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 StateOff, 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 StateOff, 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
如果在tv.cpp和use_tv.cpp中包含tvfm.h而不是tv.h,程序的行为与前一个程序相同,区别在于,只有一个Remote方法是Tv类的友元,而在原来的版本中,所有的Remote方法都是Tv类的友元。
共同的友元:
需要使用友元的另一种情况是,函数需要访问两个类的私有数据。从逻辑上看,这样的函数应是每个类的成员函数,但这是不可能的。它可以是一个类的成员,同时是另一个类的友元,但有时将函数作为两个类的友元更合理。例如,假定有一个Probe类和一个Analyzer类,前者表示某种可编程的测量设备,后者表示某种可编程的分析设备。这两个类都有内部时钟,且希望它们能够同步,则应该包含下述代码行:
class Analyzer;// forward declaration
class Probe
friend void sync(Analyzer& a, const Probe& p);// sync a to p
friend void sync(Probe& p, const Analyzer& a);// sync p to a
...
;
class Analyzer
friend void sync(Analyzer& a, const Probe& p);// sync a to p
friend void sync(Probe& p, const Analyzer& a);// sync p to a
...
;
// define the friend functions
inline void sync(Analyzer& a, const Probe& p) ...
inline void sync(Probe& p, const Analyzer& a) ...
前向声明使编译器看到Probe类声明中的友元声明时,知道Analyzer是一种类型。
二、嵌套类
对类进行嵌套通常是为了帮助实现另一个类,并避免名称冲突。
下面Queue类示例嵌套了结构定义,从而实现了一种变相的嵌套类:
class Queue
private:
struct Node Item item; struct Node* next;;
...
由于结构是一种其成员在默认情况下为公有的类,所以Node实际上是一个嵌套类,但该定义并没有充分利用类的功能。具体地说,它没有显式构造函数。
enqueue()方法创建了结构:
bool Queue::enqueue(const Item& item)
if (isfull())
return false;
Node* add = new Node;// create node
// on failure, new throws std::bad_alloc exception
add->item = item;
add->next = NULL;
...
上述代码创建Node后,显式地给Node成员赋值,这种工作更适合由构造函数来完成。
知道应在什么地方以及如何使用构造函数后,便可以提供一个适当的构造函数定义:
class Queue
// class scope definition
// Node is a nested class definition local to this class
class Node
public:
Item item;
Node* next;
Node(const Item& i) : item(i), next(0)
;
...
;
该构造函数将节点的item成员初始化为i,并将next指针设置为0,这是使用C++编写空值指针的方法之一(使用NULL时,必须包含一个定义NULL的头文件;如果你使用的编译器支持C++11,可使用nullptr)。由于使用Queue类创建的所有节点的next的初始值都被设置为空指针,因此这个类只需要该构造函数。
接下来,需要使用构造函数重新编写enqueue( ):
bool Queue::enqueue(const Item& item)
if (isfull())
return false;
Node* add = new Node(item);// create, initialize node
// on failure, new throws std::bad_alloc exception
...
这个例子在类声明中定义了构造函数。假设想在方法文件中定义构造函数,则定义必须指出Node类是在Queue类中定义的。这是通过使用两次作用域解析运算符来完成的:
Queue::Node::Node(const Item& i) : item(i), next(0) ...
嵌套类和访问权限:
➊作用域
如果嵌套类是在另一个类的私有部分声明的,则只有后者知道它。因此,Queue成员可以使用Node对象和指向Node对象的指针。对于从Queue派生而来的类,Node也是不可见的,因为派生类不能直接访问基类的私有部分。
如果嵌套类是在另一个类的保护部分声明的,则它对于后者来说是可见的,但是对于外部世界则是不可见的。然而,在这种情况中,派生类将知道嵌套类,并可以直接创建这种类型的对象。
如果嵌套类是在另一个类的公有部分声明的,则允许后者、后者的派生类以及外部世界使用它,因为它是公有的。然而,由于嵌套类的作用域为包含它的类,因此在外部世界使用它时,必须使用类限定符。例如,假设有下面的声明:
class Team
public:
class Coach ...;
...
;
现在假定有一个失业的教练,他不属于任何球队。要在Team类的外面创建Coach对象,可以这样做:
Team::Coach forhire;// create a Coach object outside the Team class
嵌套结构和枚举的作用域与此相同。其实,很多程序员都使用公有枚举来提供可供客户程序员使用的类常数。
嵌套类、结构和枚举的作用域特征,如下表:
➋访问控制
类可见后,起决定作用的将是访问控制。对嵌套类访问权的控制规则与对常规类相同。在Queue类声明中声明Node类并没有赋予Queue类任何对Node类的访问特权,也没有赋予Node类任何对Queue类的访问特权。因此,Queue类对象只能显示地访问Node对象的公有成员。由于这个原因,在Queue示例中,Node类的所有成员都被声明为公有的。这样有悖于应将数据成员声明为私有的这一惯例,但Node类是Queue类内部实现的一项特性,对外部世界是不可见的。这是因为Node类是在Queue类的私有部分声明的。所以,虽然Queue的方法可直接访问Node的成员,但使用Queue类的客户不能这样做。
总之,类声明的位置决定了类的作用域或可见性。类可见后,访问控制规则(公有、保护、私有、友元)将决定程序对嵌套类成员的访问权限。
未完待续。。。
以上是关于-cpp友元异常和其他的主要内容,如果未能解决你的问题,请参考以下文章