C++基类派生类与指针

类的指针总结

  1. 基类指针可以指向派生类对象,那么经由该指针只能访问基础类定义的函数(静态联翩)
  2. 派生类指针指向基类必须经过强制类型转换

    1
    2
    //eg.
    B *aa=(B*)new A();//(子类指针指向父类对象地址必须强转才行)
  3. 如果基础类和衍生类定义了相同名称的成员函数,那么通过对象指针调用成员函数时,到底调用那个函数要根据指针的原型来确定,而不是根据指针实际指向的对象类型确定。

原理

转载自https://www.cnblogs.com/rednodel/p/4122781.html

简单来说,C++的多态就是靠父类指针指向子类对象+虚函数来实现的。父类指针指向子类对象,可以调用子类从父类继承来的那一部分,但如果父类中声明了virtual,则可以调用子类中的方法,这样就实现了多态。而子类指针指向父类对象,可能会调用到父类中没用的方法,因此这是不对的。
至于两类指针的互换是另一个问题。

例如:

1
2
3
4
5
6
7
8
9
10
class a
{
public:
int aa;
};
class b:public a
{
public:
int bb;
}

从内存的来看:

int 类型大小 int 类型大小
a aa数据 -
b aa数据(继承而来) bb数据

\uparrow
当定义一个基类类型的指针时:a *p;这时,这个指针指向的是a类型的数据
当p指针指向派生类的时候,因为p是a类型的指针,所以*p只解释为a类型数据的长度,即

int 类型大小 int 类型大小
a aa数据 -
b aa数据(继承而来) bb数据
*p$\rightarrow$a $\uparrow$ NULL

因此,当基类的指针(P)指向派生类的时候,只能操作派生类中从基类中继承过来的数据。
指向派生类的指针,因为内存空间比基类长,会导致严重了后果,所以不允许派生类的指针指向基类。而基类的指针可以指向派生类。

C++的多态性能解决基类指针不能操作派生类的数据成员的问题。

另外从上代码可看出,aa与bb存放在不同的地址,类似于函数的隐藏,我在知乎上发现这段代码,也可以很好的说明这个问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <cstdio>
class A{
public:
A(){
a = 0;
}
int a;
};
class B:public A{
public:
B(){
a = 1;
}
int a;
};
int main(){
B* pb = new B();
A* pa = pb;
printf("a in B is: %d\n", pb->a);
printf("a in A is: %d\n", pa->a);
printf("value of pb is: %p\n", pb);
printf("value of pa is: %p\n", pa);
printf("address of pb->a is: %p\n", &(pb->a));
printf("address of pa->a is: %p\n", &(pa->a));
return 0;
}
/*
运行输出:
a in B is: 1
a in A is: 0
value of pb is: 0x7fa97ac04b20
value of pa is: 0x7fa97ac04b20
address of pb->a is: 0x7fa97ac04b24
address of pa->a is: 0x7fa97ac04b20
作者:刘项
链接:https://www.zhihu.com/question/31345300/answer/51536945
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
*/

以下现象:

  1. 输出中的第1行和第2行。确实如题主所说,pb->a是1,pa->a是0。这应该没有任何疑问。
  2. 输出中的第3行和第4行。可以看出来,pb和pa的值完全一样(指向同一个地址)。
  3. 输出中的第5行和第6行。pb->a和pa->a的地址不一样。

以下解释:

  1. 父类中的a和子类中的a是两个不同的变量。
  2. p->a的地址是p的值加上a的偏移量。
  3. 父类的属性一般比较靠前,父类中的a的偏移量为0,子类中的a的偏移量为4。
  4. 如果p是父类的指针,p->a的地址等于p的值加上0。如果p是子类的指针,p->a的地址等于p的值加上4。

类型兼容性原则 

类型兼容规则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代。通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。类型兼容规则中所指的替代包括以下情况:

  1. 子类对象可以当作父类对象使用
  2. 子类对象可以直接赋值给父类对象
  3. 子类对象可以直接初始化父类对象
  4. 父类指针可以直接指向子类对象
  5. 父类引用可以直接引用子类对象

函数隐藏与多态原理

函数隐藏

在派生类中可以通过定义与基类相同函数名的方式将基类的函数隐藏,通过子类对象或者在子类内部只能访问子类的同名成员,但是父类的成员仍然存在。这便是函数隐藏机制。

多态(覆盖)

多态:就是根据实际的对象类型决定函数调用语句的具体调用目标。总结一句话就是,同样的调用语句有多种不同的表现形态。

父类中用virtual关键字定义虚函数后,可在子类定义相同函数名的函数,当父类指针指向子类对象时,此时子类对象中的父类的虚函数表指针所指的虚函数表中的父类重写虚函数的地址被改写为子类的重写虚函数的地址,所以此时父类指针访问子类中父类的虚函数表时,找到的要调用的同名虚函数是子类的同名虚函数地址;所以父类指针调用子类的重写的同名虚函数;

虚函数重写(覆盖)的实质就是重写父类虚函数表中的父类虚函数地址;

多态的实现——虚函数表

有关虚函数表具体原理,可以查看以下博客:
看雪。
castle_kao的博客Jim’sBlog

虚函数表的大概总结:

  1. 有虚函数的类才有虚函数表。
  2. 虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。
  3. 当类中声明虚函数时,创建对象时,编译器会在类中生成一个虚函数表
  4. 虚函数表是一个存储类成员函数指针的数据结构
  5. 虚函数表是由编译器自动生成与维护的,virtual成员函数会被编译器放入虚函数表中
  6. 如果调用是虚函数,就根据不同对象的vptr指针找属于自己的函数。而且父类对象和子类对象都会有vptr指针,传入对象不同,编译器会根据vptr指针,到属于自己虚函数表中找自己的函数。即:vptr—>虚函数表——>函数的入口地址,从而实现了迟绑定(在运行的时候,才会去判断)。

本文标题:C++基类派生类与指针

文章作者:微石

发布时间:2018年05月26日 - 09:05

最后更新:2018年07月19日 - 11:07

原始链接:akihoo.github.io/posts/75668e14.html

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。