C++ 中基类和派生类对象的赋值与转换
在 C++ 的继承关系中,基类(Base)和派生类(Derived)对象之间的赋值与转换是常见问题。理解这些规则不仅能避免常见的陷阱,还能帮助我们写出更安全的面向对象代码。本文将逐步介绍几种典型场景。
1. 基类与派生类对象的赋值
class Person
{
protected:std::string _name; std::string _sex; int _age;
};class Student : public Person
{
public:int _No; // 学号
};void Test()
{Student sobj;Person pobj = sobj; // ✅ 子类对象可以赋值给基类对象
}
这里发生了 切片(object slicing):
sobj
的基类部分被复制给pobj
;pobj
中只保留了Person
的那部分数据;Student
的扩展部分(如_No
)被“切掉”了。
👉 注意:基类对象无法保存派生类的完整信息。
2. 基类和派生类的指针/引用转换
(1)子类 → 父类
Student sobj;
Person* pp = &sobj; // ✅ 合法
Person& rp = sobj; // ✅ 合法
这是 向上转型(upcasting),始终安全,因为派生类一定包含完整的基类子对象。
(2)父类 → 子类
Person pobj;
Person* pp = &pobj;Student* ps = (Student*)pp; // ⚠️ 危险:强制类型转换
ps->_No = 10; // ❌ 未定义行为
虽然编译器允许你写 (Student*)pp
,但运行时 pp
指向的其实是一个 Person 对象,根本没有 _No
成员。这样会导致 非法内存访问,属于未定义行为。
3. 为什么有的情况能转,有的情况不能?
来看两个对比:
Student sobj;
Person pobj;Person* pp = &sobj;
Student* ps1 = (Student*)pp; // ✅ 安全,因为 pp 本来就指向 Student
ps1->_No = 10;pp = &pobj;
Student* ps2 = (Student*)pp; // ⚠️ 危险,因为 pp 指向的是 Person
ps2->_No = 10; // ❌ 越界访问
区别在于 指针的动态类型:
pp = &sobj;
→pp
指向的确实是一个Student
,强转后访问合法;pp = &pobj;
→pp
指向的只是Person
,强转后访问不存在的内存,出错。
👉 结论:强制转换不会检查真实类型,只是告诉编译器“相信我”。
4. 更安全的做法:dynamic_cast
如果需要从基类指针“安全”地转回派生类指针,应使用 dynamic_cast
:
Person* pp = new Student;Student* ps = dynamic_cast<Student*>(pp);
if (ps) {ps->_No = 100; // ✅ 转换成功
}Person* pp2 = new Person;
Student* ps2 = dynamic_cast<Student*>(pp2);
if (ps2 == nullptr) {std::cout << "转换失败,pp2 不是 Student!" << std::endl;
}
前提:基类中必须有至少一个虚函数(支持 RTTI 运行时类型识别)。
5. 总结
对象赋值:子类对象赋值给基类对象会发生切片,子类特有部分丢失。
向上转型(子类 → 基类):始终安全。
向下转型(基类 → 子类):必须小心,强制转换不安全,只有当指针真实指向派生类对象时才有效。
使用
dynamic_cast
能在运行时检查类型,避免未定义行为