
这里的是&l为什么不是l呢?
这其实是 C++ 里的一个语法糖,叫"引用传递"。之所以写成 LinkList &L 而不是 LinkList L,核心原因就一句话:为了让函数内部对 L 的修改,能够真正影响到函数外部的原始链表。
核心区别:值传递 vs 引用传递
如果是 LinkList L(值传递)
这就好比**"复印件"**。
- 当你把链表传给函数时,系统会复制一份
L的副本(拷贝)给函数使用。 - 函数在内部对
L做任何修改(比如改变头指针指向、删除节点),都只是在那份"复印件"上操作。 - 函数结束后,复印件被销毁,原本外面的链表没有任何变化。
这里是 LinkList &L(引用传递)
这就好比**"本体"或者"外号"**。
LinkList &L意味着L就是外部传进来的那个链表变量的别名。- 函数内部对
L的操作,就是直接操作原本的链表。 - 函数结束后,链表的结构确实发生了改变(比如节点真的被删掉了)。
结合 ListDelete 函数的具体分析
这个函数的功能是删除 链表中第 i 个元素。删除操作通常涉及以下两种情况,这两种情况都必须修改链表本身:
修改头指针的情况(最常见)
如果要删除的是第一个节点(头节点之后的那个节点,或者直接删除头节点),你需要改变链表的"入口"(头指针)。
- 如果不加
&:你只是改变了副本的头指针指向,外面的链表头指针依然指着那个已经被逻辑删除的节点,程序会出错或内存泄漏。 - 加了
&:直接修改了外部的头指针,让它指向新的第一个节点。
修改指针指向
删除节点需要把前一个节点的 next 指针绕过当前节点,指向下下个节点(p->next = q->next)。虽然单靠指针遍历也能做到这一点,但使用引用传递能确保整个链表结构的状态在函数调用后是同步更新的。
总结
代码中使用 LinkList &L 是为了**"传引用"**。
- 如果不加
&:函数删了半天,外面的链表纹丝不动(或者只改了局部副本),删除失败。 - 加了
&:函数能直接"动手术",真正把节点从原链表上摘除。
补充提示 :这种 Type &Name 的写法是 C++ 的语法。如果是纯 C 语言,要实现同样的效果,通常需要写成二级指针 LinkList *L。
------------------------------------------
值传递与引用传递的核心区别在于:值传递传递的是参数的副本,修改形参不会影响实参;引用传递传递的是参数的引用(别名),修改形参会直接影响实参。
一、值传递详解
1. 基本概念
值传递是指函数调用时,编译器会创建原参数的一份"副本",将这份副本传递给函数的形参。函数内部对形参的所有修改,仅作用于这个副本,不会影响函数外部原参数的值。
2. 工作原理
- 基本数据类型(如int、float、boolean等)直接存储值
- 传递时,系统会为形参分配新的内存空间,将实参的值复制过去
- 形参和实参拥有各自独立的内存空间,互不影响
3. 代码示例
java
public static void modifyValue(int x) {
x = 20; // 只修改副本,不影响原始变量
}
public static void main(String[] args) {
int a = 10;
modifyValue(a);
System.out.println(a); // 输出:10(原始值未变)
}
4. 适用场景
- 传递基本数据类型
- 需要保护原始数据,避免被函数内部意外修改
- 小型数据的传递,避免不必要的引用开销
二、引用传递详解
1. 基本概念
引用传递是指函数调用时,不会创建原参数的副本,而是将原参数本身的引用(相当于"别名")直接传递给形参。函数内部对形参的修改,本质是通过引用直接操作外部原参数,会直接影响原参数的值。
2. 工作原理
- 引用类型(如对象、数组等)存储的是内存地址
- 传递时,形参和实参指向同一块内存地址
- 对形参的操作实际上是直接操作原对象
3. 代码示例
java
public static void modifyObjectProperty(User user) {
user.setAge(30); // 通过引用修改原对象属性
}
public static void main(String[] args) {
User user = new User("张三", 20);
modifyObjectProperty(user);
System.out.println(user); // 输出:User{name='张三', age=30}(原对象被修改)
}
4. 适用场景
- 需要修改对象状态的场景
- 大型对象的传递,避免深拷贝的性能开销
- 需要函数返回多个值的情况
三、关键误区与语言差异
1. Java中的特殊性
Java中只有值传递,没有真正的引用传递。这是许多开发者容易混淆的点:
- 基本类型:传递的是值的副本
- 引用类型:传递的是引用地址的副本(不是引用本身)
关键区别:
- 修改引用对象的属性:会影响原对象(因为副本和原引用指向同一块内存)
- 修改形参的引用指向:不会影响原对象(因为只是修改了副本的指向)
2. C++中的三种传递方式
C++提供了更丰富的选择:
- 值传递:默认方式,复制实参的值
- 引用传递 :使用
&符号,形参是实参的别名 - 指针传递:传递指针的副本,通过指针操作原对象
cpp
// 值传递
void modifyValue(int x) { x = 20; }
// 引用传递
void modifyReference(int &x) { x = 20; }
// 指针传递
void modifyPointer(int *x) { *x = 20; }
3. Python中的规则
Python根据数据类型自动选择传递方式:
- 值传递:用于不可变对象(如整数、字符串、元组)
- 引用传递:用于可变对象(如列表、字典、对象)
四、常见陷阱与最佳实践
1. 修改引用指向的陷阱
java
public static void modifyReferencePoint(User user) {
user = new User("李四", 25); // 仅修改副本指向,不影响原对象
}
public static void main(String[] args) {
User user = new User("张三", 20);
modifyReferencePoint(user);
System.out.println(user); // 仍输出:User{name='张三', age=20}
}
2. 避免意外修改
- 需要保护原始数据:使用值传递或创建对象的深拷贝
- 需要修改对象:明确文档说明,避免副作用
- 大型对象:优先考虑引用传递,避免性能开销
3. 实用技巧
- Java中模拟"引用传递" :可以使用数组或包装类(如
Integer[]) - C++中安全使用引用:确保引用不为null,避免悬空引用
- Python中控制可变性 :使用
tuple代替list,使用frozenset代替set
五、总结对比表
| 特性 | 值传递 | 引用传递 |
|---|---|---|
| 传递内容 | 参数的副本 | 参数的引用(别名) |
| 内存空间 | 独立的内存空间 | 与实参共享内存空间 |
| 修改影响 | 不影响实参 | 直接影响实参 |
| 适用类型 | 基本数据类型、不可变对象 | 对象、可变数据结构 |
| Java实现 | 基本类型 | 引用类型(实际是引用地址的副本) |
| C++实现 | 默认方式 | 使用&符号 |
| 性能开销 | 小对象开销小,大对象开销大 | 通常开销小(不复制数据) |
理解值传递与引用传递的区别是编写高效、可维护代码的基础。关键在于记住:值传递传递的是副本,引用传递传递的是别名。不同语言的实现细节可能有所不同,但核心概念是相通的。在实际编程中,应根据数据类型和需求选择合适的传递方式,避免不必要的性能开销和意外的副作用。