C#语言入门详解笔记(8)—P18 传值/输出/引用/数组/具名/可选参数,扩展方法

14 篇文章 4 订阅

C#语言入门详解_哔哩哔哩_bilibiliC#语言入门详解搬运,随youtube上进度更新。刘老师的C#视频教程到30集就告一段落了,感谢刘老师在这5年间的付出。能上youtube的同学可以去刘老师的主页观看,https://www.youtube.com/channel/UCmvAggiJGwJGSzAtqFRBQ7g重新上传,修复31P无声音问题https://www.bilibili.com/video/BV1wx411K7rb目录

1、传值参数

1.1、定义

1.2、传值参数➡值类型

1.3、传值参数➡引用类型,并且新创建对象 

1.4、传值参数➡引用类型,只操作对象,不创建新对象

2、输出参数

2.1、定义

2.2、输出参数➡值类型

2.2.1、调用一个带有输出参数的方法

2.2.2、声明一个带有输出参数的方法

2.3、输出参数➡引用类型

3、引用参数

3.1、定义

3.2、引用参数➡值类型

3.3、引用参数➡引用类型,创建新对象

3.4、引用参数➡引用类型,不创建新对象只改变对象值

4、数组参数

4.1、必需是形参列表中的最后一个,由params修饰

4.2、举例:String.Format方法和String.Split方法

5、具名参数

5.1、参数的位置不再受约束;

5.2、不具名调用和具名调用对比

5.3、严格来说,具名参数不是参数的种类,而是参数的使用方法 

6、可选参数

6.1、参数因为具有默认值而变得“可选”

6.2、不推荐使用可选参数

7、扩展方法(this参数)

7.1、方法必需是公有、静态的,即被public static所修饰

7.2、必需是形参列表中的第一个,由this修饰

7.3、必需由一个静态类(一般类名为SomeTypeExtension)来统一收纳对SomeType类型的扩展方法

7.4、举例;LINQ方法

各种参数的使用场景总结

传值参数:参数的默认传递方式

输出参数:用于除返回值外还需要输出的场景

引用参数:用于需要修改实际参数值的场景

数组参数:用于简化方法的调用

具名参数:提高可读性

可选参数:参数拥有默认值

扩展方法(this 参数):为目标数据类型“追加”方法


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 参数):为目标数据类型“追加”方法

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值