【C#学习】18传值,输出,引用,数组,具名,可选参数,扩展方法

传值参数(值参数)

1.传值参数的定义

在这里插入图片描述
值参数创建变量的副本,对值参数的操作永远不会影响变量的值

2.值类型的传值参数

在这里插入图片描述

代码示例
在这里插入图片描述
说明:
(1)首先创建一个Student类,为该类创建一个AddOne()方法,该方法的形参是 int x,是一个值类型的传值参数(int 32 是结构体类型,且声明该参数时没有带任何修饰符),该方法的逻辑是把传入的实参在数值上加一,然后打印出来

(2)创建Student类的实例,并交给变量stu引用

(3)声明一个变量int y,将100赋值给y

(4)调用实例方法AddOne(),将变量y作为实参传入方法(由于传值形参的初始值来自该方法调用所提供的相应实参,所以值形参的初始值为100),程序跳转到方法内部运行

(5)但是,由于值参数会创建变量的副本,对值参数的操作永远不会影响变量的值,所以在方法调用后,参数有了新值,而变量y的值却没有改变

3.引用类型的传值参数(创建新对象)

在这里插入图片描述
图解:
(1)首先明确:引用类型的变量引用着引用类型的实例,引用类型变量存储的值,是引用类型实例在堆内存当中的地址

(2)当把一个引用着实例的引用类型的变量作为参数传进方法之后,会产生一个副本,该副本存储的值显然也是一个地址;也就是说:该副本(方法的参数)所存储的地址和方法外部的变量所存储的地址实际上是同一个地址,指向同一个对象

扫描二维码关注公众号,回复: 8823434 查看本文章

(3)如果在方法内部为参数赋了新值,也就是把一个新对象交给参数去引用,强调:由于值参数创建变量的副本,对值参数的操作不会影响变量的值,所以方法外部的变量还引用着先前的那个对象
(一把情况下,为引用类型变量赋值时,赋值符号右边都是new操作符的表达式;new操作符的作用:根据数据类型创建对象,并且调用对象的实例构造器,然后把实例在堆内存上的地址通过赋值符号交给引用变量)

代码示例
在这里插入图片描述
说明:
(1)创建一个Student类,为该类声明一个Name属性,在创建该类的实例时可以初始化Name属性的值

(2)声明一个静态方法SomeMethod(),该方法的传值形参 “stu” 的数据类型是引用类型,该方法的逻辑是:创建Student类的实例,将其Name属性的值初始化为 “Tom”,然后打印出来

(3)在Main()方法中声明一个引用类型的变量 “stu” ,其引用着一个Student类的实例,该实例的Name属性值被初始化为 “Tim”

(4)调用SomeMethod()方法,并将声明的变量 “stu” 作为实参传入进去

(5)再次强调:传值参数创建变量的副本,对值参数的操作永远不影响变量的值,所以方法运行的结果是打印出 “Tom”,而变量 "stu"的属性依然是 “Tim”

4.小知识:GetHashCode()方法

如果在方法体内部创建的实例的Name属性值也是 “Tim”,这时该如何分辨变量和参数引用的实例是否是同一个呢?
在这里插入图片描述
结果都为 “Tim”,不能再根据对象的Name属性值分辨出来参数和实例引用的是两个不同的对象,此时就需要用到GetHashCode()方法;该方法是Object类的方法,但由于所有类都是Object类直接或间接的派生类,所以所有类型都具有GetHashCode()方法
当调用该方法时,会得到一个代表对象的唯一值,即每个对象的HashCode都不一样
在这里插入图片描述

5.初学者常见的两个疑惑

(1)方法的参数名叫 “stu”,方法外部的变量名也叫 “stu”,是否在调用该方法时传入的变量(实参)的名字和参数(形参)的名字必须一样呢?
答:不需要一样,注意对应即可
在这里插入图片描述

(2)方法外部的变量 “stu”,和方法内部的参数 “stu” 不会冲突吗?
答:不会,因为它们两个的作用域完全不同;前者的作用域是在Main()方法体中,而后者的作用域是在SomeMethod()方法体中

6.引用类型的传值参数(不创建新对象,只操作对象)

在这里插入图片描述
图解:
(1)当引用类型的变量以传值参数的形式传进方法,此时参数的值和传进来的变量的值是同一个值:也就是对象在堆内存中的地址

(2)接着,通过参数访问了由参数所引用的对象,并修改了对象中的某个值;此时会发现:由于变量和参数指向的是同一个对象,通过参数修改了对象的值,那么当尝试用变量去访问该对象的值时,拿到的也是更新之后的值

代码示例
在这里插入图片描述

待解问题:传值参数不是创建变量的副本,对副本操作而不会影响变量本身吗?为什么在这种情况下变量所引用的对象的值还是会改变?

7.提醒

(1)第3点:像这种通过值参数把一个引用类型变量传进来,再把该变量先前所引用的对象抛弃掉,而在方法内部创建一个新的对象,这种操作在平时的编程当中并不常见
因为一般情况下,对于传进来的参数,我们只是读取它的值
(2)第6点:通过值参数把一个引用类型变量传进来并修改该变量所引用的对象的值的情况也是很少见的
因为作为一个方法而言,它的主要输出还是靠它的返回值;这种操作一般称为某个方法的副作用(side-effect),基本上是要注意避免的

输出参数

  • 可以把方法当做一个数据加工厂,方法的参数——加工厂的原料;方法的返回值——加工厂的产品;
  • 方法的返回值只有一个,如果希望一个方法一次调用能够产出多个数据时,就需要用到输出参数了;
  • 带有输出参数的方法一定具有副作用,而且我们是有意利用这个副作用:通过输出参数,来获得除返回值之外的额外的输出利用引用参数的副作用是打算修改传进来的那个值(因而也就可以理解为什么引用参数在传进方法之前必须要有一个明确的赋值:如果不提前赋值,那你修改个锤子?)

1.输出参数的定义

输出参数也不会为传进来的实参创建副本,输出形参与实参指向的是同一个内存位置
在这里插入图片描述
输出形参的作用是通过该参数向外输出,那么原来赋的值也就会被丢弃,也就没什么意义了;所以C#并不要求输出参数在传入方法之前要明确赋值,相反,由于输出参数是为了把值从方法体内输出到方法之外,所以在方法体内就必须对其有明确赋值

2.值类型的输出参数

在这里插入图片描述
图解:
(1)值类型的变量以输出参数的形式传进方法,输出参数和变量指向的内存地址是同一个;注意:作为输出参数传进方法的变量的初始值可有可无

(2)在方法体内为输出参数进行赋值,由于输出参数和外部变量指向的是同一内存地址,所以变量也获得了新值

3.调用带有输出参数的方法

在C#的类库当中,有些类型已经有了带有输出参数的方法,比如 int类型,double类型;在这里用double类型的TryParse()方法示例
在这里插入图片描述

4.声明带有输出参数的方法

在这里插入图片描述

5.引用类型的输出参数

在这里插入图片描述
图解:
(1)把引用类型的变量以输出参数的形式传进方法,输出参数不为变量创建副本,参数和变量指向同一个地址;输出参数的初始值可有可无

(2)对于一个引用类型的变量来说,如果它有初始值,则该变量引用着堆上的一个对象;如果没有初始值,则说明该变量没有引用任何对象

(3)如果在方法体内为输出参数赋了新值,也就是说把一个新对象的地址交还给输出参数,那么由于变量和参数指向的是同一个地址,所以变量所引用的内存地址也被更新,指向了新对象

代码示例:
在这里插入图片描述

引用参数

1.引用参数的定义

在这里插入图片描述
引用参数不会为传进来的实参创建副本,它直接指向传进来的实参所指向的内存地址
当调用带有引用参数的方法时,作为实参所传进来的变量,之前一定要有明确的赋值,而且,在调用时,引用参数前也要加 “ref” 关键字(起提示作用:此方法的副作用是改变实参的值)

2.值类型的引用参数

在这里插入图片描述
图解:
(1)有一个值类型的变量,具有初始值;然后以引用参数的形式将其传进方法,但是要注意:引用参数并不为传进来的变量创建副本

(2)在方法内部为引用参数赋了新值,由于引用参数和变量指向的是同一个地址,所以当参数获得了新值以后,方法外部的变量同时也获得了新值

代码示例:
在这里插入图片描述
说明:
方法的参数x与方法外部的变量y所指向的是同一个内存地址,当我们在方法内部改变了这个地址上的值的时候(x = x + 100),方法外部的变量y所指向的内存地址上的值也就改变了,所以当通过外部的变量y来访问内存地址中的值时,拿到的就是更新之后的值

3.引用类型的引用参数(创建新对象)

在这里插入图片描述
图解:
(1)有一个引用类型的变量引用着一个实例,把该变量以引用参数的形式传进方法;传进之后,引用参数并不会为该变量创建副本,引用参数和变量指向的是内存中的同一个地址,该地址中存储的是实例在堆内存上的地址

(2)在方法体中为参数赋了新值,也就是把一个新的对象交由参数引用,由于引用参数和变量指向的是内存中的同一个地址,所以此时方法外部的变量也指向了新创建的对象

代码示例:
在这里插入图片描述

4.引用类型的引用参数(不创建新对象,只改变对象值)

在这里插入图片描述
在方法体内并没有为引用类型赋新值,而是只通过引用参数去访问它所引用的对象,并改变了对象中的值;注意:此时对象并不是一个新对象,其HashCode并没有改变,改变的只是对象内部的字段或是属性的值
在这里插入图片描述

在这种情况下使用引用参数和使用传值参数效果是一样的,只是内存机理不一样

  • 如果在IWantSideEffect()方法中使用传值参数,那么传值参数就会在内存中创建实参的副本,也就是说:参数 “stu” 和变量 “outterstu” ,它们两个所指向的内存地址是不一样的,但这两个不一样的内存地址中,却存储着相同的地址——即实例在堆内存中的地址
  • 但如果在IWantSideEffect()方法中使用引用参数,参数 “stu” 和变量 “outterstu” ,它们两个所指向的内存地址就是完全一样

数组参数

在这里插入图片描述

1.为什么需要数组参数

  • 如果不用数组参数,要求一个数组的和,需要这样写代码:
    在这里插入图片描述
  • 如果用数组参数,就不用了再提前声明一个数组,在调用该方法时传入数组元素即可
    在这里插入图片描述
    当编译器发现是 “params参数” 时, 会自动声明一个数组,并把给出的元素放进数组,再传进方法

2.其实早就已经见过数组参数

(1)String.Format方法

int x = 100;
int y = 50;
int z = x + y;
Console.WriteLine("{0} + {1} = {2}",x,y,z)

(2)String.Split方法

string str = "Tom;Tim,Alice.Amy";
string[] result = str.Split(';',',','.');
foreach (var item in result)
{
	Console.WriteLine(item);
}
//Split()方法的返回值是字符串数组类型

3.使用数组参数需要注意

在一个方法的参数列表中,只能有一个参数是 params 数组参数,且必须是参数列表中的最后一个参数;否则,编译器会无法判断哪些参数是数组参数

具名参数

当调用一个方法时,传进去的参数是带有名字的

1.代码示例

在这里插入图片描述

2.具名参数的优点

(1)提高代码可读性
(2)参数的位置不再受参数列表顺序的约束
严格来说,具名参数并不是参数的某个种类,而是参数的使用方法

可选参数

在这里插入图片描述
可选参数:当调用方法时,该参数可写可不写;因为在声明方法时该参数是具有默认值的
代码示例:

static void Main(string[] args)
{
	Print();
}
static void Print(string Name = "Bob",int Age = 23);
{
	Console.WriteLine("{0} is {1} years old!",Name,Age);
}

扩展方法(this参数)

在这里插入图片描述

1.为什么需要扩展方法?

double x = 3.14159;
double y = Math.Round(x,4);
//double类型没有round()方法,必须要调用 Math的round()方法

如何让double类型具有round()方法呢?
一个天真的方法是修改源代码,但这是不可能实现的;首先,拿不到源代码,其次,就算拿到了,并且完成修改,也无法把编译结果放进C#类库当中
此时,就需要用到扩展方法,声明一个静态类,使用该类扩展double类型,约定俗成,这个类的名字应为 “DoubleExtension”(SomeTypeExtension)

2.代码示例

在这里插入图片描述
注意扩展方法的图标是:方法图标带有一个黑色的向下的小箭头

当我们无法修改一个类型的源代码时,就可以使用扩展方法为这种目标数据类型追加方法,C#中有很多重要的功能都是基于扩展方法的,比如:语言集成查询:LINQ方法

3.什么是LINQ?

LINQ方法又称为语言集成查询,功能非常强大
示例:
需要实现的逻辑:判断一个集合中的值是否都大于10

  • 如果不用LINQ方法,则需要写一个方法
    在这里插入图片描述

  • 如果使用LINQ方法,则只需要这样写代码
    在这里插入图片描述

各种参数使用场景总结

在这里插入图片描述

发布了29 篇原创文章 · 获赞 3 · 访问量 942

猜你喜欢

转载自blog.csdn.net/weixin_44813932/article/details/103967564