1、继承对象模型
- 类在C++编译器的内部类可以理解为结构体
- 子类是由父类成员叠加子类新成员得到的
class Derived : public Demo
{
int mk;
};
如何证实子类里面成员变量的分布真的如图所示,父类在前,子类在后:
#include <iostream>
#include <string>
using namespace std;
class Demo
{
protected:
int mi;
int mj;
};
class Derived : public Demo
{
private:
int mk;
public:
Derived(int i, int j, int k)
{
mi = i;
mj = j;
mk = k;
}
void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << ", "
<< "mk = " << mk << endl;
}
};
struct Test
{
int mi;
int mj;
int mk;
};
int main()
{
cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
Derived d(1, 2, 3);
cout << "Before changing" << endl;
d.print();
cout << "After changing" << endl;
Test* p = reinterpret_cast<Test*>(&d);
p->mi = 100;
p->mj = 200;
p->mk = 300;
d.print();
return 0;
}
从这个程序我们可以看出,子类继承后的对象模型和struct 这个结构体的对象模型是一致的,因为赋值可以直接赋。所以我们可以得出结论:子类里面成员变量的分布是排放父类继承的成员变量,再排放子类本身有的成员变量。
2、多态对象模型
- 基本知识点:
— 多态是面向对象理论中的一个概念,多态的表现形式通过虚函数来表现的。表现为相同的行为方式,不同的行为结果。
相同的行为方式:同一条调用语句
不同的行为结果:同一条调用语句得到不同的执行结果 - C++多态的实现原理
— 当类中声明虚函数时,编译器会在类中生成一个虚函数表
— 虚函数表是一个存储成员函数地址的数据结构
— 虚函数表是由编译器自动生成与维护的
— virtual 成员函数会被编译器放入虚函数表中
— 存在虚函数时,每个对象中都有一个指向虚函数表的指针。
证明虚函数指针的存在:
#include <iostream>
#include <string>
using namespace std;
class Demo
{
protected:
int mi;
int mj;
public:
virtual void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << endl;
}
};
class Derived : public Demo
{
private:
int mk;
public:
Derived(int i, int j, int k)
{
mi = i;
mj = j;
mk = k;
}
void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << ", "
<< "mk = " << mk << endl;
}
};
struct Test
{
void *p;
int mi;
int mj;
int mk;
};
int main()
{
cout << "sizeof(Demo) = " << sizeof(Demo) << endl;
cout << "sizeof(Derived) = " << sizeof(Derived) << endl;
Derived d(1, 2, 3);
cout << "Before changing" << endl;
d.print();
cout << "After changing" << endl;
Test* p = reinterpret_cast<Test*>(&d);
p->mi = 100;
p->mj = 200;
p->mk = 300;
d.print();
return 0;
}
果然,当我们定义了虚函数时,对象占用的字节数就加了4,明显就是多了一个指针。并且我们这个程序也证实了虚函数表指针存放在成员变量的最开始的位置,因为我们利用struct 的对象模型去验证,当void *指针放在最开始的位置时,赋值使我们想要的。
用C语言解析虚函数:
51-2.h
#ifndef _51_2_H_
#define _51_2_H_
#pragma once
typedef void Demo;
typedef void Derived;
Demo* Demo_Creat(int i, int j);
int Demo_getI(Demo* pThis);
int Demo_getJ(Demo* pThis);
int Demo_add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);
Derived* Derived_Creat(int i, int j, int k);
int Derived_getI(Derived* pThis);
int Derived_getJ(Derived* pThis);
int Derived_getK(Derived* pThis);
int Derived_add(Derived* pThis, int value);
#endif
51-2.c
#include "51-2.h"
#include <malloc.h>
static int Demo_Virtual_add(Demo* pThis, int value);
static int Derived_Virtual_add(Derived* pThis, int value);
struct Vtable //2、定义一个虚函数表,里面存放的是虚函数的地址
{
int(*p_add)(Derived*, int); //3、虚函数表里面存放什么
};
static struct Vtable Demo_v_v =
{
Demo_Virtual_add
}; //Demo里面虚函数表变量,static表示隐藏
struct Demo_Class
{
struct Vtable* vptr; //1、指向虚函数表的指针
int mi;
int mj;
};
Demo* Demo_Creat(int i, int j)
{
struct Demo_Class* ret = (struct Demo_Class*)malloc(sizeof(struct Demo_Class));
if (ret != NULL)
{
ret->vptr = &Demo_v_v; //4、指针指向Demo里面虚函数表变量来初始化
ret->mi = i;
ret->mj = j;
}
return ret;
}
int Demo_getI(Demo* pThis)
{
struct Demo_Class* ret = (struct Demo_Class*)pThis;
return ret->mi;
}
int Demo_getJ(Demo* pThis)
{
struct Demo_Class* ret = (struct Demo_Class*)pThis;
return ret->mj;
}
//6、定义虚函数表中指针所指向的具体函数
static int Demo_Virtual_add(Demo* pThis, int value)
{
struct Demo_Class* ret = (struct Demo_Class*)pThis;
return ret->mi + ret->mj + value;
}
//5、分析具体虚函数
int Demo_add(Demo* pThis, int value)
{
struct Demo_Class* ret = (struct Demo_Class*)pThis;
return ret->vptr->p_add(pThis, value);
}
static struct Vtable Derived_v_v =
{
Derived_Virtual_add
};
struct Derived_Class
{
struct Demo_Class v;
int mk;
};
Derived* Derived_Creat(int i, int j, int k)
{
struct Derived_Class* ret = (struct Derived_Class*)malloc(sizeof(struct Derived_Class));
if (ret != NULL)
{
ret->v.vptr = &Derived_v_v;
ret->v.mi = i;
ret->v.mj = j;
ret->mk = k;
}
return ret;
}
int Derived_getI(Derived* pThis)
{
struct Derived_Class* ret = (struct Derived_Class*)pThis;
return ret->v.mi;
}
int Derived_getJ(Derived* pThis)
{
struct Derived_Class* ret = (struct Derived_Class*)pThis;
return ret->v.mj;
}
int Derived_getK(Derived* pThis)
{
struct Derived_Class* ret = (struct Derived_Class*)pThis;
return ret->mk;
}
static int Derived_Virtual_add(Derived* pThis, int value)
{
struct Derived_Class* ret = (struct Derived_Class*)pThis;
return ret->mk + value;
}
int Derived_add(Derived* pThis, int value)
{
struct Derived_Class* ret = (struct Derived_Class*)pThis;
return ret->v.vptr->p_add(pThis, value);
}
void Demo_Free(Demo* pThis)
{
free(pThis);
}
main.c
#include <stdio.h>
#include "51-2.h"
void run(Demo* p, int value)
{
printf("p.add(value) = %d\n", Demo_add(p, value));
}
int main()
{
Demo* db = Demo_Creat(1, 2);
Derived* dp = Derived_Creat(1, 22, 333);
printf("db.add(3) = %d\n", Demo_add(db, 3));
printf("dp.add(3) = %d\n", Derived_add(dp, 3));
run(db, 3);
run(dp, 3);
Demo_Free(db);
Demo_Free(dp); //这种强制类型转换只会转换内存的分布,实际的值还是自己的,Derived* 类型的dp转换成Demo*,只是内存布局变成Demo*的,但是里面的值还是Derived原始里面的值。
return 0;
}
我觉得这个C语言程序的多态在于我们已经用两个指针指向了两块不同的内存,每块内存的都有不同的VPTR指针,只要是通过VPTR指针指向,那么就会指向不同的函数。
小结:
- 继承的本质就是父子间成员变量的叠加
- C++中的多态是通过虚函数表实现的
- 虚函数表是由编译器自动生成和维护的
- 虚函数的调用效率低于普通成员函数