在上一篇文章中,我们初始了类以及对象。今天我们继续学习面向对象中的封装特性中的成员变量及成员方法。
一、什么是封装?
官方解释 :封装是面向对象编程的基石,通过绑定数据与操作 并控制访问权限 ,实现数据保护和逻辑隔离。
程序员翻译:
"把你的代码想象成一座城堡:
- 成员变量是藏在金库里的黄金
- 成员方法是城堡的守卫和交易窗口
- 访问修饰符是金库的密码锁"
示例:
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的作用
核心用途:
- ++程序集内部共享:++
- ++适用于在同一个项目(如一个DLL或EXE)中共享类、方法或字段,但对其他项目不可见。++
- ++替代
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 ,通过属性暴露 |
可设为public 、private 等 |
典型操作 | 直接赋值或读取 | 通过参数传递数据,执行逻辑 |