我们学习了 C++ 这么长时间了,我们来看看 C++ 中对象的本质。它里面是用 class 定义的对象,class 是一种特殊的 struct。在内存中 class 依旧可以看做变量的集合,class 与 struct 遵循相同的内存对齐规则。class 中的成员函数与成员变量是分开存放的,及每个对象有独立的成员变量,所有对象共享类中的成员函数。那么我们如果在 class 和 struct 中同时定义相同的成员变量的话,它们所占的内存大小会一样嘛?我们来做个实验,代码如下
#include <iostream>
using namespace std;
class A
{
int i;
int j;
char c;
double d;
};
struct B
{
int i;
int j;
char c;
double d;
};
int main()
{
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl;
return 0;
}
我们根据之前学的知识可知,sizeof(B) 应该是等于 20 的,我们来看看 sizeof(A) 等于多少呢?
我们看到 A 和 B 所占的内存大小是一样的,那便说明它们的内存分布是相同的。我们下来在 class A 中定义一个 print 函数用来打印几个成员变量的值,再定义 B 类型的指针用来强制转换指向对象 A。再用指针来改变 A 中成员变量的值,具体程序如下
#include <iostream>
using namespace std;
class A
{
int i;
int j;
char c;
double d;
public:
void print()
{
cout << "i = " << i << ", "
<< "j = " << j << ", "
<< "c = " << c << ", "
<< "d = " << d << endl;
}
};
struct B
{
int i;
int j;
char c;
double d;
};
int main()
{
A a;
cout << "sizeof(A) = " << sizeof(A) << endl;
cout << "sizeof(a) = " << sizeof(a) << endl;
cout << "sizeof(B) = " << sizeof(B) << endl;
a.print();
B* p = reinterpret_cast<B*>(&a);
p->i = 1;
p->j = 2;
p->c = 'c';
p->d = 3;
a.print();
return 0;
}
那么我们进行强制类型转换后是否可以访问 class 的私有成员变量呢?我们来看看编译结果
我们看到在进行类型转换后,我们可以直接在外部对 class 的成员变量进行直接的改变。在运行时对象会退化位结构体的形式,此时所有的成员变量在内存中一次排布,成员变量间可能存在内存空隙。我们便可以通过内存地址来直接访问成员变量,访问权限的关键字在运行时失效。
类中的成员函数是位于代码段中,调用成员函数时对象地址作为参数隐式传递。成员函数通过对象地址访问成员变量,C++ 语法规则隐藏了对象地址的传递过程。下来我们以代码为例进行分析。
#include <iostream>
using namespace std;
class Demo
{
int mi;
int mj;
public:
Demo(int i, int j)
{
mi = i;
mj = j;
}
int getI()
{
return mi;
}
int getJ()
{
return mj;
}
int add(int v)
{
return mi + mj + v;
}
};
int main()
{
Demo d(1, 2);
cout << "d.i = " << d.getI() << endl;
cout << "d.j = " << d.getJ() << endl;
cout << "d.add(3) = " << d.add(3) << endl;
return 0;
}
我们定义了一个很平常的类,在里面定义了几个返回成员变量的函数,并定义了 一个 add 函数。我们来编译看看
我们看到已经正确实现。那么我们来想想,为什么我们在 getI 函数中能直接返回成员变量 mi 的值呢?是因为在 C++ 中的每个类对象都有一个隐藏的 this 指针,它时刻的指向整个对象,所以才能访问到它中的成员变量。下来我们就用 C 语言来实现上面的 C++ 程序,看看用 C 语言怎么写出面向对象的代码。
class.h 源码
#ifndef _CLASS_H_
#define _CLASS_H_
typedef void Demo;
Demo* Demo_Create(int i, int j);
int Demo_getI(Demo* pThis);
int Demo_getJ(Demo* pThis);
int Demo_add(Demo* pThis, int v);
void Demo_Free(Demo* pThis);
#endif
class.c 源码
#include "class.h"
#include <malloc.h>
struct ClassDemo
{
int mi;
int mj;
};
Demo* Demo_Create(int i, int j)
{
struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
if( ret != NULL )
{
ret->mi = i;
ret->mj = j;
}
return ret;
}
int Demo_getI(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi;
}
int Demo_getJ(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mj;
}
int Demo_add(Demo* pThis, int v)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi + obj->mj + v;
}
void Demo_Free(Demo* pThis)
{
free(pThis);
}
test.c 源码
#include <stdio.h>
#include "class.h"
int main()
{
Demo* d = Demo_Create(1, 2); // Demo d(1, 2);
printf("d.i = %d\n", Demo_getI(d)); // cout << "d.i = " << d.i << endl;
printf("d.j = %d\n", Demo_getJ(d)); // cout << "d.j = " << d.j << endl;
printf("add(3) = %d\n", Demo_add(d, 3)); // cout << "d.add(3) = " << d.add(3) << endl;
Demo_Free(d);
return 0;
}
我们编译结果如下
我们看到跟它后面的 C++ 代码的效果是一样的,感觉是不是很炫酷呢?下来我们来说说 C++ 中的继承对象模型。在 C++ 编译器的内部类可以理解为结构体,子类是由父类成员叠加子类新成员得到的。如下
下来我们还是以代码为例来进行分析
#include <iostream>
using namespace std;
class Demo
{
protected:
int mi;
int mj;
public:
void print()
{
cout << "mi = " << mi << ", "
<< "mj = " << mj << endl;
}
};
class Derived : public Demo
{
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);
Test* p = reinterpret_cast<Test*>(&d);
cout << "Before changing ..." << endl;
d.print();
p->mi = 10;
p->mj = 20;
p->mk = 30;
cout << "After changing ..." << endl;
d.print();
*/
return 0;
}
我们先通过打印两个类的大小来看看它们所占的内存大小
分别是 8 和 12,也和我们之前所分析的是一致的。由于我们重写了 print 函数,所以我们应该将其声明为虚函数,再加上 virtual 关键字之后再来看看他们的内存大小是多少
变成 12 和 16 了,加了 4 个字节的空间。我们再将注释掉的内容展开,看看结果
我们通过强制类型转换来改变了他们的成员变量的值。在 struct 结构体中第一个为 void* 的指针,也就是说,在 class 类对象中还有一个指针存在。这个指针便是我们指向虚函数表的指针。那么 C++ 中多态究竟是怎么实现的呢?当类中声明虚函数时,编译器会在类中生成一个虚函数表,虚函数表是一个存储成员函数地址的数据结构。虚函数表是由编译器自动生成与维护的,virtual 成员函数会被编译器放入虚函数表中。当存在虚函数时,每个对象都有一个指向虚函数表的指针。多态对象模型如下所示
那么在编译器确认 add 函数是否为虚函数时,如果是,编译器则在对象 VPTR 所指的虚函数表中查找 add 函数的地址;如果不是,编译器则直接可以确定被调成员函数的地址。那么我们来看看具体它是怎么调用的,如下
由此看来,就调用的效率来说,虚函数肯定是小于普通成员函数的。我们再次完善之前用 C 语言实现继承的代码,用 C 代码实现多态的用法。
class.h 源码
#ifndef _CLASS_H_
#define _CLASS_H_
typedef void Demo;
typedef void Derived;
Demo* Demo_Create(int i, int j);
int Demo_getI(Demo* pThis);
int Demo_getJ(Demo* pThis);
int Demo_add(Demo* pThis, int v);
void Demo_Free(Demo* pThis);
Derived* Derived_Create(int i, int j, int k);
int Derived_getK(Derived* pThis);
int Derived_add(Derived* pThis, int v);
#endif
class.c 源码
#include "class.h"
#include <malloc.h>
static int Demo_Virtual_Add(Demo* pThis, int v);
static int Derived_Virtual_Add(Derived* pThis, int v);
struct VTable // 2. 定义虚函数表数据结构
{
int (*pAdd)(void*, int); // 3. 虚函数表里存储的东西
};
struct ClassDemo
{
// 1. 定义虚函数表指针 ==> 虚函数表指针类型
struct VTable* vptr;
int mi;
int mj;
};
struct ClassDerived
{
struct ClassDemo d;
int mk;
};
static struct VTable g_Demo_vtbl =
{
Demo_Virtual_Add
};
static struct VTable g_Derived_vtbl =
{
Derived_Virtual_Add
};
Demo* Demo_Create(int i, int j)
{
struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
if( ret != NULL )
{
ret->vptr = &g_Demo_vtbl; // 4. 关联对象和虚函数表
ret->mi = i;
ret->mj = j;
}
return ret;
}
int Demo_getI(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi;
}
int Demo_getJ(Demo* pThis)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mj;
}
// 6. 定义虚函数表中指针所指向的具体函数
static int Demo_Virtual_Add(Demo* pThis, int v)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->mi + obj->mj + v;
}
// 5. 分析具体虚函数
int Demo_add(Demo* pThis, int v)
{
struct ClassDemo* obj = (struct ClassDemo*)pThis;
return obj->vptr->pAdd(pThis, v);
}
void Demo_Free(Demo* pThis)
{
free(pThis);
}
Derived* Derived_Create(int i, int j, int k)
{
struct ClassDerived* ret = (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
if( ret != NULL )
{
ret->d.vptr = &g_Derived_vtbl;
ret->d.mi = i;
ret->d.mj = j;
ret->mk = k;
}
return ret;
}
int Derived_getK(Derived* pThis)
{
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->mk;
}
static int Derived_Virtual_Add(Derived* pThis, int v)
{
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->mk + v;
}
int Derived_add(Derived* pThis, int v)
{
struct ClassDerived* obj = (struct ClassDerived*)pThis;
return obj->d.vptr->pAdd(pThis, v);
}
test.c 源码
#include <stdio.h>
#include "class.h"
void run(Demo* p, int v)
{
int r = Demo_add(p, v);
printf("r = %d\n", r);
}
int main()
{
Demo* pb = Demo_Create(1, 2);
Derived* pd = Derived_Create(1, 22, 333);
printf("pb.add(3) = %d\n", Demo_add(pb, 3));
printf("pd.add(3) = %d\n", Derived_add(pd, 3));
run(pb, 3);
run(pd, 3);
Demo_Free(pb);
Demo_Free(pd);
return 0;
}
我们来编译看下是不是和我们在 C++ 中实现的多态的效果是否一致呢?
我们看到它的效果和 C++ 中的多态的效果是一样的,也就是说,我们用 C 语言实现了多态。屌爆了!!通过今天对 C++ 对象模型的分析,总结如下:1、C++ 中的类对象在内存布局上与结构体相同;2、成员变量和成员函数在内存中分开存放;3、访问权限关键字在运行时失效;4、调用成员函数时对象地址作为参数隐式传递;5、继承的本质就是父子间成员变量的叠加;6、C++ 中的多态是通过虚函数表实现的7、虚函数表是由编译器自动生成与维护的,虚函数的调用效率低于普通成员函数。
欢迎大家一起来学习 C++ 语言,可以加我QQ:243343083。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。