第九阶段:面向对象编程–>类与对象

QcrTiMo 发布于 9 天前 13 次阅读


我们已经学会了如何将代码组织成可重用的方法,是时候该接触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,运行结果如下:

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

斯哈斯哈斯哈,佳代子啊啊啊啊啊啊ᕕ(◠ڼ◠)ᕗᕕ(◠ڼ◠)ᕗᕕ(◠ڼ◠)ᕗᕕ(◠ڼ◠)ᕗᕕ(◠ڼ◠)ᕗ
最后更新于 2025-05-19