谈面向对象语言不能不谈继承,这也是面向对象语言天生的优势,大大提高了代码的复用,在c语言中我们也可以通过结构体嵌套的方式,来实现继承,倘若看本c++的书,一个继承都能来来回回讲个几十回合。我们通过反汇编来看看继承的本质究竟是什么?
一:继承
1、不继承的写法
struct Person { int age; int sex; }; struct Teacher { int age; int sex; int level; int classId; }; struct Student { int age; int sex; int code; int score; }; Teacher t; 观察反汇编: t.age = 1; mov dword ptr [ebp-10h],1 t.sex = 2; mov dword ptr [ebp-0Ch],2 t.level = 3; mov dword ptr [ebp-8],3 t.classId = 4; mov dword ptr [ebp-4],4 push 10h printf("%d\n",sizeof(t)); push offset string "%d\n" (0042201c) call printf (004010e0) add esp,8 Student s; mov dword ptr [ebp-10h],1 mov dword ptr [ebp-0Ch],2 s.age = 1; mov dword ptr [ebp-8],3 s.sex = 2; mov dword ptr [ebp-4],4 s.code = 3; push 10h s.score = 4; push offset string "%d\n" (0042201c) call printf (004010e0) printf("%d\n",sizeof(s)); add esp,8
2、改变写法: 继承方式
struct Person { int age; int sex; }; struct Teacher:Person { int level; int classId; }; struct Student:Person { int code; int score; }; 反汇编: Teacher t; mov dword ptr [ebp-10h],1 mov dword ptr [ebp-0Ch],2 t.age = 1; mov dword ptr [ebp-8],3 t.sex = 2; mov dword ptr [ebp-4],4 t.level = 3; push 10h t.classId = 4; push offset string "%d\n" (0042201c) call printf (004010e0) printf("%d\n",sizeof(t)); add esp,8 Student s; mov dword ptr [ebp-10h],1 mov dword ptr [ebp-0Ch],2 s.age = 1; mov dword ptr [ebp-8],3 s.sex = 2; mov dword ptr [ebp-4],4 s.code = 3; push 10h s.score = 4; push offset string "%d\n" (0042201c) call printf (004010e0) printf("%d\n",sizeof(s)); add esp,8
总结:
1、什么是继承?
继承就是数据的复制,从汇编的角度看是,完全一样的,但是从编写的角度看,显然继承的方式编写大大提高了代码的复用
其实本质上,就是继承的时候,那部分继承代码会由编译器帮我们生成而不用我们自己编写,这也是为什么说,继承的本质就是数据的复制,其实就是将父类的拷贝到子类。
2、为什么要用继承?
减少重复代码的编写
3、Person 称为父类或者基类
4、Teacher、Student称为子类或者派生类
5、t和s可以称为对象或者实例.
6、可以用父类指针指向子类的对象.
这是我们要讨论的一个核心问题,那就是类指针,其实上面的东西明白了,类指针也就非常明白了。下面的代码实现了父类指针指向子类
而且可以访问修改,这其实就是源于继承的本质,从内存的角度来看,继承的父类成员放在了对象的开始,低地址处,父类指针和子类指针的区别
最为明显的就是指针步长的不同,而把父类指针指向开始,恰好是父类的成员,符合父类的指针步长,也就恰好可以访问。当然世间没有那么多的恰好
这必然是源于语言设计者的心血。那么可否用父类指针访问子类成员,编译器是不允许的,这还是源于指针步长的合理性,当然从底层的角度来看,没有什么不能访问,我们可以通过一些手段来实现访问。反过来,子类指针访问父类,其实是没有什么价值的,源于父类里面没有子类的成员。而且编译器肯定是不允许的。
#include <iostream> using namespace std ; struct Person { int age; int sex; }; struct Teacher:Person { int level; int classId; }; struct Student:Person { int code; int score; }; int main (void) { Teacher *point, t; point=&t; point->age=1; point->classId=2; point->level=3; point->sex=4; printf("%d %d %d %d\n",t.age,t.classId,t.level,t.sex); Person * point_test; // 父类指针 point_test=&t; // 子类对象地址 point_test->age=5; point_test->sex=6; printf("%d %d\n",t.age,t.sex); return 0; }
二: 多层继承
多层继承比较简单,那就是你可以继承你父亲的遗产,也可以继承你爷爷的遗产。其实都可以想到,还是数据的拷贝。
多层继承: 观察反汇编: 1)无相同成员 struct X { mov dword ptr [ebp-18h],1 int a; mov dword ptr [ebp-14h],2 int b; mov dword ptr [ebp-10h],3 }; mov dword ptr [ebp-0Ch],4 struct Y:X mov dword ptr [ebp-8],5 { mov dword ptr [ebp-4],6 int c; push 18h int d; push offset string "%d\n" (0042201c) }; call printf (004010e0) struct Z:Y add esp,8 { int e; int f; }; Z z; z.a = 1; z.b = 2; z.c = 3; z.d = 4; z.e = 5; z.f = 6; printf("%d\n",sizeof(z)); 2)有相同成员 ,二异性问题 ,但是编译器不一定会报错,默认是使用当前类的成员 struct X { int a; int b; }; struct Y:X { int a; int d; }; struct Z:Y { int e; int f; }; Z z; z.X::a = 1; //通过::区分了a,但是对于底层来说和上面的完全一样, //这里的区分仅仅是让编译器明白。 z.b = 2; z.Y::a = 3; z.d = 4; z.e = 5; z.f = 6; printf("%d\n",sizeof(z)); printf("%d\n",z.X::a); printf("%d\n",z.b); printf("%d\n",z.Y::a); printf("%d\n",z.d); printf("%d\n",z.e); printf("%d\n",z.f);
对于二义性问题,其本质是不存在任何问题的,从汇编看,和正常的成员完全一样,关键的就是让编译器可以区分。
三:多重继承
何为多重继承,就是有多个爹,在java 中是没有多重继承的,但是在c++中是支持多重继承的。也就是一个父类有多个子类。
3、继承的特性 struct X { int a; int b; }; struct Y { int c; int d; }; struct Z:X,Y { int e; int f; };
——————————————————2017年9月10日17:45:29
其实我要说的是,有时候概念多了反而不是什么好事,也许事物的本质就一种。