《第五章》——方法

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34536551/article/details/83713280


类型推断和var 关键字


● 可以使用关键字 var, 来表示任何可以从初始化语句的右边推断出的类型,(类似于C++11 中的auto 语句)。

使用var关键字注意的有:

只能用于本地变量,不能用于数据成员的声明,和成员函数的声明。

只能在变量声明中包含初始化时使用。

一旦编译器推断出变量的类型,它就是固定且不能更改的。

namespace Ch05Ex03
{
    class Program
    {
        public var mydad(int o); //错误
        public var mydd=19; //错误
        static void Main(string[] args)
        {
            var ival = 15;
            ival = 17;
            var jiji; //错误
            WriteLine(ival);
                
            ReadKey();
        }
    }
}


●   在C和C++中,可以声明一个本地变量,然后在嵌套块中声明另一个相同名称的本地变量。在内部范围,内部的名称隐藏了外部的名称。

在C#中不管嵌套级别如何,都不能在第一个名称的有效范围内声明另一个同名的本地变量。


值参数


●  使用值参数,通过将实参的值复制到形参的方式把数据传递给方法。 在方法调用时,系统做如下操作:

在栈中为形参分配空间。

将实参的值复制给形参。

●  值参数的实参不一定是变量,也可以是任何能计算成相应数据类型的表达式。

● 注意: 在把变量用作实参之前 ,变量必须被赋值(除非是输出参数),对于引用类型,变量可以设置为一个实际的引用或者null.

●  注意:值类型就是指类型本身包含其值。 值参数是把实参的值复制给形参。

●注意: 如果实参是引用类型的,并且使用值传递,当函数调用时, 引用被复制,结果实参和形参都引用堆中的同一个对象。


引用参数


● 使用引用参数时, 必须在函数的声明中和调用中都使用ref。

● 实参必须是变量,在用作实参前该变量必须被赋值。  如果该实参是引用类型变量, 可以赋值为一个引用 或者 null。

● 对于值传递,系统在栈上为形参分配内存,引用参数具有以下特征:

不会在栈上分配内存

实际情况是,形参的参数名将作为实参变量的别名,指向相同的位置。


引用类型作为值参数和引用参数


● 对于一个引用类型对象,不管是将其作为值参数传递还是作为引用参数传递,我们都可以在方法成员内部修改它的值。

不过它们还是有区别的的:

如果是值传递引用类型对象,当函数调用时,系统在栈上为形参分配内存,结果实参和形参都引用堆中的同一个对象。 意识就是说,有两个独立的变量指向同一个对象。

如果是引用传递类型对象,当函数调用时,系统不会为形参在栈上分配内存,实际情况是,形参的参数名将作为实参变量的别名,指向相同的位置。 意识就是说,形参和实参共用一块内存,它们都指向同一个对象。

●  如果我们在方法的内部设置形参本身,将引用类型对象作为值传递或者引用传递、将发生不同的行为:

如果将引用类型对象作为值传递,如果在方法的内部创建一个新对象并赋值给形参,将切断形参和实参之间的关联, 并且在方法调用结束后,新对象也将不复存在。

将引用类型对象作为引用传递, 如果在方法的内部创建一个新对象并赋值给形参, 在方法调用结束后,新对象依然存在,并且是实参所引用的值。

如果将引用类型对象作为值传递的代码:

namespace Ch05Ex03
{
    class MyClass
    {
        public int Val = 20;
    }
    class Program
    {
       static void RefAsParameter(MyClass f1)
        {
            f1.Val = 50;
            WriteLine($"赋值之后的成员数据为:{f1.Val}");

            f1 = new MyClass();
            WriteLine($"创建一个新对象的数据成员为:{f1.Val}");
        }

        static void Main(string[] args)
        {
            MyClass a1 = new MyClass();
            WriteLine($"调用函数之前的数据为:{a1.Val}");
            RefAsParameter(a1);
            WriteLine($"调用函数之后的数据为:{a1.Val}");

            ReadKey();
        }
    }
}

输出结果为:
调用函数之前的数据为:20
赋值之后的成员数据为:50
创建一个新对象的数据成员为:20
调用函数之后的数据为:50

当方法分配新的对象并赋值给形参时,方法外部的实参仍指向原始对象,而形参指向的是新对象。

在方法调用之后,实参指向原始对象,形参和新对象都会消失。

如果将引用类型对象作为引用传递的代码:

namespace Ch05Ex03
{
    class MyClass
    {
        public int Val = 20;
    }
    class Program
    {
       static void RefAsParameter(ref MyClass f1)
        {
            f1.Val = 50;
            WriteLine($"赋值之后的成员数据为:{f1.Val}");

            f1 = new MyClass();
            WriteLine($"创建一个新对象的数据成员为:{f1.Val}");
        }

        static void Main(string[] args)
        {
            MyClass a1 = new MyClass();
            WriteLine($"调用函数之前的数据为:{a1.Val}");
            RefAsParameter(ref a1);
            WriteLine($"调用函数之后的数据为:{a1.Val}");

            ReadKey();
        }
    }
}

输出结果为:
调用函数之前的数据为:20
赋值之后的成员数据为:50
创建一个新对象的数据成员为:20
调用函数之后的数据为:20

因为引用参数的行为就像是将实参做为形参的别名:

在方法调用时,形参和实参都指向堆中相同的对象。

对成员值的修改会同时影响到形参和实参。

当方法创建新的对象并赋值给形参时,形参和实参的引用都指向该新对象。

在方法结束后,实参指向在方法内创建的新对象。


输出参数


● 输出参数用于从方法体内把数据传出到调用代码,它的行为类似于引用参数。 使用输出参数注意:

必须在声明和调用中都使用修饰符 out。

和引用参数类似,实参必须是变量,而不能是其他类型的表达式。这是因为方法需要内存位置保存返回值。

● 与引用参数类似,输出参数的形参作为实参的别名。 形参和实参都是指向同一块内存位置的名称。

输出参数的要求有:

在方法内部,输出参数在能够被输出之前必须被赋值。 这意味着这与形参的初始值是无关的,而且没有必要在方法调用之前为实参赋值。

在方法返回之前, 方法内部贯穿的任何可能路径都必须为所有输出参数进行一次赋值。

●  因为在函数的体内我们需要读取输出变量之前必须对其写入,所以不可能使用输出参数把数据传入方法,

事实上,如果函数中有任何路径试图在输出参数被函数赋值之前读取它,就会发生错误。

namespace Ch05Ex03
{
    class MyClass
    {
        public int Val = 20;
    }
    class Program
    {
       static void RefAsParameter(out MyClass f1,out int f2)
        {
            f1 = new MyClass();
            f1.Val = 25;
            f2 = 15;
            WriteLine($"在函数中输出f1.va1的值和 f2的值:{f1.Val} , {f2}");
        }

        static void Main(string[] args)
        {
            MyClass a1 = null;
            int a2;
            RefAsParameter(out a1,out a2);
            WriteLine($"在主函数中输出a1.va1的值和 a2的值:{a1.Val} , {a2}");
            ReadKey();
        }
    }
}

输出结果为:
在函数中输出f1.va1的值和 f2的值:25 , 15
在主函数中输出f1.va1的值和 f2的值:25 , 15


参数数组


● 参数数组允许零个或者多个实参对应一个特殊的形参,重点如下:

在一个参数列表中只能有一个参数数组。而且必须是最后一个。

由参数数组表示的所有参数都必须具有相同的类型。

●  注意: 数组也是一个引用类型,所以它的所有数据项都保存在堆中。

可以使用两种方式为参数数组提供实参,下面我们来看一下:

namespace Ch05Ex03
{
    class Program
    {
       static void ListInits(params int[] inVals)
        {
           foreach(var arr in inVals)
            {
                WriteLine(arr);
               
            }
            WriteLine();
        }

        static void Main(string[] args)
        {
            //第一种初始化方式,又是被称为延伸式,这种形式在调用中使用分离的实参。
            ListInits(10, 20, 30); //直接列表初始化,而且所有元素必须是函数中声明指定的类型。
            int[] inArray = { 11, 22, 33, }; //第二种初始化方式, 一个该数据类型元素的一维数组
            ListInits(inArray);
            ReadKey();
        }
    }
}

● 在使用第一种方式为参数熟数组分离实参的调用时,编译器会做以下的工作:

接受实参列表,用它们在堆中创建并初始化一个数组。

把数组的引用保存在栈中的形参里。

如果在对应数组的形参中没有实参,编译器会创建一个有零个元素的数组来使用。

na
namespace Ch05Ex03
{
    class Program
    {
       static void ListInits(params int[] inVals)
        {
            if (inVals != null &&  inVals.Length!=0 )
            {
                for(int i=0; i<inVals.Length;++i)
                {
                    inVals[i] = inVals[i] * 10;
                    WriteLine($"{inVals[i]}");
                }
            }
        }

        static void Main(string[] args)
        {
         
            int first = 5, second = 6, third = 7;
            ListInits(first, second, third);
            WriteLine($"{first}, {second}, {third}");
            ReadKey();
        }
    }
}

输出结果为:
50
60
70
5, 6, 7

● 注意: 该程序中当数组在堆中被创建时, 实参的值被复制到数组中。 采用的是值传递的方式。

● 如果数组参数是采用值传递时,那么值被复制,实参不受函数内部的影响。

如果数组参数采用的是引用类型传递,那么引用被复制,实参引用的对象可以受到函数内部的影响。


命名参数


● 如果在在函数中调用函数,该函数的实参必须跟形参的位置一一对应。

● 命名参数指的是只要在实参列表中指定参数的名字,同时后面跟一个 :符号,在在写一个实参值(也可以是表达式),这样我们就可以在调用函数中以任意顺序列出实参。

namespace Ch05Ex03
{
    class Program
    {
       static int ListInits(int a,int b,int c)
        {
            return (a + b) * c;
           
        }

        static void Main(string[] args)
        {
            int result = ListInits(c: 2, a: 4, b: 3); // 这样使用命名参数来传递实参
            WriteLine($"输出result的结果:{result}");

            ReadKey();
        }
    }
}

在调用函数中我们既可以使用位置参数也可以使用命名参数,但注意的是,所有的位置参数必须先写在实参表的前面。

namespace Ch05Ex03
{
    class Program
    {
       static int ListInits(int a,int b,int c)
        {
            return (a + b) * c;
           
        }

        static void Main(string[] args)
        {
            int result = ListInits(c: 2, a: 4, b: 3);
            WriteLine($"输出result的结果:{result}");

            WriteLine($"采用位置参数传递:{ListInits(5, 7, 8)}");

            WriteLine($"采用位置参数和命名参数传递:{ListInits(2, c: 6, b: 7)}"); //这里是位置参数和命名参数 混合使用

            WriteLine($"采用表达式传递:{ListInits(c: 2, a: 5 + 5, b: 7 + 7)}");
            ReadKey();
        }
    }
}

可选参数


●  对于默认值形参的声明,需要注意的有:

不是所有的数据类型都可以作为默认值形参的数据类型。

只要值类型的默认值可以在编译时确定,就可以使用值类型作为函数的默认值形参。

只有在默认值是null的时候, 引用类型才可以作为默认值形参来使用。

注意:如果有params 参数,必须在默认值参数之后声明。

猜你喜欢

转载自blog.csdn.net/qq_34536551/article/details/83713280