面试题答案
一键面试性能差异分析
- 内存分配:
- 指针:指针本身是一个变量,存储的是所指向对象的地址,在函数调用时,会为指针变量在栈上分配内存空间,其大小取决于系统架构(32 位系统通常为 4 字节,64 位系统通常为 8 字节)。
- 引用:引用本质上是一个别名,在函数调用时,并不会为引用额外分配内存空间,它与被引用的对象共享同一块内存。
- 栈空间使用:
- 指针:由于指针本身需要在栈上分配内存来存储地址,所以会占用一定的栈空间。
- 引用:因为不额外分配内存,所以相比指针,在栈空间使用上更节省。
- 数据拷贝次数:
- 指针:传递指针时,只是将指针变量的值(即地址)拷贝一份到函数栈中,无论所指向的数据量多大,拷贝的只是一个固定大小的地址值,数据本身不会被拷贝。
- 引用:传递引用时,同样不会拷贝数据本身,而是直接使用原对象的别名,所以在数据拷贝次数上,指针和引用是相同的,都只进行了地址(或别名)的传递,而非数据内容的拷贝。
不同场景下的选择
- 当需要表示“无对象”情况时:
- 指针:指针可以为
nullptr
,表示不指向任何对象。例如,在链表节点的删除操作中,删除节点后,指向该节点的指针可以设为nullptr
。
struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; void deleteNode(ListNode*& head, int val) { if (head == nullptr) return; if (head->val == val) { ListNode* temp = head; head = head->next; delete temp; return; } ListNode* cur = head; while (cur->next!= nullptr && cur->next->val!= val) { cur = cur->next; } if (cur->next!= nullptr) { ListNode* temp = cur->next; cur->next = cur->next->next; delete temp; } }
- 引用:引用必须初始化,不能表示“无对象”的情况。所以在这种可能需要表示“无对象”的场景下,应选择指针。
- 指针:指针可以为
- 当需要改变指向对象时:
- 指针:指针可以重新赋值,指向不同的对象。例如,在动态内存管理中,有时需要改变指针的指向。
int* ptr = new int(5); int* newPtr = new int(10); ptr = newPtr; delete newPtr;
- 引用:引用一旦初始化,就不能再引用其他对象。所以如果需要在函数内部改变所指向的对象,应选择指针。
- 当需要确保对象存在且不想额外处理空指针情况时:
- 引用:由于引用必须初始化且不能为
nullptr
,所以在调用函数时,能确保引用所关联的对象一定存在,代码更加简洁和安全。例如,在一些内部逻辑中,已知对象必然存在的场景。
void printValue(const int& ref) { std::cout << ref << std::endl; } int main() { int num = 10; printValue(num); return 0; }
- 指针:使用指针时,每次使用前都需要检查是否为
nullptr
,否则可能导致程序崩溃。在这种情况下,选择引用更优。
- 引用:由于引用必须初始化且不能为
- 当传递大对象时:
- 指针和引用:因为指针和引用都不会拷贝大对象本身,只是传递地址或别名,所以在性能上都优于值传递。在这种场景下,两者都可以选择,但如果确定对象一定存在,引用的语法更简洁。例如:
这里使用引用class BigObject { public: int data[10000]; BigObject() { for (int i = 0; i < 10000; i++) { data[i] = i; } } }; void processObject(const BigObject& obj) { // 处理对象逻辑 } void processObjectPtr(const BigObject* ptr) { if (ptr!= nullptr) { // 处理对象逻辑 } }
processObject
函数调用更简洁,且不需要额外检查nullptr
,如果对象可能为空,processObjectPtr
使用指针更合适。