C++友元、异常和其他

友元

可以将类作为友元。这种情况下,友元类的所有方法都可以访问原始类的私有成员和保护成员。

现在有一个tv类和一个remote类(遥控器),我们就可以把remote作为tv的友元。

friend class Remote;

友元声明可以位于公有、私有或保护部分,位置无关紧要。由于remote类提到了tv类,所以编译器必须了解tv类后才能处理remote类。为此,我们需要首先定义tv类。

所有的remote方法都将一个tv对象引用作为引用,因此可以对任何一个tv对象进行操作。

class Tv{
  public:
    friend class Remote;
    ...
  private:
    int channel;
    ...
}

class Remote{
  public:
    void set_chan(Tv & t,int c){t.channel=c;}//这个方法可以调用tv的私有成员
    ...
}

当然也可以不把remote类设置成tv的友元,而是仅仅把setchan这个方法设置成tv的友元,但是这需要小心排列各种声明和定义的顺序。

class Tv{
  friend void Remote::set_chan(Tv &t, int c);
  ...
}

这意味着应将remote放到tv之前,但是remote中的方法也依靠tv来完成,因此tv应该在remote之前,这就导致了循环。此时就应当使用前向声明。

class Tv;
class Remote{...};
class Tv{...};

如果是这样:

class Remote;
class Tv{...};
class Remote{...};

这不可行。因为编译器在tv类中看到remote的一个方法被声明为tv类的友元之前,应该先看到remote类的声明。

如果使用了上面的形式,那么remote类中只能包含声明,而不能包含定义,因为编译器那时还不知道tv中的声明。

class Tv;
class Remote{...};//声明
class Tv{...};
//remote中方法的定义

还可以让tv和remote类互为友元。只不过也有一定的顺序问题。

class Tv{
  friend class Remote;
  public:
    void buzz(Remote &r);//不能有定义,只能声明
    ...
};

class Remote{
  friend class Tv;
  public:
    void volup(Tv & t){t.vol++;}
    ...
};

inline void Tv::buzz(Remote &r){
  ...
}

有时候一个函数能成为两个类的友元。

class Analyzer;
class Probe{
  friend void sync(Analyzer &a,const Probe &p);
  friend void sync(Probe &p,const Analyzer &a);
  ...
};
class Analyzer{
  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){
...
}

嵌套类

在另一个类中声明的类被称为嵌套类,它通过提供新的类型类作用域来避免名称混乱。包含类的成员函数可以创建和使用被嵌套类的对象;而仅当声明位于公有部分,才能在包含类的外面使用嵌套类,而且必须使用作用域解析运算符。

对类进行嵌套与包含并不同。包含意味着将类对象作为另一个类的成员,而对类进行嵌套不创建类成员,而是定义了一种类型,该类型仅在包含嵌套类声明的类中有效。

template <class T>
class Queue{
  private:
    class Node{
      public:
        T item;
        Node * next;
        Node(const T & i):item(i),next(0){}
      };
    Node * front;
    Node * rear;
    int items;
    const int qsize;
    Queue(const Queue & q):qsize(0){}
    Queue & operator=(const Queue &q){return *this;}
  public:
    Queue(int qs=10);
    ~Queue();
    bool enqueue(const T &item);
    bool deuqe(T &item);
};

template<class T>
Queue<T>::Queue(int qs):qsize(qs){
  front=rear=0;  //设置为空指针
  items=0;
}

template<class T>
Queue<T>::~Queue(){
  Node * temp;
  while (front!=0){
    temp=front;
    front=front->next;
    delete front;
  }
}

template<class T>
bool Queue<T>::enqueue(const T &item){
  Node * add = new Node(item);
  items++;
  if (front=0) front=add;else rear->next=add;
  rear=add;
  return true;
}

template<class T>
bool Queue<T>::dequeue(T & item){
  item=front->item;
  items--;
  Node * temp=front;
  front=front->next;
  delete temp;
  if (items==0) rear=0;
  return true;
}

这是一个在模板类中使用嵌套类的例子。

嵌套类声明的位置、以及嵌套类自身的访问限制符控制对类成员的访问。

声明位置 包含它的类是否可以使用 从包含它的类派生而来的类是否可以使用 在外部是否可以使用
私有部分
保护部分
公有部分 是,通过类限定符

当嵌套类可见之后,访问控制规则仍然和平常一致。(即被嵌套类可以访问public部分,不能访问private部分等等)

异常

当程序想要直接停止时,可以使用std::abort()方法。此时不管在哪个函数中,都将直接结束程序。

当程序运行出现错误时,我们应该使用异常机制来处理而不是使用abort()。

C++的try-catch机制大致和Java相同,却也有所不同:

try{
  cin>>x>>y;
  z=hmean(x,y);
}catch(const char *s){
  cout<<s<<endl;
}
...

double hmean(double a,double b){
  if (b==0)  throw "divided by zero";
  return a/b;
}

当try中的某句语句出现异常就会导致catch被引发。抛出的异常类型需要与捕获的异常类型一致。一个try后面可以跟多个catch。

异常类型可以为字符串,也可以为其他C++类型。一般传递对象,因为对象可以携带信息。

如果抛出异常之后找不到合适的catch,程序最终会调用abort()。

如果函数出现异常而终止,程序也将释放栈中的内存,但不会在释放栈的第一个返回地址后停止,而是继续释放栈,直到找到一个位于try块中的返回地址。随后,控制权将跳转到块尾的异常处理程序,这个过程被称为栈解退。栈解退时仍然会调用析构函数。

引发异常时,编译器会创建一个临时拷贝,即使catch块中指定的是一个引用。

当返回的类有继承关系时,应该合理安排catch的顺序。

使用catch(…)可以捕获所有会引发的异常。

C++编译器将异常合并到语言中。exception头文件定义了exception类,C++可以把它用作其他异常类的基类。代码可以引发exception异常,也可以将exception作为基类。有一个名为what()的虚拟成员函数,它返回一个字符串。由于这是一个虚方法,因此可以在派生类中重新定义。

#include<exception>
class bad_hmean:public std::exception{
  public:
    const char * what(){return "bad arguments";}
  ...
};
try{
  ...
}catch(std::exception &e){
 ...
}

stdexexcept异常类定义了logic_error和runtime_error类,它们都是以公有方式从exception派生而来的。

logic_error包含以下几个异常:

domain_error;  //参数不在定义域中
invalid_argument; //参数出乎意料
length_error; //长度过长
out_of_bounds. //索引错误

runtime_error包含以下几个异常:

range_error; //计算结果溢出
overflow_error; //上溢
underflow_error; //下溢

logic_error表明存在可以通过编程修复的问题,而runtime_error表明存在无法避免的问题。

对于new导致的内存分配问题,new会引发bad_alloc异常,包含在头文件new中,从exception类公有派生而来。

也可以使用下面的用法,使得new不抛出异常,而是返回一个空指针:

Big * pb;
pb=new (std::nothrow) Big[10000];
if (pb==0){
  cout<<"too big";
}

还有一个注意点:

void test(int n){
  double *ar=new double[n];
  ...
  if (oh_no)
    throw exception();
  ...
  delete [] ar;
  return;
}

抛出异常会导致ar没有被释放,应该这样设计:

void test(int n){
  double *ar=new double[n];
  ...
  try{
    if (oh_no)
      throw exception();
  }catch(exception & ex){
    delete [] ar;
    throw;
  }
  ...
  delete [] ar;
  return;
}

RTTI

RTTI是运行阶段类型识别的简称。通过RTTI,我们可以得知引用或指针具体所指的类型。

C++有3个支持RTTI的元素。

1.dynamic_cast运算符

dynamic_cast能够回答“类型转换是否安全”这个问题。

Superb * pm=dynamic_cast<Superb *>(pg);

如果pg的类型可以被安全地转换为Superb*,则返回对象地址,否则返回空指针。

dynamic_cast<Type *>(pt);

如果指向的对象(*pt)的类型为Type或者Type直接或间接派生而来的类型,则pt则会转换为Type类型的指针,否则返回空指针。

也可以将其运用于引用,只不过是当引用不正确时,会抛出bad_cast异常,这个异常在头文件typeinfo中定义。

#include<typeinfo>
...
try{
  Superb & rs=dynamic_cast<Superb &>(reg);
  ...
}catch(bad_cast &){
 ...
};

2.typeid运算符和type_info类

typeid运算符使得能够确定两个对象是否为同种类型,它可以接受两种参数:(1)类名(2)结果为对象的表达式

typeid运算符返回一个type_info对象的引用,type_info是在头文件typeinfo中定义的一个类,它重载了==和!=运算符,以便用来比较。

typeid(Superb)==typeid(*pg)

如果pg是一个空指针,程序将进引发bad_typeid异常,该异常也是在typeinfo中声明的。

typeid(*pg).name()

这将返回一个字符串,通常是类的名称。

如果发现在扩展的ifelse语句系列中使用了typeid,应考虑是否应该使用虚函数和dynamic_cast。

类型转换运算符

C++添加了四个类型转换运算符,以便更严格地限制类型转换。

dynamic_cast;
const_cast;
static_cast;
reinterpret_cast.

const_cast只用于改变值为const或volatile,不能用于改变类型的其他方面,否则会出错。

const int* const_p = &constant;
int* modifier = const_cast<int*>(const_p);
*modifier = 7;//但是const_p指向的值仍然不会改变

这样就把const标签去除了。

static_cast可以用于显式类型转换。

Father bar;
Son *pl=static_cast<Son *>(& bar);//ok
int i;
float f = 166.71;
i = static_cast<int>(f);//ok

reinterpret_cast主要是将数据从一种类型的转换为另一种类型。所谓“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式的重新解释。

int i;
char *p = "This is an example.";
i = reinterpret_cast<int>(p);//i和p完全相同

猜你喜欢

转载自blog.csdn.net/dxy18861848756/article/details/113871493