C++面向对象的继承与多态

[toc]

继承

◼ 继承,可以让子类拥有父类的所有成员(变量\函数)

image-20210403150553278

对象的内存布局

struct Person {

};

struct Student : Person {
    int m_no;
};

struct GoodStudent : Student {
    int m_money;
};

父类的成员变量在前,子类的成员变量在后

image-20210403151255004

成员访问权限

◼ 成员访问权限、继承方式有3种

  • public:公共的,任何地方都可以访问(struct默认)

  • protected:子类内部、当前类内部可以访问

  • private:私有的,只有当前类内部可以访问(class默认)

◼ 子类内部访问父类成员的权限,是以下2项中权限最小的那个

  • 成员本身的访问权限

  • 上一级父类的继承方式

◼ 开发中用的最多的继承方式是public,这样能保留父类原来的成员访问权限

访问权限不影响对象的内存布局

struct Person {
private:
   int m_age;

public:
   void setAge(int age) {
      m_age = age;
   }

   int getAge() {
      return m_age;
   }
};

struct Student : public Person {

};

struct GoodStudent : public Student {
   void work() {

   }
};
class Person {
private:
   int m_age;
public:
   void run() {

   }
};

struct Student : public Person {

};

class GoodStudent : public Student {

};

初始化列表

◼ 特点

  • 一种便捷的初始化成员变量的方式

  • 只能用在构造函数中

  • 初始化顺序只跟成员变量的声明顺序有关

struct Person {
   int m_age;
   int m_height;

   /*Person(int age, int height) {
      m_age = age;
      m_height = height;
   }*/

   // 语法糖
   Person(int age = 0, int height = 0) :m_age(age), m_height(height) {}
};
int myAge() {
    cout << "myAge()" << endl;
    return 10;
}
int myHeight() {
    cout << "myHeight()" << endl;
    return 10;
}
struct Person {
    int m_age;
    int m_height;
    Person(int age , int height):m_age(myAge()), m_height(myHeight()){
        
    }
};
int main() {
    Person person(18, 180);

    cout << person.m_age << endl;
    cout << person.m_height << endl;
}

myAge()
myHeight()
10
10

  • 初始化顺序只跟成员变量的声明顺序有关
image-20210405132725878
image-20210405132742522

说明输出和定义在类中成员的顺序有关,和代码Person(int age , int height):m_age(myAge()), m_height(myHeight())定义的顺序无关。

初始化列表与默认参数配合使用

◼ 如果函数声明和实现是分离的

  • 初始化列表只能写在函数的实现中

  • 默认参数只能写在函数的声明中

struct Person {
   int m_age;
   int m_height;
   Person(int age = 0, int height = 0);
};

Person::Person(int age, int height) :m_age(age), m_height(height) {

}
int main() {
    Person person1;
    Person person2(17);
    Person person(18, 180);
}

构造函数的互相调用

image-20210405134229541
struct Person {
    int m_age;
    int m_height;

    /*Person() :Person(10, 20) {

    }*/

    Person() :Person(10, 20) {
        // 创建了一个临时的Person对象
        // Person(10, 20);
        /*Person person;
        person.m_age = 10;
        person.m_height = 20;*/
    }

    Person(int age, int height) {
        this->m_age = age;
        this->m_height = height;
    }
};

int main() {

    Person person;
    cout << person.m_age << endl;
    cout << person.m_height << endl;

    return 0;
}

父类的构造函数

◼ 子类的构造函数默认会调用父类的无参构造函数

◼ 如果子类的构造函数显式地调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数

◼ 如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数

struct Person {
   Person() {
      cout << "Person::Person()" << endl;
   }
   ~Person() {
      cout << "Person::~Person()" << endl;
   }
};

struct Student : Person {
   Student() {
      // call Person::Person
      cout << "Student::Student()" << endl;
   }
   ~Student() {
      cout << "Student::~Student()" << endl;
      // call Person::~Person
   }
};
int main() {

   {
      Student student;
   }

   // Student student(18, 34);

   return 0;
}
/*
Person::Person()
Student::Student()
Student::~Student()
Person::~Person()
*/
  • 子类的构造函数默认会调用父类的无参构造函数
image-20210405135555425

构造、析构顺序

image-20210405140202765

多态

默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态

◼ 多态是面向对象非常重要的一个特性

  • 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果

  • 在运行时,可以识别出真正的对象类型,调用对应子类中的函数

◼ 多态的要素

  • 子类重写父类的成员函数(override

  • 父类指针指向子类对象

  • 利用父类指针调用重写的成员函数

父类指针、子类指针

image-20210405140630028
struct Person {
   int m_age;
};

struct Student : public Person {
   int m_score;
};

void test() {
   // 父类指针 指向 子类对象
// Person *p = new Student();
// p->m_age = 10;

   Student *p = (Student *) new Person();
   p->m_age = 10;
   p->m_score = 100;//危险,动了age后面的4个字节,修改了非自己内存的数据
}
struct Animal {
   virtual void speak() {
      cout << "Animal::speak()" << endl;
   }
   virtual void run() {
      cout << "Animal::run()" << endl;
   }
};

struct Dog : Animal {
   // 重写(覆写、覆盖、override)
   void speak() {
      cout << "Dog::speak()" << endl;
   }
   void run() {
      cout << "Dog::run()" << endl;
   }
};

struct Cat : Animal {
   void speak() {
      cout << "Cat::speak()" << endl;
   }
   void run() {
      cout << "Cat::run()" << endl;
   }
};

struct Pig : Animal {
   void speak() {
      cout << "Pig::speak()" << endl;
   }
   void run() {
      cout << "Pig::run()" << endl;
   }
};
  • 同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
//同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
void liu(Animal *p) {
   p->speak();
   p->run();
}

int main() {

    liu(new Dog());
    liu(new Cat());
    liu(new Pig());
    

    return 0;
}
/*
Dog::speak()
Dog::run()
Cat::speak()
Cat::run()
Pig::speak()
Pig::run()
*/
  • 默认情况下,编译器只会根据指针类型调用对应的函数,不存在多态
struct Animal {
   void speak() {
      cout << "Animal::speak()" << endl;
   }
   void run() {
      cout << "Animal::run()" << endl;
   }
};
/*
Animal::speak()
Animal::run()
Animal::speak()
Animal::run()
Animal::speak()
Animal::run()
*/
**不存在多态**

虚函数

◼ C++中的多态通过虚函数(virtual function)来实现

◼ 虚函数:被virtual修饰的成员函数

◼ 只要在父类中声明为虚函数,子类中重写的函数也自动变成虚函数(也就是说子类中可以省略virtual关键字

struct Animal {
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
    virtual void run() {
        cout << "Animal::run()" << endl;
    }
};
int main() {
   Animal *p = new Pig();
   p->speak();
   p->run();
}
/*
Pig::speak()
Pig::run()
*/
虚函数

虚表

虚函数的实现原理是虚表,这个虚表里面存储着最终==需要调用的虚函数地址==,这个虚表也叫虚函数表

image-20210405144422323
struct Animal {
    int m_age;
    void speak() {
        cout << "Animal::speak()" << endl;
    }
    void run() {
        cout << "Animal::run()" << endl;
    }
};

struct Cat : Animal {
    int m_life;
    void speak() {
        cout << "Cat::speak()" << endl;
    }
    void run() {
        cout << "Cat::run()" << endl;
    }
};

int main() {

    cout<< sizeof (Cat)<<endl;
}
    

8

struct Animal {
    int m_age;
    virtual void speak() {
        cout << "Animal::speak()" << endl;
    }
    void run() {
        cout << "Animal::run()" << endl;
    }
};

virtual void speak()

cout<< sizeof (Cat)<<endl;

16

image-20210405145641286

虚表里面存储着最终需要调用的虚函数地址,这个虚表也叫虚函数表

(x86环境的图)

x86环境的图
int main() {


    Animal *cat = new Cat();
    cat->m_age = 20;
    cat->speak();
    cat->run();
}

下面我们通过上述代码的内存数据及汇编指令,来窥探虚表的原理

cat堆内存数据

image-20210405152135545
image-20210405152143497
image-20210405152904006
image-20210405152915125
image-20210405154238056
image-20210405154515650

总结一下Animal类由于存在虚函数virtual void speak()virtual void run() 所以Animal对象创建完成后,前8个字节是一个地址指针,指向了一个虚表的地址

image-20210405154943917

由于有两个虚函数,所以指向的地址,前8位speak函数的地址,后8个字节为run函数的地址。

Animal 虚表地址之后,跟着为属性成员的地址,movl $0x14, 0x8(%rax) 修改m_age的内存地址的值为20.

0x10fe79035 <+85>: callq *(%rcx)0x10fe79041 <+97>: callq *0x8(%rcx)都是间接调用,先找到虚表地址,从内存地址中,取出函数地址间接调用到对应类对象真实实现函数的地址上。

调用父类的成员函数实现

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

推荐阅读更多精彩内容