• 方法的组成

  • 方法声明

  • 方法体

  • 参数

    • 签名
  • 返回值

  • 重载

  • 递归

方法的组成

方法一般是由三部分组成。即方法名,参数,方法体。

在下面的代码里

handler – 方法名

c – 参数(可传可不传)

return – 后跟返回值(可返回可不返回)

方法名前面的类型 – 返回值类型

image-20220804230102670
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
using System;

class Test {
static public int handler0(){} // 最简单函数
static public int handler()
{
int a = 1;
int b = 2;
return a + b;
}
static public int handler1(int c = 2)
{
int a = 1;
int b = 2;
return a + b + c;
}
}

class Program {
static void Main(string[] args) {
Console.WriteLine("Hello, world!");
Test.handler0(); // 方法执行
int res = Test.handler(); // 返回值接收
Console.WriteLine(res);
int res1 = Test.handler1(); // 返回值接收
Console.WriteLine(res1);
}
}
/**
output:
3
5
*/

方法体

变量

方法里的变量被称为本地变量。存在生命周期。

本地常量

和变量相对应,区别是:只能在声明的时候赋值一次。后面不能改变。

const 要加在类型前面

const 有点像一个修饰符

1
2
3
4
5
关键字
|
const int Identifier = Value;
|
初始化的值是必须的

块中存在生命周期,当块中的代码执行结束后,所有声明的变量都会被销毁。

如果变量在栈里,会被踢出。

一个执行体,可以去嵌套。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test {
static public int handler1(int c = 2)
{
int a = 1;
int b = 2;
{

Console.WriteLine("1111: {0}", a + b);
var d = a + b;
{
Console.WriteLine("1111: {0}", d);
}
}
return a + b + c;
}
}

image-20220804235303709

可以使用 var 去声明变量,前提是一个可以推断出类型的变量。

c#的静态编译器,从右往左执行代码,同时可以推断出变量的类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

class Test {
static public int handler1(int c = 2)
{
int a = 1;
int b = 2;
return a + b + c;
}
}

class Program {
static void Main(string[] args) {
var res1 = Test.handler1();
Console.WriteLine(res1);
}
}

控制流(代码执行顺序)

代码总不是顺序执行的,所以我们需要其他的执行方式。譬如跳转、循环、条件==。

顺序执行

条件执行

if

if…else

switch

循环

for

while

do

foreach

跳转语句

break

continue

goto

return

参数

参数定义

实参
实参类型(取决于参数数据类型)

​ 不同类型的实参,实际产生的效果不同。对于普通整型参数传入时,属于值传入,方法执行的时候会复制一份,不会改变原始值。而参数数组值时,输入引用传入,方法执行传入的是一个引用,方法执行完成,如果有改变形参,则会改变原始值。

形参

形参是本地变量;

调用方法的时候,在代码执行前形参完成初始化;

值参数

值参数取决于参数类型,对于简单类型,那么则是值传递,对于复杂类型则类似下面说道的引用参数。

引用参数

类似于引用传递,会修改原始值。

由于形参名和实参名的行为就好像指向相同的内存位置,所以在方法的执行过程中对形参作
的任何改变在方法完成后依然有效(表现在实参变量上)。

可以强行把简单类型的数值换为引用参数,只需要通过修饰符去修饰即可。

声明和调用中都需要使用ref修饰符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 声明
public void fn5(ref int f1) {
f1 = f1 + 2;
}

// 调用
var argsIns = new Args();
var refParam = 4;
Console.WriteLine(refParam);
argsIns.fn5(ref refParam);
Console.WriteLine(refParam);

/**
* 4
* 6
*/

输出参数

输出参数用于从方法体内把数据导出到调用代码

声明和调用中都需要使用out修饰符

和返回值毫无关系

可以有多个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Args {
// 参数out
public void fn2(out int x, params int[] args) {
x = 1;
}
}
...
int paramOut;
argsIns.fn2( out paramOut, paramList);
Console.WriteLine("{0}", paramOut);

/**
* 1
*/

参数数组

常规参数

形参,实参一一对应

参数数组的特点

参数无需对应,但是参数数组需要保持相同的类型

通过 params 关键字去修饰为参数列表形式

实参可以传入一个数组类型(如果是数组的话,则类似于引用传递)

实参也可以传入一组数据

1
2
3
4
5
6
7
8
9
static public void fn2(params int[] args) // params关键字修饰,int[] 修饰的是数组里元素的类型。在C#里一般一个数组的元素是相同的。
{
Console.WriteLine(args[0]);
}

int[] arr = {1,2,3,4};
fn2(arr); // 可以传入一个数组
fn2(1,2,3,4,5,6); // 传入一串实参

参数类型总结

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
class Args {
// 实参类型
static void Main(params int[] args) {
Console.WriteLine("111");
}

public void fn1(params int[] args) {
if ((args != null) && (args.Length != 0))
{
for(int i = 0; i < args.Length; i++) {
args[i] = args[i] * 10;
Console.WriteLine("{0}", args[i]);
}
}
}
}
class Program {
static void Main(string[] args) {
var argsIns = new Args();
int params1 = 1, params2 = 2, params3 = 3;
argsIns.fn1(params1,params2,params3);
Console.WriteLine("{0}, {1}, {2}", params1, params2, params3); // 值传递打印

Console.WriteLine("=========================");

int[] paramList = {1,2,3};
argsIns.fn1(paramList);
foreach(int x in paramList)
Console.Write("{0}, ", x); // 引用传递打印
}
}
/*
10
20
30
1, 2, 3
=========================
10
20
30
10, 20, 30,
*/

具名参数(named parameter)

在调用方法的时候,可以给参数具名,语法看上更加直观。

可选参数(optional parameter)

参数需具有默认值

如果一个参数具有默认值,那么这个参数可以作为可选参数,可选参数的话,就是这个参数可传递可不传递。

如果存在必传参数,那么可选参数应该放在必传参数之后

方法重载(method overload)

和继承里的方法重写(method override) 不同

一个类中可以有一个以上的方法拥有相同的名称,使用相同名称的方法必须要有一个和其他方法不同的形式,这个叫做签名(signature)

方法的签名可以由下列信息组成。

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

以上如果不满足任意一条,则重载声明成功,否则失败。

c#中的重载

递归

1
2
3
4
5
6
7
8
9
10
11
12
13
// 阶乘
static public int Factorial(int Value) {
if (Value <= 1)
return Value;
else
return Factorial(Value - 1) * Value;
}
// 调用
Console.WriteLine(Factorial(4));

/**
* 24
*/

总结

签名是重要的,它描述了重载的可能。

C#的方法(函数)玩法是很多的,一个参数都有输入输出,参数列表等。其实在JS中,没有参数输出的概念,其实个人感觉也是很无必要,因为之前用JS也没觉得是必要的。反而参数列表来说,JS是在ECMA2015中实现的。但它不是直接在函数里去扩展,而是通过一个新的语法形式,扩展运算符(spread operator)。从个人理解上来说,参数列表更像是语法糖,而非语法。所以更推崇JS里的实现形式。同时还有一点,在C#的参数列表里,如果方法定义了参数列表,那么实参可以是一个array 也一可以正常的一串参数,这里感觉会有歧义。但在JS里不会,始终是一串参数,只是可以扩展运算符去解释。

具名参数的可玩性很高啊!JS里没有实现这个,哈哈。