C++学习(5)继承和派生

1.继承和派生

  • 继承:在定义一个新的类B时,如果该类与某
    个已有的类A相似(指的是B拥有A的全部特点),那么就可以把A作为一个基类,而把B作为基类的一个派生类(也称子类)。
  • 派生类:
    通过对基类进行修改和扩充得到的,在派生类中可以扩充新的成员变量和成员函数。
    派生类一经定义后,可以独立使用,不依赖于基类。
    派生类拥有基类的全部成员函数和成员变
    量,不论是private、 protected、 public 。
    在派生类的各个成员函数中,不能访问基类中的private成员。

派生类的写法:
class 派生类名: public 基类名
{
};

class CStudent {
private:
    string sName;
    int nAge;
public:
    bool IsThreeGood() { };
    void SetName(const string & name)
    {
        sName = name;
    }
    //......
};
class CUndergraduateStudent : public CStudent {
private:
    int nDepartment;
public:
    bool IsThreeGood() { ...... }; //覆盖
    bool CanBaoYan() { .... };
}; // 派生类的写法是:类名: public 基类名

2.派生类对象的内存空间

派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。 在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。


内存空间.png

3.继承关系和复合关系

  • 类之间的两种关系
    • 继承:“是”关系。
    – 基类 A, B是基类A的派生类。
    – 逻辑上要求:“一个B对象也是一个A对象”。
    • 复合:“有”关系。
    – 类C中“有” 成员变量k, k是类D的对象,则C和D是复合
    关系
    – 一般逻辑上要求:“D对象是C对象的固有属性或组成部
    分”。

4.派生类覆盖基类成员

  • 覆盖
    派生类可以定义一个和基类成员同名的成员,这叫覆盖。在派生类中访问这类成员时,缺省的情况是访问派生类中定义的成员。要在派生类中访问由基类定义的同名成员时,要使用作用域符号::。

5.类的保护成员

• 基类的private成员:可以被下列函数访问
– 基类的成员函数
– 基类的友元函数


• 基类的public成员:可以被下列函数访问
– 基类的成员函数
– 基类的友元函数
– 派生类的成员函数
– 派生类的友元函数
– 其他的函数


• 基类的protected成员:可以被下列函数访问
– 基类的成员函数
– 基类的友元函数
– 派生类的成员函数可以访问当前对象的基类的保护成员


6.派生类的构造函数

• 在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数。


• 调用基类构造函数的两种方式
– 显式方式:在派生类的构造函数中,为基类的构造函数提供参数.
derived::derived(arg_derivedlist):base(arg_base-list)
– 隐式方式:在派生类的构造函数中,省略基类构造函数时,派生类的构造函数则自动调用基类的默认构造函数.


• 派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数。
包含成员对象的派生类的构造函数写法

class Bug {
private:
    int nLegs; int nColor;
public:
    int nType;
    Bug(int legs, int color);
    void PrintBug() { };
};
class Skill {
public:
    Skill(int n) { }
};
class FlyBug : public Bug {
    int nWings;
    Skill sk1, sk2;
public:
    FlyBug(int legs, int color, int wings);
};
FlyBug::FlyBug(int legs, int color, int wings) :
    Bug(legs, color), sk1(5), sk2(color), nWings(wings) {
}

封闭派生类对象的构造函数的执行顺序
在创建派生类的对象时:

  1. 先执行基类的构造函数,用以初始化派生类对象中从基类
    继承的成员;
  2. 再执行成员对象类的构造函数,用以初始化派生类对象中
    成员对象。
  3. 最后执行派生类自己的构造函数

封闭派生类对象消亡时析构函数的执行顺序
在派生类对象消亡时:

  1. 先执行派生类自己的析构函数
  2. 再依次执行各成员对象类的析构函数
  3. 最后执行基类的析构函数
    析构函数的调用顺序与构造函数的调用顺序相反。

7.public继承的赋值兼容规则

1) 派生类的对象可以赋值给基类对象
b = d;
2) 派生类对象可以初始化基类引用
base & br = d;
3) 派生类对象的地址可以赋值给基类指针
base * pb = & d;


如果派生方式是 private或protected,则上述三条不可行。

8.protected继承和private继承

• protected继承时,基类的public成员和protected成员成为派生类的protected成员。
• private继承时,基类的public成员成为派生类的private成员,基类的protected成员成
为派生类的不可访问成员。
• protected和private继承不是“是”的关系。

9.基类与派生类的指针强制转换

 公有派生的情况下,派生类对象的指针可以直接赋值给基类指针
Base * ptrBase = &objDerived;
ptrBase指向的是一个Derived类的对象;
*ptrBase可以看作一个Base类的对象,访问它的public成员直接通过ptrBase即可,但不能通过ptrBase访问objDerived对象中属于Derived类而不属于Base类的成员


 即便基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类没有,而派生类中有的成员。


 通过强制指针类型转换,可以把ptrBase转换成Derived类的指针
Base * ptrBase = &objDerived;
Derived *ptrDerived = (Derived * ) ptrBase;
程序员要保证ptrBase指向的是一个Derived类的对象,否则很容易会出错。

#include <iostream>
using namespace std;
class Base {
protected:
    int n;
public:
    Base(int i) :n(i) {
        cout << "Base " << n <<
            " constructed" << endl;
    }
    ~Base() {
        cout << "Base " << n <<
            " destructed" << endl;
    }
    void Print() { cout << "Base:n=" << n << endl; }
};

class Derived :public Base {
public:
    int v;
    Derived(int i) :Base(i), v(2 * i) {
        cout << "Derived constructed" << endl;
    }
    ~Derived() {
        cout << "Derived destructed" << endl;
    }
    void Func() { };
    void Print() {
        cout << "Derived:v=" << v << endl;
        cout << "Derived:n=" << n << endl;
    }
};

int main() {
    Base objBase(5);
    Derived objDerived(3);
    Base * pBase = &objDerived;
    //pBase->Func(); //err;Base类没有Func()成员函数
    //pBase->v = 5; //err; Base类没有v成员变量
    pBase->Print();
    //Derived * pDerived = & objBase; //error
    Derived * pDerived = (Derived *)(&objBase);
    pDerived->Print(); //慎用,可能出现不可预期的错误
    pDerived->v = 128; //往别人的空间里写入数据,会有问题
    objDerived.Print();
    return 0;
}
/*
输出结果:
Base 5 constructed
Base 3 constructed
Derived constructed
Base:n=3
Derived:v=1245104 //pDerived->n 位于别人的空间里
Derived:n=5
Derived:v=6
Derived:n=3
Derived destructed
Base 3 destructed
Base 5 destructed
*/

10.直接基类和间接基类

在声明派生类时, 只需要列出它的直接基类
– 派生类沿着类的层次自动向上继承它的间接基类
– 派生类的成员包括
• 派生类自己定义的成员
• 直接基类中的所有成员
• 所有间接基类的全部成员

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