面试题答案
一键面试1. 对象创建
- 指针:
- 当需要动态分配对象时,使用指针是常见选择。例如,如果基类
Base
有虚函数,且需要在运行时根据具体需求决定创建派生类Derived1
或Derived2
的对象,可这样做:
Base* ptr = nullptr; if (condition) { ptr = new Derived1(); } else { ptr = new Derived2(); }
- 指针的优势在于其灵活性,可在运行时改变指向的对象类型,实现多态行为。但需注意手动释放内存,否则会导致内存泄漏。在使用完
ptr
后,应使用delete ptr;
释放内存。
- 当需要动态分配对象时,使用指针是常见选择。例如,如果基类
- 引用:
- 引用通常用于在栈上创建对象的场景。如果已经确定了对象类型,且不需要改变其引用的对象,可使用引用。例如:
Derived1 obj; Base& ref = obj;
- 引用的好处是语法简洁,且无需担心空引用(引用必须初始化),在一定程度上提高了安全性。但由于引用一旦初始化后就不能再引用其他对象,缺乏指针那种动态改变指向的灵活性。
2. 对象销毁
- 指针:
- 对于通过
new
分配的对象指针,必须使用delete
进行释放。为避免内存泄漏,可结合智能指针使用。例如std::unique_ptr
:
std::unique_ptr<Base> ptr = std::make_unique<Derived1>(); // 当 ptr 离开作用域时,会自动调用 delete 释放对象内存
- 如果是数组形式的动态分配(
new[]
),则需要使用delete[]
释放,使用std::unique_ptr
时可使用std::make_unique<T[]>
来处理数组分配和释放。
- 对于通过
- 引用:
- 因为引用本身不负责对象的创建和销毁,所以在对象销毁方面,引用主要依赖其所引用对象的生命周期管理。当所引用对象在栈上创建时,它会随着栈的清理而自动销毁;如果所引用对象是通过动态分配并由指针管理,那么引用不会影响该对象的销毁操作,仍然由指针负责释放内存。
3. 对象间关系管理
- 指针:
- 在实现对象间的复杂关系(如父子关系、链表等)时,指针很有用。例如,一个链表节点类:
class ListNode { public: Base* data; ListNode* next; ListNode(Base* d, ListNode* n = nullptr) : data(d), next(n) {} ~ListNode() { delete data; delete next; } };
- 指针允许节点之间灵活地建立关系,并且可以在运行时修改这些关系。但在删除节点时,需要小心管理所指向对象的内存释放,以避免悬空指针(dangling pointer)问题。
- 引用:
- 当对象间关系相对固定,且不希望关系发生改变时,引用可用于表示这种关系。例如,一个容器类包含对其他对象的引用:
class Container { public: Base& ref; Container(Base& r) : ref(r) {} };
- 引用确保了对象间关系的稳定性,因为无法重新绑定引用。但如果被引用对象提前销毁,会导致未定义行为,所以在使用引用管理对象间关系时,需要保证被引用对象的生命周期足够长。
在复杂类层次结构且涉及动态内存分配的场景中,指针适合需要动态改变对象类型和建立灵活对象关系的情况,但要注意内存释放和悬空指针问题;引用适合对象类型固定且关系稳定的场景,能提供一定的安全性,但使用时要关注对象生命周期。合理结合智能指针使用指针,可以在保证灵活性的同时提高内存管理的安全性和高效性。