第 13 节 · 第二部分 流程、方法与数据

封装

封装就是把数据藏进类里,只把该给的功能用 public 露出来;能藏到什么程度由访问修饰符决定。

时长 5:32授课计划第 5 周

学完这一节,你能

讲义

跟着视频的顺序写。每行代码都有注释,代码和运行结果都是实际运行得到的。

1封装是什么

封装就是把一个或几个东西装进一个包里,这个包可以是实物,也可以是逻辑上的,比如一个类。目的是不让外面随便碰里面的实现细节。

视频里用一台面积仪代表类 Rectangle:两个读数窗记着长度和宽度,叫成员变量;算面积的齿轮和负责显示的按钮,教程叫成员函数,C# 里一般叫方法。机壳外的控制台代表 Main 方法,它在类外面使用这个类。

2抽象与访问修饰符

抽象是只把使用者需要的东西摆到外面;封装是把其余部分藏起来,让抽象真正做到。藏到什么程度,由访问修饰符决定:它写在成员前面,规定这个成员在哪里能被看到、被使用。

C# 有六种访问修饰符:public、private、protected、internal、protected internal,还有 private protected(学继承时会用到)。

3public:外面都能访问

length 和 width 前面写了 public,Main 可以创建实例(用类造出来的一个具体对象)后直接给它们赋值。

C# · 第 27-30 行
27Rectangle r = new Rectangle();// 用 new 创建 Rectangle 的实例 r
28r.length = 4.5;// length 是 public,类外面能直接赋值
29r.width = 3.5;// width 也是 public
30r.Display();// Display 是 public,Main 能通过 r 调用它
查看完整代码(34 行)
C#
1using System;// 引入 System 命名空间
2
3namespace RectangleApplication// 定义命名空间 RectangleApplication
4{// 命名空间开始
5 class Rectangle// 定义 Rectangle 类
6 {// Rectangle 类开始
7 //成员变量
8 public double length;// 公有成员变量:长度
9 public double width;// 公有成员变量:宽度
10
11 public double GetArea()// 公有方法 GetArea:算面积
12 {// GetArea 开始
13 return length * width;// 写在类里面,可以直接用 length 和 width
14 }// GetArea 结束
15 public void Display()// 公有方法 Display:显示
16 {// Display 开始
17 Console.WriteLine("长度: {0}", length);// {0} 处填入 length 的值
18 Console.WriteLine("宽度: {0}", width);// 输出宽度
19 Console.WriteLine("面积: {0}", GetArea());// 调用 GetArea 算出面积再输出
20 }// Display 结束
21 }// Rectangle 结束// Rectangle 类结束
22
23 class ExecuteRectangle// 定义 ExecuteRectangle 类,放 Main
24 {// ExecuteRectangle 类开始
25 static void Main(string[] args)// 程序入口 Main 方法
26 {// Main 开始
27 Rectangle r = new Rectangle();// 创建实例 r
28 r.length = 4.5;// 直接给 length 赋值 4.5
29 r.width = 3.5;// 直接给 width 赋值 3.5
30 r.Display();// 调用 Display:4.5 × 3.5 = 15.75
31 Console.ReadLine();// 等待回车(现在的 VS 不写也会停住)
32 }// Main 结束
33 }// ExecuteRectangle 类结束
34}// 命名空间结束
运行结果
长度: 4.5
宽度: 3.5
面积: 15.75

public 很方便,可外面谁都能把长度随手改掉。

4private:只有这个类自己能访问

把 length 改成 private,有了实例 r 也不能用 r.length 访问:

C# · 第 43 行
43r.length = 4.5;// length 是 private,在 Rectangle 外面访问
编译报错
Program.cs(43,15): error CS0122: “Rectangle.length”不可访问,因为它具有一定的保护级别

数据靠这个类自己提供的公有方法进去。教程第二个例子加了公有方法 AcceptDetails,从键盘读入长度和宽度:

C# · 第 8-17 行
8private double length;// 私有成员变量:外面够不着
9private double width;// 私有成员变量:宽度
10
11// 公有方法,用于从用户输入获取矩形的长度和宽度
12public void AcceptDetails()// 公有方法,Main 能调用
13{// AcceptDetails 开始
14 Console.WriteLine("请输入长度:");// 提示输入
15 length = Convert.ToDouble(Console.ReadLine());// ReadLine 读入一行文字,Convert.ToDouble 把它转成小数
16 Console.WriteLine("请输入宽度:");// 提示输入
17 width = Convert.ToDouble(Console.ReadLine());// 类里面的方法可以给私有变量赋值
查看完整代码(51 行)
C#
1using System;// 引入 System 命名空间
2
3namespace RectangleApplication// 定义命名空间
4{// 命名空间开始
5 class Rectangle// 定义 Rectangle 类
6 {// Rectangle 类开始
7 // 私有成员变量
8 private double length;// 私有:长度
9 private double width;// 私有:宽度
10
11 // 公有方法,用于从用户输入获取矩形的长度和宽度
12 public void AcceptDetails()// 公有方法 AcceptDetails
13 {// AcceptDetails 开始
14 Console.WriteLine("请输入长度:");// 提示输入长度
15 length = Convert.ToDouble(Console.ReadLine());// 读入一行文字并转成小数,存进 length
16 Console.WriteLine("请输入宽度:");// 提示输入宽度
17 width = Convert.ToDouble(Console.ReadLine());// 读入并存进 width
18 }// AcceptDetails 结束
19
20 // 公有方法,用于计算矩形的面积
21 public double GetArea()// 公有方法 GetArea
22 {// GetArea 开始
23 return length * width;// 返回长 × 宽
24 }// GetArea 结束
25
26 // 公有方法,用于显示矩形的属性和面积
27 public void Display()// 公有方法 Display
28 {// Display 开始
29 Console.WriteLine("长度: {0}", length);// 输出长度
30 Console.WriteLine("宽度: {0}", width);// 输出宽度
31 Console.WriteLine("面积: {0}", GetArea());// 输出面积
32 }// Display 结束
33 }//end class Rectangle// Rectangle 类结束
34
35 class ExecuteRectangle// 定义 ExecuteRectangle 类
36 {// ExecuteRectangle 类开始
37 static void Main(string[] args)// 程序入口 Main 方法
38 {// Main 开始
39 // 创建 Rectangle 类的实例
40 Rectangle r = new Rectangle();// 创建实例 r
41
42 // 通过公有方法 AcceptDetails() 从用户输入获取矩形的长度和宽度
43 r.AcceptDetails();// 只调用公有方法,碰不到私有变量
44
45 // 通过公有方法 Display() 显示矩形的属性和面积
46 r.Display();// 显示结果
47
48 Console.ReadLine();// 等待回车(现在的 VS 不写也会停住)
49 }// Main 结束
50 }// ExecuteRectangle 类结束
51}// 命名空间结束

输入 5 和 3(下面的 5 和 3 是键盘输入的):

运行结果
请输入长度:
5
请输入宽度:
3
长度: 5
宽度: 3
面积: 15

这就是封装:数据藏在里面,只通过公有方法来操作。

private 按整个类划分:写在 Rectangle 里的方法,能直接读另一个 Rectangle 对象的私有成员:

C# · 第 16-19 行
16public bool SameSize(Rectangle other)// 参数 other 是另一个 Rectangle 对象
17{// SameSize 开始
18 return length == other.length && width == other.width;// 直接读 other 的私有成员,&& 表示两个都成立
19}// SameSize 结束
C# · 第 26-28 行
26Rectangle a = new Rectangle(4.5, 3.5);// 括号里的值交给构造函数(后面会讲):长 4.5、宽 3.5
27Rectangle b = new Rectangle(4.5, 3.5);// b:长宽一样
28Console.WriteLine(a.SameSize(b));// 长宽都相等,输出 True
运行结果
True

5protected:自己和子类能访问

受保护的成员,这个类自己和它的子类能访问。子类是从别的类派生出来的,比如正方形是一种矩形:

C# · 第 7-15 行
7 protected double length = 4.5;// 受保护的成员变量,初值 4.5
8}// Rectangle 类结束
9
10class Square : Rectangle// Square 是 Rectangle 的子类(冒号后面写父类)
11{// Square 类开始
12 public void Show()// Square 的方法 Show
13 {// Show 开始
14 Console.WriteLine("长度: {0}", length);// 子类里能直接读到 length
15 }// Show 结束
运行结果
长度: 4.5

Main 不在子类里,直接访问 r.length,照样报同一个错:

C# · 第 25 行
25r.length = 3.5;// Main 不在 Rectangle 或它的子类里
编译报错
Program.cs(25,15): error CS0122: “Rectangle.length”不可访问,因为它具有一定的保护级别

protected 主要用在继承上,到继承那一章再细讲。

6internal:同一个程序集能访问

程序集就是一个项目编译出来的 EXE 或 DLL 文件。internal 成员在同一个程序集里都能访问;一个应用程序可以由好几个程序集组成。

C# · 第 8-14 行
8internal double length;// internal:同一个程序集里都能访问
9internal double width;// 宽度也是 internal
10
11double GetArea()// 前面没写修饰符,默认是 private
12{// GetArea 开始
13 return length * width;// 返回长 × 宽
14}// GetArea 结束
C# · 第 27-29 行
27r.length = 4.5;// Main 和 Rectangle 在同一个项目里,能直接赋值
28r.width = 3.5;// width 也是 internal,同样能赋值
29r.Display();// 输出长度 4.5、宽度 3.5、面积 15.75,和 public 那次一样

GetArea 不写修饰符,默认是 private,所以 Display 能调用它,Main 一调用就报同一个错:

C# · 第 30 行
30Console.WriteLine(r.GetArea());// 在 Main 里调用默认 private 的 GetArea
编译报错
Program.cs(30,33): error CS0122: “Rectangle.GetArea()”不可访问,因为它具有一定的保护级别

7protected internal

两种范围合在一起:同一个程序集里能访问;别的程序集里,只要是这个类的子类,也能访问。

8小结

  • public 哪里都能用,private 只有这个类自己能用。
  • protected 加上子类,internal 限在同一个程序集,protected internal 是这两种合起来。
  • 常见做法:数据设成 private,再用 public 方法把该给的功能露出来。

下一节:一个方法怎么定义、怎么调用,又怎么把结果交回来?

检查题

5 道,选完马上告诉你对不对。

  1. 1length 写成 private double length;,Main 里写 r.length = 4.5; 会怎样?

  2. 2类 Rectangle 的方法里读另一个 Rectangle 对象 other 的私有字段 other.length,能编译吗?

  3. 3下面哪种成员,子类能访问、同一程序集里的其他普通类不能访问?

  4. 4类成员前面什么都不写,默认是?

  5. 5别的程序集里的子类能访问哪种成员?