C++基础3:继承

1. 语法

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

  • 语法

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

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

实例:图形继承

No. 英文 中文
1 Triangle 三角形
2 Equilateral Triangle 等边三角形
3 Isosceles Triangle 等腰三角形
4 Right-angled Triangle 直角三角形

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 <<endl;
        cout<< protected_data <<endl;
        cout<< private_data <<endl;
    }
};

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

#include <iostream>

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 不可见
public继承
protected继承
private继承

小结

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

3. 继承关系的构造顺序

  • 派生类的构造函数与析构函数的调用顺序
  • 派生类的构造函数调用顺序
    子对象构造、成员变量构造、父对象构造的顺序
  • 派生类的析构函数调用顺序
    子对象析构、成员变量析构、父对象析构的顺序
#include <iostream>
using std::cout;
using std::endl;
class Member{
public:
    Member(){
        cout << "Member Init" <<endl;
    }
    ~Member(){
        cout << "Member Destroy" <<endl;
    }
};
class Parent{
public:
    Parent(){
        cout << "Parent Init" <<endl;
    }
    ~Parent(){
        cout << "Parent Destory" <<endl;
    }
};
class Son : public Parent{
public:
    Son(){
        cout << "Son Init" <<endl;
    }
    ~Son(){
        cout << "Son Destroy" <<endl;
    }
private:
    Member m;
};
int main(){
    Son son;
}

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

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

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

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()"<<endl;
  }
};

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

函数同名的情况总结

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

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

三种情况

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

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

问题:

  1. 子类特有的成员变量能否被父类访问?
  2. 此时父类对象(base)的大小与子类对象(derive)大小是否一致?
  3. 子类对象是否可以初始化父类对象?
    设计一段代码验证
  1. 派生类的对象可以初始化基类的引用。
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 <iostream>
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 <iostream>

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.

对象构造顺序总结

对象构造顺序
  • 基本原则
  1. 先父后子
  2. 从左到右
  3. 先虚后实
  4. 从上到下
  5. 由内及外
  • 示例
#include <iostream>

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 227,428评论 6 531
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 98,024评论 3 413
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 175,285评论 0 373
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 62,548评论 1 307
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 71,328评论 6 404
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 54,878评论 1 321
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 42,971评论 3 439
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 42,098评论 0 286
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 48,616评论 1 331
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 40,554评论 3 354
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 42,725评论 1 369
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 38,243评论 5 355
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 43,971评论 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 34,361评论 0 25
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 35,613评论 1 280
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 51,339评论 3 390
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 47,695评论 2 370

推荐阅读更多精彩内容

  • C++ 面向对象编程 博客园地址:http://www.cnblogs.com/xiongxuanwen/p/42...
    先之阅读 687评论 0 1
  • 1. 结构体和共同体的区别。 定义: 结构体struct:把不同类型的数据组合成一个整体,自定义类型。共同体uni...
    breakfy阅读 2,135评论 0 22
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,707评论 18 399
  • C++文件 例:从文件income. in中读入收入直到文件结束,并将收入和税金输出到文件tax. out。 检查...
    SeanC52111阅读 2,810评论 0 3
  • C++ 基础 概念及工方式 保持已有类的特性而构造新类的过程称为继承。在已有类的基础上新增自己的特性而产生新类的过...
    I踏雪寻梅阅读 305评论 0 2