键盘敲烂,月薪过万作业不做,等于没学
当前系列: C#语法 修改讲义

复习:J&C:面向过程:更严格语法 / 作用域 / 多维数组 / enum / 方法重载


VS使用技巧

先使用再声明

有时候为了书写方便,我们还可以先直接“调用”方法,然后再利用VS的Show potential fixes(Alt+Enter)功能自动生成方法

VS的这个功能非常有用!除了可以生成方法,还可以生成类、字段……,添加using、引用……等等,几乎无所不能,超级利器!

///注释

方法声明

在方法声明的时候,在方法上输入三个斜杠(///),VS就会自动生成方法注释。

        /// <summary>
        /// 找到最低分数
        /// </summary>
        /// <param name="scores">学生成绩</param>
        /// <param name="first">如果有重复,是否取第一个最低分数即可</param>
        /// <returns>最低分数</returns>
        static double min(double[] scores, bool first)
        {
            return -1;
        }
我们可以由此填入:
  • summary:方法的概要说明
  • param:方法参数的说明
  • returns:方法返回值的说明

这样,当我们调用方法的时候,就可以得到VS的智能提示:

多行注释

普通注释也可以使用3个反斜杠(///)。

严格来讲这应该就是行注释(//)的延伸,它不属于C#语法,仅作用于VisualStudio。和//的区别就是:

///引导的注释内容在换行时会自动添加///头(演示),
    ///第一行注释
    ///换行自动生成"///"
    ///

这时候我们将其称之为:多行注释。

转到定义

我们可以在方法调用处使用快捷键F12转到定义,比如:

public static void WriteLine(string value);

因为这些方法都是通过已编译的dll文件提供的,所以我们只能看到方法的签名(具体原因后文解释)。如果是我们自己写的方法,F12是可以看到方法体的。


类库方法

到目前为止,其实我们已经接触过一下.NET自带的类库方法了:(复习:Main()、Console.Write/ReadLine(),类型转换函数

Math

可以点出很多数学运算方法,比如取三角函数(Sin()/Cos())、取绝对值(Ab()),但最常用的是:

  • 向上/下取整
    Console.WriteLine("Ceiling:" + Math.Ceiling(19.8));
    Console.WriteLine("Floor:" + Math.Floor(19.8));
    注意返回的仍然是double类型而是int类型,@想一想@:为什么
    Console.WriteLine(double.MaxValue - int.MaxValue);
  • 四舍五入
    Console.WriteLine("Round:" + Math.Round(19.875, 2));

取随机数

有时候我们需要模拟抽签摇色子,取随机数,这就需要用到Random:

Console.WriteLine(new Random().Next());

还可以指定随机数的生成范围(最大值/最小值)

Console.WriteLine(new Random().Next(1000));
Console.WriteLine(new Random().Next(100, 1000));

#体会#:方法重载的使用

伪随机

在某些电脑上运行:

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(new Random().Next(100));
}

可能得到重复的随机值:

///23
///23
///23
///65
///65
///65
///65
///12

这是怎么回事呢?

因为计算机是不可能真正随机的,随机数不是“凭空产生”的,计算机不会摇色子,随机数是依据特定的输入(seed),采用固定的算法(源代码)计算出来的。

我们可以在生成Random对象时指定这个seed试试:

for (int i = 0; i < 10; i++)
{
    Console.WriteLine(new Random(986).Next(100));
}

演示:这时候得到的“随机数”一点都不随机了,这被称之为伪随机

如果不指定seed,.NET用当前时间做seed。所以,如果随机数是在同一时间“”(CPU时钟周期内)生成的,那他们的值一定是相同的。

解决的办法是,在循环外部生成Random对象,在循环体内调用同一Random对象的Next()方法

Random random = new Random();
for (int i = 0; i < 10; i++)
{
    Console.WriteLine(random.Next(100));

因为.NET会在new Random() 的时候就准备好一系列不重复的随机数,类似于:[28, 23, 17, 5, 92, 88, ....],然后每次调用Next()方法,就给其下一个!


表达式主体定义

好吧,我承认,这是一个非常蹩脚的翻译。我们有时候又将其称之为“箭头方法

简单的来说,如果方法体只有一条语句的话,从C# 6.0开始,就可以使用新的写法:

static int Add(int a, int b) => a + b;

上述语句等于:

    static int Add(int a, int b) { return a + b; }

也可以是没有返回值的方法:

static void Add(int a, int b) =>  Console.WriteLine(a + b);

PS:这种写法还可以应用于属性、索引器、构造函数等……


引用传递

C#中可以传递变量本身,这就需要用ref或out标记:

ref

复习:值类型参数传递

然后,修改方法参数声明:

static void grow(ref int age)
{
    age++;
}

注意调用的时候也要添加ref关键字

int a = 18;
grow(ref a);

@想一想@:这时候age的值是多少?

ref也可以修饰引用类型变量(但不推荐)。演示区别:

static void grow(/*ref*/ Student student)
{
    //有无这行代码的差别
    //student = new Student();
    student.Age++;
}


引用传递会将变量wx直接传递给grow()方法,也就是说,student参数就是wx本身,那么在方法中新new出来的对象地址也就直接赋值给了wx,所以现在wx.age就是0了(字段的默认值),++之后就变成了1。


ref的经典例子是:交换两个变量的值

    static void swap(ref int a, ref int b)
    {
        int temp = a;
        a = b;
        b = temp;
    }

out

out和ref的效果类似,都是属于引用传递。

但是,使用out不能将该参数的值传入方法中:

        static void grow(out int age)
        {
            //age++;       //会报错:不能使用out参数的原有值
            age = 20;
        }

所以传入的out参数根本不需要事先被赋值:

            int age;
            //输出参数:传入一个未赋值(赋值也没用)
            grow(out age);
            
            //在C#的后期版本中可以直接简写为(推荐):
            //grow(out int age);

out常用于一个方法需要多个返回的时候(即:获取除了返回值以外的结果),比如类库中的TryParse:

    //如果能够Parse,方法返回值为true,input为转换结果
    //否则,方法返回值为false,input为默认值0
    if (double.TryParse(Console.ReadLine(), out double input))
    {
        input += 10;
    }
    else
    {
        Console.WriteLine("输入错误……");
    }

常见面试题:ref和out的区别

  • 语法:ref参数传入前需要赋值,out参数不需要(也不推荐,赋值也没用)
  • 意图:使用ref是为了“改变”传入参数,out是为了“获得”

牢记C#中除非方法参数做了标记(ref/out),一律都是值传递!!!


可选(optional)参数

复习:方法重载的作用

实现默认参数值的效果。

C#更进一步,为这种实现推出语法糖:可选参数

声明和调用

在参数后面加一个等号,赋一个默认值:

static void exam(int basic = 100)

在调用的时候,该参数:

  • 可写,bonus的值为指定值50
    exam(50);
  • 可不写,bonus的值为默认值100
    exam();
理解“可选”:调用时可以选择写或者不写

其他语法点

顺序

所有可选参数都要放在必须(required)参数后面。这样是不行的:

static void exam(int basic = 100, bool passed)

编译时

可选参数的默认值必须是“编译时”确定的,比如:

  • 字面值
  • 常量
    const int basic = 60;
    static void exam(int bonus = basic){}
  • default()表达式:取某类型的默认值主要用于泛型 
    static void exam(int bonus = default(int)){}
  • new ValueType,值类型
    static void exam(DateTime bonus = new DateTime()){}

参数名

可以引入参数名。比如方法是这样声明的:

static void exam(int basic = 60, int bonus = 20){}

这样的调用,首先“可读性”就不高,86究竟是指basic还是bonus?(按语法规则是basic,从左往右排……)

exam(86);

然后,如果我想将86指定给bonus,basic使用默认值呢,怎么办?

exam(bonus: 86);

其他

可选和非可选参数之间不能构成“重载”……


params

当方法的参数是数组的时候,我们可以在数组类型前面加一个关键字params:
static void getMax(params int[] array)
这样,当我们调用该方法的时候,就可以(不是必须只能)直接传递数组元素了:
    getMax(3, 9, 18, 23);
    //等效于:
    getMax(new int[] { 3, 9, 18, 23 });


作业

  1. 利用可选参数重构之前作业getRandom()
  2. 两个同学的床位号分别用两个int变量存储,将其传入Swap()方法,就可以交换(利用ref调用
  3. 重构方法LogOn(),调用之后能够获得:
    1. true/false,表示登陆是否成功
    2. string,表示登陆失败的原因
    提示:使用out关键字
其他见:J&C:面向对象:构造函数(参数重载)...
学习笔记
源栈学历
大多数人,都低估了编程学习的难度,而高估了自己的学习能力和毅力。

作业

觉得很 ,不要忘记分享哟!

任何问题,都可以直接加 QQ群:273534701

在当前系列 C#语法 中继续学习:

多快好省!前端后端,线上线下,名师精讲

  • 先学习,后付费;
  • 不满意,不要钱。
  • 编程培训班,我就选源栈

更多了解 加:

QQ群:273534701

答疑解惑,远程debug……

B站 源栈-小九 的直播间

写代码要保持微笑 (๑•̀ㅂ•́)و✧

公众号:源栈一起帮

二维码