C++基础3:继承

1. 语法

  • 原则:is-a
    父类/子类
    基类/派生类

  • 语法

class 派生类 : [访问限定符] 基类 {
  成员
}

如果不写继承访问限定符,默认是private

实例:图形继承

2. 成员的访问权限

public protected private
类成员函数
友元函数
子类函数 ×
类对象 × ×

子类继承了父类所有的成元变量和成员函数。与访问限定符无关。访问限定符只是限制了访问。
子类访问父类成员变量,把父类成员变量访问限制符,改为protected

  • 继承访问权限变化

分为子类内部和子类对象两种访问方式。

子类内部访问public继承的父类成员变量

class Base {
public:
    int public_data;
protected:
    int protected_data;
private:
    int private_data;
};
class Derive:public Base {
public:
    void test() {
        cout<< public_data <

子类内部访问public继承的父类成员函数

#include 

using std::cout;
using std::endl;

class Base {
public:
    void public_func(){};
protected:
    void protected_func(){};
private:
    void private_func(){};
};
class Derive:public Base {
public:
    void test() {
        public_func();
        protected_func();
        private_func();
    }
};
  • 子类内部访问父类成员
public protected private
public 继承 ×
protected 继承 ×
private 继承 ×

子类内部访问父类成员,只能访问publicprotected成员。

  • 子类对象访问父类成员
public protected private
public 继承 × ×
protected 继承 × × ×
private 继承 × × ×

子类只有public继承父类的时候,才能访问父类的public成员,其他都不能访问。
通常子类使用public继承父类。

子类对象访问父类成员访问限定符的变化

继承方式\父类成员 public protected private
public 继承 public protected 不可见
protected 继承 protected protected 不可见
private 继承 private private 不可见
C++基础3:继承_第1张图片
public继承
C++基础3:继承_第2张图片
protected继承
C++基础3:继承_第3张图片
private继承

小结

  • public无论类内部还是类对象都可以访问。
  • protected类对象不可访问,类内部与继承类的内部可以访问。
  • private只有类内部可以访问。

3. 继承关系的构造顺序

  • 派生类的构造函数与析构函数的调用顺序
  • 派生类的构造函数调用顺序
    子对象构造、成员变量构造、父对象构造的顺序
  • 派生类的析构函数调用顺序
    子对象析构、成员变量析构、父对象析构的顺序
#include 
using std::cout;
using std::endl;
class Member{
public:
    Member(){
        cout << "Member Init" <

没有默认构造函数的基类在派生类的初始化,必须在初始化列表中初始化。

  • 同名隐藏规则
    概念:子类的成员函数与基类成员函数同名,子类的函数将会隐藏基类的所有同名函数。
#include 
using std::cout;
using std::endl;
class Base { 
public:
  void show(int data){
    cout<<"Base::show("<

修改成指针方式,试一下。

int main() {
      Derive* pderive = new Derive;
      pderive->show();
      pderive->show(123); 
      delete pderive;
}

解决方法:

  1. Derived类对象调用被隐藏的父类函数时,在函数前面加上父类限定符。例如:
  • derive.show(123)derive.Base::show(123)
  • pderive->show(123)pderive->Base::show(123)
  1. Derived类中的名称会隐藏Base类中同名的名称,在public继承中我们可以通过引入using声明。
class Derive:public Base {
public:
  using Base::show;
  void show(){
    cout<<"Derive::show()"<

隐藏背后原因是为防止在程序库或应用框架内建立新的derived class时从疏远的base classes继承重载函数。
--- Effetive C++

函数同名的情况总结

名称 英语
重载 overload
重写(覆盖) override
隐藏 hide

  • 赋值兼容规则
    概念:在任何需要基类对象的地方都可以使用公有的派生类对象来代替。反之,不可。

三种情况

  1. 派生类的对象可以赋值给基类对象。
Base base;
Derive derive;
base = derive;

对象切割(Object Slicing):在赋值时舍弃派生类自己的成员,只进行基类数据成员的赋值。

问题:

  1. 子类特有的成员变量能否被父类访问?

  2. 此时父类对象(base)的大小与子类对象(derive)大小是否一致?
    设计一段代码验证

  3. 派生类的对象可以初始化基类的引用。

Derive derive;
Base& base = derive;

验证上述问题。

  1. 派生类对象的地址可以赋给指向基类的指针。
    指向基类对象的指针变量也可以指向派生类对象。
Derive derive;
Base* base = &derive;

验证上述问题。

  • 向上转换:派生类对象赋值给基类
  • 向下转换:基类对象赋值给派生类

  • 派生类的对象可以赋值给基类的对象

int main() {
      Base base;
      Derive derive;
      base = derive;
      base.show(123);
      //base.show();
      //base.Derive::show();
}
  • 派生类对象的地址赋给基类的指针变量
int main() {
      Base* pbase = new Derive;
      pbase->show(123);
      //pbase->show();
      //pbase->Derive::show();
}

指针访问派生类中由基类继承来的对象,不能访问派生类中的新成员

  • 派生类对象可以初始化基类的引用
int main() {
      Derive derive;
      Base& base = derive;
      base.show(123);
      //base.show();
      //base.Derive::show();
}

引用访问派生类中由基类继承来的对象,不能访问派生类中的新成员

  • 多重继承
    一个类可以同时继承多个父类的行为和特征功能。
    逗号分割的基类列表
class 类名 : public 基类1,public 基类2{
};

案例:等腰直角三角形继承等腰三角形与直角三角形

多重继承基类构造顺序?

  • 钻石继承/菱形继承

  • 概念:两个子类继承同一个父类,而又有子类同时继承这两个子类。

  • 问题:下面BC继承与AD同时继承BC,那么同时D的实例调用A的成员函数会有什么情况?

#include 
using std::cout;
using std::endl;
class A{
public:
      void test();
private:
      int id;
};
void A::test(){
    cout << __func__ << endl;
}
class B : public A {};
class C : public A {};
class D : public B,public C{};
int main(){
    cout << "A size:" << sizeof(A) << endl;
    cout << "B size:" << sizeof(B) << endl;
    cout << "C size:" << sizeof(C) << endl;
    cout << "D size:" << sizeof(D) << endl;
    D d;
    d.test();
}
  • 原因:BC都继承了A的成员函数test()D同时继承了BC,调用test()无法确定是B还是C的。
  • 解决:给调用的成员函数前加上访问限定符,明确指定调用成员函数所属的类d.B::test();或者d.C::test();
  • 扩展:尝试d.A::test(),会有什么结果?

注意:DsizeofBC的和,也就是两个A
问题:不同途径继承来的同名的成员在内存中有不同的拷贝,造成数据不一致。

  • 解决:虚继承&虚基类
  • 虚继承&虚基类
    虚继承:在继承定义中包含了virtual关键字的继承关系。
    虚基类:在虚继承体系中的通过virtual继承而来的基类。
class 类名:public virtual 基类{
}

虚基类是一个相对概念,在虚继承关系中,父类相对与子类是虚基类。

虚基类与普通基类的构造顺序?

扩展:UML

派生类的编码

  1. 构造与析构
  2. 继承基类成员
  3. 重载基类成员函数
  4. 增加新成员

测验

  • 写出下列程序的执行结果,并分析结果。(如果程序编译有错,请分析原因,并写出解决方法)
#include 

using namespace std;

class Base{
    public:
    Base(){
        cout << "Base constuct" << endl;
    }
    ~Base(){
        cout << "Base destuct" << endl;
    }
};
class Member{
public:
    Member(){
        cout << "Member constuct" << endl;
    }
    ~Member(){
        cout << "Member destuct" << endl;
    }
};
class Derive:public Base{
public:
    Derive(){
        cout << "Derive constuct" << endl;
    }
    ~Derive(){
        cout << "Derive destuct" << endl;
    }
private:
    Member m;
};
int main(){
    Derive d;
}

关于多重继承

  1. 什么是多重继承?同时继承多个父类。
  2. 多重继承有什么危害?菱形继承/钻石继承。
  3. 什么是菱形继承/钻石继承?多重继承的两个或多个父类具有相同的祖先类。
  4. 菱形继承/钻石继承有什么危害?因为多重继承的两个或多个父类具有相同的祖先类。所以会有完全相同的属性和方法。因此当前多重继承类有两份相同的属性和方法。使用时会出现冲突。
  5. 如何解决菱形继承/钻石继承导致的冲突?使用虚继承。
  6. 什么是虚继承?父类在继承具有相同的祖先类时,加上virtual.

对象构造顺序总结

C++基础3:继承_第4张图片
对象构造顺序
  • 基本原则
  1. 先父后子
  2. 从左到右
  3. 先虚后实
  4. 从上到下
  5. 由内及外
  • 示例
#include 

using namespace std;

#define SIMPLE_CLASS(name)\
class name{\
public:\
    name(){ cout << #name << " Constructor" << endl;}\
    ~name(){ cout << #name << " Destructor" << endl;}\
};

SIMPLE_CLASS(Base1)
SIMPLE_CLASS(Base2)
SIMPLE_CLASS(Base3)

SIMPLE_CLASS(VBase1)
SIMPLE_CLASS(VBase2)
SIMPLE_CLASS(VBase3)

SIMPLE_CLASS(Member1)
SIMPLE_CLASS(Member2)
SIMPLE_CLASS(Member3)

#undef SIMPLE_CLASS

class Test : public Base1,
             public Base2,
             public Base3,
             public virtual VBase1,
             public virtual VBase2,
             public virtual VBase3 {
public:
  Test() { cout << "Test Constructor" << endl; }
  ~Test() { cout << "Test Destructor" << endl; }
private:
  Member1 m1;
  Member2 m2;
  Member3 m3;
};

int main() {
  Test t;
}
  • 执行结果
VBase1 Constructor
VBase2 Constructor
VBase3 Constructor
Base1 Constructor
Base2 Constructor
Base3 Constructor
Member1 Constructor
Member2 Constructor
Member3 Constructor
Test Constructor
Test Destructor
Member3 Destructor
Member2 Destructor
Member1 Destructor
Base3 Destructor
Base2 Destructor
Base1 Destructor
VBase3 Destructor
VBase2 Destructor
VBase1 Destructor

你可能感兴趣的:(C++基础3:继承)