目录
[1.1 对于值类型(如int, double, struct等)](#1.1 对于值类型(如int, double, struct等))
[1.2 对于引用类型(如class)](#1.2 对于引用类型(如class))
[2. 按引用传递](#2. 按引用传递)
[2.1 ref](#2.1 ref)
[2.2 out](#2.2 out)
[2.3 in](#2.3 in)
[2.4 ref readonly](#2.4 ref readonly)
[3. params修饰符](#3. params修饰符)
1.按值传递(默认)
默认情况下,C#中的参数是按值传递的。这意味着方法获得的是变量的副本,而不是原始变量本身。
1.1 对于值类型(如int, double, struct等)
- 方法得到的是值的副本,所以在方法内修改参数不会影响原始变量。
举例:
void ChangeValue(int x)
{
x = 10;
}
int number = 5;
ChangeValue(number);
Console.WriteLine(number); // 输出5
1.2 对于引用类型(如class)
引用类型(如类class、数组、字符串等)的变量存储的是对象的引用(地址)。当按值传递引用类型时,传递的是引用的副本(即地址的副本),而不是对象本身的副本。
这意味着:
-
方法内通过引用副本可以访问和修改原始对象的内容(因为引用副本和原始引用指向同一个对象)。
-
但是,如果方法内将引用副本指向一个新的对象,那么这不会影响原始引用(因为修改的是引用的副本,而不是原始引用)。
举例:
class Person
{
public string Name { get; set; }
}
void ChangePerson(Person p)
{
p.Name = "Jane"; // 影响原始对象
p = new Person { Name = "John" }; // 不影响原始变量
}
var person = new Person { Name = "Jack" };
ChangePerson(person);
Console.WriteLine(person.Name); // 输出"Jane"
解释一下这个例子:
-
在调用ChangePerson时,我们将变量
person(它包含对原始Person对象的引用)按值传递给方法。注意,对于引用类型,按值传递的是引用的副本。所以,在方法内部,参数p是原始引用的一份拷贝,它指向同一个对象。 -
步骤1:通过p修改对象的Name属性为"Jane"。因为p和原始变量
person指向同一个对象,所以这个修改会影响原始对象。因此,此时原始对象的Name变为"Jane"。 -
步骤2:将p重新赋值为一个新的Person对象(Name为"John")。注意,**这里只是改变了p(它是原始引用的副本)的指向,现在p指向一个新的对象。**但是,原始变量
person仍然指向原来的对象。所以,这个重新赋值不会影响原始变量。 -
因此,方法调用后,原始对象的Name是"Jane"。
2. 按引用传递
有时希望方法能够修改原始变量(无论是值类型还是引用类型),这时就需要按引用传递。C#提供了几个修饰符来实现按引用传递:ref, out, in, ref readonly。
2.1 ref
-
使用
ref关键字,方法获得的是原始变量的引用(即直接操作原始变量)。 -
调用方法前,变量必须被初始化。
举例:
-
使用ref按引用传递值类型
void ChangeValueRef(ref int x)
{
x = 10;
}int number = 5;
ChangeValueRef(ref number);
Console.WriteLine(number); // 输出10 -
使用ref按引用传递引用类型
void ChangePersonRef(ref Person p)
{
p.Name = "Jane"; // 修改对象内容
p = new Person { Name = "John" }; // 重新赋值,会影响原始变量
}var person = new Person { Name = "Jack" };
ChangePersonRef(ref person);
Console.WriteLine(person.Name); // 输出"John"
解释:
-
使用ref关键字按引用传递参数。这意味着我们传递的是原始变量
person的引用(即变量本身的地址,而不是引用的副本)。所以,在方法内部,参数p就是原始变量person的别名,对p的任何操作都会直接作用于原始变量。 -
步骤1:通过p修改对象的Name属性为"Jane"。这同样会修改原始对象,因为p和
person指向同一个对象。此时原始对象的Name变为"Jane"。 -
步骤2:将p重新赋值为一个新的Person对象。由于p是原始变量的别名,所以这个赋值操作实际上改变了原始变量
person的指向,现在person指向新的对象(Name为"John")。 -
因此,方法调用后,原始变量
person已经指向了新的对象,所以输出的是新对象的Name:"John"。
2.2 out
-
与
ref类似,但方法必须在返回前为out参数赋值。 -
调用方法前,变量可以不初始化。
void Initialize(out int x)
{
x = 100; // 必须赋值
}int number;
Initialize(out number);
Console.WriteLine(number); // 输出100
2.3 in
-
按引用传递,但方法不能修改参数(只读)。
-
调用方法前,变量必须被初始化。
void ReadOnlyIn(in int x)
{
// x = 10; // 错误,不能修改只读参数
Console.WriteLine(x);
}int number = 5;
ReadOnlyIn(in number);
2.4 ref readonly
- 类似于
in,但更明确地表示按引用传递只读参数。
注:方法声明时 用 ref readonly,方法调用时 用 ref或 in(或者都不用),并且调用时用的修饰符表达了参数的"性质"。
| 调用时修饰符 | 含义 | 适用情况 |
|---|---|---|
ref |
"我这个变量是可写的" | 必须是可写变量 |
in |
"我这个变量可能只读" | 可写变量或只读变量 |
| 无修饰符 | "让编译器决定" | 任何表达式(不推荐) |
举例:
int writableVar = 10; // 可写变量
readonly int readOnlyVar = 20; // 只读变量
// 方法声明:必须用 ref readonly
void ProcessData(ref readonly int data)
{
Console.WriteLine(data); // 只能读取,不能修改
}
// 用 ref:表示"这个变量是可写的"
ProcessData(ref writableVar); // 正确:writableVar 确实可写
// 用 ref:错误!
ProcessData(ref readOnlyVar); // 错误:readOnlyVar 是只读的
// 用 in:表示"这个变量可能是只读的"
ProcessData(in writableVar); // 正确:可写变量可以用 in
ProcessData(in readOnlyVar); // 正确:只读变量也可以用 in
// 表达式:不能用 ref 或 in
ProcessData(ref 10); // 错误!42不是变量
ProcessData(in (x + y)); // 错误!x+y是表达式,不是变量
ProcessData(10); // 正确!但会有警告
3. params修饰符
params关键字允许方法接受可变数量的参数。它通常用于数组参数。使用 params 修饰符可以简化方法调用,因为你可以直接传递多个参数,而不需要显式地创建数组。
举例:
传统方式(没有params)
// 需要先创建数组
int[] numbers = {1, 2, 3, 4, 5};
CalculateSum(numbers);
void CalculateSum(int[] numbers)
{
foreach (int num in numbers) {
Console.WriteLine(num);
}
}
使用params的方式
// 在参数类型前加 params 关键字
void PrintNumbers(params int[] numbers)
{
foreach (var number in numbers)
{
Console.WriteLine(number);
}
}
PrintNumbers(1, 2, 3); // 可以传递多个参数
PrintNumbers(new int[] { 4, 5, 6 }); // 也可以传递数组
注:只能有一个params参数;必须是数组类型;不能与ref、out等修饰符共用
举例:
void Method(string a, params int[] numbers) //正确
void Method(params int[] numbers1, params string[] numbers2) // 错误
void Method(params int[] numbers) // 正确:整数数组
void Method(params string[] names) // 正确:字符串数组
void Method(params int number) // 错误:不是数组!
void Method(params ref int[] numbers) // 错误:不能组合使用
4.总结
| 修饰符 | 作用 | 调用前要初始化? | 方法内要赋值? |
|---|---|---|---|
| 无 | 传副本 | 是 | 可选 |
| ref | 传引用 | 是 | 可选 |
| out | 输出结果 | 否 | 必须 |
| in | 只读引用 | 是 | 不能修改 |
学到了这里,咱俩真棒,记得按时吃饭(祝大家节日快乐~)
【本篇结束,新的知识会不定时补充】
感谢你的阅读!如果内容有帮助,欢迎 点赞❤️ + 收藏⭐ + 关注 支持! 😊