欢迎光临
我们一直在努力

第14篇 C# 零基础入门——面向对象入门:类与对象

第14篇 C# 零基础入门——面向对象入门:类与对象

欢迎来到 C# 零基础入门系列的第14篇!在前面的13篇中,我们已经掌握了控制台交互、变量与常量、条件判断、循环结构、数组、List 集合、Dictionary 字典、字符串处理以及异常处理等核心知识。但到目前为止,我们写的程序都是"面向过程"的——所有代码都挤在 Main 方法里,像一团乱麻。

今天,我们将迈入 C# 最核心、最强大的领域——面向对象编程(OOP)。面向对象是 C# 的灵魂,理解了它,你才算真正掌握了这门语言。本篇将带你从"类与对象"的基本概念出发,系统学习封装、继承、多态这三大特性,并通过一个综合案例将它们串联起来。准备好了吗?让我们开始吧!

一、什么是面向对象编程(OOP)

现实世界类比

想象你要开一家餐厅:

  • 面向过程:你亲自去菜市场买菜、洗菜、切菜、炒菜、端盘子,一个人干所有事。代码写出来就是一行接一行的指令,所有逻辑堆在一起。
  • 面向对象:你雇了厨师、服务员、收银员,每个人有自己的职责,你只需要指挥他们协作。代码写出来就是一堆"对象"互相调用,各司其职。

💡 OOP的核心思想:把程序看作一组相互协作的"对象",每个对象都有自己的数据(状态)和行为(方法),就像现实世界中的事物一样。

OOP三大特性

特性含义生活类比
封装 把数据和操作数据的方法绑在一起,隐藏内部细节 手机:你只需按按钮,不用知道内部电路原理
继承 子类自动拥有父类的属性和方法,并可扩展自己的功能 孩子继承父母的基因,但有自己的特点
多态 同一个方法,不同对象有不同的表现 “叫”:狗汪汪叫,猫喵喵叫,鸟叽叽叫

📌 记忆技巧:封装 = 藏起来保护数据;继承 = 子类继承父类;多态 = 同一个调用,不同表现。

二、类与对象的基本概念

类是模板/蓝图

类(Class)是对一类事物的抽象描述,它定义了这类事物"有什么数据"和"能做什么事"。类本身不占用运行时的内存(它只是代码的一种组织方式),只有根据类创建出对象时,才会真正分配内存。

对象是实例

对象(Object)是根据类创建出来的具体个体,每个对象都有自己独立的数据副本。

现实世界类比

🚗 汽车设计图 vs 实际汽车:

  • 类 = 汽车设计图(定义了颜色、品牌、速度、启动、刹车等属性和行为)
  • 对象 = 根据设计图造出的具体汽车(一辆红色宝马、一辆蓝色奔驰,它们各自有独立的颜色、速度)

// ✅ 正确理解:类是模板,对象是产品
// 设计图(类)只有一份,但可以根据它造出无数辆汽车(对象)
// 每辆汽车的颜色、车牌号、速度都是独立的

// 类比代码(伪代码,帮助理解概念):
// Car myCar = new Car(); // 根据Car类创建一辆具体汽车对象
// myCar.Color = "红色"; // 这辆车是红色的
// myCar.Start(); // 这辆车启动了

// Car yourCar = new Car(); // 再创建另一辆汽车对象
// yourCar.Color = "蓝色"; // 这辆车是蓝色的,和myCar互不影响

⚠️ 新手常见误区:把类和对象搞混。记住:类是"图纸"(抽象的),对象是"实物"(具体的)。你在代码中写 class Student 只是在定义一个类,只有执行 new Student() 时才创建了对象。

三、定义类:字段、属性与方法

一个类通常包含三种成员:字段(Field)、属性(Property)、方法(Method)。下面用一个完整的"学生"类来演示。

完整代码示例

using System;

// 定义一个"学生"类,作为学生的模板/蓝图
public class Student
{
// ========== 字段(Field)==========
// 字段用于存储对象的内部数据
// 通常用 private 修饰符,不让外部直接访问(这是封装的体现)
private string _name; // 学生姓名,下划线开头是C#命名惯例,表示私有字段
private int _age; // 学生年龄
private string _studentId; // 学号

// ========== 属性(Property)==========
// 属性是外部访问字段的"门",通过 get/set 控制读写的权限
// 外部代码不能直接访问 _name,但可以通过 Name 属性来读写

// 可读可写属性:Name
public string Name
{
get { return _name; } // get访问器:读取字段值
set { _name = value; } // set访问器:设置字段值,value是C#关键字,代表传入的值
}

// 可读可写属性:Age,带验证逻辑
public int Age
{
get { return _age; }
set
{
// ✅ 在set中加入验证逻辑,防止外部传入非法值
if (value > 0 && value < 150)
{
_age = value; // 年龄合法,正常赋值
}
else
{
_age = 0; // 年龄不合法,给一个默认安全值
Console.WriteLine("警告:年龄不合法,已设置为0");
}
}
}

// 只读属性:只有get没有set,外部不能修改学号
public string StudentId
{
get { return _studentId; } // 外部只能读,不能写
}

// ========== 方法(Method)==========
// 方法定义对象能做什么事(行为)

// 无返回值的方法:学生自我介绍
public void Introduce()
{
// 方法内部可以直接访问自己的私有字段
Console.WriteLine($"大家好,我叫{_name},今年{_age}岁,学号是{_studentId}。");
}

// 带参数的方法:学生选课
public void EnrollCourse(string courseName)
{
Console.WriteLine($"{_name} 成功选修了《{courseName}》课程。");
}

// 有返回值的方法:获取学生完整信息
public string GetInfo()
{
// 返回一个格式化好的信息字符串
return $"姓名:{_name}, 年龄:{_age}, 学号:{_studentId}";
}
}

使用类创建对象

// 创建学生对象
Student stu = new Student(); // 用new关键字创建对象

// 通过属性设置字段值(不能直接访问私有字段)
stu.Name = "张三"; // ✅ 通过公开属性设置
stu.Age = 20; // ✅ 通过属性设置,会触发验证逻辑
// stu._name = "李四"; // ❌ 编译错误!_name是private的,外部不能直接访问

// 调用方法
stu.Introduce(); // 输出: 大家好,我叫张三,今年20岁,学号是。
stu.EnrollCourse("C#程序设计"); // 输出: 张三 成功选修了《C#程序设计》课程。

// 调用有返回值的方法
string info = stu.GetInfo();
Console.WriteLine(info); // 输出: 姓名:张三, 年龄:20, 学号:

💡 字段 vs 属性的区别:

  • 字段是"仓库",真正存数据的地方,通常用 private 修饰
  • 属性是"门",控制外部如何读写仓库,通常用 public 修饰
  • ⚠️ 重要规范:永远不要直接暴露 public 字段!应该用属性来保护数据。这是 C# 开发的基本规范。

四、构造函数

构造函数是一种特殊方法,在创建对象时自动调用,用于初始化对象的字段。它的方法名必须和类名完全相同,且没有返回值类型。

默认构造函数与带参数构造函数

public class Student
{
private string _name;
private int _age;
private string _studentId;

// 默认构造函数(无参数)
// 如果不写任何构造函数,C#编译器会自动提供一个空的默认构造函数
// 但一旦我们自己定义了构造函数,默认构造函数就不再自动提供了
public Student()
{
_name = "未知"; // 给字段设置默认值
_age = 0;
_studentId = "N/A";
Console.WriteLine("调用了默认构造函数"); // 可以看到构造函数被自动调用
}

// 带参数构造函数:创建对象时直接传入初始值
// 这样就不需要创建后再逐个赋值,更安全、更简洁
public Student(string name, int age, string studentId)
{
_name = name; // 用传入的参数初始化字段
_age = age;
_studentId = studentId;
Console.WriteLine($"调用了带参构造函数,创建了学生:{_name}");
}

// 属性(简写形式,自动属性)
public string Name { get { return _name; } set { _name = value; } }
public int Age { get { return _age; } set { _age = value; } }
public string StudentId { get { return _studentId; } }
}

// 使用构造函数创建对象
Student s1 = new Student(); // 调用默认构造函数
Student s2 = new Student("张三", 20, "S001"); // 调用带参构造函数

this 构造函数链(构造函数重载)

当一个类有多个构造函数时,可以用 this 来调用同一个类中的其他构造函数,避免代码重复。

public class Student
{
private string _name;
private int _age;
private string _studentId;

// 完整的带参构造函数
public Student(string name, int age, string studentId)
{
_name = name;
_age = age;
_studentId = studentId;
}

// 简化的构造函数:只传姓名和年龄,学号自动生成
// : this(name, age, "AUTO_" + DateTime.Now.Ticks) 这是在声明处调用另一个构造函数
public Student(string name, int age) : this(name, age, "AUTO_GEN")
{
// 这里可以补充只传两个参数时的额外初始化逻辑
Console.WriteLine("使用了双参数构造函数(学号自动生成)");
}

// 最简构造函数:只传姓名,年龄和学号都用默认值
public Student(string name) : this(name, 0)
{
// 继续往上链式调用
Console.WriteLine("使用了单参数构造函数");
}
}

// 测试构造函数链
Student s1 = new Student("张三"); // 调用 Student(string) → 链式调用 Student(string, int) → 链式调用 Student(string, int, string)
Student s2 = new Student("李四", 22); // 调用 Student(string, int) → 链式调用 Student(string, int, string)
Student s3 = new Student("王五", 20, "S003"); // 直接调用完整构造函数

⚠️ 注意:this() 调用必须是构造函数体中的第一行语句,编译器会强制检查。

五、this 关键字

this 关键字代表当前对象实例,有三个主要用途:

1. 区分字段和参数(最常见用法)

当方法参数的名字和字段名相同时,用 this.字段名 来明确指向字段。

public class Student
{
private string name; // 字段

// 参数名也叫 name,和字段名冲突
public void SetName(string name)
{
// ❌ 错误写法:这样赋值给了参数自己,字段根本没变!
// name = name; // 左边的name是参数,右边的name也是参数,等于没改

// ✅ 正确写法:this.name 指向字段,name 指向参数
this.name = name; // 把参数值赋给字段
}

public string GetName()
{
return name; // 方法内没有参数冲突,可以直接用字段名
}
}

2. this 调用同一个类的其他构造函数

// 见第四节示例:public Student(string name) : this(name, 0) { }
// 注意:this() 调用必须在构造函数体的第一行

3. this 返回当前对象(链式调用)

public class Student
{
private string name;

// 返回 this 可以支持链式调用
public Student SetName(string name)
{
this.name = name;
return this; // 返回当前对象本身
}

public Student SetAge(int age)
{
if (age > 0 && age < 150)
_age = age;
return this; // 返回当前对象本身
}
}

// 链式调用:一行代码设置多个属性
Student s = new Student();
s.SetName("张三").SetAge(20); // 先设置姓名,再设置年龄,链式调用

💡 记忆口诀:this = “我自己”,在哪个对象的方法里调用,this 就指向哪个对象。

六、封装

为什么需要封装?

封装(Encapsulation)的核心思想是:把数据(字段)藏起来,只暴露必要的操作接口(属性/方法)。这样可以防止外部代码随意修改内部数据,保证对象状态的合法性。

// ❌ 没有封装的危险:任何人都可以随意修改数据
public class BadStudent
{
public string name; // public字段!任何人都能改
public int age; // public字段!任何人都能改
}

// 使用
BadStudent s = new BadStudent();
s.name = "张三";
s.age = 100; // ❌ 年龄可以是负数?这显然不合理!但没有封装,谁也拦不住

// ✅ 有封装的安全:通过属性控制访问
public class GoodStudent
{
private string _name; // 字段私有化,外部无法直接访问
private int _age;

public string Name
{
get { return _name; }
set { _name = value; }
}

public int Age
{
get { return _age; }
set
{
// ✅ 在set中加入验证逻辑,拦截非法值
if (value > 0 && value < 150)
_age = value;
else
throw new ArgumentOutOfRangeException("年龄必须在0到150之间");
}
}
}

// 使用
GoodStudent s = new GoodStudent();
s.Name = "张三";
// s.age = -100; // ❌ 编译错误!_age是private的
s.Age = 20; // ✅ 合法值,正常设置
// s.Age = -100; // ⚠️ 运行时抛出异常,被拦截了

public / private / protected 访问修饰符

修饰符含义谁能访问
public 公共的,任何地方都能访问 所有代码
private 私有的,只有类内部能访问 只有当前类
protected 受保护的,类和子类能访问 当前类 + 子类
internal 程序集内部可见 同一个项目内的所有代码

自动属性(C# 简化写法)

当属性不需要额外的验证逻辑时,可以用自动属性的简化语法:

public class Student
{
// 自动属性:编译器自动生成背后的私有字段
// 等价于:private string _name; public string Name { get; set; }
public string Name { get; set; }
public int Age { get; set; }

// 只读自动属性:只有get,初始化后不能修改
public string StudentId { get; private set; }

public Student(string name, int age, string studentId)
{
Name = name; // 构造函数中可以设置
Age = age;
StudentId = studentId; // private set 允许类内部设置
}
}

💡 最佳实践:

  • 字段一律 private,属性一律 public
  • 需要验证逻辑的用完整属性(手动 get/set)
  • 不需要验证的用自动属性({ get; set; })
  • 只读数据用 private set 或只写属性

七、继承

继承(Inheritance)允许一个类(子类/派生类)继承另一个类(父类/基类)的字段、属性和方法,从而实现代码复用。

继承语法与 base 关键字

// ========== 父类(基类)==========
public class Person
{
// protected 修饰的字段:类内部和子类都可以访问
protected string _name;
protected int _age;

// 父类构造函数
public Person(string name, int age)
{
_name = name;
_age = age;
Console.WriteLine($"Person构造函数被调用:{_name}");
}

// virtual 关键字:标记这个方法"可以被子类重写"
// 如果没有virtual,子类不能用override重写
public virtual void Introduce()
{
Console.WriteLine($"我是{_name},今年{_age}岁,是一个普通人。");
}

public void Eat(string food)
{
Console.WriteLine($"{_name} 正在吃 {food}。");
}
}

// ========== 子类(派生类):用冒号 : 表示继承 ==========
public class Student : Person
{
private string _studentId; // 子类自己的字段

// 子类构造函数必须用 base() 调用父类构造函数
// ⚠️ base() 和 this() 一样,必须在构造函数体的第一行
public Student(string name, int age, string studentId) : base(name, age)
{
_studentId = studentId;
Console.WriteLine("Student构造函数被调用");
}

// override 重写父类的virtual方法,提供子类特有的实现
public override void Introduce()
{
// base.Introduce() 调用父类的同名方法,复用父类逻辑
base.Introduce(); // 先输出父类的自我介绍
Console.WriteLine($"学号:{_studentId}"); // 再补充子类特有信息
}

// 子类可以有自己的新方法
public void Study(string course)
{
Console.WriteLine($"{_name} 正在学习 {course}。");
}
}

// 使用继承
Student stu = new Student("张三", 20, "S001");
// 输出: Person构造函数被调用:张三
// 输出: Student构造函数被调用

stu.Introduce();
// 输出: 我是张三,今年20岁,是一个普通人。
// 输出: 学号:S001

stu.Eat("米饭"); // ✅ 继承自父类的方法
stu.Study("C#"); // ✅ 子类自己的方法

sealed 防止进一步继承

// sealed 类不能被其他类继承,表示这个类已经是"终点"
public sealed class FinalPerson : Person
{
public FinalPerson(string name, int age) : base(name, age)
{
}
}

// ❌ 编译错误:不能继承sealed类
// public class SuperStudent : FinalPerson { } // 报错!

// ✅ sealed也可以修饰方法,防止子类重写
public class Base
{
public sealed virtual void Method() // 这个方法不能被重写
{
Console.WriteLine("Base.Method");
}
}

💡 继承的使用场景:

  • 多个类有共同的属性和方法时,提取公共部分到父类
  • 实现"是一个"关系:学生是一个人,老师是一个人
  • ⚠️ C# 不支持多重继承:一个类只能继承一个父类(但可以实现多个接口)

八、多态

多态(Polymorphism)是面向对象的第三大特性,它允许"同一个方法调用,在不同对象上有不同的表现"。多态的实现依赖于 virtual、override 和继承。

virtual + override 实现多态

// ========== 父类 ==========
public class Animal
{
protected string _name;

public Animal(string name)
{
_name = name;
}

// virtual 声明:这个方法"可以被重写"
public virtual void Speak()
{
Console.WriteLine($"{_name} 发出声音");
}

public virtual void Sleep()
{
Console.WriteLine($"{_name} 正在睡觉");
}
}

// ========== 子类1:狗 ==========
public class Dog : Animal
{
public Dog(string name) : base(name)
{
}

// override 重写:提供狗特有的实现
public override void Speak()
{
Console.WriteLine($"{_name} 说:汪汪汪!");
}

// 子类可以定义父类没有的新方法
public void Fetch()
{
Console.WriteLine($"{_name} 跑去捡球");
}
}

// ========== 子类2:猫 ==========
public class Cat : Animal
{
public Cat(string name) : base(name)
{
}

public override void Speak()
{
Console.WriteLine($"{_name} 说:喵喵喵~");
}

public void Climb()
{
Console.WriteLine($"{_name} 爬树");
}
}

// ========== 子类3:鸟 ==========
public class Bird : Animal
{
public Bird(string name) : base(name)
{
}

public override void Speak()
{
Console.WriteLine($"{_name} 说:叽叽叽!");
}

public void Fly()
{
Console.WriteLine($"{_name} 飞翔");
}
}

多态的实际威力

// ✅ 多态的核心用法:用父类引用指向子类对象
Animal[] animals = new Animal[]
{
new Dog("旺财"),
new Cat("咪咪"),
new Bird("小鸟")
};

// 遍历数组,调用同一个方法,每个对象表现不同!
foreach (Animal animal in animals)
{
animal.Speak(); // 不需要知道具体是什么动物,自动调用正确的版本
}
// 输出:
// 旺财 说:汪汪汪!
// 咪咪 说:喵喵喵~
// 小鸟 说:叽叽叽!

// 💡 多态的价值:新增一种动物只需新建类,不用修改现有代码!
// 比如新增一只猪:
// public class Pig : Animal { … }
// animals = new Animal[] { new Dog("旺财"), new Cat("咪咪"), new Bird("小鸟"), new Pig("猪猪") };
// 上面的foreach代码完全不用改,这就是多态的强大之处——开闭原则!

base 调用父类方法

public class Dog : Animal
{
public Dog(string name) : base(name)
{
}

public override void Speak()
{
base.Speak(); // 先执行父类的逻辑:"旺财 发出声音"
Console.WriteLine($"{_name} 说:汪汪汪!"); // 再执行子类的逻辑
}
}

// 输出:
// 旺财 发出声音
// 旺财 说:汪汪汪!

💡 多态的价值总结:

  • 调用者不需要关心具体类型,代码更灵活
  • 新增子类不需要修改已有代码(开闭原则)
  • 可以用父类类型作为方法参数,接收任意子类对象

// 多态作为方法参数
public void MakeItSpeak(Animal animal)
{
// 不管传进来的是Dog/Cat/Bird,都能正确调用Speak()
animal.Speak();
}

// 使用
MakeItSpeak(new Dog("旺财")); // 汪汪汪!
MakeItSpeak(new Cat("咪咪")); // 喵喵喵~
MakeItSpeak(new Bird("小鸟")); // 叽叽叽!

⚠️ 注意:通过父类引用调用方法时,只能访问父类中声明的成员。如果想调用子类特有的方法(如 Dog 的 Fetch),需要向下转型(as 或 (Dog)),但这会破坏多态的设计初衷,应尽量避免。

九、新手常见错误

错误1:忘记 new 关键字

// ❌ 错误:没有new,对象是null,调用方法会报NullReferenceException
Student student;
student.Name = "张三"; // 运行时崩溃!NullReferenceException

// ✅ 正确:必须用new创建对象
Student student = new Student();
student.Name = "张三"; // 没问题

错误2:字段用 public 直接暴露(破坏封装)

// ❌ 错误:public字段,任何人都能随意修改
public class Student
{
public string Name; // 危险!
public int Age; // 危险!
}

// ✅ 正确:字段private,通过属性公开
public class Student
{
private string _name;
private int _age;

public string Name
{
get { return _name; }
set
{
if (!string.IsNullOrWhiteSpace(value))
_name = value;
}
}
}

错误3:C# 不支持多重继承

// ❌ 错误:C#不允许一个类继承多个类
// public class Student : Person, StudentInfo { } // 编译报错!

// ✅ 正确:只能继承一个类,但可以实现多个接口
public class Student : Person { } // 单继承,正确

// 💡 需要"多重继承"功能时,用接口(后续篇章会讲)或组合模式

错误4:override 忘记在父类声明 virtual

// ❌ 错误:父类方法没有virtual,子类不能override
public class Person
{
public void Speak() { } // 没有virtual
}
public class Student : Person
{
public override void Speak() { } // 编译报错!
}

// ✅ 正确:父类必须声明virtual(或override)
public class Person
{
public virtual void Speak() { } // 加上virtual
}
public class Student : Person
{
public override void Speak() { } // ✅ 正确
}

错误5:this()/base() 调用不在第一行

// ❌ 错误:this()不在构造函数第一行
public Student(string name)
{
Console.WriteLine("先做点其他事"); // 这行在this()前面!
: this(name, 0); // 编译错误!
}

// ✅ 正确:this()或base()必须在第一行
public Student(string name) : this(name, 0)
{
Console.WriteLine("先调用this构造函数,再执行这里"); // ✅ 正确
}

十、综合小练习:学生管理系统

需求

创建一个简单的学生管理系统,要求:

  • 定义 Course(课程)类,包含课程名称和学分
  • 定义 Student(学生)基类,包含姓名和学号
  • 定义 Undergraduate(本科生)和 GraduateStudent(研究生)继承 Student
  • 使用多态显示不同类型学生的信息
  • 给每个学生分配课程
  • 完整代码

    using System;
    using System.Collections.Generic;

    // ========== 课程类 ==========
    public class Course
    {
    // 课程名称(自动属性,公开读写)
    public string CourseName { get; set; }
    // 学分(自动属性,公开读写)
    public int Credit { get; set; }

    // 构造函数:创建课程时必须提供名称和学分
    public Course(string courseName, int credit)
    {
    CourseName = courseName;
    Credit = credit;
    }

    // 显示课程信息
    public void Display()
    {
    Console.WriteLine($" 课程:{CourseName},学分:{Credit}");
    }
    }

    // ========== 学生基类 ==========
    public class Student
    {
    // protected:子类可以访问,外部不能直接访问
    protected string _name;
    protected string _studentId;

    // 构造函数:子类通过base()调用
    public Student(string name, string studentId)
    {
    _name = name;
    _studentId = studentId;
    }

    // virtual:允许子类重写显示逻辑
    public virtual void DisplayInfo()
    {
    Console.WriteLine($"姓名:{_name},学号:{_studentId}");
    }

    // 选课方法
    public void Enroll(Course course)
    {
    Console.WriteLine($"{_name} 选修了《{course.CourseName}》({course.Credit}学分)");
    }
    }

    // ========== 本科生:继承Student ==========
    public class Undergraduate : Student
    {
    public string Major { get; set; } // 专业

    // 用base调用父类构造函数初始化姓名和学号
    public Undergraduate(string name, string studentId, string major)
    : base(name, studentId)
    {
    Major = major;
    }

    // override重写:本科生额外显示专业信息
    public override void DisplayInfo()
    {
    base.DisplayInfo(); // 先调用父类显示基本信息
    Console.WriteLine($"类型:本科生,专业:{Major}");
    }
    }

    // ========== 研究生:继承Student ==========
    public class GraduateStudent : Student
    {
    public string ResearchArea { get; set; } // 研究方向

    public GraduateStudent(string name, string studentId, string researchArea)
    : base(name, studentId)
    {
    ResearchArea = researchArea;
    }

    // override重写:研究生额外显示研究方向
    public override void DisplayInfo()
    {
    base.DisplayInfo();
    Console.WriteLine($"类型:研究生,研究方向:{ResearchArea}");
    }
    }

    // ========== 主程序 ==========
    class Program
    {
    static void Main(string[] args)
    {
    Console.WriteLine("=== 学生管理系统 ===");
    Console.WriteLine();

    // 创建不同类型的学生对象(多态:父类引用指向子类对象)
    Student s1 = new Undergraduate("张三", "U2024001", "计算机科学");
    Student s2 = new GraduateStudent("李四", "G2024001", "人工智能");
    Student s3 = new Undergraduate("王五", "U2024002", "软件工程");

    // 创建课程
    Course c1 = new Course("C#程序设计", 4);
    Course c2 = new Course("数据结构", 3);
    Course c3 = new Course("机器学习", 3);

    // 给学生选课
    s1.Enroll(c1);
    s1.Enroll(c2);
    s2.Enroll(c2);
    s2.Enroll(c3);
    s3.Enroll(c1);

    Console.WriteLine();

    // ⭐ 多态演示:用父类Student数组存储不同类型的学生
    Student[] students = new Student[] { s1, s2, s3 };

    Console.WriteLine("=== 学生信息汇总 ===");
    foreach (Student s in students)
    {
    s.DisplayInfo(); // 自动调用各自重写后的版本
    Console.WriteLine();
    }

    Console.WriteLine("=== 课程信息汇总 ===");
    Course[] courses = new Course[] { c1, c2, c3 };
    foreach (Course c in courses)
    {
    c.Display();
    }
    }
    }

    运行结果

    === 学生管理系统 ===

    张三 选修了《C#程序设计》(4学分)
    张三 选修了《数据结构》(3学分)
    李四 选修了《数据结构》(3学分)
    李四 选修了《机器学习》(3学分)
    王五 选修了《C#程序设计》(4学分)

    === 学生信息汇总 ===
    姓名:张三,学号:U2024001
    类型:本科生,专业:计算机科学

    姓名:李四,学号:G2024001
    类型:研究生,研究方向:人工智能

    姓名:王五,学号:U2024002
    类型:本科生,专业:软件工程

    === 课程信息汇总 ===
    课程:C#程序设计,学分:4
    课程:数据结构,学分:3
    课程:机器学习,学分:3

    十一、小结

    核心要点回顾

    知识点核心内容关键语法
    类与对象 类是模板/蓝图,对象是实例 Student s = new Student();
    字段与属性 字段私有存储,属性公开访问 private string _name; public string Name { get; set; }
    构造函数 创建对象时自动调用,用于初始化 public Student(string name) { … }
    this 关键字 指向当前对象实例 this.name = name; / : this(…)
    封装 用访问修饰符保护数据 private 字段 + public 属性
    继承 子类继承父类,代码复用 class Student : Person / base()
    多态 同一方法,不同表现 virtual + override
    sealed 禁止被继承/重写 public sealed class X

    下一篇预告:第15篇——面向对象进阶(接口与抽象类)

    赞(0)
    未经允许不得转载:171主机测评 » 第14篇 C# 零基础入门——面向对象入门:类与对象
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址