基类与派生类

类型相互转换

派生类的对象可以赋给基类,反之不行

基类的指针可以指向派生类,反之不行

基类的引用可以初始化为派生类的对象,反之不行

派生类指针必须强制转换为基类指针后才可以指向基类

基类指针转换为派生类指针容易导致崩溃性错误

虚基类的引用或派生不能转换为派生类

class father{};
class son:public father{}; 
int main()
{
    father f; 
    son s;
    f = s;//正确 
    s = f;//错误 

    father *pf = new son;//正确 
    son *ps = new father;//错误 

    father &rf = s;//正确
    son &rs = f;//错误 
    return 0; 
}
继承方式对基类成员被继承后的访问属性的影响
公有成员 保护成员 私有成员
公有继承 公有 保护 不可访问
保护继承 保护 保护 不可访问
私有继承 私有 私有 不可访问
基类成员的访问属性对被访问权限的影响
公有成员 保护成员 私有成员
基类成员函数 可以访问 可以访问 可以访问
基类对象 可以访问 不可访问 不可访问
派生类成员函数 可以访问 可以访问 不可访问
派生类对象 可以访问 不可访问 不可访问

当所有成员都变成不可访问时,再往下派生就没有意义了

派生类的构造函数与析构函数调用顺序

单一继承

构造派生类对象时,先执行基类的构造函数,再执行派生类的构造函数,析构反之

class father
{
public: 
    father(){cout<<"father construct"<<endl;}
    ~father(){cout<<"father delete"<<endl;}
};
class son : public father
{
public:
    son(){cout<<"son construct"<<endl;}
    ~son(){cout<<"son delete"<<endl;}
};
int main()
{
    son s;
    return 0;
}

输出:

father construct

son construct

son delete

father delete

多重继承

如果是多重继承,仍然是先执行基类的构造函数,再执行派生类的构造函数

调用基类构造函数的顺序与定义基类的顺序相同,

析构反之

class father
{
public:
    father(){cout<<"father construct"<<endl;}
    ~father(){cout<<"father delete"<<endl;}
};
class mother
{
public:
    mother(){cout<<"mother construct"<<endl;}
    ~mother(){cout<<"mother delete"<<endl;}
};
class son : public father, public mother
{
public:
    son(){cout<<"son construct"<<endl;}
    ~son(){cout<<"son delete"<<endl;}
};
int main()
{
    son s;
    return 0;
}

输出:

father construct

mother construct

son construct

son delete

mother delete

father delete

构造函数的执行效率

利用基类的构造函数构造派生类,执行效率更高

class father
{
    int x;
public:
    father(int a):x(a){cout<<"father construct:"<<x<<endl;}
};
class son : public father
{
    int y;
public:
    son(int a, int b):father(a), y(b){cout<<"son construct:"<<y<<endl;}
};
int main()
{
    son s(1, 2);
    return 0;
}

输出:

father construct:1

son construct:2

多重继承

多重继续的二义性

多重继承的二义性是指派生类的多个基类具有相同名字的成员,编译器无法确定派生类所要使用的是哪一个。

造成这种情况出现通常的原因是:派生类的两个基类都继承于同一个祖先,那么祖先所拥有的成员这两个基类都有,这样就出现了上述的情况。如图 假如A有Test(),则B和C都有Test(),于是D产生了二义性

编译器的策略

编译器通常都是从离自己最近的目录树向上搜索的

假如把派生类的继承关系看作是一棵树,派生类是树的根结点,派生类的基类是根结点的孩子结点,依此类推

那么当派生类遇到多重继承的二义性问题时,编译器会从根结点向下搜索。遇到了离根结点最近的结点的成员作为派生的默认继承的成员。如果有多个结点都实现了这一成员且离根结点距离相同,编译器就会报错,这时需要通过显式的方式来指定。

class A
{
public:
    void Test(){cout<<"A"<<endl;}  
};
class B : public A
{
public:
    void Test(){cout<<"B"<<endl;}
};
class C : public A
{
public:
    void Test(){cout<<"C"<<endl;}
};
class D : public B, public C
{
};
int main()
{
    D d;
    d.Test();      //错误
    d.A::Test();   //正确,输出:A
    d.B::Test();   //正确,输出:B
    d.C::Test();   //正确,输出:C
    return 0;
}
访问基类的成员

派生类的成员覆盖了基类的同名成员,并不代表基类的成员消失了,只是不能直接访问

class A  
{  
public:  
    void Test(){cout<<"A"<<endl;}  
};  
class B  
{  
public:  
    void Test(){cout<<"B"<<endl;}  
};  
class C : public A, public B  
{  
    void Test(){cout<<"C"<<endl;}  
};  
int main()  
{  
    C c;             
    c.Test();      //正确,输出:C  
    c.A::Test();   //正确,输出:A  
    c.B::Test();   //正确,输出:B  
    return 0;  
}
多重继承与单一继承在派生类访问基类方式上的区别
  • 对于单一继承,子类能否访问父类的父类,只与继承的方式有关

  • 对于多重继承,子类不能直接访问父类的父类。

用virtual来避免二义性。
class B : virtual public A.

继承与包含

一个类的成员变量列表中包含另一个类的对象,叫做包含(包容)。

包含和继承都能使一个类使用另一个类的成员,但它们的适用场合和目的不同

  • 包含:

1)使程序看上去更清晰易懂

2)不存在继承带来的问题

3)可以包括另一个类的多个对象

  • 私有继承:

1)可以访问基类的保护成员

2)可以重定义虚函数,实现多态

results matching ""

    No results matching ""