【C#基础学习】第十章、方法

本文深入解析C#中的方法,包括方法的定义、局部变量(var关键字、嵌套块中的局部变量、局部常量)、程序控制流结构、方法调用、返回值(返回语句和void方法)、参数的六种类型(值参数、引用参数、输出参数、参数数组、命名参数、可选参数)以及方法重载。重点阐述了参数的使用细节和方法之间的交互,为理解和应用C#的方法提供了全面指导。

目录

方法

1.方法的定义

2.方法组成详解

2.1 局部变量

2.1.1 var关键字(类型推断)

2.1.2  嵌套块中的局部变量

2.2 局部常量(常变量)

2.3 程序控制流结构

2.4 方法调用

2.5 返回值

2.5.1 返回语句和void方法

3 参数的六种类型

3.1 值参数

3.2 引用参数

3.2.1 ref局部变量和ref返回

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

3.4 输出参数

3.5 参数数组

3.6 四种参数类型总结

3.7 命名参数

3.8 可选参数

4 方法重载

5 栈帧

6 递归


方法

1.方法的定义

方法是一块有名称的代码。(方法是类的函数成员)

我们可以使用方法的名称从别的地方执行这块代码,也可以把数据输入方法并接受数据输出。

方法由两部分组成:方法头和方法体

1.1方法的结构

方法头的具体结构:

1.2方法头的具体结构

方法体的具体结构: 

1.3方法体的具体结构

注意:

与C和C++不同:

  • C#中没有 全局函数(也就是方法) 声明在类型声明的外部。
  • C#中方法也没有默认的返回类型,所有方法必须包含返回类型或void。

2.方法组成详解

我们会先从方法体的局部变量讲起,再讲程序控制流结构和方法调用;然后讲方法体的返回类型和参数列表;最后讲方法重载。除此之外,我们还会简单讲一下栈帧和递归的含义。

2.1 局部变量

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

  • 局部变量的生存周期仅限于创建它的块以及它的块包含的嵌套块。

  • 可以在方法块的任意位置声明局部变量,但必须在使用它们前声明。

字段是在类或结构中直接声明的任意类型的变量。 字段是其包含类型的成员。 

1.5 实例字段和局部变量的区别

2.1.1 var关键字(类型推断)

我们在声明局部变量时,通常的写法如下:

class 第十章方法
{
    static void Main()
    {
        int a=15;
        第十章方法 meth=new 第十章方法();
    }
}

我们可以这样理解,var关键字的作用是使编译器能从初始化语句的右边推断出数据的类型。 

var的意义在于当类型的名称过于长时,我们可以像下面写法来简化代码:

class 第十章方法
{
    static void Main()
    {
        var a=15;
        var meth=new 第十章方法();
    }
}

使用var关键字的条件:

  • 只能用于局部变量,不能用于字段。(也就是说不能用于类或结构中直接声明的变量)

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

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

注意:var关键字不像javascript那样可以引用不同的类型。不改变C#的强类型性质。 

2.1.2  嵌套块中的局部变量

我们可以从下图了解嵌套块中的局部变量以及局部变量的生存周期。

1.6 嵌套块中的局部变量以及局部变量的生存周期

注意:在C和C++中,可以先声明一个局部变量,再在其嵌套块中再声明一个名称相同的局部变量。(在嵌套块范围内,嵌套块内的局部变量掩盖嵌套块外的局部变量) 然而对于C#,无论怎样,都不允许第一个局部变量生成周期内,含有另一个同名的局部变量。


2.2 局部常量(常变量)

局部常量与局部变量类似,只是一旦被初始化,它的值就不能被改变。

使用局部常量的两个条件:

  • 在声明时必须初始化
  • 在声明后不能改变

局部常量语法:const 数据类型 变量名=值;

const为关键字,不是修饰符,必须放在最前面


2.3 程序控制流结构

详见第九章中1.程序控制流结构。


2.4 方法调用

我们可以从方法体内部调用其他方法。

我们可以发现,想要在一个方法块内调用另一个不在本方法块内的方法,我们需要实例化一个类,再从实例成员调用方法。(那些嵌套在方法内的方法叫局部函数)

方法块内的代码可以直接调用局部函数。

1.7调用方法演示

1.8调用方法执行顺序


2.5 返回值

方法可以向调用它的代码返回一个值。

 方法可以选择返回一个值,也可以不返回值。

1.9两个方法声明

 

方法要返回一个值的时候要在方法块内添加一句 return 返回值(变量名或者对象名);

1.10 示例图

2.5.1 返回语句和void方法

方法会执行到return时结束,所以即使没有返回值,但运行到一定程度想要结束方法时,可以在想要结束的位置后面添一句reutrn;就可以了,但要注意,有返回值时要记得在后面添加返回值

1.11

3 参数的六种类型

参数允许你实现两件事。一,在方法开始执行的时候把数据传入方法。二,返回多个值给调用代码。

参数有六种类型,各自以略微不同的方式从方法出入或传出数据。

  • 值参数
  • 引用参数
  • 输出参数
  • 参数数组
  • 命名参数
  • 可选参数

方法中的参数又可以分为形参和实参。

形参(局部变量)

  • 形参与方法快内的其他局部变量不同,参数在方法体的外面定义并初始化。(参数在方法体的外面定义并在方法开始之前初始化)
  • 可以有任意数目的形参声明,需要用逗号隔开

1.12

实参

当代码调用一个方法时,参数在方法体的外面定义并在方法开始之前初始化。

用于初始化形参的表达式和变量称为实参。

1.13

3.1 值参数

上图看到的这种类型是默认的类型,称为值参数。

值参数的定义:把实参的值复制给形参。

当方法被调用时,系统执行以下操作:

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

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

 注意:

  1. 用作实参之前,变量必须被赋值。
  2. 对于引用类型,变量可以设置为null或者实际的引用。
  3. 值参数和值类型不是同一个东西。值参数是把实参的值复制给形参、值类型是值类型本身包含其值。

 示例代码:

1.14

方法的调用过程:

 我们可以发现,值参数中值类型在方法结束后不会改变,而引用类型则会发生改变。

1.15

3.2 引用参数

引用参数的定义:

  • 使用引用参数时,必须在方法的声明和调用中使用ref修饰符。
  • 实参必须是变量,在用作实参前必须赋值。对于引用类型变量可以设置为null或者实际的引用。
  • 不会在栈中为形参分配空间。
  • 形参的参数名将作为实参变量的别名,指向同一内存位置。因此在方法执行中发生对形参的改变,在方法结束后也不会消失。

示例:

1.16引用参数语法

1.17示例

当方法被调用时,系统执行以下操作:

  • 不会在栈中为形参分配空间
  • 形参的参数名将作为实参变量的别名,指向同一内存位置

1.18调用过程

3.2.1 ref局部变量和ref返回

ref除了可以用作引用参数的关键字。还有两种额外的功能。

  • 一个是ref局部变量,它允许一个变量是另一个变量的别名。
  • 一个是ref返回功能,它允许你将一个引用发送到方法外,然后在调用上下文内使用这个引用。

ref局部变量

ref局部变量的定义:

  • ref可以使一个变量是另一个变量的别名(值类型也可以)
  • 使用ref创建的别名被影响都会反映到另一个变量上,因为它们引用的是相同的对象(值类型也一样)

 语法: ref 数据类型 变量=ref 别名

 示例:ref int a= ref y;

ref返回

 ref局部变量通常与ref返回一起使用。

ref返回功能提供一种使方法返回变量引用而不是变量值的方法。

示例:

示例图

 结果返回:

Value inside class object:5

Value inside class object:10

ref返回的限制:

  • 不能将返回类型是void的方法声明为ref返回方法
  • return ref 返回值 表达式不能返回如下内容:
    • 空值
    • 常量
    • 枚举成员
    • 类或者结构体的属性
    • 指向只读位置的指针
  • return ref 返回值 表达式只能指向原先就在调用域内的位置、或字段。所以,它不能指向方法的局部变量
  • ref局部变量只能被赋值一次。
  • 即使将一个方法声明为ref返回方法,但要是调用方法的时候省略了ref关键字,则返回的是值,不是指向值内存的指针。
  • 如果ref局部变量作为常规的实际参数传递给其他方法,则该方法仅获得该变量的值。

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

这里我们主要讨论 在方法块内部设置形参会发生声明。

  • 将引用类型对象作为值参数传递:
    • 如果在方体块内创建一个新的对象赋值给形参,将切断形参和实参的连系。在方法调用结束后,新对象将不复存在。
    • 示例图
      1.19 示例
    • 1.20调用过程图
    • 将引用类型对象作为引参数传递:
      • 如果在方体块内创建一个新的对象赋值给形参,在方法调用结束后,新对象依然存在,并且是实参所引用的值
        1.21示例图
        ​​​​​​
      • 1.22调用过程图

3.4 输出参数

输出参数用于从方法体内把数据传出到调用代码,它们的行为与引用参数类似。

使用输出参数的要求:

  • 它的行为与引用参数类似。
    • 使用输出参数时,必须在方法的声明和调用中使用out修饰符。
    • 实参必须是变量,在用作实参前必须赋值。因为方法需要内存位置来保存返回值。
    • 形参的参数名将作为实参变量的别名,指向同一内存位置。因此在方法执行中发生对形参的改变,在方法结束后也不会消失。
  • 与引用参数不同的是、
    • 在方法块内,你需要给输出参数赋值之后才能读取它。这意味着参数的初始值是无关的,而且没有必要再方法调用之前赋值。
    • 在方法块内,在方法返回之前,代码中每条可能的路径都必须为所有输出参数赋值。

1.23输出参数的语法

1.24示例代码

1.25调用过程

 在C#7.0后,我们可以采用 输出参数 新的语法:

1.26语法

3.5 参数数组

我们从上面的参数可以知道,一个形参必须严格对应一个实参。

参数数组则不同,它允许特定类型的零个或多个实参对应一个特定的形参。

参数数组的使用条件:

  • 在一个参数列表中只能有一个参数数组,且它必须是列表中最后一个
  • 参数数组的所有参数必须是同一类型

 声明参数数组时的要求:

  • 在数据类型前使用 params修饰符
  • 在数据类型后放置一组空的方括号

1.27 参数数组语法

 (关于数组后续章节会讲,这里只需要知道数组就是多个拥有相同数据类型的数据项的集合、我们可以通过一个数字索引来访问数组、数组是引用类型,所以它的所有数据项都在堆里)

参数数组提供实参的两种方式:

  • 一个用逗号分隔的该数据类型元素的列表
    • void ListInt(params int[] array){...}        //方法
    • ListInt(1,2,3);        //3个int(调用方法)
  • 一个该数据类型元素的一维数组
    • void ListInt(params int[] array){...}         //方法
    • int[] intArray=new int[]{1,2,3};        //声明并初始化数组
    • ListInt(intArray);              //调用方法

 注意:

  •   对于参数数组,声明方法时要写params修饰符,而调用方法时并不用写params修饰符。
  • 对于其他参数类型,值参数声明和调用都不用修饰符、输出参数和引用参数则都需要修饰符。
  • 如果实参的参数数组位置没有变量,形参那里则会创建一个有零个元素的数组。
  • 参数数组可以作为值参数和引用参数
    • 作为值参数,参数数组在方法块内的变化不会保存。
    • 作为引用参数,参数数组在方法块内的变化会保存。 

3.6 四种参数类型总结

1.28 四种参数类型使用总结

3.7 命名参数

以上四种参数都是位置参数,也就是说每一个实参的位置都必须与相应的形参位置对应。

定义:而C#提供的命名参数,则允许我们只要显示指定参数的名字,就可以以任意顺序在方法调用中列出实参。

命名参数的使用:

  • 在方法的声明上没有什么不一样。
  • 在方法调用时,形参的名字后面跟着冒号和实际的参数值或表达式

1.29 命名参数示例

1.30 命名参数详解

  

 命名参数和位置参数可以一起混合使用:

1.31 命名参数和位置参数共用

命名参数可以在我们调用函数的时候提供更多的信息,我们可以给形参提供更有意义的名字。

比如在算正方形面积的方法里给形参的变量名取“height” “long”,当我们调用方法的时候,就可以知道每个实参的含义。为前面编写、后续阅读提供便利。

3.8 可选参数

可选参数允许我们在调用方法的时候包含这个参数,也可以省略它。

为了表明某个参数是可选的,你需要在方法声明中为该参数提供默认值。

1.32示例图

可选参数的使用要求:

  • 可选参数只能是值参数中的值类型和引用类型,且引用类型默认值只能是null。(也就是说参数不能加修饰符ref、out、params)
  • 所有必填参数必须在可选参数之前声明。如果有params参数,则params参数必须在所有可选参数之后声明。
  • 可选参数的省略顺序不能随意填写。
    • 你必须从可选参数的末尾开始省略。(也就是说,假设有n个可选参数,你可以省略第n个可选参数,或者第n个和第n-1个可选参数,以此类推)
    • 你要是想要以随意顺序省略,则可以搭配命名参数使用。

4 方法重载

方法重载(method overloading)的定义:就是一个类中有多个同名的方法。

使用相同方法名称的每个方法必须有一个和其他方法不同的签名(signature)

  • 在方法头中,方法的签名由以下信息组成:
    • 方法的名称
    • 参数的数目
    • 参数的数据类型和顺序
    • 参数修饰符
  • 返回类型不是签名的一部分。
  • 形参的名称也不是签名的一部分。
  • 要想使用方法重载,方法的名称必须相同。

//下面是4个加法方法的重载

class MyClass
{
    float Add(int a,int b)
    { 
        return a + b; 
    }
    float Add(float a,int b)
    { 
        return a + b; 
    }
    float Add(float a,float b,int c)
    { 
        return a + b + c; 
    }
    float Add(float a,float b)
    { 
        return a + b; 
    }
}

5 栈帧

我们已经知道了局部变量和参数是位于栈上的,下面深入讨论以下其组织。

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

  • 栈帧保存的内容包含:
    • 返回地址。也就是方法退出的时候继续执行的位置。
    • 分配内存的参数。也就是值参数,或者数组参数。
    • 和方法调用相关的其他管理数据项。
  • 在方法调用时,整个栈帧都会压入栈
  • 在方法退出的时候,整个栈帧都会从栈上弹出。这一过程叫栈展开。

调用方法时栈帧压入栈和栈展开的过程

6 递归

递归的定义:除了调用其他方法,方法还可以调用自身。

调用方法自身的机制与调用其他方法一样,都是每一次方法调用时把新的栈帧压入栈顶。

示例:

递归示例图
递归展开的过程

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ohrkaninchen

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值