马鞍山做网站公司西安今天出大事
基类与派生类之间的转换
- **Day7-4 基类与派生类之间的转换**
- **一、问题回顾**
- **二、基类与派生类间的转换**
- **1. 类型适应(Upcasting)**
- **2. 逆向转换(Downcasting)**
- **三、代码示例**
- **四、派生类间的复制控制**
- **五、总结**
- **1. `base = derived`(对象赋值,发生切片)**
- **特点:**
- **2. `Base& ref = derived`(引用绑定,无切片)**
- **特点:**
- **3. 推荐程度**
- **更推荐的替代方案:`Base*`(指针)**
- **4. 要点**
- **最佳实践**
Day7-4 基类与派生类之间的转换
一、问题回顾
- 继承的形式有哪些?
- 三种继承方式的特点?
- 派生类的生成过程?
- 继承的局限?
- 派生类对象的创建和销毁的特点是什么?
- 多继承的问题有哪些?如何解决?
二、基类与派生类间的转换
1. 类型适应(Upcasting)
派生类对象可以适用于基类对象,即一个派生类的实例可以用基类的引用或指针来操作。C++ 允许以下转换:
-
赋值转换:
Base base; Derived derived; base = derived; // 允许,但发生对象切片
注意:派生类特有的数据会被丢弃。
-
引用转换(推荐):
Base& ref = derived; // 允许,ref 绑定到 derived 的基类部分
-
指针转换(推荐):
Base* pBase = &derived; // 允许,pBase 指向 derived 的基类部分
2. 逆向转换(Downcasting)
基类对象不能直接转换为派生类对象,除非使用强制转换:
Derived* pDerived = static_cast<Derived*>(&base); // 不安全!
原因:基类对象可能不包含派生类的数据,强制转换可能导致非法访问。
使用 dynamic_cast
在多态类中更安全:
Derived* pDerived = dynamic_cast<Derived*>(&base);
if (pDerived) {// 转换成功,pDerived 可用于 Derived 类型的操作
}
三、代码示例
#include <iostream>
using namespace std;class Base {
public:Base(double base = 0.0) : _base(base) {cout << "Base(double base = 0.0)" << endl;}virtual ~Base() { cout << "~Base()" << endl; }void print() const { cout << "Base::_base = " << _base << endl; }
private:double _base;
};class Derived : public Base {
public:Derived(double base = 0.0, double derived = 0.0) : Base(base), _derived(derived) {cout << "Derived(double = 0.0, double = 0.0)" << endl;}~Derived() { cout << "~Derived()" << endl; }void show() const { cout << "Derived::_derived = " << _derived << endl; }
private:double _derived;
};int main()
{Base base(11.11);base.print();Derived derived(22.22, 33.33);derived.show();cout << "派生类向基类转换";base = derived;//1.可以将派生类对象赋值给基类对象base.print();/*与用基类对象直接初始化不同(这时会发生对象切片),引用绑定不会拷贝对象,只是为派生类对象的基类部分创建了一个别名。*/Base& ref = derived;//2.基类的引用可以绑定到派生类对象ref.print();Base* pbase = &derived;//3.基类的指针可以指向派生类对象(向上转型)pbase->print();cout << " 基类向派生类转化" << endl;Base base1(100);Derived derived1(200, 300);//derived1 = base1;//error! 1.不能将基类对象赋值给派生类对象//等价转换的形式如下:ERROR!!!//Derived& operator=(const Derived & rhs);//derived1.operator=(base1);//不存在用户定义的从 "Base" 到 "const Derived" 的适当转换//const Derived& rhs = base1;//Derived& dref = base1; //error! 2.派生类的引用不能绑定到基类对象//Derived* pref = &base1;//error! 3.派生类的指针不能指向基类对象,只指向Base基类的前8个字节,后面的指针操纵了非法内存,有内存越界的风险//语法层面不支持向下转型 基类-->派生类 ,使用强制转换(本质上是不安全的)Derived* pderived1 = static_cast<Derived*>(&base1) ;//间接的表明了pderived2指向pderived1(可以认为是安全的)Derived* pderived2 = static_cast<Derived*>(&base1);test();return 0;
}
输出示例:
Base(double base = 0.0)
Derived(double = 0.0, double = 0.0)
Base::_base = 22.22
Base::_base = 22.22
~Derived()
~Base()
四、派生类间的复制控制
在继承体系中,复制控制(拷贝构造、赋值运算符)需要正确处理基类部分,否则可能导致资源泄漏或未定义行为。
要点:
- 在拷贝构造和赋值运算符中,必须显式调用基类的对应函数,否则基类部分不会被正确复制。
- 避免 资源泄露,在赋值运算符中先删除旧数据,再分配新数据。
- 析构函数要释放动态分配的资源,否则会导致内存泄漏。
//DerivedCopyControl1.cpp
#include <iostream>
#include <string.h>using namespace std;class Base
{friend std::ostream& operator<<(std::ostream& os, const Base& rhs);
public:Base():_pbase(nullptr){cout << "Base()" << endl;}Base(const char* pbase):_pbase(new char[strlen(pbase) + 1]){cout << "Base(const char* pbase)" << endl;strcpy_s(_pbase, strlen(pbase) + 1, pbase);}Base(const Base& rhs):_pbase(new char[strlen(rhs._pbase) + 1]()){cout << "Base(const Base& rhs)" << endl;strcpy_s(_pbase, strlen(rhs._pbase) + 1, rhs._pbase);}Base& operator=(const Base& rhs){cout << "Base& operator=(const Base& rhs)" << endl;if (this != &rhs){delete[] _pbase;_pbase = nullptr;_pbase = new char[strlen(rhs._pbase) + 1]();strcpy_s(_pbase, strlen(rhs._pbase) + 1, rhs._pbase);}return *this;}~Base(){cout << "~Base()" << endl;if (_pbase){delete[] _pbase;_pbase = nullptr;}}private:char* _pbase;
};std::ostream& operator<<(std::ostream& os, const Base& rhs)
{if (rhs._pbase){os << rhs._pbase;}return os;
}class Derived : public Base
{friend std::ostream& operator<<(std::ostream& os, const Derived& rhs);
public:Derived(const char* pbase,const char* pDerived):Base(pbase),_pDerived(new char[strlen(pDerived) + 1]){cout << "Derived(const char* pbase,const char* pDerived)" << endl;strcpy_s(_pDerived, strlen(pDerived) + 1, pDerived);}Derived(const Derived& rhs):_pDerived(new char[strlen(rhs._pDerived) + 1]()){cout << "Derived(const Derived& rhs)" << endl;strcpy_s(_pDerived, strlen(rhs._pDerived) + 1, rhs._pDerived);}Derived& operator=(const Derived& rhs){cout << "Derived& operator=(const Derived& rhs)" << endl;if (this != &rhs){delete[] _pDerived;_pDerived = nullptr;_pDerived = new char[strlen(rhs._pDerived) + 1]();strcpy_s(_pDerived, strlen(rhs._pDerived) + 1, rhs._pDerived);}return *this;}~Derived(){cout << "~Derived()" << endl;if (_pDerived){delete[] _pDerived;_pDerived = nullptr;}}private:char* _pDerived;
};std::ostream& operator<<(std::ostream& os, const Derived& rhs)
{//Base& ref = rhs;//error!将 "Base &" 类型的引用绑定到 "const Derived" 类型的初始值设定项时,限定符被丢弃const Base& ref = rhs;//正确写法os << ref << "," << rhs._pDerived;return os;
}void test()
{Derived derived("hello","world");cout << "derived = " << derived << endl;cout << endl;//用一个已经存在的对象去初始化一个刚刚创建的对象Derived derived2(derived);cout << "derived = " << derived << endl;cout << "derived2 = " << derived2 << endl;cout << endl;Derived derived3("cpp", "difficult");cout << "derived3 = " << derived3 << endl;cout << endl;//两个派生类对象之间进行赋值derived3 = derived;cout << "derived = " << derived << endl;cout << "derived3 = " << derived3 << endl;
}int main()
{test();return 0;
}
DerivedCopyControl2.cpp
版本的cpp文件是为了解决上一个版本(DerivedCopyControl1.cpp
)的潜在问题,主要问题如下:
- Base的拷贝构造函数和赋值运算符未处理源对象的_pbase为nullptr的情况,导致潜在崩溃。
- Derived的拷贝构造函数未调用Base的拷贝构造函数,导致基类部分未被正确复制。
- Derived的赋值运算符未调用Base的赋值运算符,导致基类部分未被正确赋值。
//DerivedCopyControl2.cpp
#include <iostream>
#include <string.h>using namespace std;class Base
{friend std::ostream& operator<<(std::ostream& os, const Base& rhs);
public:Base():_pbase(nullptr){cout << "Base()" << endl;}Base(const char* pbase):_pbase(new char[strlen(pbase) + 1]){cout << "Base(const char* pbase)" << endl;strcpy_s(_pbase, strlen(pbase) + 1, pbase);}/*Base(const Base& rhs):_pbase(new char[strlen(rhs._pbase) + 1]()){cout << "Base(const Base& rhs)" << endl;strcpy_s(_pbase, strlen(rhs._pbase) + 1, rhs._pbase);}*/// 拷贝构造函数Base(const Base& rhs) : _pbase(nullptr) {cout << "Base(const Base& rhs)" << endl;if (rhs._pbase) {_pbase = new char[strlen(rhs._pbase) + 1];strcpy_s(_pbase, strlen(rhs._pbase) + 1, rhs._pbase);}}/*Base& operator=(const Base& rhs){cout << "Base& operator=(const Base& rhs)" << endl;if (this != &rhs){delete[] _pbase;_pbase = nullptr;_pbase = new char[strlen(rhs._pbase) + 1]();strcpy_s(_pbase, strlen(rhs._pbase) + 1, rhs._pbase);}return *this;}*/// 赋值运算符Base& operator=(const Base& rhs) {cout << "Base& operator=(const Base& rhs)" << endl;if (this != &rhs) {delete[] _pbase;_pbase = nullptr;if (rhs._pbase) {//base = new char[strlen(rhs._pbase) + 1]();//使用new char[size]()会进行零初始化,随后strcpy_s会覆盖这些零。此举虽无害但影响性能。_pbase = new char[strlen(rhs._pbase) + 1]; // 无需()strcpy_s(_pbase, strlen(rhs._pbase) + 1, rhs._pbase);}}return *this;}~Base(){cout << "~Base()" << endl;if (_pbase){delete[] _pbase;_pbase = nullptr;}}private:char* _pbase;
};std::ostream& operator<<(std::ostream& os, const Base& rhs)
{if (rhs._pbase){os << rhs._pbase;}return os;
}class Derived : public Base
{friend std::ostream& operator<<(std::ostream& os, const Derived& rhs);
public:Derived(const char* pbase, const char* pDerived):Base(pbase), _pDerived(new char[strlen(pDerived) + 1]){cout << "Derived(const char* pbase,const char* pDerived)" << endl;strcpy_s(_pDerived, strlen(pDerived) + 1, pDerived);}/*Derived(const Derived& rhs):_pDerived(new char[strlen(rhs._pDerived) + 1]()){cout << "Derived(const Derived& rhs)" << endl;strcpy_s(_pDerived, strlen(rhs._pDerived) + 1, rhs._pDerived);}*/// 拷贝构造函数Derived(const Derived& rhs): Base(rhs), // 调用基类拷贝构造_pDerived(new char[strlen(rhs._pDerived) + 1]) {cout << "Derived(const Derived& rhs)" << endl;strcpy_s(_pDerived, strlen(rhs._pDerived) + 1, rhs._pDerived);}/*Derived& operator=(const Derived& rhs){cout << "Derived& operator=(const Derived& rhs)" << endl;if (this != &rhs){delete[] _pDerived;_pDerived = nullptr;_pDerived = new char[strlen(rhs._pDerived) + 1]();strcpy_s(_pDerived, strlen(rhs._pDerived) + 1, rhs._pDerived);}return *this;}*/// 赋值运算符Derived& operator=(const Derived& rhs) {if (this != &rhs) {Base::operator=(rhs); // 调用基类赋值运算符delete[] _pDerived;_pDerived = new char[strlen(rhs._pDerived) + 1];strcpy_s(_pDerived, strlen(rhs._pDerived) + 1, rhs._pDerived);}return *this;}~Derived(){cout << "~Derived()" << endl;if (_pDerived){delete[] _pDerived;_pDerived = nullptr;}}private:char* _pDerived;
};std::ostream& operator<<(std::ostream& os, const Derived& rhs)
{//Base& ref = rhs;//error!将 "Base &" 类型的引用绑定到 "const Derived" 类型的初始值设定项时,限定符被丢弃const Base& ref = rhs;//正确写法os << ref << rhs._pDerived;return os;
}void test()
{Derived derived("hello", "world");cout << "derived = " << derived << endl;cout << endl;//用一个已经存在的对象去初始化一个刚刚创建的对象Derived derived2(derived);cout << "derived = " << derived << endl;cout << "derived2 = " << derived2 << endl;cout << endl;Derived derived3("cpp", "difficult");cout << "derived3 = " << derived3 << endl;cout << endl;//两个派生类对象之间进行赋值derived3 = derived;cout << "derived = " << derived << endl;cout << "derived3 = " << derived3 << endl;}int main()
{test();return 0;
}
总结:
1、如果基类实现了拷贝构造函数或赋值运算符函数,但是派生类没有实现拷贝构造函数或赋值运算符函数,那么在将一个已经存在的派生类对象初始化一个刚刚创建的派生类对象,或者将两个派生类对象进行赋值,那么派生部分会执行缺省行为,而基类部分会执行基类的拷贝构造函数或者赋值运算符函数。
2、如果基类实现了拷贝构造函数或赋值运算符函数,并且派生类也实现拷贝构造函数或赋值运算符函数,那么在将一个已经存在的派生类对象初始化一个刚刚创建的派生类对象,或者将两个派生类对象进行赋值,那么派生部分会执行派生类自己的拷贝构造函数或者赋值运算符函数,而基类部分不会自动执行基类的拷贝构造函数或者赋值运算符函数,除非显示在派生类中调用基类的拷贝与赋值。
五、总结
1. base = derived
(对象赋值,发生切片)
Base base = derived; // 对象赋值,发生切片(slicing)
base.print(); // 调用 Base::print()
特点:
- 对象切片(Object Slicing):
derived
的派生类部分被丢弃,只保留Base
部分,base
是一个全新的Base
对象。 - 不推荐:除非你明确只需要基类部分,否则通常应该避免这种写法,因为它丢失了派生类的信息。
2. Base& ref = derived
(引用绑定,无切片)
Base& ref = derived; // 引用绑定,无切片
ref.print(); // 如果 print() 是虚函数,调用 Derived::print()
特点:
✅ 优点:
- 无对象切片:
ref
只是derived
的基类部分的引用,不会复制数据,派生类信息仍然完整。 - 支持多态:如果
Base::print()
是virtual
的,调用ref.print()
会正确调用Derived::print()
(动态绑定)。 - 更高效:避免了不必要的拷贝(特别是当
Base
较大时)。
❌ 缺点:
- 只能访问基类成员:通过
ref
无法直接访问Derived
的特有方法(如derived.extra()
)。 - 必须确保
derived
的生命周期:如果derived
被销毁,ref
会变成悬空引用(dangling reference),导致未定义行为(UB)。
3. 推荐程度
写法 | 是否推荐 | 适用场景 |
---|---|---|
Base base = derived; (赋值) | ❌ 不推荐 | 除非明确只需要基类部分 |
Base& ref = derived; (引用) | ✅ 推荐 | 需要多态、避免切片时 |
Base* ptr = &derived; (指针) | ✅ 更推荐 | 更灵活,可以存储、传递 |
更推荐的替代方案:Base*
(指针)
Base* ptr = &derived; // 基类指针指向派生类对象
ptr->print(); // 多态调用 Derived::print()
优点:
- 比引用更灵活(可以设为
nullptr
,可以修改指向的对象)。 - 常用于运行时多态(如工厂模式、容器存储不同派生类对象)。
4. 要点
Base& ref = derived
是推荐的,因为它避免了切片并支持多态,但必须确保derived
的生命周期足够长。Base* ptr = &derived
更推荐,因为指针更灵活,适用于更多场景(如存储在不同容器中)。Base base = derived
不推荐,除非你明确只需要基类部分(但通常应该使用组合而非继承)。
最佳实践
// ✅ 推荐:指针方式(更灵活)
Base* ptr = &derived;
ptr->print();// ✅ 也可以:引用方式(适用于局部作用域)
Base& ref = derived;
ref.print();// ❌ 不推荐:赋值方式(切片问题)
Base base = derived;
base.print();
如果你需要在函数参数中传递派生类对象,通常使用 const Base&
(避免拷贝)或 Base*
(更灵活)。