什么是继承
继承的本质是复制,编译器按照一定规则把基类代码复制到子类。
先写一个没有继承的类,Man中2个字段x和y:
class Man
{
public:
int x;
int y;
};
int main()
{
Man man;
man.x = 1;
man.y = 2;
return 0;
}
查看反编译代码:
53: Man man;
54: man.x = 1;
C7 45 F4 01 00 00 00 mov dword ptr [ebp-0Ch],1
55: man.y = 2;
C7 45 F8 02 00 00 00 mov dword ptr [ebp-8],2
再写一个有继承的类,子类Man继承基类Human。Man中两个字段:x和y。
class Human
{
public:
int x;
};
class Man : public Human
{
public:
int y;
};
int main()
{
Man man;
man.x = 1;
man.y = 2;
return 0;
}
反汇编:
46: Man man;
47: man.x = 1;
C7 45 F0 01 00 00 00 mov dword ptr [ebp-10h],1
48: man.y = 2;
C7 45 F4 02 00 00 00 mov dword ptr [ebp-0Ch],2
我们发现反汇编代码是一样的,由此可以得出结论:继承的本质是赋值,编译器帮助我们把基类的代码复制到子类,帮助我们减少写代码的工作量。
继承相关特性:
1、因为编译器把基类代码拷贝到子类,所以可以用基类的指针指向子类的对象。
Human* pt = &man;
printf("x= %d", pt->x);
2、如何访问y。但是编译器认为Human只有x,不允许访问y。通过上面反编译代码,我们知道结构y的地址在x地址的下面,所以可以通过pt+1获取到y地址。代码如下:
Man man;
man.x = 1;
man.y = 2;
Human* pt = &man;
//打印y的值
printf("y= %d", *(int*)(pt + 1));
3、如何用子类的指针访问基类的对象。通过上面分析,我们知道子类代码不存在基类中,编译器当然也知道,所以编译器不允许直接访问。可以尝试通过指针访问,但是这种访问是错误的,这个位置上存的是垃圾数据,VS编译器无法编译。
Human human ;
human.x = 3;
Man* man = (Man*)&human;
printf("man.x= %d\n", man.x);
printf("man.y= %d\n", man.y);
4、当基类还存在基类时,编译器会把基类和基类的基类都代码复制到子类。
5、如果子类和基类中有群成员重名,使用::符号进行赋值。
class Human
{
public:
int x;
};
class Man : public Human
{
public:
int x;
};
int main()
{
Man man;
man.x = 1;
//等于 man.x = 2;
man.Man::x = 2;
man.Human::x = 3;
}
6、基类私有成员和函数也会被继承。
class Human
{
public:
Human() {
x = 1;
}
private:
int x;
};
class Man : public Human
{
public:
int y;
Man() {
y = 2;
}
};
int main()
{
Man man;
int* ptr = (int*)&man;
printf("x=%d\n", *ptr); //访问x
printf("y=%d\n", man.y); //访问y
printf("y=%d\n", *(ptr+1)); //访问y
return 0;
}
编译器不允许使用私有成员,但我们通过指针可以访问,通过*ptr 可以访问x。
7、构造函数调用顺序。当子类初始化时,会先调用基类构造函数,再调用子类构造函数。
8、如果函数原型相同,则会覆盖。