《C#本质论》第五章学习笔记

217 阅读7分钟

C#的方法和参数

学到第五章,已经感觉很吃力了,和C语言不同的地方越来越多了,以前没接触的概念越来越多,得冷静下来慢慢学了。

方法的调用

方法和函数很类似,但也有本质上的不同。相同点在于,它们都是语句按顺序的集合,都接收参数,可以处理参数,可以返回特定的数据类型;不同点在于,方法是属于类的(class),函数是相对于程序的。

方法调用分两种情况,类之内和类之外。类之内,方法就像C语言程序那样,只要把方法的定义写在类之内,就可以直接用方法名调用到。如果是类之外,就必须用相对应的命名空间、类名、方法名以及点运算符连接起来以调用。

方法名尽量以动词来命名。

C#不支持全局方法,所有方法都必须写在类声明中,如果想达到类似于C语言中全局函数的效果,就必须使用static声明方法,这也是前面学习中一直使用static声明Main方法的原因.

命名空间

命名空间是一种分类机制,用于分组功能相关的所有类,并且命名空间是分级的。比如最开始的Hello World程序,打印”Hello World“时用的是System.Console.WriteLine()方法,这之中,System是命名空间,ConsoleSystem命名空间中的一个类,WriteLine是方法名。

在程序的开头使用using关键字来导入命名空间,可以在程序中的方法调用代码处省略一系列命名空间名,比如上面的打印方法,如果使用了以下代码,就可以直接用WriteLine()打印了。

using System; //使用System命名空间
using static System.Console; //使用System命名空间中的Console类
class HW{
  static void Main(){
     WriteLine("Hello World!"); //调用Console类中的WriteLine方法
  }
}

几乎所有C#程序都要使用System命名空间中的类型。

using指令

即便前面使用了using System来省略调用方法时最开头的System,调用方法时依然要写类名称,显得很麻烦,如何更加方便呢?那就要用到using static了。

调用静态方法时,如果目标方法和调用者不在同一个类型中,就需要添加类型名称限定符,如上方代码中使用的using static

使用using static,就尅可以导入静态类了,后面的代码就可以直接写类中的方法以及成员名。

不过,这也有一定的缺点,就是不同命名空间里可能会有相同的类名以及相同的方法名,而C#编译器是不允许这种低级的重名情况的,为了避免这种尴尬的情况发生,就需要对导入的静态类进行重命名,类似于Python中的import numpy as np这样。不过C#的重命名导入格式是下面这样,先写重命名的名字,在接赋值运算符,然后写要替换的类名。

using System;
using static System.Console;
using Css = System.Console;

namespace Program
{
    class main
    {
        static void Main()
        {
            Css.WriteLine("Hello Wolrd!");
        }
    }
}

方法的参数

在C#中,方法的参数默认情况下都是被传值的,把值传递给参数,生成一个副本,然后交给函数去处理。

读写参数ref

对于C语言,如果我们要通过函数去改变变量,那么就需要传入指向这个变量的指针或者该变量的地址。那么对于C#而言呢?这里就要用到新的概念--传引用。联想之前学习的内容,C#中,数据分为两大类型:值类型和引用类型。值类型直接存储值,引用类型存储的是指向数据堆的地址。然而,学到这里,一系列新知识告诉我,引用不能直接粗暴的用指针去理解。

以传引用的方式为参数给方法,就可以达到更改实参本身的值得作用。传引用关键字是ref

using System;
using static System.Console;

namespace Program
{
    class main
    {
        static void Main()
        {
            string a = "wdnmd!";
            Recovery(ref a);
            WriteLine(a);
        }
        static void Recovery(ref string x)
        {
            x = ReadLine();
        }
    }
} //最后打印出来的是我们输入的内容,而非a初始化的内容

要注意的是,不管是声明阶段还是调用阶段,传引用都必须使用ref关键字在前面。

只写参数out

ref作为引用参数,方法是要对这个参数进行读取和写入两个操作。然而,很多时候我们只需要写入,并不需要读取,为了实现这种功能,C#提供了输出参数out

using System;
using static System.Console;

namespace Program
{
    class main
    {
        static void Main()
        {
            string a = "wdnmd!";
            Recovery(ref a);
            WriteLine(a);
        }
        static void Recovery(out string x)
        {
            x = ReadLine();
        }
    }
} //实现的功能和上面代码最后输出一致

对于CPU来说,即使不改变值,传地址依然比传值更快更高效。C#也有类似的技巧,那就是只读参数in

static void WriteString(in string x)
        {
            WriteLine(x);
        }

只读参数能确保参数不被修改,也能提升性能。

参数数组

C#也支持在同数据类型参数量不确定时,以参数数组的形式传递给方法。最常用的就是字符串数组,这也类似于命令行参数。

static void WriteStringLine(params string[] list)
{
   foreach (string i in list){
      WriteLine(i);
   }
} //将参数数组中每一个字符串打印出来

参数数组需要关键字params

注意事项:

  • 参数数组不一定是方法的唯一参数,但必须是参数内容中最后一个参数。一个方法有且只有一个参数列表。
  • 为了确保方法至少接收到了一个实参,最好不要把所有参数都放入参数列表,即便所有参数类型相同。

方法重载

在面向对象语言中,一个类中,也经常遇到需要处理不同情况参数的同一个方法。C#中,依据方法名、方法参数数据类型或参数类型来定义方法的唯一性,同名的方法,只要参数内容不完全一样,就可以作为另一个同名不同功能的方法而存在。我们对这种情况叫做方法重载

方法重载本质上是一种操作性多态(多态是面向对象语言中极为重要的概念)。

static void WriteContent(string a){ //这个方法的参数是字符串
  WriteLine(a);
}

static void WriteContent(int a){ //这个方法的参数是整数
  a = a.Tostring();
  WriteLine(a);
}

static void WriteContent(double a){ //这个方法的参数是浮点数
  a = a.Tostring();
  WriteLine(a);
}

可选参数

C# 4.0新增了可选参数的特性,在这之后,就可以像Python那样,给方法预设参数的默认值了,有默认值的参数在调用时就不必须传入值,如果需要传入,写上传入值后,就会忽略掉定义方法时的默认值。

static int Func(int a, int b = 1){
  return a*b;
}
int x = 3;
int y = Func(x);//y的值初始化为3
int z = Func(x,y);//z的值初始化为3*3=9;
int h = Func(b = z,a = x);//h的值初始化为3*9=27,这里用到的是具名参数特性,不需要考虑参数的绝对顺序,直接显式对形参传值

小结

学了这一章,更多的是回忆起学Python的函数时那些知识了。

至少,清楚的认知到了,函数和方法是不同的概念。

书中其他没有提及的知识,是目前我的水平无法读懂的,比如返回引用,比如异常处理(我不太喜欢异常处理这块知识)。