C++基类和派生类指针的相互赋值和转换
在公有派生的情况下,派生类的指针可以直接赋值给基类指针。但即便基类指针指向的是一个派生类的对象,也不能通过基类指针访问基类没有而派生类中有的成员。
基类的指针不能赋值给派生类的指针。但是通过强制类型转换,也可以将基类指针强制转换成派生类指针后再赋值给派生类指针。只是在这种情况下,程序员需要保证被转换的基类指针本来就指向一个派生类的对象,这样才是安全的,否则很容易出错。
下面的代码演示了基类和派生类指针的互相转换:
CBase:n=3
1)------------
CDerived:n=5
CDerived:v=5
2)------------
CDerived:n=3
CDerived:v=6
3)------------
CDerived:n=128
CDerived:v=6
第 27 行使得基类指针 pBase 指向派生类对象 objDerived,这是合法的。执行完此行后,虽然 pBase 指向的是派生类对象,但是第 28 行如果不注释掉,编译是会出错的,因为 CBase 类并没有 Func 成员函数。
同理,第 29 行若不注释掉也会出错。
第 30 行,尽管基类和派生类都有 Print 成员函数,而且 pBase 指向的是派生类对象,本行执行的依然是基类的 Print 成员函数,产生第一行输出。
总之,编译器看到的是哪个类的指针,那么就会认为要通过它访问哪个类的成员,编译器不会分析基类指针指向的到底是基类对象还是派生类对象。
第 33 行,通过强制类型转换,使得派生类的指针 pDerived 指向了基类对象 objBase。第 34 行调用的 Print 成员函数就是 CDerived 类的 Print 成员函数。
这是有风险的语句。在 CDerived 对象中,成员变量 v 紧挨成员变量 n 存放,如图 1 所示。
图1:派生类对象的内存空间
执行 pDerived->Print() 时,虽然 pDerived 指向的是一个基类对象,但这并不影响成员变量 v 的地址计算方式,因此第 20 行
该位置并不属于 objBase 对象,可能属于其他变量,也可能是存放指令的。第 20 行输出
同理,第 38 行的
图2:派生类指针指向基类对象
图3:派生类指针指向派生类对象
因此,
本程序用 Dev C++ 4.9.9.2 编译后,输出结果如上所述。不同编译器在栈中放置局部变量的方式有所不同,用 Visual Studio 编译本程序,输出结果就未必如此。
基类引用也可以强制转换为派生类引用。将基类指针强制转换为派生类指针,或将基类引用强制转换为派生类引用,都有安全隐患。
C++提供了 dynamic_cast 强制类型转换运算符,可以判断这两种转换是否安全(即转换后的指针或引用是否真的指向或引用派生类对象)。为方便讲述,本教程将 dynamic_cast 运算符放在最后一章“C++高级主题”中讲解,但是强烈建议读者掌握这部分内容。
基类的指针不能赋值给派生类的指针。但是通过强制类型转换,也可以将基类指针强制转换成派生类指针后再赋值给派生类指针。只是在这种情况下,程序员需要保证被转换的基类指针本来就指向一个派生类的对象,这样才是安全的,否则很容易出错。
下面的代码演示了基类和派生类指针的互相转换:
#include <iostream> using namespace std; class CBase { protected: int n; public: CBase(int i) :n(i) { } void Print() { cout << "CBase:n=" << n << endl; } }; class CDerived :public CBase { public: int v; CDerived(int i) :CBase(i), v(2 * i) { } void Func() { }; void Print() { cout << "CDerived:n=" << n << endl; cout << "CDerived:v=" << v << endl; } }; int main() { CDerived objDerived(3); CBase objBase(5); CBase * pBase = &objDerived; // 使得基类指针指向派生类对象 //pBase->Func(); //错, CBase类没有Func()成员函数 //pBase->v = 5; //错 CBase类没有v成员变量 pBase->Print(); cout << "1)------------" << endl; //CDerived * pDerived = & objBase; //错,不能将基类指针赋值给派生类指针 CDerived * pDerived = (CDerived *)(&objBase); pDerived->Print(); //慎用,可能出现不可预期的错误 cout << "2)------------" << endl; objDerived.Print(); cout << "3)------------" << endl; pDerived->v = 128; //往别人的空间里写入数据,会有问题 objDerived.Print(); return 0; }在 Dev C++ 4.9.9.2 下的运行结果:
CBase:n=3
1)------------
CDerived:n=5
CDerived:v=5
2)------------
CDerived:n=3
CDerived:v=6
3)------------
CDerived:n=128
CDerived:v=6
第 27 行使得基类指针 pBase 指向派生类对象 objDerived,这是合法的。执行完此行后,虽然 pBase 指向的是派生类对象,但是第 28 行如果不注释掉,编译是会出错的,因为 CBase 类并没有 Func 成员函数。
同理,第 29 行若不注释掉也会出错。
第 30 行,尽管基类和派生类都有 Print 成员函数,而且 pBase 指向的是派生类对象,本行执行的依然是基类的 Print 成员函数,产生第一行输出。
总之,编译器看到的是哪个类的指针,那么就会认为要通过它访问哪个类的成员,编译器不会分析基类指针指向的到底是基类对象还是派生类对象。
第 33 行,通过强制类型转换,使得派生类的指针 pDerived 指向了基类对象 objBase。第 34 行调用的 Print 成员函数就是 CDerived 类的 Print 成员函数。
这是有风险的语句。在 CDerived 对象中,成员变量 v 紧挨成员变量 n 存放,如图 1 所示。
图1:派生类对象的内存空间
执行 pDerived->Print() 时,虽然 pDerived 指向的是一个基类对象,但这并不影响成员变量 v 的地址计算方式,因此第 20 行
cout << "CDerived:v=" << v << endl;
所访问的 v 就位于如图 2 所示的阴影位置。该位置并不属于 objBase 对象,可能属于其他变量,也可能是存放指令的。第 20 行输出
CDerived:v=3
是因为将阴影位置的 4 个字节看作一个 int 类型变量,其值是 3。但这个值是不确定、不可预测的。此处存放什么,不同编译器的处理办法不同,如果该位置是操作系统规定不可访问的区域,那么程序就可能由于出错而中止。同理,第 38 行的
pDerived -> v = 128;
也是不安全的,它往图 5.4 的阴影部分写入 128,这就可能修改其他变量的值。通过对比第 36 行和第 39 行的输出可以发现,objDerived.n 的值莫名其妙地变成了 128,这实际上就是由于pDerived->v = 128;
造成的。因为,碰巧在栈中 objDerived 对象是紧挨着 objBase 下方存放的,如图 3 所示。图2:派生类指针指向基类对象
图3:派生类指针指向派生类对象
因此,
pDerived -> v = 128;
实际上改写了 objDerived.n。本程序用 Dev C++ 4.9.9.2 编译后,输出结果如上所述。不同编译器在栈中放置局部变量的方式有所不同,用 Visual Studio 编译本程序,输出结果就未必如此。
基类引用也可以强制转换为派生类引用。将基类指针强制转换为派生类指针,或将基类引用强制转换为派生类引用,都有安全隐患。
C++提供了 dynamic_cast 强制类型转换运算符,可以判断这两种转换是否安全(即转换后的指针或引用是否真的指向或引用派生类对象)。为方便讲述,本教程将 dynamic_cast 运算符放在最后一章“C++高级主题”中讲解,但是强烈建议读者掌握这部分内容。
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算