C++ 和 Python 中的引用的区别
一、引言
今天读代码的时候,看到了这样一段:
1
2
graphs = [torch.cuda.CUDAGraph() for _ in range(4)]
tensors = sequential_V_exec(models, caches, graphs)
我对 Python 的掌握称不上熟练,读到这里时突然恍惚了:
传入函数 sequential_V_exec
的 graphs,在函数内被修改后,这种修改能否保留到函数返回后?
或者更简洁地说:Python 的函数传参,是值传递还是引用传递。
查阅资料发现,Python 的传参,与值传递(pass by value)以及引用传递(pass by reference)都不完全一样,可以被理解为是「对象引用传递(pass by object reference)」。这意味着,当你将一个变量作为参数传递给函数时,实际上传递的是对该变量所引用对象的引用,而不是变量本身的副本。
听上去更像是引用传递,但还是有区别的。
不过最开始的问题解决了:修改能否保留到函数返回后?能。
二、函数内发生修改?
2.1 Python 的参数传递
在上面的例子中,graphs
是一个列表,被作为参数传递到了函数 sequential_V_exec
内,如果在这个函数内部对其进行了修改,例如,添加、删除或修改列表中的元素,这些修改会反映到函数外部的 graphs
变量上。
这是因为,函数内外的 graphs
变量引用的是同一个列表对象。
截止到这里,它的表现还与引用传递相同。
但是,如果在函数内部将 graphs
重新绑定到一个新的列表上,这个改变不会影响到外部的 graphs
变量。
因为这样做仅仅改变了函数内部的 graphs
变量所引用的对象,而外部的 graphs
变量仍然引用原来的列表。
举例来说:
- 如果
sequential_V_exec
函数内部执行了如graphs.append(another_graph)
这样的操作,那么外部的graphs
列表也会包含这个新增的元素。 - 如果
sequential_V_exec
函数内部执行了如graphs = [some_other_graph]
这样的操作,那么这个改变不会影响到外部的graphs
变量,因为这仅仅改变了函数内部的graphs
变量所引用的对象。
2.2 C++ 的引用传递
C++ 中的引用传递(pass-by-reference)与 Python 中的对象引用传递有相似之处,但也有其特定的语法和规则。
在 C++ 中,引用作为参数传递,意味着函数接收的是原始变量的直接引用,同样不是其副本。因此,函数内对引用参数的任何修改都会直接反映到外部的原始变量上。
但是,C++ 的引用一旦被初始化后,就无法被改变去引用另一个对象。
补充:
引用在 C++ 中相当于原始变量的别名,对引用的任何操作实际上都是直接作用于原始变量上的。对引用求地址,就是对目标变量求地址。
引用在定义上是说引用不占据任何内存空间,但是编译器在一般将其实现为 const 指针,即指向位置不可变的指针,所以引用实际上与一般指针同样占用内存。
sizeof 指针对象和引用对象的意义不一样。sizeof 引用得到的是所指向的变量的大小,而 sizeof 指针是对象地址的大小。
那么,如果我在函数内部,尝试改变引用指向呢?
比如这段代码:
1
2
3
4
5
6
7
8
9
10
void attemptToRebindReference(std::vector<int>& vec) {
std::vector<int> newVec = {10, 20, 30};
vec = newVec; // 尝试改变引用指向
}
int main() {
std::vector<int> originalVec = {1, 2, 3};
attemptToRebindReference(originalVec);
return 0;
}
在函数调用后,输出 originalVec
,结果是什么呢?
发现,originalVec
的值确实改变了。
类似功能的代码在 Python 中运行,originalVec
是不会发生改变的,在 C++ 中,vec = newVec;
这行代码不会改变 vec
引用的指向,而是将 newVec
的内容复制到 vec
引用的原始对象 originalVec
中。引用的指向始终未变,始终指向 originalVec
。
在 C++ 中对引用重新赋值,相当于对原对象赋值,而在 Python 中则是更改引用的对象。