Skip to Content
Week 03Day 1 - 类与对象

Day 1 - 类与对象

建议用时:180-210 分钟

你将学会什么

  • 为什么程序需要类和对象
  • class Productnew Product()、变量 product 分别是什么
  • 属性、方法、构造函数分别负责什么
  • 对象初始化器和构造函数有什么区别
  • 为什么要把数据和行为放到同一个类里
  • 如何写一个有校验、有方法的商品类

第三周开始进入面向对象。不要把类理解成语法壳子,先把它理解成“把一类数据和它的动作放在一起”。

本页固定顺序

  1. 先学第一部分:弄懂今天最小、最重要的知识,并运行短例子。
  2. 再学第二部分:把刚学的知识组合成一个完整例子。
  3. 然后做第三部分:自己跟着敲,再完成重复训练和每日小测。
  4. 最后做第四部分:先独立完成作业,再用完整答案检查。

学习衔接

上一页学习的是“复盘与巩固”,今天继续学习“类与对象”。先使用上一页已经会的写法,再只增加今天这个新知识点;如果前置内容还不能独立敲出,先回上一页复习,不要硬跳。

今天的最低通过线

第一次学习不要求背完整页。完成下面 3 项,就可以继续:

  • 能用自己的话说明“类与对象”解决什么问题。
  • 把第一部分的短例子亲手敲完,并确认每个例子都能运行。
  • 不看完整答案完成第三部分至少前 3 个例子,再主动改一个值观察结果。

第一部分:先学原理和最小知识

这一部分先解决“这是什么、为什么要这样写”。每看到一个短例子就亲手敲一遍并运行,不要先背完整程序。

今天先抓住什么

前面写程序时,数据和方法经常是分散的:

string name decimal price int count CalculateTotal(price, count) PrintProduct(name, price)

类的作用是把一类数据和相关动作放到一起:

Product Name Price ChangePrice CalculateTotal Print

这样代码更像业务本身。

名词先讲清楚

名词直接解释例子
一类对象的模板class Product
对象按类创建出来的具体实例new Product(...)
实例对象的另一种说法一个具体商品
属性对象保存的数据NamePrice
方法对象能执行的动作ChangePrice
构造函数创建对象时执行的初始化方法public Product(...)
new创建对象new Product(...)
this当前对象自己this.Name
封装把数据和规则放在一起价格校验放进 Product

从今天开始按文件顺序学习

Day 5 学的是 Program.cs 里的本地方法,所以可以在同一个文件中先定义、后调用。

从今天开始学习类,真实项目通常把类和使用它的代码放在不同文件:

第 1 步:先写 Product.cs,定义 Product 类、属性和方法 第 2 步:再写 Program.cs,创建 Product 对象并调用方法

后面的示例如果标了文件名,就按文件名逐个创建,不要把两个文件的代码全塞进同一个 Program.cs

有些完整示例为了方便展示,会把顶级语句和类放在同一个代码块里。那时 C# 的顶级语句必须写在类声明前面,但并不是“运行到后面才创建这个类”。编译时,C# 会先检查并生成整个项目里的类和方法;运行时,才从程序入口开始执行。练习时优先按 Product.csProgram.cs 两个文件来写,最不容易混乱。

类和对象的区别

类是模板,对象是具体数据。

先创建 Product.cs,定义类:

class Product { public string Name { get; } public decimal Price { get; } public Product(string name, decimal price) { Name = name; Price = price; } public void Print() { Console.WriteLine($"{Name}: {Price:F2}"); } }

再打开 Program.cs,创建和使用对象:

var keyboard = new Product("Keyboard", 199m); var mouse = new Product("Mouse", 59m); keyboard.Print(); mouse.Print();

Product 是类,keyboardmouse 是两个不同对象。它们使用同一张模板,但保存不同数据。

属性保存状态

属性回答“这个对象现在是什么样”。继续使用刚才的 Product.cs,这里只替换 Program.cs

var product = new Product("Keyboard", 199m); Console.WriteLine(product.Name); Console.WriteLine(product.Price);

输出:

Keyboard 199

方法表达动作

方法回答“这个对象能做什么”。先在 Product.cs 的类里面加入方法定义:

public decimal CalculateTotal(int count) { return Price * count; }

再在 Program.cs 调用已经定义好的对象方法:

var product = new Product("Keyboard", 199m); decimal total = product.CalculateTotal(3); Console.WriteLine(total);

CalculateTotal 放在 Product 里,是因为总价计算和商品价格密切相关。

构造函数保证对象一开始就有效

如果这样写:

var product = new Product(); product.Name = ""; product.Price = -1m;

对象可能一开始就是错误状态。更稳的做法是在构造函数里收齐必要数据并校验。

先把带校验的构造函数写进 Product.cs

class Product { public string Name { get; } public decimal Price { get; } public Product(string name, decimal price) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("名称不能为空"); } if (price < 0) { throw new ArgumentException("价格不能小于 0"); } Name = name.Trim(); Price = price; } }

再在 Program.cs 创建对象:

var product = new Product("Keyboard", 199m); Console.WriteLine(product.Name);

getsetprivate set

public string Name { get; } public decimal Price { get; private set; }

意思是:

写法含义
{ get; }外部只能读取,不能修改
{ get; set; }外部可以读取,也可以修改
{ get; private set; }外部可以读取,类内部可以修改

价格一般不建议外部随便改,可以通过 ChangePrice 方法统一校验。

对象初始化器

对象初始化器适合简单数据类:

先创建 Customer.cs

class Customer { public string Name { get; set; } = ""; public string Level { get; set; } = ""; }

再在 Program.cs 创建对象:

var customer = new Customer { Name = "Tom", Level = "VIP" }; Console.WriteLine($"{customer.Name}: {customer.Level}");

如果创建对象时必须校验必要数据,优先用构造函数。

类和对象常用写法速查

需求写法说明
定义类class Product { }描述一种对象
创建对象new Product(...)得到一个具体对象
只读属性public string Name { get; }构造后不允许改
内部可改属性public decimal Price { get; private set; }外部不能直接改
构造函数public Product(...)保证对象创建时有效
对象方法product.ChangePrice(99m)让对象自己处理规则
对象初始化器new Product { Name = "A" }适合简单数据对象
放进列表List<Product>保存多个对象
遍历对象foreach (Product p in products)批量处理对象

常用模板:

class Product { public string Name { get; } public decimal Price { get; private set; } public Product(string name, decimal price) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("商品名称不能为空"); } if (price <= 0) { throw new ArgumentException("价格必须大于 0"); } Name = name.Trim(); Price = price; } public void ChangePrice(decimal price) { if (price <= 0) { throw new ArgumentException("价格必须大于 0"); } Price = price; } }

这就是类最常见的写法:属性保存状态,构造函数保证初始有效,方法负责修改规则。

今天写类时按这个顺序

  1. 先找业务名词,例如商品、客户、订单。
  2. 把名词变成类。
  3. 把这类数据变成属性。
  4. 把这类动作变成方法。
  5. 用构造函数保证必要数据不为空。
  6. 创建对象并调用方法验证。

第二部分:把知识组合成完整例子

前面已经学过最小知识。现在把它们组合起来,先读懂执行顺序,再完整敲一遍。今天最终要写一个 Product 类,用它创建商品对象,并让对象自己计算总价。

最终你要写出这个程序

var product = new Product("Keyboard", 199m); product.ChangePrice(179m); product.Print(); decimal total = product.CalculateTotal(2); Console.WriteLine($"购买 2 个总价: {total:F2}"); class Product { public string Name { get; } public decimal Price { get; private set; } public Product(string name, decimal price) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("商品名称不能为空"); } if (price < 0) { throw new ArgumentException("价格不能小于 0"); } Name = name.Trim(); Price = price; } public void ChangePrice(decimal price) { if (price < 0) { throw new ArgumentException("价格不能小于 0"); } Price = price; } public decimal CalculateTotal(int count) { if (count <= 0) { throw new ArgumentException("数量必须大于 0"); } return Price * count; } public void Print() { Console.WriteLine($"{Name}: {Price:F2}"); } }

输出:

Keyboard: 179.00 购买 2 个总价: 358.00

这个例子里,Product 类把商品名称、价格、改价、计算总价、打印信息放在了一起。

第三部分:跟着敲代码

从这里开始动手。每个例子都可以直接放进 Program.cs 运行。

例子 1:创建第一个类和对象

var product = new Product(); product.Name = "Keyboard"; product.Price = 199m; Console.WriteLine($"{product.Name}: {product.Price:F2}"); class Product { public string Name { get; set; } = ""; public decimal Price { get; set; } }

预期输出:

Keyboard: 199.00

例子 2:给类添加方法

var product = new Product(); product.Name = "Keyboard"; product.Price = 199m; decimal total = product.CalculateTotal(2); Console.WriteLine($"总价: {total:F2}"); class Product { public string Name { get; set; } = ""; public decimal Price { get; set; } public decimal CalculateTotal(int count) { return Price * count; } }

例子 3:用构造函数创建有效对象

var product = new Product("Keyboard", 199m); product.Print(); class Product { public string Name { get; } public decimal Price { get; } public Product(string name, decimal price) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("名称不能为空"); } if (price < 0) { throw new ArgumentException("价格不能小于 0"); } Name = name.Trim(); Price = price; } public void Print() { Console.WriteLine($"{Name}: {Price:F2}"); } }

例子 4:用方法统一修改价格

var product = new Product("Keyboard", 199m); product.ChangePrice(179m); product.Print(); class Product { public string Name { get; } public decimal Price { get; private set; } public Product(string name, decimal price) { Name = name; Price = price; } public void ChangePrice(decimal price) { if (price < 0) { throw new ArgumentException("价格不能小于 0"); } Price = price; } public void Print() { Console.WriteLine($"{Name}: {Price:F2}"); } }

例子 5:多个对象放进 List

var products = new List<Product> { new("Keyboard", 199m), new("Mouse", 59m), new("Cable", 19m) }; foreach (Product product in products) { product.Print(); } class Product { public string Name { get; } public decimal Price { get; } public Product(string name, decimal price) { Name = name; Price = price; } public void Print() { Console.WriteLine($"{Name}: {Price:F2}"); } }

例子 6:对象初始化器

var customer = new Customer { Name = "Tom", Level = "VIP" }; Console.WriteLine($"{customer.Name}: {customer.Level}"); class Customer { public string Name { get; set; } = ""; public string Level { get; set; } = ""; }

常见错误和修法

错误为什么错修法
类里只放字段不放行为对象只变成数据袋把和数据强相关的规则放进方法
属性没有默认值创建对象后可能出现空值给字符串属性设置 = "" 或通过构造函数传入
构造函数参数太乱创建对象时容易传错参数顺序按字段重要性排列,必要时分行写
方法直接修改外部集合对象边界不清楚让对象只管理自己的状态
所有类都写在 Program.cs项目变大后难维护小练习可放一起,项目里按类拆文件

小白重复敲写训练

类和对象要靠反复创建不同对象来记忆。

训练 1:第一个商品类

var product = new Product(); product.Name = "Keyboard"; product.Price = 199m; Console.WriteLine($"{product.Name}: {product.Price:F2}"); class Product { public string Name { get; set; } = ""; public decimal Price { get; set; } }

第二遍再创建一个 Mouse 对象,不要修改第一个对象。

训练 2:给类增加方法

var product = new Product { Name = "Mouse", Price = 59m }; product.Print(); class Product { public string Name { get; set; } = ""; public decimal Price { get; set; } public void Print() { Console.WriteLine($"{Name}: {Price:F2}"); } }

改动任务:增加 Stock 属性,并在 Print 中输出库存。

训练 3:构造时传入数据

var product = new Product("Monitor", 899m); Console.WriteLine(product.Name); class Product { public Product(string name, decimal price) { Name = name; Price = price; } public string Name { get; } public decimal Price { get; } }

第三遍关掉答案,自己写出构造方法。

每日小测

做完本页后,用这 5 题检查是否真的掌握。

1. 判断题

本页的目标不是只把代码运行起来,还要能说清楚“为什么这样写”。

答案:对。能运行只是第一步,能解释原理、常用操作和常见错误,才说明本页内容进入了可复用能力。

2. 填空题

本页主题是:类与对象。今天至少要掌握的 3 个点是:

1. 为什么程序需要类和对象 2. `class Product`、`new Product()`、变量 `product` 分别是什么 3. 属性、方法、构造函数分别负责什么

答案:以上 3 点必须能用自己的代码跑通,不能只停留在阅读。

3. 流程题

遇到本页相关功能时,先按什么顺序处理?

答案:先看完整例子,确认最终效果;再读原理和名词;然后跟着第三部分从空项目敲代码;最后对照作业答案检查。

4. 找错误题

如果本页代码运行失败,第一步应该做什么?

答案:先看终端或 IDE 里的第一条错误,找到文件名和行号;不要同时改很多地方。再回到本页的“常见错误和修法”表格,对照错误类型逐项排查。

5. 改需求题

在本页完整例子跑通后,至少改一个小需求。

可选改法:

  • 改一个字段名称。
  • 多加一个校验条件。
  • 多输出一行结果。
  • 把固定数据改成用户输入。
  • 把一次处理改成多条数据处理。

答案标准:修改后能重新运行,并能说明这次修改影响了哪一段逻辑。重点检查:为什么程序需要类和对象。

上位机专项练习

上位机中的设备、点位、采集记录和报警都适合建模成类。先定义类型,再创建对象。

下面 3 个例子都要亲手敲。先运行原代码,再完成每个例子后面的改动任务。

专项例子 1:定义并创建设备对象

class Device { public string Name { get; } public bool IsOnline { get; private set; } public Device(string name) { Name = name; } public void Connect() { IsOnline = true; } } class Program { static void Main() { var device = new Device("PLC-01"); device.Connect(); Console.WriteLine($"{device.Name}: {device.IsOnline}"); } }

运行结果或界面效果:

PLC-01: True

改动任务: 增加 Disconnect 方法,把 IsOnline 改回 false。

专项例子 2:用构造函数校验设备编号

class Device { public string Id { get; } public Device(string id) { if (string.IsNullOrWhiteSpace(id)) throw new ArgumentException("设备编号不能为空"); Id = id.Trim(); } } class Program { static void Main() { var device = new Device(" PLC-02 "); Console.WriteLine(device.Id); } }

运行结果或界面效果:

PLC-02

改动任务: 传入空字符串,观察异常。

专项例子 3:让设备自己判断报警

class TemperatureDevice { public double AlarmLimit { get; } public TemperatureDevice(double alarmLimit) { AlarmLimit = alarmLimit; } public bool IsAlarm(double value) { return value >= AlarmLimit; } } class Program { static void Main() { var device = new TemperatureDevice(80); Console.WriteLine(device.IsAlarm(82.5)); } }

运行结果或界面效果:

True

改动任务: 把报警线改成 90。

第四部分:作业完整答案

这一部分给出当天作业的完整答案。先按第三部分做一遍,再用这里检查。

作业要求

写一个 Product 类,要求:

  1. Name 属性。
  2. Price 属性,外部只能读取,类内部可以修改。
  3. 构造函数里校验名称不能为空、价格不能小于 0。
  4. ChangePrice 方法可以修改价格,但新价格不能小于 0。
  5. CalculateTotal 方法根据数量计算总价。
  6. Print 方法输出商品信息。
  7. 创建多个商品,放进 List<Product>,逐个输出。

完整代码

var products = new List<Product> { new("Keyboard", 199m), new("Mouse", 59m), new("Cable", 19m) }; products[0].ChangePrice(179m); foreach (Product product in products) { product.Print(); Console.WriteLine($"购买 2 个总价: {product.CalculateTotal(2):F2}"); } class Product { public string Name { get; } public decimal Price { get; private set; } public Product(string name, decimal price) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException("商品名称不能为空"); } if (price < 0) { throw new ArgumentException("价格不能小于 0"); } Name = name.Trim(); Price = price; } public void ChangePrice(decimal price) { if (price < 0) { throw new ArgumentException("价格不能小于 0"); } Price = price; } public decimal CalculateTotal(int count) { if (count <= 0) { throw new ArgumentException("数量必须大于 0"); } return Price * count; } public void Print() { Console.WriteLine($"{Name}: {Price:F2}"); } }

正确运行结果

你应该能看到:

Keyboard: 179.00 购买 2 个总价: 358.00 Mouse: 59.00 购买 2 个总价: 118.00 Cable: 19.00 购买 2 个总价: 38.00

你要能说清楚这 6 件事

  • 类是模板,对象是具体实例。
  • 属性保存对象状态。
  • 方法表达对象动作。
  • 构造函数负责创建对象时初始化数据。
  • private set 可以防止外部绕过校验随便改值。
  • 对象可以放进 List,再逐个调用方法。