首页 > 编程笔记 > C#笔记 阅读:6

C#数组作为函数参数(附带实例)

一般变量调用函数的传递过程是进行传值调用的过程,在传值的时候,程序可以很顺利地将数据传递给目标函数,然后可以利用 return 回传数据,整个概念如下所示:


从上图可以看到调用方可以利用参数传递数据给目标函数,目标函数则使用 return 回传数据给原始函数,如下所示:
return xx;

目前流行的 Python 语言,return 可以一次回传多个值,如下:
return xx, yy;
如果使用 C#语言想要回传多个数值,就我们目前所学来看的确没有太便利。

C#数组的传递

如果想要传递多变量数据可以将多变量以数组的形式表达。主程序在调用函数时,将整个数组传递给函数的基础概念如下所示:


C# 语言在传递数组时和传递一般变量不同。一般变量在调用函数中的传递过程使用了传值调用(call by value)的概念,也就是将变量内容复制到函数所属变量内存内,这样在传值的时候,可以很顺利地将数据传递给目标函数,但是无法取得回传结果。

C# 在调用函数传递数组时使用了传址调用(call by address)的方式,这种方式的好处是可以有比较高的效率。假设一个数组很大,有 1000 多笔数据,如果采用传值的方式处理,会需要较多的内存空间,同时也会耗用 CPU 时间。如果采用传址的方式,则可以很简单地处理。传递数组到函数后,可以在函数内处理数组内容,更新此数组内容后,再回到调用位置,就可以从数组地址获得新的结果。

例如,设计 Display() 函数可以输出数组内容,主程序则是将数组名传给输出函数 Display():
void Display(int[] num)
{
    for (int i = 0; i < num.Length; i++)
        Console.WriteLine($"{num[i]}");
}

int[] data = { 5, 6, 7, 8, 9 };
Console.WriteLine("输出数组内容");
Display(data);
执行结果为:

输出数组内容
5
6
7
8
9

C#函数使用ref参数交换数据

假设我现在要设计函数 Swap() 将 x 和 y 的数据交换,在没有地址概念前,可能会设计下列程序,而获得失败的结果。

例如,设计数据交换函数 Swap(),获得失败的结果:
void Swap(int x, int y)
{
    int tmp;
    tmp = x;
    x = y;
    y = tmp;
}

int x = 5;
int y = 1;
Console.WriteLine("执行对调前");
Console.WriteLine($"x = {x} \t y = {y}");
Swap(x, y);
Console.WriteLine("执行对调后");
Console.WriteLine($"x = {x} \t y = {y}");
执行结果为:

执行对调前
x = 5    y = 1
执行对调后
x = 5    y = 1

上述程序因为第 13 行调用函数 Swap(x, y) 时,使用了传值调用(call by value),所以产生交换失败的结果。

为了改良上述问题可以使用传址的方式调用 Swap() 函数,这时需使用 ref 参数,如下所示:
Swap(ref x, ref y);  // 关键词ref 可以传递变量x和y的地址
所设计的 Swap() 函数也须改由 ref 接收地址参数,如下所示:
Swap(ref int x, ref int y)
{
    ...
}

例如,设计正确的交换函数 Swap():
void Swap(ref int x, ref int y)
{
    int tmp;
    tmp = x;
    x = y;
    y = tmp;
}

int x = 5;
int y = 1;
Console.WriteLine("执行对调前");
Console.WriteLine($"x = {x} \t y = {y}");
Swap(ref x, ref y);
Console.WriteLine("执行对调后");
Console.WriteLine($"x = {x} \t y = {y}");
执行结果为:

执行对调前
x = 5    y = 1
执行对调后
x = 1    y = 5

C#函数用关键词out回传数据

函数调用在变量名称使用 ref 关键词时,可以传递地址信息,但是需要初始化变量值。

同样是函数调用,如果将 ref 关键词改为 out 也可以传递地址信息,这时可以不需要初始化变量值,这样函数就可以使用此未初始化的变量,将数值回传。

例如,输入字符串,这个程序会响应有多少个字符‘A’:
void CountA(string str, out int counter)
{
    counter = 0;
    for (int i = 0; i < str.Length; i++)
        if (str[i] == 'A')
            counter += 1;
}

int num;
Console.Write("请输入字符串 : ");
string mystr = Console.ReadLine();
CountA(mystr, out num);
Console.WriteLine($"A 字符的数量 = {num}");
执行结果为:

请输入字符串 : ABCDAAA
A 字符的数量 = 4

C#函数只读关键词in

如果在函数声明中增加 in 关键词,则这个关键词是只读关键词,这类关键词的内容在函数内将具有只读属性,假设有一个程序片段与函数如下:
void InArgMethod(in int num, ... )
{
    ...
    // num 变量只能引用,不可变更其值
}

...
int readOnlyNumber = 30; // 定义变量 readOnlyNumber
InArgMethod(readOnlyNumber, ... ); // 调用 InArgMethod 函数
上述程序定义 InArgMethod() 函数时,从参数行可以知道第 1 个整数参数 num 已经定义为只读,在此函数内 num 的内容只能引用不可更改,只读变量一般应用在需要特别保护的情况中。

例如计算汇率,这个程序会要求输入 VIP 等级,以及美金金额,这个程序会依据 VIP 等级输出可以兑换的金额。
void UsaToNt(int money, in double rate, out double rtn)
{
    rtn = money * rate;
}

double dollars;
Console.Write("请输入 VIP 等级 : ");
string vip = Console.ReadLine();
Console.Write("请输入美金金额 : ");
int money = Convert.ToInt32(Console.ReadLine());
double rate = 30; // 汇率标准
if (vip == "AAA")
    rate = 30 * 0.95; // AAA客户可换汇率
else if (vip == "AA")
    rate = 30 * 0.93; // AA 客户可换汇率
else
    rate = 30 * 0.9; // 其他客户可换汇率
UsaToNt(money, rate, out dollars);
Console.WriteLine($"{vip} 客户 {money} 可以换汇 : {dollars}");
执行结果为:

请输入 VIP 等级 : AAA
请输入美金金额 : 100
AAA 客户 100 可以换汇 : 2850

这个程序为了安全理由不希望 VIP 换汇优待被更动,所以在 UsAToNT() 函数内将 rate 设为只读变量。

C#函数可变动数量参数params

设计函数时若是在参数行放置 params,则表示在设定可变量的参数,不过只限定是一维数组,声明时需留意后面不可以有其他参数。

例如:
void UseParams1(params int[] arr)
{
    for (int i = 0; i < arr.Length; i++)
    {
        Console.Write(arr[i] + " ");
    }
    Console.WriteLine();
}

void UseParams2(params object[] arr)
{
    for (int i = 0; i < arr.Length; i++)
    {
        Console.Write(arr[i] + " ");
    }
    Console.WriteLine();
}

UseParams1(1, 10, 20, 30, 40);
UseParams1(5, 15);
UseParams1(); // 没有参数则显示空白行
UseParams2(1, 'a', "test", "C语言中文网");
UseParams2("c.biancheng.net", "C语言中文网");
执行结果为:

1 10 20 30 40
5 15
1 a test C语言中文网
c.biancheng.net C语言中文网


再例如,计算不同数组数量的总和:
void MySum(params int[] values)
{
    Console.WriteLine(values.Sum().ToString());
}

MySum(1, 2, 3, 4, 5);
MySum(6, 7);
MySum(8, 9, 10);
执行结果为:

15
23
27

C#传递二维数组数据

主程序在调用函数时、传递二维数组时,可以只传递数组名,然后由二维数组的 GetLength() 属性获得行(row)数和列(column)数。

假设所传递的二维数组是 sc,则:
例如,基本二维数组数据传送的应用。本程序的函数会将二维数组各行(row)的前三个元素的平均值,平均分数向下取整数,放在最后一个元素的位置上。
void Average(int[,] sc)
{
    int rows = sc.GetLength(0); // 行数
    int cols = sc.GetLength(1); // 列数
    int sum;
    for (int i = 0; i < rows; i++)
    {
        sum = 0;
        for (int j = 0; j < cols; j++)
            sum += sc[i, j]; // 每一行的总分
        sc[i, cols - 1] = sum / 3; // 平均值放入各行最右
    }
}

int[,] num = { { 88, 79, 91, 0 },
               { 86, 84, 90, 0 },
               { 77, 65, 70, 0 } };

Average(num);
for (int i = 0; i < 3; i++) // 打印新的数组
{
    for (int j = 0; j < 4; j++)
        Console.Write($"{num[i, j],5}");
    Console.WriteLine();
}
执行结果为:
   88   79   91   86
   86   84   90   87
   77   65   70   70

C#匿名数组

在执行调用方法时,有时候要传递的是一个数组,可是这个数组可能使用一次以后就不需要再使用,如果我们为此数组重新声明然后配置内存空间,似乎有点浪费系统资源,此时可以考虑使用匿名数组(anonymous array)来处理。

匿名数组的完整意义是,一个可以让我们动态配置的有初始值但是没有名称的数组。
int add(int[] nums)
{
    int sum = 0;
    foreach (int n in nums)
        sum += n;
    return sum;
}

int[] data = { 1, 2, 3, 4, 5 };
Console.WriteLine(add(data));
执行结果为:

15

在上述实例中,很明显所声明的数组 data 可能用完就不再需要了,此时可以考虑不要声明数组,直接用匿名数组来处理,将匿名数组当作参数传递。

对上述程序的 data 数组而言,如果处理成匿名数组其内容如下:
new int[ ] {1, 2, 3, 4, 5};
以声明匿名数组的方式重新设计上面的实例:
int add(int[] nums)
{
    int sum = 0;
    foreach (int n in nums)
        sum += n;
    return sum;
}

//int[] data = { 1, 2, 3, 4, 5 };
Console.WriteLine(add(new int[] {1, 2, 3, 4, 5}));
执行结果为:

15

相关文章