cplusplusprimer_note7

友元,异常和其他

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

例子:遥控器和电视机

class TV{
    public:
    friend class Remote;
}

一个遥控器可以控制多个电视机。类友元用于表示一些关系,否则必须要把tv类的私有部分设置为共有的,或者创建一个笨拙的大型类来包含电视机和遥控器。

友元成员函数:只允许某个类的成员函数是另一个类的友元

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

这时出现了循环依赖,Remote定义必须在Tv之前,但是Remote的方法又会提到Tv对象,因此Tv类应该在前面?

避开循环的方法:前向声明

class Tv;//forward declaration
class Remote {
    void onoff(Tv & t) {t.onoff();}//若这样写,此时还不知道Tv类有什么方法呢
    void onoff (Tv & t);
};
class Tv {};

void Remote::onoff(Tv & t) {

}

//不能这样,因为编译器在Tv中看到Remote的一个方法被声明为友元方法之前,必须先看到Remote类的声明和set_chan()方法的声明
class Remote;
class Tv{};
class Remote {};

内联函数的链接性为内部,函数定义必须包含在使用函数的文件中,比如在头文件中

成为彼此的友元

函数需要访问两个类的私有数据:可以是一个类的成员函数和另一个类的友元,但是有时候成为两个类的友元更合理。

class A;
class B{
    friend void sync(A & a, const B & b);
    friend void sync(B & b, const A & a);
}
class A {
    friend void sync(A & a, const B & b);
    friend void sync(B & b, const A & a);
}

inline void sync(A & a, const B & b)
{
}
inline void sync(B & b, const A & a)
{

}

嵌套类

仅当嵌套类为公有的外部才可访问,还需要加上作用域解析运算符。

对类进行嵌套不创建类成员,而是定义了一种类型。

在父类中声明嵌套类没有赋予父类任何对嵌套类的访问特权,也没有赋予嵌套类任何对父类的访问特权。因此,父类只能显式访问嵌套类对象的公有成员。

异常规范:废弃了

特殊的异常规范:关键字noexcept指出函数不会引发异常

double marm() noexcept 好处:帮助编译器优化代码

运算符noexcept() 判断操作符是否会引发异常

栈解退: unwinding the stack

函数返回仅处理该函数放在栈中的对象,throw语句则处理try块和throw块之间的整个函数调用序列放在栈中的对象。自动调用了中间函数的析构函数

函数fun中的返回语句将控制权交给调用fun的函数,throw语句将控制权向上返回到第一个包含能够捕获异常的函数

引发异常时编译器总是创建一个临时拷贝,即使catch块中指定了引用类型。因为函数执行完后那个异常对象将不复存在。那为什么还要用引用呢?因为多态,基类引用可以指向派生类对象。

try{

}catch(...) {//捕获任何异常

}

如果捕获异常时不使用引用,则也将捕获所有派生类对象但是派生特性将被剥去。

exception类,what()

#include <exception>
class bad_hman : public std::exception
{
    public :
    const char * what() {return "xxx"}
}

try{

}catch(std::exception & e) {
    cout << e.what() << endl;
}

头文件stdexcept定义了其他几个具体的异常类,logic_error,runtime_error类继承自exception

  • logic_error 典型逻辑错误,通过合理编程可以避免
    • domain_error
    • invalid_argument
    • length_error
    • out_of_bounds
  • runtime_error 可能在运行期间发生但难以预计和防范的错误
    • range_error
    • overflow_error
    • underflow_error

exit(EXIT_FAILURE)

new失败时抛出bad_alloc异常,可以选择在失败时返回空指针

int * pi = new (std::nothrow) int;

例子p635

未捕获异常:首先调用函数terminate()。默认terminate调用abort()。可以修改其行为:set_terminate()

typedef void (*terminate_handler)();
terminate_handler set_terminate(terminate_handler f) noexcept;

set_terminate:接受的参数类型为terminate_handler。

#include<exception>
using namespace std;
void myQuit()
{
    cout << "....";
    exit(5);
}
set_terminate(myQuit);

如果程序引发了异常规范里面没有的异常?程序将调用unexcepted(),其将调用terminate(), 默认调用abort()

set_unexpected()

异常降低了程序运行速度,异常规范不适用于模板,异常和动态内存分配并非总能协同工作。

void test(int n)
{
    double * ar = new double[n];

    if (oh_no)
        throow exception();

    delete [] ar;//如果引发了异常,此语句不得执行,内存泄漏
    return ;
}
//解决方法:先捕获该异常,做处理后重新引发;
//zui'hao
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

runtime type identification运行阶段类型识别

3个支持rtti的元素

  • dynamic_cast如果可能的话,使用一个指向基类的指针来生成一个指向派生类的指针 否则返回空指针

    不能回答:指针指向的是哪一类对象

    能回答:是否可以安全地将对象的地址赋给特定类型的指针

    class A {};
    class B : public A {};
    class C: public B {};
    
    A * pa = new A;
    A * pb = new B;
    A * pc = new C;
    
    C * p1 = (C *) pc; //safe
    C * p2 = (C *) pa; //not safe
    B * p3 = (C *) pc; //safe
    
    B * pc = dynamic_castpa;//pa的类型能否被安全转换成B * ?可以的话返回对象地址,否则返回空指针。

    也可用于引用,当失败时,抛出bad_cast的异常

    Superb & rs = dynamic_cast<Superb &>(rg)

  • typeid:返回一个指出对象类型的值

    用来确定两个对象是否为同种类型。类似sizeof,可以接受参数:

    • 类名
    • 结果为对象的表达式

    返回一个对type_info对象的引用。type_info重载了==和!=。比如:

    typeid(Mag) == typeid(*pg)

    如果pg是一个空指针,引发bad_typeid异常。

    包含一个name成员函数。返回表示类型的字符串

    如果在扩展的ifelse语句中使用了typeid,则应该考虑虚函数和dynamic_cast

  • type_info结构储存了有关特定类型的信息

注意RTTI只能用于虚函数的类,不然多态的可能都不存在了,怎么在运行阶段识别类型呢?

类型转换运算符

c++设计者认为c的类型转换太过松散

严格限制类型转换:

  • dynamic_cast

  • const_cast:需要这样一个值,在大多情况下是常量,有时候又可以修改,这种情况下可以声明为const,需要修改时再用const_cast改。

    这个功能也可以通过通用类型转换实现,但是通用转换可能同时改变了类型,不严格

    High bar;
    const High * pbar = & bar;
    ...
    High * pb = (High *) (pbar); //valid
    Low * pl = (Low *)(pbar);//valid !同时也改了类型

    只可以添加 、删除const或voatile特征。

  • static_cast

    static_cast <type-name> (expression)

    仅当type_name可被隐式转换成expression所属的类型expression可被隐式转换为type_name所属的类型时,上述转换合法。适用于进行低风险转换,比如Low到High这种继承关系的转换,可以用于数值转换,double到int等,枚举值到整型不用类型转换,因此可以用static_cast将整型转换成枚举值0。不能把Low转换成一个无关类的指针,不能用于在不同类型的指针之间互相转换,也不能用于整型和指针之间的互相转换,当然也不能用于不同类型的引用之间的转换。因为这些属于风险比较高的转换。

  • reinterpret_cast:适用于天生危险的类型转换。

    struct dat {short a; short b;};
    long value = 0xA224B118;
    dat * pd = reinterpret_cast (&value);
    cout << hex << pd->a;//display first 2 bytes of value

    依赖于底层不可移植

    支持将指针类型转换成足以储存指针表示的整型,不支持将指针转换为更小的整型或浮点。不能将函数指针转换成数据指针反之亦然。

    char ch = char (&d); //c中可以,c++不行,因为char类型太小不能存储指针

()

发表评论