1类和对象
C# 是面向对象的语言:程序由一个个对象组成,互相配合着干活,同一种对象用同一个类来描述。比如矩形,有长和宽两个数,还要能接收这两个数、算出面积、再显示出来。类就像一张图纸,照着它造出来的东西才是对象。
2矩形的例子
上面是 Rectangle 类,下面的 ExecuteRectangle 类里有 Main。运行后打出三行,4.5 × 3.5 正好是 15.75:
Length: 4.5 Width: 3.5 Area: 15.75
查看完整代码(36 行)
| 1 | using System; | // using 指令:引入 System 命名空间 |
| 2 | namespace RectangleApplication | // 命名空间 RectangleApplication |
| 3 | { | // RectangleApplication 命名空间开始 |
| 4 | class Rectangle | // 声明 Rectangle 类(矩形的图纸) |
| 5 | { | // Rectangle 类开始 |
| 6 | // 成员变量 | |
| 7 | double length; | // 字段 length:存长度,double 是小数类型 |
| 8 | double width; | // 字段 width:存宽度 |
| 9 | public void Acceptdetails() | // public:类外面也能调用(后面会讲);注意 d 是小写 |
| 10 | { | // Acceptdetails 开始 |
| 11 | length = 4.5; | // 长 = 4.5 |
| 12 | width = 3.5; | // 宽 = 3.5 |
| 13 | } | // Acceptdetails 结束 |
| 14 | public double GetArea() | // 方法 GetArea:算面积,返回一个 double |
| 15 | { | // GetArea 开始 |
| 16 | return length * width; | // 返回 长 × 宽 |
| 17 | } | // GetArea 结束 |
| 18 | public void Display() | // 方法 Display:把三个数显示出来 |
| 19 | { | // Display 开始 |
| 20 | Console.WriteLine("Length: {0}", length); | // {0} 是占位符,换成后面的 length:Length: 4.5 |
| 21 | Console.WriteLine("Width: {0}", width); | // 输出 Width: 3.5 |
| 22 | Console.WriteLine("Area: {0}", GetArea()); | // 先调用 GetArea 算出 15.75,再输出 |
| 23 | } | // Display 结束 |
| 24 | } | // Rectangle 类结束 |
| 25 | | |
| 26 | class ExecuteRectangle | // ExecuteRectangle 类,里面放 Main |
| 27 | { | // ExecuteRectangle 类开始 |
| 28 | static void Main(string[] args) | // 程序入口 Main 方法 |
| 29 | { | // Main 开始 |
| 30 | Rectangle r = new Rectangle(); | // 用 new 造出矩形对象 r,长和宽都是 0 |
| 31 | r.Acceptdetails(); | // 长和宽变成 4.5 和 3.5 |
| 32 | r.Display(); | // 打出三行 |
| 33 | Console.ReadLine(); | // 等待回车(现在的 VS 不写也会停住) |
| 34 | } | // Main 结束 |
| 35 | } | // ExecuteRectangle 类结束 |
| 36 | } | // RectangleApplication 命名空间结束 |
3using 关键字
using 把一个命名空间引进来,后面就能直接用里面的类,比如 Console;一个程序里可以写好几条。程序不一定以它开头:现在新建的项目默认已经引入了 System,删掉这一行照样能运行。严格地说,它不叫语句,叫 using 指令。
| 5 | static void Main(string[] args) | // Main 方法 |
| 6 | { | // Main 开始 |
| 7 | Console.WriteLine("Hello, World!"); | // 文件里没有 using System;,照样能用 Console |
Hello, World!
4class 和注释
class 关键字用来声明一个类,这里的类名叫 Rectangle。// 开头的是单行注释;多行注释用 /* 开头、*/ 结束。注释写给人看,编译器直接忽略。做个实验,把中间那句输出注释掉。下面这段是顶级语句写法(不写类和 Main,代码从第一行开始执行),本节最后会讲:
| 3 | /* 这个程序演示 | // 多行注释:/* 开头 |
| 4 | C# 的注释 | // 中间可以写好几行 |
| 5 | 使用 */ | // */ 结束 |
| 6 | | |
| 7 | // 这一行是注释 | |
| 8 | Console.WriteLine("A"); | // 输出 A |
| 9 | // Console.WriteLine("B"); | |
| 10 | Console.WriteLine("C"); | // 输出 C |
A C
5字段和方法
| 7 | double length; | // 成员变量 length,正式名字叫字段,存一个小数 |
| 8 | double width; | // 字段 width |
| 9 | public void Acceptdetails() | // 成员函数,在 C# 里叫方法,写在类的大括号里 |
注释下面两行是矩形的数据,叫字段;再往下三个是方法:Acceptdetails 给长和宽赋值,GetArea 算面积,Display 把三个数显示出来。
6用 new 造对象
| 30 | Rectangle r = new Rectangle(); | // 实例化:照着图纸造出对象 r,此时长和宽都是 0 |
| 31 | r.Acceptdetails(); | // 调用 r 的方法:长 = 4.5,宽 = 3.5 |
| 32 | r.Display(); | // Display 里调 GetArea 算出面积,再打出三行 |
| 33 | Console.ReadLine(); | // 等待回车(现在的 VS 不写也会停住) |
对象在内存里占一块地方,存着长和宽。
7标识符
类名、变量名、方法名都叫标识符。第一个字符只能是字母、下划线或 @,后面可以跟字母、数字和下划线;区分大小写,age 和 Age 是两个名字。
| 3 | int age = 16; | // int 是整数类型;age 是合法的名字 |
| 4 | int Age = 17; | // Age 和 age 是两个不同的名字 |
| 5 | int _count = 3; | // 下划线开头可以 |
| 6 | int @if = 5; | // if 是关键字,前面加 @ 就能当名字 |
| 7 | int Math = 8; | // 和类库里的 Math 类同名,编译器也允许 |
16 17 3 5 8
名字和类库里的类同名,编译器是允许的(上面的 Math 照样能跑),只是容易把人搞糊涂,最好别这么起。
数字开头(2nd)、名字中间有空格(my age)或减号(my-age)都会编译报错。@ 只能放在名字最前面,放在中间也报错:
| 1 | int my@age = 16; | // @ 放在了名字中间 |
Program.cs(1,7): error CS1003: 语法错误,应输入“,”
名字和关键字一样也不行(前面加 @ 写成 @if 才行):
| 1 | int if = 5; | // if 是关键字 |
Program.cs(1,5): error CS1001: 应输入标识符 Program.cs(1,5): error CS1003: 语法错误,应输入“,”
方法定义时写的是小写 d 的 Acceptdetails,调用时写成大写 D 的 AcceptDetails:
| 31 | r.AcceptDetails(); | // 大写 D 的 AcceptDetails 和 Acceptdetails 是两个名字 |
Program.cs(31,15): error CS1061: “Rectangle”未包含“AcceptDetails”的定义,并且找不到可接受第一个“Rectangle”类型参数的可访问扩展方法“AcceptDetails”(是否缺少 using 指令或程序集引用?)
8关键字
关键字是 C# 预先留给自己用的词。
- 保留关键字不能直接拿来当名字,除非前面加
@。刚才代码里的using、class、double、public、void都是。 - 上下文关键字只在特定位置有特殊意思,别处可以当名字用,比如
get和set。现在的上下文关键字比教程里的表更多,比如var。
9顶级语句
顶级语句是 C# 9.0 引入的写法。以前打一句 Hello World,要写命名空间、类和 Main:
查看完整代码(12 行)
| 1 | using System; | // 引入 System 命名空间 |
| 2 | | |
| 3 | namespace MyApp | // 命名空间 MyApp |
| 4 | { | // MyApp 命名空间开始 |
| 5 | class Program | // Program 类 |
| 6 | { | // Program 类开始 |
| 7 | static void Main(string[] args) | // 程序入口 Main 方法 |
| 8 | { | // Main 开始 |
| 9 | Console.WriteLine("Hello, World!"); | // 输出 Hello, World! |
| 10 | } | // Main 结束 |
| 11 | } | // Program 类结束 |
| 12 | } | // MyApp 命名空间结束 |
用顶级语句,直接在文件里写这一句就行,运行结果一模一样,都是 Hello, World!:
| 1 | using System; | // 引入 System 命名空间 |
| 2 | | |
| 3 | Console.WriteLine("Hello, World!"); | // 不写类和 Main,编译器自动生成一个 Main 把它放进去 |
所以写了顶级语句的文件就是程序的入口。上一节要勾“不使用顶级语句”,就是为了看到完整的 Main。
顶级语句里能写什么
常见的写法都能用:声明变量并输出、直接定义方法、用 LINQ(在 C# 里查询、筛选数据的写法,后面会讲)挑出偶数、用 try 和 catch 接住除以零的错误。这些后面会一个个细讲,现在认个脸就行:
| 12 | int Add(int a, int b) => a + b; | // 直接定义方法 Add,叫局部函数;=> 后面是返回的结果 |
| 13 | Console.WriteLine($"Sum of 1 and 2 is {Add(1, 2)}."); | // $"..." 里的 {} 会换成算出来的值:Add(1, 2) = 3 |
| 14 | | |
| 15 | // 使用 LINQ | |
| 16 | var numbers = new[] { 1, 2, 3, 4, 5 }; | // 一个整数数组 |
| 17 | var evens = numbers.Where(n => n % 2 == 0).ToArray(); | // LINQ:挑出除以 2 余 0 的数,即 2 和 4 |
查看完整代码(29 行)
| 1 | using System; | // 引入 System 命名空间 |
| 2 | using System.Linq; | // 引入 LINQ 所在的命名空间 |
| 3 | | |
| 4 | // 顶级语句中的变量声明 | |
| 5 | int number = 42; | // 声明整数变量 number |
| 6 | string message = "The answer to life, the universe, and everything is"; | // 声明字符串变量 message |
| 7 | | |
| 8 | // 输出变量 | |
| 9 | Console.WriteLine($"{message} {number}."); | // $"..." 里的 {变量} 会换成变量的值 |
| 10 | | |
| 11 | // 定义和调用方法 | |
| 12 | int Add(int a, int b) => a + b; | // 局部函数 Add:返回 a + b |
| 13 | Console.WriteLine($"Sum of 1 and 2 is {Add(1, 2)}."); | // Add(1, 2) = 3 |
| 14 | | |
| 15 | // 使用 LINQ | |
| 16 | var numbers = new[] { 1, 2, 3, 4, 5 }; | // 一个整数数组 |
| 17 | var evens = numbers.Where(n => n % 2 == 0).ToArray(); | // 挑出偶数 2 和 4 |
| 18 | Console.WriteLine("Even numbers: " + string.Join(", ", evens)); | // 用逗号把 2、4 连起来输出 |
| 19 | | |
| 20 | // 异常处理 | |
| 21 | try | // try:试着执行下面的代码 |
| 22 | { | // try 开始 |
| 23 | int zero = 0; | // zero = 0 |
| 24 | int result = number / zero; | // 42 除以 0,出错 |
| 25 | } | // try 结束 |
| 26 | catch (DivideByZeroException ex) | // catch:接住“除以零”的错误,ex 里是错误信息 |
| 27 | { | // catch 开始 |
| 28 | Console.WriteLine("Error: " + ex.Message); | // 输出错误信息 |
| 29 | } | // catch 结束 |
The answer to life, the universe, and everything is 42. Sum of 1 and 2 is 3. Even numbers: 2, 4 Error: Attempted to divide by zero.
顶级语句写起来短,读起来清楚,适合小项目和小脚本,也能和原来的代码放在一起用。
三点注意
- 一个项目里只能有一个文件写顶级语句(报错里的“编译单元”就是一个 .cs 文件)。另一个文件 Other.cs 已经写了一句顶级语句
Console.,Program.cs 里再写:WriteLine("Hi");
| 3 | Console.WriteLine("Hello, World!"); | // 第二个文件里的顶级语句,编译报错 |
Program.cs(3,1): error CS8802: 只有一个编译单元可具有顶级语句。
- 写了顶级语句的那个文件,就是程序的入口。
- 顶级语句里的变量只是自动生成的 Main 里的局部变量(只在 Main 里有效),同一个文件的类里用不了:
| 3 | int number = 42; | // 顶级语句里的变量 number |
| 4 | Console.WriteLine(number); | // 这里能用:输出 42 |
| 5 | | |
| 6 | class Helper | // 同一个文件里的 Helper 类 |
| 7 | { | // Helper 类开始 |
| 8 | static void Show() | // Show 方法 |
| 9 | { | // Show 开始 |
| 10 | Console.WriteLine(number); | // 在类里用 number,编译报错 |
| 11 | } | // Show 结束 |
| 12 | } | // Helper 类结束 |
Program.cs(10,27): error CS8801: 在此上下文中,无法使用在顶级语句中声明的局部变量或本地函数“number”。
10小结
- 类是图纸,里面有字段(数据)和方法(动作);
new照着图纸造出对象,再调用它的方法。 - 标识符以字母、下划线或
@开头,@只能在最前面;区分大小写;关键字前加@才能当名字。 - 顶级语句由编译器包进自动生成的 Main,一个项目只能有一个文件写。
下一节:length 和 width 前面的 double,到底是什么意思?
