面试题答案
一键面试1. 原理差异
- 内存寻址方式:
- 按引用传递:引用本质上是一个别名,它和被引用的对象共享同一块内存地址。在函数调用时,编译器会确保引用始终指向传递进来的实参对象,无需额外的间接寻址。
- 按指针传递:指针存储的是对象的内存地址。在函数内部通过指针访问对象时,需要先获取指针所指向的地址,然后通过该地址访问对象,这涉及到间接寻址操作。
- 语法特点:
- 按引用传递:在函数声明和定义中,参数列表使用
&
符号来标识引用参数。例如void func(int& num)
。在调用函数时,直接传递变量名,无需取地址操作。 - 按指针传递:在函数声明和定义中,参数列表使用
*
符号来标识指针参数。例如void func(int* num)
。在调用函数时,需要传递变量的地址,使用&
符号取地址。
- 按引用传递:在函数声明和定义中,参数列表使用
- 使用场景:
- 按引用传递:适用于需要修改实参且希望代码更简洁、直观的场景,尤其是在处理对象时,避免了指针可能带来的空指针等问题。常用于函数需要返回多个值,通过引用参数来“输出”结果。
- 按指针传递:适用于需要动态分配内存、处理可能为空的对象或者需要进行指针运算(如数组遍历)的场景。在实现链表、树等数据结构的操作时经常使用。
- 可能引发的错误:
- 按引用传递:由于引用必须初始化且不能重新赋值,一旦初始化后就不会变为空引用。但是如果传递的实参本身是一个无效的对象,那么引用操作可能会导致未定义行为。
- 按指针传递:空指针是常见问题,如果在使用指针前没有检查其是否为空,对空指针进行解引用操作会导致程序崩溃。另外,指针运算时如果越界,也会引发未定义行为。
2. 代码示例 - 自定义链表
// 定义链表节点结构体
struct ListNode {
int data;
ListNode* next;
ListNode(int val) : data(val), next(nullptr) {}
};
// 使用引用传递修改节点数据
void modifyNodeDataByReference(ListNode& node, int newData) {
node.data = newData;
}
// 使用指针传递修改节点数据
void modifyNodeDataByPointer(ListNode* node, int newData) {
if (node != nullptr) {
node->data = newData;
}
}
int main() {
ListNode* head = new ListNode(10);
// 使用引用传递修改节点数据
modifyNodeDataByReference(*head, 20);
// 使用指针传递修改节点数据
modifyNodeDataByPointer(head, 30);
delete head;
return 0;
}
在上述代码中,modifyNodeDataByReference
函数通过引用传递修改节点数据,直接操作节点对象。modifyNodeDataByPointer
函数通过指针传递修改节点数据,需要先检查指针是否为空,再进行操作。两种方式都能成功修改链表节点的数据,但原理和语法略有不同。