我们已经学会了如何将代码组织成可重用的方法,是时候该接触C#(以及许多编程语言)的核心思想了:面向对象编程 (Object-Oriented Programming, OOP)。
什么是面向对象编程 (OOP)?
想象一下我们之前的编程方式,我们关注的是过程和动作(比如计算、打印、循环)。面向对象编程则换了一种思路,它更关注现实世界中的“事物” 以及这些事物之间的交互。
OOP 的核心思想是把构成问题的各个事物抽象成程序中的对象 (Object)。每个对象都有自己的状态 (数据/属性) 和 行为 (功能/方法)。
举个例子:
如果我们想要做一个简单的小怪。
- 事物:哥布林
- 哥布林的状态(数据/属性):攻击力(Damage)、生命值(Health)、防御力(Defense)
- 哥布林的行为(功能/方法):攻击玩家、追击玩家、随处走动
OOP就是教我们如何把“哥布林”这个概念,连同它的属性和行为,封装成一个程序单元。
主要优点:
- 更贴近现实世界: 代码结构更能反映真实世界的问题,更容易理解。
- 封装: 将数据和操作这些数据的方法捆绑在一起(在类里面),隐藏内部实现细节,只暴露必要的接口。这提高了安全性和模块性。
- 继承: 允许创建一个新类(子类)来继承现有类(父类)的属性和方法,并可以添加或修改功能。这促进了代码重用。 (我们后面会学)
- 多态: 允许不同类的对象对同一个消息(方法调用)做出不同的响应。这提高了灵活性和可扩展性。 (我们后面会学)
这一篇文档我们先学习最基础的类和对象,以及封装的概念。
类 (Class):对象的蓝图
类 (Class) 是创建对象的模板或蓝图 。它定义了一类对象共同拥有的属性 (数据) 和 方法 (行为)。
- 类本身不是对象。 它只是一个定义、一个规范。就像“汽车”的设计图纸,它描述了汽车应该有哪些部件(轮子、引擎、方向盘)和功能(启动、加速、刹车),但图纸本身不能开动。
- 类定义了对象的结构和能力。
对象 (Object):类的实例
对象是根据类这个蓝图实际创建出来的具体实例。
- 对象是真实存在的。 就像根据汽车图纸制造出来的每一辆具体的汽车(你的车、我的车、张三的车)。
- 每个对象都拥有类定义的属性和方法。
- 每个对象的状态 (属性值) 可以是不同的。 你的车是红色的,我的车是蓝色的,但它们都遵循“汽车”这个类的定义。
定义一个简单的类
我们来定义一个表示”怪物哥布林“的类。通常,我们会为每个类创建一个单独的.cs文件(虽然你仍然可以在一个cs文件里塞入多个类,但是这是良好的组织方式)。
- 在 Visual Studio 的主界面中,找到上侧栏的项目(P),单击下拉菜单,然后点击添加类(C)。
- 在弹出的窗口中,选择空类定义(我们还没接触其它类的用法和作用,先使用空类)。
- 改写类名,我改为Master.cs。
- 然后点击 "添加"。

添加完成后Visual Studio会自动打开创建好的类Master.cs,默认内容类似这样:
//Master.cs文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace website_study //这是你的命名空间,与你的项目一致
{
internal class Master //默认是internal访问修饰符
{
//我们将在这里定义哥布林的属性和方法
}
}
现在,我们来为Master类添加属性和方法:
字段 (Fields) - 存储内部数据 (通常设为 private)
字段是类内部用来存储数据的变量。按照封装的原则,字段通常被声明为private,意味着它们只能够在类的内部被访问,不能从类的外部直接修改,以保护数据的完整性。
//Master.cs文件
namespace website_study
{
internal class Master
{
// --- 字段 (Fields) ---
// 通常设为 private,用于存储对象的内部状态
private string _name; // 名字字段 (约定俗成用下划线开头)
private int _damage; // 伤害字段
private int _defense; // 防御力字段
// ... 后面添加属性和方法 ...
}
}
属性 (Properties) - 控制对数据的访问 (通常设为 public)
既然字段是private的,我们怎么从外部读取或设置哥布林的名字和伤害、防御力呢?答案是使用属性 (Properties) 。属性提供了一种受控的方式来访问对象的私有字段。它看起来像字段,但实际上是包含了特殊方法(get 和 set访问器)的代码块。
- get 访问器: 用于读取私有字段的值。
- set 访问器: 用于设置私有字段的值。你可以在 set 中添加验证逻辑。value 是一个隐式关键字,代表要赋给属性的值。
//Master.cs文件
namespace website_study
{
internal class Master
{
// --- 字段 (Fields) ---
// 通常设为 private,用于存储对象的内部状态
private string _name; // 名字字段 (约定俗成用下划线开头)
private int _damage; // 伤害字段
private int _defense; // 防御力字段
private double _health;
// --- 属性 (Properties) ---
// 提供对私有字段 _name 的公共访问
public string Name // 属性名通常用 PascalCase
{
get { return _name; } // 读取时返回 _name 的值
set { _name = value; } // 写入时将传入的 value 赋给 _name
}
// 提供对私有字段 _damage 的公共访问,并添加验证
public int damage
{
get { return _damage; }
set
{
if (value >= 0 && value <= 120)
{
_damage = value;
}
else
{
// 可以抛出异常或给一个默认值,这里简单打印错误信息
Console.WriteLine("错误:伤害必须在 0 到 120 之间!");
// 或者可以设置一个默认值,比如 _damage = 0;
}
}
}
public double Health
{
get { return _health; }
set
{
if (value >= 0 && value <= 100)
{
_health = value;
}
else
{
// 可以抛出异常或给一个默认值,这里简单打印错误信息
Console.WriteLine("错误:生命值必须在 0 到 100 之间!");
// 或者可以设置一个默认值,比如 _health = 0;
}
}
}
// 只读属性 (只有 get 访问器),防御力通常设置后不应随意更改
public int Defense
{
get { return _defense; }
// 没有 set 访问器,所以无法从外部直接修改 Defense
// (通常通过构造函数或特定方法设置)
}
// --- 自动实现的属性 (Auto-Implemented Properties) ---
// 如果属性的 get 和 set 只是简单地存取一个私有字段,没有额外逻辑
// C# 提供了更简洁的语法,编译器会自动生成一个匿名的私有后备字段
public int Rage { get; set; } // 怒气值 (读写)
public string weapon { get; private set; } // 武器 (外部可读,只能内部或构造函数设置)
// ... 后面添加构造函数和方法 ...
}
}
自动实现的属性public string weapon { get; private set; }是编写简单属性的快捷方式,非常好用。
方法 (Methods) - 定义对象的行为
方法定义了对象能做什么。在类中定义方法和我们之前在 Program 类中定义 static 方法类似,但这里的方法通常不是 static 的(除非特殊需要)。非静态方法属于对象,而不是类本身,它们可以访问该对象的字段和属性。
// Master.cs 文件 (续)
namespace website_study
{
internal class Master
{
// --- 字段, 属性 (省略)... ---
// --- 方法 (Methods) ---
// 定义学生的行为,通常是 public
// 注意:这些方法不是 static 的!它们属于 Student 对象
// 介绍的方法
public void Introduce()
{
// 方法内部可以直接访问当前对象的属性和字段
Console.WriteLine($"这是{Name},伤害{damage} 。");
// 注意这里用的是属性 Name 和 damage(因为它们是 public 的,且有 get)
// 也可以访问私有字段,比如 Console.WriteLine($"防御力是 {_defense}");
}
// 更新生命的方法 (示例,演示可以有 private set 的属性)
public void Updatehealth(double newhealth)
{
if (newhealth >= 0.0 && newhealth <= 120.0)
{
this.health = newhealth; // 使用 this 关键字明确指代当前对象的 health 属性
// 这里的 health 属性是 { get; private set; },所以可以在类内部的方法中设置
}
else
{
Console.WriteLine("错误:health 必须在 0.0 到 120.0 之间!");
}
}
// (可选) `this` 关键字:
// 在方法内部,`this` 关键字引用当前对象实例。
// 当方法参数名与字段或属性名相同时,必须用 `this` 来区分。
// 例如: public void SetName(string name) { this._name = name; }
// 在上面的 Updatehealth 中,虽然不是必须,但用 this.health 更清晰。
// ... 后面添加构造函数 ...
}
}
创建对象 (实例化)
有了类的蓝图 (Student.cs),我们就可以在需要的地方(比如 Program.cs 的 Main 方法中)创建具体的怪物哥布林对象了。这通常使用 new 关键字,并调用类的构造函数 (Constructor)。
构造函数 (Constructor) 是一个特殊的方法,它的名字与类名完全相同,并且没有返回类型(连 void 都没有)。它的主要作用是在创建对象时初始化对象的状态(给字段/属性赋初始值)。
- 默认构造函数: 如果你没有在类中定义任何构造函数,C# 编译器会自动为你生成一个无参数的、公开的默认构造函数。这个默认构造函数不做任何事情,对象的字段会被初始化为它们的默认值(0, false, null 等)。
- 自定义构造函数: 你可以定义自己的构造函数来接收参数,以便在创建对象时就提供初始值。一旦你定义了任何构造函数,编译器就不再自动生成默认的无参构造函数了。如果还需要无参构造函数,你必须自己显式定义一个。
我们来给Master类添加一个构造函数,用于在创建哥布林对象时设置名字、伤害和防御力:
//Master.cs文件
namespace website_study
{
internal class Master
{
// --- 字段 (Fields) ---
// 通常设为 private,用于存储对象的内部状态
private string _name; // 名字字段 (约定俗成用下划线开头)
private int _damage; // 伤害字段
private int _defense; // 防御力字段
private double _health;
// --- 属性 (Properties) ---
// 提供对私有字段 _name 的公共访问
public string Name // 属性名通常用 PascalCase
{
get { return _name; } // 读取时返回 _name 的值
set { _name = value; } // 写入时将传入的 value 赋给 _name
}
// 提供对私有字段 _damage 的公共访问,并添加验证
public int damage
{
get { return _damage; }
set
{
if (value >= 0 && value <= 120)
{
_damage = value;
}
else
{
// 可以抛出异常或给一个默认值,这里简单打印错误信息
Console.WriteLine("错误:伤害必须在 0 到 120 之间!");
// 或者可以设置一个默认值,比如 _damage = 0;
}
}
}
public double Health
{
get { return _health; }
set
{
if (value >= 0 && value <= 100)
{
_health = value;
}
else
{
// 可以抛出异常或给一个默认值,这里简单打印错误信息
Console.WriteLine("错误:生命值必须在 0 到 100 之间!");
// 或者可以设置一个默认值,比如 _health = 0;
}
}
}
// 只读属性 (只有 get 访问器),防御力通常设置后不应随意更改
public int Defense
{
get { return _defense; }
// 没有 set 访问器,所以无法从外部直接修改 Defense
// (通常通过构造函数或特定方法设置)
}
// --- 自动实现的属性 (Auto-Implemented Properties) ---
// 如果属性的 get 和 set 只是简单地存取一个私有字段,没有额外逻辑
// C# 提供了更简洁的语法,编译器会自动生成一个匿名的私有后备字段
public int Rage { get; set; } // 怒气值 (读写)
public string weapon { get; private set; } // 武器 (外部可读,只能内部或构造函数设置)
// ... 后面添加构造函数和方法 ...
// --- 构造函数 (Constructor) ---
// 特殊方法,名字与类名相同,没有返回类型
// 用于初始化对象的状态
public Master(string initialName, int initialDamage, int initialDefense)
{
Console.WriteLine("Master构造函数被调用");
this.Name = initialName; //this.Name调用Name属性的set
this.damage = initialDamage; //调用damage属性的set
this._defense = initialDefense; //直接访问私有字段
//weapon没有在构造函数中赋值,所以它的默认值是 null
}
}
}
现在,我们可以在Program.cs中创建Master对象了:
using System;
using website_study;
internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("程序开始,准备创建怪物对象");
// 使用 new 关键字和构造函数创建 Student 类的实例 (对象)
// Master1 是一个引用变量,指向内存中新创建的Master对象
Master master1 = new Master("哥布林一号", 100, 50);
Master master2 = new Master("哥布林二号", 80, 60);
Master master3 = new Master("哥布林三号", 90, 70);
Console.WriteLine("怪物对象创建完毕!");
// 现在可以通过对象变量 master1 和 master2 来访问对象的属性和方法
// 访问属性(调用get访问器)
Console.Write($"第一个哥布林的名字:{master1.Name}");
Console.WriteLine($"第二个哥布林的伤害:{master2.damage}");
//修改属性(调用set访问器)
master1.Name = "哥布林王";
Console.WriteLine($"哥布林一号名字更新为:{master1.Name}");//输出哥布林王
master2.damage = 150; // 尝试设置无效伤害
Console.WriteLine($"尝试设置哥布林二号伤害为150后,实际伤害:{master2.damage}");
Console.WriteLine("程序结束");
Console.ReadKey();
}
}
访问对象成员
创建对象后,使用点运算符 (.) 来访问对象的公共 (public) 属性和调用其公共 (public) 方法。
基本语法:
objectName.PropertyName //访问属性
objectName.MethodName([arguments]) //调用方法
之后按下ctrl + F5运行Program.cs,运行结果如下:

以下是我的代码文件,仅供参考(由于是边学边写边写文档,代码文件可能乱看不懂,可以在底下留言)
Comments NOTHING