在 C# 中,方法参数的传递方式是一个重要的概念,尤其是在处理引用类型时。尽管引用类型的对象是通过引用传递的,但传递的实际上是引用的副本。这意味着修改引用的指向不会影响原始对象的引用指向。
直接看代码,我写了一个基本的学生类和一个交换学生信息的函数:
class Student
{
int id;
string name;
Student(int id,string name)
{
this.id = id;
this.name = name;
}
static void Main(string[] args)
{
Student a = new Student(1, "小a");
Student b = new Student(2, "小b");
SwapStudent(a, b);
Console.WriteLine($"{a.id} {a.name}");
Console.WriteLine($"{b.id} {b.name}");
}
public static void SwapStudent(Student s1 , Student s2)
{
Student temp = s1;
s1 = s2;
s2 = temp;
}
}
如果你认为打印之后,输出的是2 小b 1 小a
那你就和我犯了相同的错误!实际上输出的还是1 小a 2 小b
我原本认为,这不是引用类型吗,传递的是引用啊,我修改了引用的指向,那么原对象的信息应该会交换啊?后来经过学习才发现:
在 C# 中,引用类型存储的是对象的内存地址,指向该对象在堆中的实际数据。当你传递一个引用类型的对象时,传递的是这个对象的引用的副本,而不是原始的引用。
这意味着:
- 如果你修改引用所指向对象的属性,由于两个引用指向同一个对象,所以这些修改会影响到原对象。
- 如果你重新赋值引用本身,这个赋值只会影响这个副本引用,而不会影响原始引用。
同理,在C++中传递指针时,也是类似的,传递的是指针的副本而非原始的指针。
那么可能你就想问了,那我就没有办法传递原始的引用吗?我就想在函数中修改引用的指向!
答案是有的—— ref !我们来修改一下代码
static void Main(string[] args)
{
Student a = new Student(1, "小a");
Student b = new Student(2, "小b");
SwapStudent(ref a, ref b);
Console.WriteLine($"{a.id} {a.name}");
Console.WriteLine($"{b.id} {b.name}");
}
public static void SwapStudent(ref Student s1 , ref Student s2)
{
Student temp = s1;
s1 = s2;
s2 = temp;
}
加了ref关键字后,函数接收到的是变量的实际引用。通过这种方式,函数内部修改引用的指向会影响外部的实际变量,因此就可以正确地交换两个变量了。
明白以后,我开始思考,C++中没有ref关键字,C++中可以通过&来传递引用,但是通过&传递的引用是不可以重新修改指向的,那C++中有没有办法实现类似C#中的ref的效果了吗?
gpt给了我这样的解答:
#include <iostream>
#include <string>
class Student {
public:
int id;
std::string name;
Student(int id, const std::string& name) {
this->id = id;
this->name = name;
}
};
// 交换学生对象的函数,使用指针的指针
void SwapStudent(Student** s1, Student** s2) {
Student* temp = *s1; // 保存原 s1 的指向
*s1 = *s2; // 将 s1 指向 s2
*s2 = temp; // 将 s2 指向原 s1
}
int main() {
Student* a = new Student(1, "小a");
Student* b = new Student(2, "小b");
SwapStudent(&a, &b); // 传递指针的地址
std::cout << a->id << " " << a->name << std::endl; // 输出 2 小b
std::cout << b->id << " " << b->name << std::endl; // 输出 1 小a
// 清理内存
delete a;
delete b;
return 0;
}