第6章 方法 笔记

第6章 方法 笔记

6.1 方法的结构

方法是一段具有名称的代码。

方法主要有两部分:方法头、方法体。

方法头指定方法的特征,包括:

  • 返回类型。
  • 方法名称。
  • 参数列表。

方法体 包含可执行代码的语句序列。

6.2 方法体内部的代码执行

方法体是一个块,包含以下内容:

  • 局部变量;
  • 控制流结构;
  • 方法调用;
  • 内嵌的块;
  • 其他方法(局部函数)。
static void Main()
{
    // 局部变量
    int myInt = 3;
    
    // 控制流
    while(myInt > 0)
    {
        --myInt;
        // 调用方法
        PrintMyMessage();
    }
}

6.3 局部变量

局部变量用于保存局部或者临时的计算数据。

  • 局部变量的生存周期仅限于创建它的块内。
    • 声明时开始存在。
    • 块尾结束存在。
  • 可以在方法体内部任意位置声明,必须声明后才能使用。

6.3.1 类型推断和 var 关键字

var关键字是句法上的速记,可以从初始化语句的右边推断出类型。使用 var 关键字不需要明确指定变量类型。var 关键字并不改变 C# 的强类型性质。

使用条件:

  • 只能用于局部变量,不能用于字段。
  • 只能在变量声明中包含初始化时使用。
  • 一旦编译器推断出变量的类型,它就是固定且不能更改的。
static void Main()
{
    // 显示声明
    int total = 15;

	// 使用var
    var total = 15;
   
}

6.3.2 嵌套块中的局部变量

方法体内部可以嵌套其他的块。

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

6.4 局部常量

局部常量必须声明在块的内部。

重要特征

  • 声明时必须初始化。
    • 初始化值必须在编译时就可以确定,通常为预定义简单类型或 null 引用。
  • 声明后不能改变。
// 声明语法示例:
const Type Identifier = Value;

// 举例:
const double PI = 3.14;

注意:const 不是修饰符,而是核心声明的一部分,必须放在类型前面。

和局部变量一样,局部常量声明在方法体或代码块里,并在声明它的块结束的地方失效。

6.5 控制流

控制流语句运行改变执行的顺序

常用的控制流语句:

  • 选择语句 可以选择要执行的语句或语句块
    • if
    • if … else
    • switch
  • 迭代语句 可以在一个语句块上循环
    • for循环 - 顶部
    • while循环 - 顶部
    • do循环 - 底部
    • foreach
  • 跳转语句 可以从代码块或方法体内部一个地方跳到另一个地方
    • break 跳出当前循环
    • continue 到当前循环底部
    • goto 跳转到一个命名的语句
    • return 返回到调用方法继续执行
void SomeMethod() {
    int intVal = 3;
	
    // if statement
    if( intVal == 3 )
        Console.WriteLine("Value is 3. "); 
    
    // for statement
    for( int i=0; i<5; i++ ) 
        Console.WriteLine($"Value of i: { i }");
}

6.6 方法调用

可以在方法体的内部调用其他方法,调用时需要使用方法名,并加上参数列表。

class MyClass
{
	// Declare the method. 
    void PrintDateAndTime() 
    {
        // Get the current date and time.
        DateTime dt = DateTime.Now; 
        // Write it out.
        Console.WriteLine($"{ dt }"); 
    }
    
    // Declare the method.
    static void Main() 
    {
        MyClass mc = new MyClass();
        
        // Invoke the method.
        mc.PrintDateAndTime( ); 
    }
}

6.7 返回值

方法可以返回一个值,

要返回值,需要在方法名前面声明一个返回类型,使用返回语句从方法中返回一个值。

int GetHour( )
{
    // Get the current date and time.
    DateTime dt = DateTime.Now; 

    // Return an int.
    int hour = dt.Hour; 

    // Return an int.
    return hour; 
}

6.8 返回语句和 void 方法

使用返回语句提前退出方法,只能用于 void 方法

class MyClass
{
    void TimeUpdate()
    {
        DateTime dt = DateTime.Now; // Get the current date and time.
        if (dt.Hour < 12) // If the hour is less than 12,
            return; // then return.
        Return to calling method
            Console.WriteLine("It's afternoon!"); // Otherwise, print message.
    }
    
    static void Main()
    {
        MyClass mc = new MyClass(); // Create an instance of the class.
        mc.TimeUpdate(); // Invoke the method.
    }
}

6.9 局部函数

C# 7.0 开始,可以在一个方法中声明另一个单独的方法,称为局部函数。

class Program 
{
    public void MethodWithLocalFunction() 
    {
        // Local function declaration
        int MyLocalFunction(int z1) 
        {
	        return z1 * 5;
    	}
        // Call local function
        int results = MyLocalFunction(5);
        
        Console.WriteLine($ "Results of local function call: {results}");
    }
    
    static void Main(string[] args) 
    {
        Program myProgram = new Program();
        myProgram.MethodWithLocalFunction(); // Call Method
    }
}

6.10 参数

参数用来传入数据、返回数据

形参是局部变量,声明在方法的参数列表中。

形参有类型、名称,声明必须用逗号隔开,参数在方法体外面定义并初始化(输出参数是例外)

public void PrintSum( int x, int y )
{
	int sum = x + y;
	Console.WriteLine($"Newsflash:  { x } + { y } is { sum  }");
}

实参用于初始化形参的表达式或变量,

实参位于方法调用的参数列表中,

每一个实参必须与对应形参的类型相同,或者编译器能够把实参隐式转换为对应类型

class MyClass 
{
    // Formal parameters
    public int Sum(int x, int y) 
    {
        return x + y;
    }

    // Formal parameters
    public float Avg(float input1, float input2) 
    {
        return (input1 + input2) / 2.0 F;
    }
}

class Program 
{
    static void Main() 
    {
        MyClass myT = new MyClass();
        int someInt = 6;
        
        Console.WriteLine("Newsflash:  Sum: {0} and {1} is {2}",
            5, someInt, myT.Sum(5, someInt));
        
        Console.WriteLine("Newsflash:  Avg: {0} and {1} is {2}",
            5, someInt, myT.Avg(5, someInt));
    }
}

6.11 值参数

值参数,通过将实参的值复制到形参把数据传到方法。

使用值参数时,会发生如下操作:

在栈中为形参分配空间。
将实参的值复制给形参。

值参数的实参可以是变量(需要被赋值)、表达式。

方法使用值参数不能改变原始的值类型数据,但是可以改变引用类型的数据:

class MyClass { 
    public int Val = 20; 
}

class Program 
{
    // 形参
    static void MyMethod(MyClass f1, int f2) 
    { 
        f1.Val = f1.Val + 5;
        f2     = f2 + 5;
    }
    

    static void Main() 
    {
        MyClass a1 = new MyClass();
        int a2 = 10;
        // 实参
        MyMethod(a1, a2); 
    }

}

6.12 引用参数

引用参数是第二种参数类型

在使用引用参数时,必须在方法的声明和调用中都使用 ref 修饰符。
实参必须是已经被赋值的变量,引用类型变量可以是 null。

引用参数具有如下特征:

不会在栈上为形参分配内存。
形参的参数名将作为实参变量的别名,指向相同的内存位置。

class MyClass 
{
	// Initialize field to 20.
    public int Val = 20;
}

class Program
{
  	//  ref modifier ref modifier 
    static void MyMethod(ref MyClass f1, ref int f2) 
    {
        f1.Val = f1.Val + 5;
        f2 = f2 + 5;
        // Add 5 to field of f1 param.
        // Add 5 to second param.
        Console.WriteLine($ "f1.Val: {f1.Val}, f2: {f2 }");
    }

    static void Main() 
    {
        MyClass a1 = new MyClass();
        int a2 = 10;
        // ref modifiers
        MyMethod(ref a1, ref a2);
        // Call the method.
        Console.WriteLine($ "a1.Val: {a1.Val}, a2: {a2 }");
    }
}

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

对于引用类型对象:

作为值参数传递:如果在方法内创建一个新对象并赋值给形参,对实参没有影响。
作为引用参数传递:如果在方法内创建一个新对象并赋值给形参,则实参也会随之改变。

将引用类型对象作为引用参数传递,目的是改变引用对象;

如果仅需改变引用类型对象的内容,只需值参数传递即可。

6.14 输出参数

输出参数用于从方法体内把数据传出到调用代码,要求如下:

在方法的声明和调用中都使用 out 修饰符。
实参必须是变量,使用前可以不赋值。
形参的参数名也作为实参变量的别名,指向同一块内存位置。

与 ref 不同,out 有如下要求:

给输出参数赋值之后,才能读取。
方法返回之前,必须给输出参数赋值。

从C# 7.0 开始,对输出参数进行简化声明,不需要预先声明一个变量来用作 out 参数了。

声明后的 a1 和 a2 可以在方法调用结束后继续使用。

static void Main()
{
	MyMethod(out MyClass a1, out int a2 );    // Call the method.
   	Console.WriteLine(a2);                   // Use the return value
    
    a2 += 5;                                 // Write to the variable
    Console.WriteLine(a2);
}

6.15 参数数组

参数数组允许特定类型的N个实参对应一个特定的形参,

参数数组使用:
参数列表中只能有一个参数数组,且必须为参数列表的最后一个。
参数数组中的所有参数类型必须相同。

参数数组的声明:

在形参前使用 params 修饰符,并在数据类型后放置一组方括号。

void ListInts(params int [] inVals)
{
	// ...
}

6.15.1 方法调用

可以使用两种方式为参数数组提供实参:

使用逗号分隔的同数据类型的元素列表。

ListInts(10, 20, 30); // 3 个 int

一个同数据类型的一维数组。

int[] intArray = {1, 2, 3};
ListInts(intArray);

注意:在调用时不使用 params 修饰符。

延伸式

void ListInts(params int[] inVals) { ... } // 方法声明

// ...
ListInts();              // 0 个实参
ListInts(1, 2, 3);       // 3 个实参
ListInts(4, 5, 6, 7, 8); // 5 个实参

使用独立实参调用时,编译器将执行以下步骤:

接受实参列表,在堆中创建并初始化一个数组;
把数组的引用保存到栈中的形参里;
如果没有实参(个数为 0),则编译器会创建一个具有 0 个元素的数组来使用。

当数组在堆中被创建时,实参的值被赋值到数组中,因此可看做值参数:

数组参数为值类型:值被复制,实参不受影响;
数组参数为引用类型:引用被复制,实参引用的对象在内部会受到影响。

6.15.2 将数组作为实参

编译器将使用传入的数组而不是重新创建一个。

static void Main() 
{
    // Create and initialize array.  
    int[] myArr = new int[] {5, 6, 7};
    MyClass mc = new MyClass();
    mc.ListInts(myArr); // Call method to print the values.
    
    foreach(int x in myArr)
    {
      Console.WriteLine($ "{ x }"); // Print out each element.
    }
}

6.16 参数类型总结

4种参数类型

参数类型修饰符是否在声明时使用是否在调用时使用执行
系统把实参的值复制到形参
引用ref形参是实参的别名
输出out仅包含一个返回的值,形参是实参的别名
数组params允许传递可变数目的实参到方法

6.17 ref 局部变量和 ref 返回

ref局部变量,允许一个变量是另一个变量的别名。

创建别名的语法需要使用 ref 两次。

ref int y = ref x;
class Program 
{
    static void Main() 
    {
        int x = 2;
        ref int y = ref x;
        Console.WriteLine($ "x = {x},   y = {y}");
        x = 5;
        Console.WriteLine($ "x = {x},   y = {y}");
        y = 6;
        Console.WriteLine($ "x = {x},   y = {y}");
    }
}

// output 
x = 2,   y = 2
x = 5,   y = 5
x = 6,   y = 6

ref 返回使得方法可以返回引用而不是值,同样也需要使用两次 ref:

class Simple 
{
    private int Score = 5;
    // Keyword for ref return method
    public ref int RefToValue() 
    {
        // ref 
        return ref Score;
    }

    public void Display() 
    {
        Console.WriteLine($ "Value inside class object:  {Score}");
    }
}

class Program 
{
    static void Main() 
    {
        Simple s = new Simple();
        s.Display();
        //  Keyword for ref local
        ref int v1Outside = ref s.RefToValue();

    	// Change the value out in the calling scope.
    	v1Outside = 10;

	    s.Display();
	}
}

// output
 Value inside class object:  5
 Value inside class object:  1

有关 ref 的使用有如下注意事项:

ref return 不能返回如下内容:

  • 空值。
  • 常量。
  • 枚举成员。
  • 类或结构体的属性。
  • 指向只读位置的指针。

ref return 不能返回方法内部的局部变量;
ref 局部变量只能被赋值一次,后面出现的等号表示赋值;
如果调用 ref 返回方法时未使用 ref 关键字,则返回的是值而不是引用;
将 ref 局部变量作为常规的实际参数传递给其他方法时,传递的仍是 ref 指向的副本而不是引用。

6.18 方法重载

一个类中可以有多个同名方法,叫做方法重载。

使用相同名称的方法必须和其他同名方法有不同的签名,签名由如下信息组成:

  • 方法名称。
  • 参数数目。
  • 参数的数据类型和顺序。
  • 参数修饰符。

返回类型和形参名称都不是签名的一部分。

class A
{
    long AddValues( int   a, int   b)         { return a + b;  }        
	long AddValues( int   c, int   d, int e)  { return c + d + e; }  
	long AddValues( float f, float g)         { return (long)(f + g); }     
	long AddValues( long  h, long  m)         { return h + m;        }        
}

6.19 命名参数

C# 可以使用命名参数,显示指定参数的名称,就能够以任意顺序在方法中列出实参。

可以同时使用位置参数和命名参数,但所有位置参数必须先列出。

class MyClass 
{
    public int Calc(int a, int b, int c) 
    {
        return (a + b) * c;
    }
    
    static void Main() {
        MyClass mc = new MyClass();
        
        // Positional Parameters
        int r0 = mc.Calc(4, 3, 2);
        
        // Positional and Named Parameters
        int r1 = mc.Calc(4, b: 3, c: 2);
        
        // Switch order
        int r2 = mc.Calc(4, c: 2, b: 3);
        
        // All named parameters
        int r3 = mc.Calc(c: 2, b: 3, a: 4);
        
		// Named parameter expressions
        int r4 = mc.Calc(c: 2, b: 1 + 2, a: 3 + 1); 
        Console.WriteLine($ "{ r0 }, { r1 }, { r2 }, { r3 }, { r4 }");
    }
}

6.20 可选参数

可选参数在调用方法时,可以忽略它,可选参数在声明时需要设置参数的默认值,

值类型的默认值在编译时可以确定,就可以作为可选参数

引用类型默认值为null时,可以作为可选参数。

所有必填参数需放在可选参数声明之前,params 参数放在可选参数之后,

可选参数使用规则:

必须从可选参数列表的最后向前开始省略,而不是任意省略参数。
如果需要任意省略参数,需要配合命名参数来实现以消除赋值的歧义。

class MyClass
{
    double GetCylinderVolume(double radius = 3.0, double height = 4.0) 
    {
        return 3.1416 * radius * radius * height;
    }
    

    static void Main() 
    {
        MyClass mc = new MyCalss();
        double volume;
        
        volume = mc.GetCylinderVolume(3.0, 4.0);    // 位置参数
        volume = mc.GetCylinderVolume(radius: 2.0); // 使用 height 默认值
        volume = mc.GetCylinderVolume(height: 2.0); // 使用 radius 默认值
        volume = mc.GetCylinderVolume();            // 使用两个默认值
    }

}

6.21 栈帧

调用方法时,内存从栈的顶部开始分配,保存和方法关联的一些数据项。这块内存称为方法的栈帧。

栈帧包含如下内容:

  • 返回地址,即方法返回时继续执行的位置。

  • 分配内存的参数,即值参数(参数数组,如果有的话)。

  • 和方法调用相关的其他管理数据项。

在方法调用时,整个栈帧都会压入栈。
方法退出时,整个栈帧会从栈上弹出,也称栈展开。

6.22 递归

方法调用自身,叫做递归,需要注意终止条件,避免死循环。

class Program 
{
    public void Count(int inVal) {
        if (inVal == 0)
            return;
        // Calls itself
        // Invoke this method again.
        Count(inVal - 1); 
        
        Console.WriteLine($ "{inVal }");
    }
    
    static void Main() 
    {
        Program pr = new Program();
        pr.Count(3);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值