C#语言入门详解笔记(8)—P18 传值/输出/引用/数组/具名/可选参数,扩展方法
作者:互联网
目录
4.2、举例:String.Format方法和String.Split方法
5.3、严格来说,具名参数不是参数的种类,而是参数的使用方法
7.1、方法必需是公有、静态的,即被public static所修饰
7.3、必需由一个静态类(一般类名为SomeTypeExtension)来统一收纳对SomeType类型的扩展方法
1、传值参数
1.1、定义
使用不带修饰符声明的参数是一个值参数。 值参数对应于一个局部变量,该局部变量从方法调用中提供的相应参数获取其初始值。
如果形参为值形参,则方法调用中的相应参数必须是可隐式转换为形参类型的表达式。
允许方法为值参数赋值。 此类分配只会影响 value 参数所表示的本地存储位置,它们对方法调用中给定的实参不起作用。
1.2、传值参数➡值类型
class Program { static void Main(string[] args) { Student stu = new Student(); int y = 100; stu.AddOne(y); Console.WriteLine(y); } } class Student { public void AddOne(int x) //x是值参数 { x = x + 1; Console.WriteLine(x); } }
1.3、传值参数➡引用类型,并且新创建对象
- 引用类型变量在内存中存储的是一个地址
- 引用不会改变原值
class Program { static void Main(string[] args) { Student stu = new Student() { Name = "Tim"}; SomeMethod(stu); Console.WriteLine(stu.Name); } static void SomeMethod(Student stu) { stu = new Student() { Name = "Tom" }; //创建了一个新对象Tom,不会影响原变量 Console.WriteLine(stu.Name); } } class Student { public string Name { get; set; } }
如果两个都是Tim,可以通过GetHashCode()区分
class Program { static void Main(string[] args) { Student stu = new Student() { Name = "Tim"}; SomeMethod(stu); Console.WriteLine("{0},{1}", stu.GetHashCode(), stu.Name); } static void SomeMethod(Student stu) { stu = new Student() { Name = "Tim" }; //这种写法没有实际意义。面试会考 Console.WriteLine("{0},{1}", stu.GetHashCode(), stu.Name); } } class Student { public string Name { get; set; } }
1.4、传值参数➡引用类型,只操作对象,不创建新对象
class Program { static void Main(string[] args) { Student stu = new Student() { Name = "Tim"}; UpdateObject(stu); Console.WriteLine("HashCode = {0}, Name = {1}", stu.GetHashCode(), stu.Name); } static void UpdateObject(Student stu) { stu.Name = "Tom";//副作用,side-effect Console.WriteLine("HashCode = {0}, Name = {1}",stu.GetHashCode(),stu.Name); } } class Student { public string Name { get; set; } }
2、输出参数
2.1、定义
使用
out
修饰符声明的参数是一个输出参数。 与引用参数类似,output 参数不会创建新的存储位置。 相反,output 参数表示作为方法调用中的自变量提供的变量所在的存储位置。如果形参为 output 参数,则方法调用中的相应参数必须包含关键字
out
后跟与形参相同的类型的variable_reference (确定明确赋值的确切规则)。 在将变量作为 output 参数传递之前,不需要明确赋值,但在将变量作为 output 参数传递的调用之后,该变量被视为明确赋值。在方法中,就像局部变量一样,output 参数最初被视为未分配,在使用其值之前必须进行明确赋值。
方法返回之前,必须为方法的每个输出参数进行明确赋值。
2.2、输出参数➡值类型
图的左边 图的右边 值类型变量以输出参数的形式传入方法的时候,输出参数不为变量创建副本,所以用虚线框表示;
输出参数和变量指向的内存地址是同一个,而且请大家注意,作为输出参数传进来的变量,它可以具有初始值,也可以没有初始值。
方法体内为输出参数进行赋值之后的效果;
因为我们的输出参数和外部变量,指向的是内存当中的同一处地址;
所以说你为输出参数赋值,导致的效果就是变量也获得了新值。
2.2.1、调用一个带有输出参数的方法
class Program { static void Main(string[] args) { Console.WriteLine("Please input first number"); string arg1 = Console.ReadLine(); double x = 0; bool b1 = double.TryParse(arg1, out x); if (b1 == false) { Console.WriteLine("Input error!"); return; } Console.WriteLine("Please input first number"); string arg2 = Console.ReadLine(); double y = 0; bool b2 = double.TryParse(arg2, out y); if (b2 == false) { Console.WriteLine("Input error!"); return; } double z = x + y; Console.WriteLine("{0} + {1} + {2}", x, y, z); } }
2.2.2、声明一个带有输出参数的方法
class Program { static void Main(string[] args) { double x = 0; bool b = DoubleParser.TryParse("789", out x); if (b==true) { Console.WriteLine(x+1); } else { Console.WriteLine(x); } } class DoubleParser { public static bool TryParse(string input, out double result) { try { result = double.Parse(input); return true; } catch (Exception) { result = 0; return false; } } } }
2.3、输出参数➡引用类型
图的左边 图的右边 把引用类型的变量以输出参数的形式传进方法,这时候输出参数不为变量创建副本,也就是说参数和变量指向同一个地址;
输出参数,它可以具有初始值,也可以没有初始值。
对于一个引用类型的变量来说,如果它有初始值,说明它引用着堆上的一个对象;没值就是没引用任何对象。
方法体内为输出参数赋值之后的情形;
我们都知道,大多数情况下,为一个引用变量进行赋值,赋值符号的右边都是new操作符表达式;
现在我们把new操作符创建出来的对象的地址,交给输出参数,而输出参数和变量,它指向的是同一个地址;
这个地址存储的是新创建对象在堆内存上的地址。
从代码上来看,我们在方法体内把新对象交由输出参数进行引用;实际效果是方法外的变量,也引用上了新创建的对象
class Program { static void Main(string[] args) { Student stu = null; bool b = StudentFactory.Create("Tim", 34, out stu); if (b == true) { Console.WriteLine("Student {0}, age is {1}", stu.Name, stu.Age); } } } class Student { public int Age { get; set; } public string Name { get; set; } } class StudentFactory { public static bool Create(string stuName, int stuAge, out Student result) { result = null; if (string.IsNullOrEmpty(stuName)) { return false; } if (stuAge < 20 || stuAge >80) { return false; } result = new Student() { Name = stuName, Age = stuAge }; return true; } }
3、引用参数
3.1、定义
使用
ref
修饰符声明的参数是一个引用参数。 与值参数不同,引用参数不会创建新的存储位置。 相反,引用参数表示作为方法调用中的自变量提供的变量所在的存储位置。如果形参是引用参数,则方法调用中的相应参数必须包含关键字
ref
后跟与形参相同的类型的variable_reference (确定明确赋值的确切规则)。 在将变量作为引用参数传递之前,必须对其进行明确赋值。在方法中,引用参数始终被视为明确赋值。
3.2、引用参数➡值类型
方法内值修改,方法外也会变化。因为他们是同一个地址的值。
class Program { static void Main(string[] args) { int y = 1; IWantSideEffect(ref y); Console.WriteLine(y); } static void IWantSideEffect(ref int x) { x = x + 100; } } class Student { public string Name { get; set; } }
3.3、引用参数➡引用类型,创建新对象
图的左边
图的右边
有一个引用类型的变量,它引用着一个实例(对象);
我们把这个引用类型的变量以引用参数的形式传进方法;
传进方法之后,引用参数并没有为引用的变量创建副本 ,引用参数和变量指向的是内存当中的同一个地址,
这个地址中存储的又是一个地址——对象在堆内存中的地址;
效果就是引用参数也引用这这个对象。
在方法体里面为我们的参数赋了新值;
我们都知道,如果你想对一个引用类型的变量赋新值的话,赋值号的右边是"new"操作符表达式;
这时候,由于参数和变量指向的是内存中的同一个地址,而且在这个地址里存储的是——对象在堆内存中的地址;
那这时候,虽然我们在方法体里是为参数赋值,而实际效果呢,是我们方法之外的这个变量,它也指向了新创建的这个对象。
class Program { static void Main(string[] args) { Student outterStu = new Student() { Name = "Tim" }; Console.WriteLine("HashCode = {0}, Name = {1}", outterStu.GetHashCode(), outterStu.Name); Console.WriteLine("____________________"); IWantSideEffect(ref outterStu); Console.WriteLine("HashCode = {0}, Name = {1}", outterStu.GetHashCode(), outterStu.Name); } static void IWantSideEffect(ref Student stu) //引用类型的引用参数 { stu = new Student() { Name = "Tom" }; Console.WriteLine("HashCode = {0}, Name = {1}", stu.GetHashCode(), stu.Name); } }
3.4、引用参数➡引用类型,不创建新对象只改变对象值
还有一种不常见的使用方式:在方法体内并没有为引用参数赋新值,而是只通过引用参数去访问它所引用的对象,并且更改了对象里的值;
这种情况下使用引用参数和传值参数,效果是一样的,只是在内存的机理上不一样。
传值参数创建了副本,引用参数是同一个地址。
class Program { static void Main(string[] args) { Student outterStu = new Student() { Name = "Tim" }; Console.WriteLine("HashCode = {0}, Name = {1}", outterStu.GetHashCode(), outterStu.Name); Console.WriteLine("__________________"); SomeSideEffect(ref outterStu); Console.WriteLine("HashCode = {0}, Name = {1}", outterStu.GetHashCode(), outterStu.Name); } static void SomeSideEffect(ref Student stu) { stu.Name = "Tom"; Console.WriteLine("HashCode = {0}, Name = {1}", stu.GetHashCode(), stu.Name); } }
和值参数做对比
class Program { static void Main(string[] args) { Student outterStu = new Student() { Name = "Tim" }; Console.WriteLine("HashCode = {0}, Name = {1}", outterStu.GetHashCode(), outterStu.Name); Console.WriteLine("__________________"); SomeSideEffect(outterStu); Console.WriteLine("HashCode = {0}, Name = {1}", outterStu.GetHashCode(), outterStu.Name); } static void SomeSideEffect(Student stu) { stu.Name = "Tom"; Console.WriteLine("HashCode = {0}, Name = {1}", stu.GetHashCode(), stu.Name); } }
4、数组参数
4.1、必需是形参列表中的最后一个,由params修饰
- 不使用params声明数组
class Program { static void Main(string[] args) { int[] myIntArray = new int[] { 1, 2, 3}; int result = CalculateSum(myIntArray); Console.WriteLine(result); } static int CalculateSum(int[] intArray) { int sum = 0; foreach (var item in intArray) { sum += item; } return sum; } }
- 使用params声明数组;如果params不是最后一个,程序无法判断参数属于数组还是下一个参数
class Program { static void Main(string[] args) { int result = CalculateSum(1, 2, 3); Console.WriteLine(result); } static int CalculateSum(params int[] intArray) { int sum = 0; foreach (var item in intArray) { sum += item; } return sum; } }
4.2、举例:String.Format方法和String.Split方法
- Console.WriteLine()方法自带的params
- String.Split()
static void Main(string[] args) { string str = "Tim;Tom,Amy.Lisa"; string[] result = str.Split(';', ',', '.'); foreach (var item in result) { Console.WriteLine(item); } }
5、具名参数
5.1、参数的位置不再受约束;
5.2、不具名调用和具名调用对比
- 不具名调用:参数类型必须一一对应
class Program { static void Main(string[] args) { PrintInfo("Tim", 34); } static void PrintInfo(string name, int age) { Console.WriteLine("Hello {0}, you are {1}", name, age); } }
- 具名调用:优点1,提高代码可读性,从代码上可以直接看出参数名字;优点2,参数的位置不再受约束。
static void Main(string[] args) { PrintInfo(name:"Tim", age:34); PrintInfo(age: 34, name:"Time"); } static void PrintInfo(string name, int age) { Console.WriteLine("Hello {0}, you are {1}", name, age); }
5.3、严格来说,具名参数不是参数的种类,而是参数的使用方法
6、可选参数
6.1、参数因为具有默认值而变得“可选”
static void Main(string[] args) { PrintInfo(); } static void PrintInfo(string name = "Tim", int age = 34) { Console.WriteLine("Hello {0}, you are {1}", name, age); }
6.2、不推荐使用可选参数
7、扩展方法(this参数)
7.1、方法必需是公有、静态的,即被public static所修饰
7.2、必需是形参列表中的第一个,由this修饰
7.3、必需由一个静态类(一般类名为SomeTypeExtension)来统一收纳对SomeType类型的扩展方法
(1)double类型没有Round方法,只能通过Math.Round()来实现
static void Main(string[] args) { double x = 3.14159; double y = Math.Round(x, 4); Console.WriteLine(y); }
(2)通过扩展方法为double追加Round()方法;当我们无法对源码进行修改时,可以这样去追加方法。
class Program { static void Main(string[] args) { double x = 3.14159; double y = x.Round(4); Console.WriteLine(y); } } // 7.1、方法必需是公有、静态的,即被public static所修饰 static class DoubleExtension // 7.3、必需由一个静态类(一般类名为SomeTypeExtension)来统一收纳对SomeType类型的扩展方法 { public static double Round(this double input, int digits) // 7.2、必需是形参列表中的第一个,由this修饰 { double result = Math.Round(input, digits); return result; } }
7.4、举例;LINQ方法
- 普通写法
class Program { static void Main(string[] args) { List<int> myList = new List<int>() { 11, 12, 13, 14, 15}; bool result = AllGreaterThanTen(myList); Console.WriteLine(result); } //List是否所有元素都大于10 static bool AllGreaterThanTen(List<int> intList) { foreach (var item in intList) { if (item <= 10) { return false; } } return true; } }
- LINQ方法
class Program { static void Main(string[] args) { List<int> myList = new List<int>() { 11, 12, 13, 14, 15}; bool result = myList.All(i => i > 0); //这里的All是扩展方法 Console.WriteLine(result); } }
- All方法来源
各种参数的使用场景总结
-
传值参数:参数的默认传递方式
-
输出参数:用于除返回值外还需要输出的场景
-
引用参数:用于需要修改实际参数值的场景
-
数组参数:用于简化方法的调用
-
具名参数:提高可读性
-
可选参数:参数拥有默认值
-
扩展方法(this 参数):为目标数据类型“追加”方法
标签:Console,Name,C#,stu,参数,WriteLine,P18,static,传值 来源: https://blog.csdn.net/qq_31121227/article/details/112648015