C#知识学习-018(方法参数传递)

目录

1.按值传递(默认)

[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修饰符)

4.总结


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,​​方法调用时​ ​用 refin(或者都不用),并且调用时用的修饰符表达了参数的"性质"。

调用时修饰符 含义 适用情况
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 只读引用 不能修改

学到了这里,咱俩真棒,记得按时吃饭(祝大家节日快乐~)

【本篇结束,新的知识会不定时补充】

感谢你的阅读!如果内容有帮助,欢迎 ​​点赞❤️ + 收藏⭐ + 关注​​ 支持! 😊

相关推荐
AndrewHZ4 小时前
【图像处理基石】通过立体视觉重建建筑高度:原理、实操与代码实现
图像处理·人工智能·计算机视觉·智慧城市·三维重建·立体视觉·1024程序员节
py有趣4 小时前
LeetCode学习之实现strStr()
学习·算法·leetcode
sniper_fandc4 小时前
Elasticsearch从入门到进阶——搜索优化原理
elasticsearch·搜索引擎·lucene·1024程序员节
第二层皮-合肥4 小时前
硬件工程师11月实战项目-10G高速数字示波器开发
1024程序员节
PONY LEE4 小时前
Flink非对齐checkpoint踩坑记
1024程序员节
懒惰蜗牛4 小时前
Day10:Python实现Excel自动汇总
python·numpy·pandas·pip·1024程序员节·python读写excel
雨奔4 小时前
Flask 学习路线图
数据库·学习·flask
TDengine (老段)4 小时前
从“事后抢险”到“事前防控”:江西水投用 TDengine 时序数据库重塑防汛模式
大数据·数据库·物联网·时序数据库·tdengine·涛思数据·1024程序员节
傻童:CPU4 小时前
C语言需要掌握的基础知识点之DFS(深度优先搜索)
c语言·1024程序员节