引言
继承是C#面向对象编程中的核心概念之一,它提供了一种强大的方式,允许开发者构建层次化的类结构,实现代码复用和功能扩展。然而,合理使用继承也很重要,以避免过度耦合和脆弱性问题。理解继承的力量与艺术,将使你能够更有效地应用面向对象的原则来解决实际问题。
一、继承的基本规则
1.基本概念
一个类 A 继承一个类 B
类 A 将会继承类 B 的所有成员
A 类将拥有 B 类的所有特征和行为
被继承的类
称为 父类 基类 超类
继承的类
称为 子类 派生类
子类可以有自己的特征和行为
特点
1.单根性 子类只能有一个父类
2.传递性 子类可以间接继承父类的父类
2.基本语法
class 类名 : 被继承的类名
{}
3.实例
class Test
{
}
class Teacher
{
// 姓名
public string name;
// 职工号
protected int number;
// 介绍名字
public void SpeakName()
{
number = 10;
Console.WriteLine(name);
}
}
class TeachingTeacher : Teacher
{
// 科目
public string subject;
// 介绍科目
public void SpeakSubject()
{
number = 11;
Console.WriteLine(subject + "老师");
}
}
class ChineseTeacher : TeachingTeacher
{
public void Skill()
{
Console.WriteLine("一行白鹭上青天");
}
}
4.使用
Console.WriteLine("继承的基本规则");
TeachingTeacher tt = new TeachingTeacher();
tt.name = "Unity打怪升级";
// tt.number = 1;
tt.SpeakName();
tt.subject = "Unity";
tt.SpeakSubject();
ChineseTeacher ct = new ChineseTeacher();
ct.name = "Unity";
// ct.number = 2;
ct.subject = "语文";
ct.SpeakName();
ct.SpeakSubject();
ct.Skill();
5.访问修饰符的影响
public - 公共内外部访问
private - 私有内部访问
protected - 保护内部和子类访问
之后讲命名空间的时候讲
internal - 内部的 只有在同一个程序集的文件中 内部类型或者是成员才可以访问
6.子类和父类的同名成员
概念
C# 中允许子类存在和父类同名的成员
但是极不建议适用
二、里氏替换原则
1.基本概念
里氏替换原则是面向对象七大原则中最重要的原则
概念:
任何父类出现的地方 子类都可以代替
重点:
语法表现 – 父类容器装子类对象 因为子类对象包含了父类所有内容
作用:
方便进行对象的存储和管理
2.基本实现
class GameObject
{
}
class Player : GameObject
{
public void PlayerAtk()
{
Console.WriteLine("玩家攻击");
}
}
class Monster : GameObject
{
public void MonsterAtk()
{
Console.WriteLine("怪物攻击");
}
}
class Boss : GameObject
{
public void BossAtk()
{
Console.WriteLine("Boss攻击");
}
}
3.使用
Console.WriteLine("里氏替换原则");
// 里氏替换原则 用父类容器 装载子类对象
GameObject player = new Player();
GameObject monster = new Monster();
GameObject boss = new Boss();
GameObject[] objects = new GameObject[] { new Player(), new Monster(), new Boss() };
4.is 和 as
// 基本概念
// is:判断一个对象是否是指定类对象
// 返回值:bool 是为真 不是为假
// as:将一个对象转换为指定类对象
// 返回值:指定类对象
// 成功返回指定类对象 失败返回 null
// 基本语法
// 类对象 is 类名 该语句块 会有一个 bool 返回值 true 和 false
// 类对象 as 类名 该语句块 会有一个对象返回值对象和 null
if(player is Player)
{
Player p = player as Player;
p.PlayerAtk();
(player as Player).PlayerAtk();
}
for(int i = 0; i < objects.Length; i++)
{
if(objects[i] is Player)
{
(objects[i] as Player).PlayerAtk();
}
else if (objects[i] is Monster)
{
(objects[i] as Monster).MonsterAtk();
}
else if (objects[i] is Boss)
{
(objects[i] as Boss).BossAtk();
}
}
三、继承中的构造函数
1.知识回顾
// 构造函数
// 实例化对象时调用的函数
// 主要用来初始化成员变量
// 每个类都会有一个默认的无参构造函数
// 语法
// 访问修饰符 类名()
// {
// }
// 不写返回值
// 函数名和类名相同
// 访问修饰符根据需求而定 一般为 public
// 构造函数可以重载
// 可以用 this 语法重用代码
// 注意
// 有参构造会定调默认的无参构造
// 如想保留无参构造需重载出来
class Test
{
public int testI;
public string testStr;
public Test()
{
}
public Test(int i)
{
this.testI = i;
}
public Test(int i, string str) : this(i)
{
this.testStr = str;
}
}
2.继承中的构造函数基本概念
特点
当申明一个子类对象时
先执行父类的构造函数
再执行子类的构造函数
注意:
1.父类的无参构造很重要
2.子类可以通过 base 关键字代表父类调用父类构造
3.继承中的构造函数的执行顺序
// 父类的父类的构造-->......父类构造-->...子类构造
class GameObject
{
public GameObject()
{
Console.WriteLine("GameObject的构造函数");
}
}
class Player : GameObject
{
public Player()
{
Console.WriteLine("Player的构造函数");
}
}
class MainPlayer : Player
{
public MainPlayer()
{
Console.WriteLine("MainPlayer的构造函数");
}
}
4.父类的无参构造函数很重要
// 子类实例化时 默认调用的是父类的无参构造 所以如果父类无参构造被顶掉 会报错
class Father
{
public Father()
{
}
public Father(int i)
{
Console.WriteLine("Father构造");
}
}
4.1.通过 base 调用指定父类构造
class Son : Father
{
public Son(int i) : base(i)
{
Console.WriteLine("Son的一个参数的构造构造");
}
public Son(int i,string str) : this(i)
{
Console.WriteLine("Son的两个参数的构造构造");
}
}
5.使用
MainPlayer mp = new MainPlayer();
Son s = new Son(1, "123");
四、万物之父和装箱拆箱
1.里氏替换知识点回顾
// 概念:父类容器装子类对象
// 作用:方便进行对象存储和管理
// 使用
// is 和 as
// is 用于判断
// as 用于转换
class Father
{
}
class Son : Father
{
public void Speak()
{
}
}
2.万物之父
万物之父
关键字:object
概念:
object 是所有类型的基类 他是一个类(引用类型)
作用:
1.可以利用里氏替换原则 用 object 容器装所有对象
2.可以用来表示不确定类型 作为函数参数类型
3.万物之父的使用
Father f = new Son();
if (f is Son)
{
(f as Son).Speak();
}
// 引用类型
object o = new Son();
// 用 is as 来判断转换即可
if (o is Son)
{
(o as Son).Speak();
}
// 值类型
object o2 = 1f;
// 用强转
float f1 = (float)o2;
// 特殊的 string 类型
object str = "123123";
string str2 = str as string;
object arr = new int[10];
int[] ar = arr as int[];
4.装箱拆箱
// 发生条件
// 用 object 存值类型(装箱)
// 再把 object 转为值类型(拆箱)
// 装箱
// 把值类型用引用类型存储
// 栈内存会迁移到堆内存中
// 拆箱
// 把引用类型存储的值类型取出来
// 堆内存会迁移到栈内存中
// 好处:不确定类型时可以方便参数的存储和传递
// 坏处:存在内存迁移 增加性能消耗
// 装箱
object v = 3;
// 拆箱
int intValue = (int)v;
TestFun(1, 2, 3, 4f, 34.5, "123123", new Son());
#endregion
static void TestFun(params object[] array)
{
}
五、密封类
1.基本概念
密封类:是使用 sealed 密封关键字修饰的类
作用:让类无法再被继承
2.实例
class Father
{
}
sealed class Son : Father
{
}
3.作用
在面向对象程序的设计中 密封类的主要作用就是不允许最底层子类被继承
可以保证程序的规范性 安全性
目前对于大家来说可能用处不大
随着大家的成长 以后制作复杂系统或者程序框架时 便能慢慢体会密封的作用