1作用域管什么
作用域管变量的两件事:可见性(在哪儿能用这个名字)和生命周期(变量能存在多久)。作用域通常由一对大括号 {} 划出来。
视频把程序比成一张沙盘:每对大括号是一圈围栏,代码找名字只能从自己这圈往外找,看不进别的围栏。这只是比喻,内存里没有围栏,作用域是编译器查名字的规则。
2局部变量
在方法里声明的变量叫局部变量。教程只给了片段,下面补上调用和打印真跑一下。这是顶级语句写法(不写类和 Main,代码从第一行开始执行),void MyMethod() 是直接写在文件里的方法:
| 1 | MyMethod(); | // 调用方法 MyMethod |
| 2 | | |
| 3 | void MyMethod() | // 定义方法 MyMethod,不接收参数、不返回值 |
| 4 | { | // MyMethod 开始 |
| 5 | int localVar = 10; // 局部变量 | // 在方法里声明 localVar,赋值 10 |
| 6 | Console.WriteLine(localVar); | // 在方法里面找得到它,输出 10 |
| 7 | } | // MyMethod 结束,localVar 跟着失效 |
10
出了方法再用 localVar,编译不过:
| 8 | // localVar 在这里不可见 | |
| 9 | Console.WriteLine(localVar); | // 在方法外面打印 localVar |
Program.cs(9,19): error CS0103: 当前上下文中不存在名称“localVar”
每次调用都重来
局部变量不光看不见,还活不长:方法一结束它就不存在了,下次调用重新创建。
| 1 | Count(); | // 第 1 次调用:n 新建为 0,加 1,输出 1 |
| 2 | Count(); | // 第 2 次调用:n 又新建为 0,加 1,还是输出 1 |
| 3 | | |
| 4 | void Count() | // 定义方法 Count |
| 5 | { | // Count 开始 |
| 6 | int n = 0; | // 每次进来都新建一个 n,赋值 0 |
| 7 | n = n + 1; | // n 变成 1 |
| 8 | Console.WriteLine(n); | // 输出 n |
| 9 | } | // Count 结束,n 随之消失 |
1 1
3块级作用域
不光是方法,随便一对大括号都能圈出一个作用域。
| 1 | { | // 代码块开始 |
| 2 | int blockVar = 20; // 块级作用域 | // 在块里声明 blockVar,赋值 20 |
| 3 | Console.WriteLine(blockVar); | // 在块里打印,输出 20 |
| 4 | } | // 代码块结束,blockVar 失效 |
| 5 | // blockVar 在这里不可见 |
出了右大括号再用,又是同一个错误:
| 6 | Console.WriteLine(blockVar); | // 在块外面打印 blockVar |
Program.cs(6,19): error CS0103: 当前上下文中不存在名称“blockVar”
块级作用域从 C# 第一个版本就有,教程说“C# 7 才引入”的说法不准。
4方法参数
参数写在方法名后面的小括号里,在整个方法里都能用,方法里再套一层大括号也能用。
| 1 | MyMethod(5); | // 调用 MyMethod,把 5 传给 parameter |
| 2 | | |
| 3 | void MyMethod(int parameter) | // 定义 MyMethod,小括号里的 int parameter 是参数 |
| 4 | { | // MyMethod 开始 |
| 5 | // parameter 在整个方法中可见 | |
| 6 | Console.WriteLine(parameter); | // 输出 5 |
| 7 | { | // 里层代码块开始 |
| 8 | Console.WriteLine(parameter * 2); | // 从里层往外找到 parameter:5 × 2,输出 10 |
| 9 | } | // 里层代码块结束 |
| 10 | } | // MyMethod 结束 |
5 10
到了方法外面,parameter 一样找不到:
| 11 | Console.WriteLine(parameter); | // 在方法外面打印 parameter |
Program.cs(11,19): error CS0103: 当前上下文中不存在名称“parameter”
5成员变量
写在类里面、方法外面的变量叫成员变量(教程叫“全局变量”)。类里的方法都找得到它。
| 8 | int memberVar = 30; // 成员变量,在整个类中可见 | // 写在 MyClass 里、方法外面,初值 30 |
| 9 | | |
| 10 | public void Add() | // Add 方法:给 memberVar 加 1 |
| 11 | { | // Add 开始 |
| 12 | memberVar = memberVar + 1; | // 找得到类里的 memberVar,加 1 |
| 13 | } | // Add 结束 |
| 14 | | |
| 15 | public void Show() | // Show 方法:打印 memberVar |
| 16 | { | // Show 开始 |
| 17 | Console.WriteLine(memberVar); | // 同样找得到 memberVar |
| 1 | MyClass obj = new MyClass(); | // 用 new 创建一个 MyClass 对象 obj |
| 2 | obj.Show(); | // 输出 30 |
| 3 | obj.Add(); | // memberVar:30 + 1 = 31 |
| 4 | obj.Show(); | // 输出 31:两次调用之间,值一直留着 |
30 31
C# 没有全局变量,命名空间里不能直接写变量:
| 3 | namespace MyApp | // 定义命名空间 MyApp |
| 4 | { | // MyApp 开始 |
| 5 | int globalVar = 50; | // 想在命名空间里直接放一个变量 |
| 6 | } | // MyApp 结束 |
Program.cs(5,9): error CS0116: 命名空间不能直接包含字段、方法或语句之类的成员
6静态变量
在类里用 static 声明的变量叫静态变量,在这个类自己的方法里直接写名字就能用。
| 1 | MyClass.Show(); | // 用“类名.方法名”调用静态方法 Show,输出 40 |
| 2 | | |
| 3 | class MyClass | // 定义 MyClass 类 |
| 4 | { | // MyClass 开始 |
| 5 | static int staticVar = 40; // 静态变量,在整个类中可见 | // static 声明,初值 40 |
| 6 | | |
| 7 | public static void Show() | // 静态方法 Show |
| 8 | { | // Show 开始 |
| 9 | Console.WriteLine(staticVar); | // 在类自己的方法里,直接写名字就能用 |
| 10 | } | // Show 结束 |
40
在类外面直接写 staticVar 还是找不到,它的作用域也只在这个类里:
| 1 | Console.WriteLine(staticVar); | // 在 MyClass 外面直接写名字 |
Program.cs(1,19): error CS0103: 当前上下文中不存在名称“staticVar”
想在外面用,要加上 public,再写成“类名.变量名”,同样输出 40(public 的细节讲封装时再说):
| 1 | Console.WriteLine(MyClass.staticVar); | // 类名.变量名:从外面访问静态变量 |
| 2 | | |
| 3 | class MyClass | // 定义 MyClass 类 |
| 4 | { | // MyClass 开始 |
| 5 | public static int staticVar = 40; // 静态变量,在整个类中可见 | // 加了 public,外面才能用 |
7循环变量
for 后面括号里声明的 i,只在这个循环里能用。
| 1 | for (int i = 0; i < 5; i++) | // i 从 0 开始,小于 5 就继续,每轮加 1 |
| 2 | { | // 循环体开始 |
| 3 | // i 在循环体内可见 | |
| 4 | Console.WriteLine(i); | // 依次输出 0、1、2、3、4 |
| 5 | } | // 循环体结束,i 失效 |
0 1 2 3 4
出了循环再打印 i,同样报错:
| 7 | Console.WriteLine(i); | // 在循环外面打印 i |
Program.cs(7,19): error CS0103: 当前上下文中不存在名称“i”
正因为这样,两个并排的循环可以各用各的 i,互不打架:
| 1 | for (int i = 0; i < 3; i++) | // 第一个循环,有自己的 i |
| 2 | { | // 第一个循环体开始 |
| 3 | Console.Write(i); | // Write 不换行:输出 012 |
| 4 | } | // 第一个循环体结束,这个 i 失效 |
| 5 | Console.WriteLine(); | // 换行 |
| 6 | for (int i = 0; i < 3; i++) | // 第二个循环,重新声明一个 i,不冲突 |
| 7 | { | // 第二个循环体开始 |
| 8 | Console.Write(i); | // 再输出 012 |
| 9 | } | // 第二个循环体结束 |
012 012
反过来不行:外面已经有一个 i,循环里不能再声明一个 i。
| 1 | int i = 100; | // 外面先声明了 i |
| 2 | for (int i = 0; i < 5; i++) | // 循环里又声明 i,和外面的撞名 |
Program.cs(2,10): error CS0136: 无法在此范围中声明名为“i”的局部变量或参数,因为该名称在封闭局部范围中用于定义局部变量或参数
8小结
- 作用域管两件事:在哪儿可见,能活多久。
- 局部变量、块里的变量、参数和循环变量,出了大括号就找不到(CS0103);局部变量每次调用都重来。
- 成员变量和静态变量在整个类里都能用,成员变量的值在两次调用之间保留;C# 没有全局变量(CS0116)。
- 找名字从里往外一圈圈找;内层不能再声明外层已有的同名局部变量(CS0136)。
下一节:有些值从头到尾都不该被改,比如圆周率,怎么让它定下来就改不了?
