C#核心学习(二)面向对象——封装(1)成员变量与成员方法

在上一篇文章中,我们初始了类以及对象。今天我们继续学习面向对象中的封装特性中的成员变量及成员方法。

一、什么是封装?

官方解释 :封装是面向对象编程的基石,通过绑定数据与操作控制访问权限 ,实现数据保护和逻辑隔离。
程序员翻译

"把你的代码想象成一座城堡:

  • 成员变量是藏在金库里的黄金
  • 成员方法是城堡的守卫和交易窗口
  • 访问修饰符是金库的密码锁"

示例:

cs 复制代码
public class Bank {
    private decimal _goldReserves; // 黄金藏在金库(private)
    
    public void Withdraw(decimal amount) { // 交易窗口(public方法)
        if (amount <= _goldReserves) _goldReserves -= amount;
    }
}

下面的示例是一个类里面我们可以申明写什么东西,可以申明各式各样的类型的变量,就和前面我们在函数中申明的变量一样,只不过这个类里面申明的变量的使用范围和访问修饰符有关。

cs 复制代码
    class Person
    {
        //特征-成员变量
        //姓名
       public string name;
        //年龄
       public int age;
        //性别
        public E_SexType sex;
        //朋友
        public Person[] boyFriend;

        //如果在类中申明一个和自己同名的成员变量 但不能对他实例化 否则内存爆炸
    
    }

二、为什么需要封装?

++1. ​**防止"魔法数值"攻击++

直接暴露字段等于在代码里写"欢迎来搞破坏":

cs 复制代码
public class User {
    public int Age = 25; // 公共字段?危险!
}

// 魔鬼操作:
User user = new User();
user.Age = -30; // 年龄-30岁?时间旅行者?

封装后的救赎

cs 复制代码
private int _age;
public int Age {
    set => _age = value >= 0 ? value : throw new Exception("年龄不能为负!");
}

注意:这里的Age是一个属性,我们后面会详细的讲述,你在这里可以直接把他理解为一个方法即可。

++2. ​代码维护:换引擎不用换车++

假设你有一个Car类:

  • 直接暴露Engine字段:升级电动引擎需修改所有调用代码
  • 封装后:只需修改内部实现,用户仍调用StartEngine()

工程师哲学

"用户不需要知道发动机是烧油还是用电,踩油门能跑就行!"

三、访问修饰符------代码的"权限卡"

public 公共的 自己内部和外部都能访问使用
private 私有的 自己的内部才能访问使用 不写 默认private
protected 保护的 自己和自己的子类 才能访问和使用

internal是C#中的访问修饰符,表示成员或类型仅在当前程序集(Assembly)内可见。

修饰符 权限说明 适用场景
private 仅限当前工牌员工访问 敏感数据(如密码、密钥)
public 访客也能用的公共电梯 工具方法(如CalculateSum()
protected 家族企业继承权 基类共享给派生类的数据
internal 公司内部机密文件 同一程序集的工具类

注意:internal的作用

核心用途
  1. ++​程序集内部共享:++
    • ++适用于在同一个项目(如一个DLL或EXE)中共享类、方法或字段,但对其他项目不可见。++
  2. ++​替代public的受限场景:++
    • ++当需要对外隐藏实现细节,但允许同一程序集内协作时使用。++
示例场景

假设你有一个类库项目 MyUtilities.dll(类库是什么我们后面再说,这里只是举例说明internal的作用),其中包含

cs 复制代码
// 仅在MyUtilities.dll内可见
internal class InternalLogger {
    public static void Log(string message) {
        Console.WriteLine($"[LOG] {message}");
    }
}

// 同一程序集中的其他类可以调用
public class DataProcessor {
    public void Process() {
        InternalLogger.Log("Processing started"); // 合法
    }
}

外部项目引用MyUtilities.dll时:无法访问InternalLogger类。

四、成员变量

基本规则
1.申明在类语块中
2.用来描述对象的特征
3.可以是任意变量类型
4.数量不做限制
5.是否赋值根据需求来定

你可以把他理解为就是申明一个变量,之前我们都是在一个函数中申明,现在跑到一个类里面进行申明和使用了。

五、成员方法

基本概念
成员方法(函数)用来表现对象的行为
1.申明在类语句块中
2.用来描述对象的行为的
3.规则和函数申明规则相同
4.收到访问修饰符规则限制
5.返回值参数不受限制
6.方法数量不受限制

注意
1.成员方法不要加static关键字
2.成员方法 必须实例化出对象 再通过对象来使用 相当于是该对象执行了某个行为
3.成员方法 收到访问修饰符的影响

就是待在类里面的函数,之前我们都是在系统直接给我们提供好的类里面进行编写代码,现在我们要将要刻画的对象抽象成一个类来自己进行描述。

下面是一个完整的示例:

cpp 复制代码
using System;

// 定义学生状态枚举
public enum StudentStatus
{
    Active,     // 在读状态
    Graduated,  // 已毕业
    Suspended   // 休学中
}

// 定义课程成绩结构体
public struct GradeRecord
{
    public string CourseName; // 课程名称
    public float Score;       // 分数值(0-100)
    public bool IsPassed;     // 是否及格(>=60为true)
}

public class Student
{
    /* ================== 成员变量 ================== */
    private int _id;                     // 学生唯一标识(学号)
    private float _gpa;                  // 平均绩点(0-4.0)
    private bool _isScholarship;         // 是否获得奖学金
    private StudentStatus _status;       // 学生当前状态
    private GradeRecord[] _grades;       // 所有课程成绩记录
    private Student[] _friends;          // 好友列表(学生对象数组)
    private static int _totalStudents;    // 静态变量:学生总数统计

    /* ================== 属性封装 ================== */
    // 学号属性(只读,初始化后不可修改)
    public int Id
    {
        get => _id;
        // set私有化,初始化时验证学号有效性
        private set => _id = value > 0 ? value : throw new ArgumentException("学号必须为正整数");
    }

    // GPA属性(只读,通过成绩自动计算)
    public float GPA
    {
        get => _gpa;
        // 私有set确保只能通过AddGrade方法修改
        private set => _gpa = Math.Clamp(value, 0f, 4.0f); // 限制在0-4.0范围
    }

    // 奖学金状态(可读写)
    public bool IsScholarship
    {
        get => _isScholarship;
        set => _isScholarship = value; // 允许外部直接修改
    }

    // 学生状态(可读写)
    public StudentStatus Status
    {
        get => _status;
        set => _status = value; // 允许外部修改状态
    }

    // 成绩记录数组(完整读写控制)
    public GradeRecord[] Grades
    {
        get => _grades;
        // 设置时进行空值检查
        set => _grades = value ?? throw new ArgumentNullException("成绩记录不可为空");
    }

    // 好友列表(安全访问控制)
    public Student[] Friends
    {
        get
        {
            // 返回数组拷贝,防止外部直接修改内部数据
            Student[] copy = new Student[_friends.Length];
            Array.Copy(_friends, copy, _friends.Length);
            return copy;
        }
    }

    /* ================== 构造方法 ================== */
    public Student(int id, float gpa, StudentStatus status)
    {
        Id = id;
        GPA = gpa;          // 初始GPA由外部传入
        Status = status;
        _grades = new GradeRecord[0];  // 初始化空成绩数组
        _friends = new Student[0];     // 初始化空好友数组
        _totalStudents++;              // 静态计数器自增
    }

    /* ================== 成员方法 ================== */
    // 添加课程成绩
    public void AddGrade(string courseName, float score)
    {
        // 数据有效性验证
        if (string.IsNullOrWhiteSpace(courseName))
            throw new ArgumentException("课程名不能为空");
        if (score < 0 || score > 100)
            throw new ArgumentException("分数必须在0-100之间");

        // 创建新的成绩记录
        var newGrade = new GradeRecord
        {
            CourseName = courseName,
            Score = score,
            IsPassed = score >= 60 // 自动计算是否及格
        };

        // 扩展数组并添加新记录
        Array.Resize(ref _grades, _grades.Length + 1);
        _grades[_grades.Length - 1] = newGrade;

        // 更新GPA(根据实际业务逻辑可能需要更复杂计算)
        UpdateGPA();
    }

    // 添加好友
    public void AddFriend(Student friend)
    {
        // 防御性编程
        if (friend == null)
            throw new ArgumentNullException(nameof(friend), "好友不能为空");
        if (friend == this)
            throw new ArgumentException("不能添加自己为好友", nameof(friend));
        if (Array.Exists(_friends, f => f.Id == friend.Id))
            return; // 避免重复添加

        // 动态扩容数组
        Array.Resize(ref _friends, _friends.Length + 1);
        _friends[_friends.Length - 1] = friend;
    }

    // 移除好友
    public bool RemoveFriend(int friendId)
    {
        // 查找好友索引
        int index = Array.FindIndex(_friends, f => f.Id == friendId);
        if (index == -1) return false;

        // 数组元素前移
        for (int i = index; i < _friends.Length - 1; i++)
        {
            _friends[i] = _friends[i + 1];
        }
        // 动态缩容
        Array.Resize(ref _friends, _friends.Length - 1);
        return true;
    }

    /* ================== 私有方法 ================== */
    // GPA计算逻辑(可根据实际需求修改)
    private void UpdateGPA()
    {
        if (_grades.Length == 0)
        {
            GPA = 0;
            return;
        }

        float totalScore = 0;
        foreach (var grade in _grades)
        {
            totalScore += grade.Score;
        }
        // 简单转换为4分制(实际可能需要学分加权)
        GPA = totalScore / _grades.Length / 25f;
    }

    /* ================== 信息输出 ================== */
    public void PrintInfo()
    {
        Console.WriteLine($"学号: {Id}");
        Console.WriteLine($"GPA: {GPA:F2}");
        Console.WriteLine($"奖学金状态: {(IsScholarship ? "是" : "否")}");
        Console.WriteLine($"在读状态: {Status}");

        Console.WriteLine("成绩记录:");
        foreach (var grade in _grades)
        {
            Console.WriteLine($"  {grade.CourseName.PadRight(15)}: {grade.Score,5:F1} ({(grade.IsPassed ? "✓" : "✕")})");
        }

        Console.WriteLine("好友列表:");
        foreach (var friend in _friends)
        {
            Console.WriteLine($"  ▶ 学号{friend.Id} | GPA: {friend.GPA:F2}");
        }
        Console.WriteLine(new string('=', 40));
    }

    /* ================== 静态方法 ================== */
    public static int GetTotalStudents() => _totalStudents;
}

class Program
{
    static void Main()
    {
        // 初始化学生对象
        Student alice = new Student(1001, 3.5f, StudentStatus.Active);
        Student bob = new Student(1002, 2.8f, StudentStatus.Active);
        Student charlie = new Student(1003, 3.9f, StudentStatus.Active);

        // 建立好友关系
        alice.AddFriend(bob);
        alice.AddFriend(charlie);
        bob.AddFriend(alice);

        // 添加课程成绩
        alice.AddGrade("面向对象编程", 88.5f);
        alice.AddGrade("数据结构", 92.0f);
        bob.AddGrade("C#基础", 78.5f);
        charlie.AddGrade("算法设计", 95.0f);

        // 修改学生状态
        alice.IsScholarship = true;
        bob.Status = StudentStatus.Suspended;

        // 输出信息
        alice.PrintInfo();
        bob.PrintInfo();
        charlie.PrintInfo();

        // 移除好友演示
        alice.RemoveFriend(1002);
        Console.WriteLine("Alice移除了Bob后的好友列表:");
        alice.PrintInfo();

        // 统计学生总数
        Console.WriteLine($"全校学生总数: {Student.GetTotalStudents()}");
    }
}

关键设计注释说明

​1、好友列表安全访问

cs 复制代码
public Student[] Friends {
    get {
        Student[] copy = new Student[_friends.Length];
        Array.Copy(_friends, copy, _friends.Length);
        return copy;
    }
}
  • 防御性拷贝:返回数组副本防止外部代码直接修改内部数据
  • 封装原则:保护对象内部状态不被意外修改

2、动态数组操作

cs 复制代码
Array.Resize(ref _friends, _friends.Length + 1);
_friends[_friends.Length - 1] = friend;
  • 灵活扩容:每次添加自动调整数组大小
  • 性能说明 :实际开发建议使用List<T>,此处用数组演示底层原理

3、 ​数据验证机制

cs 复制代码
if (friend == null)
    throw new ArgumentNullException(nameof(friend), "好友不能为空");
if (friend == this)
    throw new ArgumentException("不能添加自己为好友");
  • 防御性编程:防止无效数据进入系统
  • 异常处理:明确错误原因,提高代码健壮性

4、 状态关联更新

cs 复制代码
private void UpdateGPA() {
    // 每次添加成绩后自动更新GPA
}
  • 数据一致性:确保GPA始终与成绩记录同步
  • 业务逻辑隔离:更新逻辑封装在私有方法中

总结:

特性 成员变量(字段) 成员方法
作用 存储对象状态 定义对象行为
访问控制 通常private,通过属性暴露 可设为publicprivate
典型操作 直接赋值或读取 通过参数传递数据,执行逻辑
相关推荐
高林雨露8 分钟前
Java对比学习Kotlin的详细指南(一)
java·学习·kotlin
weixin_307779131 小时前
判断HiveQL语句为建表语句的识别函数
开发语言·数据仓库·hive·c#
我是苏苏1 小时前
C#高级:利用LINQ进行实体列表的集合运算
c#·linq
齐尹秦1 小时前
HTML5 Web Workers 学习笔记
笔记·学习
DarkBule_1 小时前
零基础驯服GitHub Pages
css·学习·html·github·html5·web
余多多_zZ2 小时前
鸿蒙学习手册(HarmonyOSNext_API16)_应用开发UI设计:Swiper
学习·ui·华为·harmonyos·鸿蒙系统
淬渊阁2 小时前
汇编学习之《扩展指令指针寄存器》
汇编·学习
lalapanda2 小时前
UE5学习记录part12
学习·ue5
du fei3 小时前
C# 窗体应用(.FET Framework) 线程操作方法
开发语言·c#
du fei3 小时前
C#文件操作
开发语言·c#