文章目录
- 静态多态
- 函数重载
- 运算符重载
- 动态多态
- 抽象类
- 虚方法
1、概念
- 同父类对象执行相同方法的不同表现形态
- 补充:函数重载也是一种多态(编译时多态)
- 本章学习内容:运行时多态
- vob
- v:
virtual
(虚函数) - o:
override
(重写) - b:
base
(父类)
- v:
- 抽象类和抽象方法
- 接口
- vob
- 解决问题:让同一个对象有唯一的行为特征
● 小栗子
new
关键字的新用法:子类重写父类需要用 new 关系修饰方法,不然会报警告你省略了关键字
class Person
{
public string Name;
public int Age;
public void Speak()
{
Console.Write("我叫{0},今年{1}岁了。", Name, Age);
}
}
class Teacher : Person
{
public string Subject; // 教学科目
// 重写父类方法
public new void Speak() {
base.Speak(); // 虽然重写了,但是父类你也别闲着,我依然可以用你的功能
Console.WriteLine("我是你们的{0}老师,现在开始上课。", Subject);
}
}
class Student : Person
{
public string School;
public new void Speak()
{
base.Speak();
Console.WriteLine("我在{0}上学。", School);
}
}
// 某方法内↓↓↓
Person pes = new Person();
pes.Name = "Mr.Bai";
pes.Age = 24;
pes.Speak(); // 我叫Mr.Bai,今年24岁了。
Console.WriteLine();
Person tec = new Teacher();
tec.Name = "peiqi";
tec.Age = 24;
tec.Speak(); // 我叫peiqi,今年24岁了。
Teacher t = tec as Teacher;
t.Subject = "C/C++/Java/C#课";
t.Speak(); // 我叫peiqi,今年24岁了。我是你们的C/C++/Java/C#课老师,现在开始上课。
Person stu = new Student();
stu.Name = "qiaozhi";
stu.Age = 10;
stu.Speak(); // 我叫qiaozhi,今年10岁了。
Student s = stu as Student;
s.School = "宇宙佩奇小学";
s.Speak(); // 我叫qiaozhi,今年10岁了。我在宇宙佩奇小学上学。
2、virtual、override、base
- v:
virtual
(虚函数) - o:
override
(重写) - b:
base
(父类)
修改上面的小栗子,让同一个对象有唯一的行为特征
class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
// 虚函数
public virtual void Speak()
{
Console.Write("我叫{0},今年{1}岁了。", Name, Age);
}
}
class Teacher : Person
{
public string Subject; // 教学科目
public Teacher(string name, int age, string subject) : base(name, age)
{
Subject = subject;
}
// 重写父类方法
public override void Speak()
{
base.Speak(); // base代表父类
Console.WriteLine("我是你们的{0}老师,现在开始上课。", Subject);
}
}
class Student : Person
{
public string School;
public Student(string name, int age, string school) : base(name, age)
{
School = school;
}
// 重写父类方法
public override void Speak()
{
base.Speak();
Console.WriteLine("我在{0}上学。", School);
}
}
// 某方法内↓↓↓
Person pes = new Person("Mr.Bai", 24);
pes.Speak(); // 我叫Mr.Bai,今年24岁了。
Console.WriteLine();
Person tec = new Teacher("peiqi", 24, "C/C++/Java/C#课");
tec.Speak(); // 我叫peiqi,今年24岁了。我是你们的C/C++/Java/C#课老师,现在开始上课。
Person stu = new Student("qiaozhi", 10, "宇宙佩奇小学");
stu.Speak(); // 我叫qiaozhi,今年10岁了。我在宇宙佩奇小学上学。
3、抽象类和抽象方法
3.1、抽象类
- 关键字:
abstract
- 特点
- 不能被实例化(但是依然适用里氏替换原则)
- 可以包含抽象方法(有抽象方法的一定是抽象类!)
- 继承抽象类必须重写其抽象方法
abstract class Thing
{
// 这里可以写类的任何东西 + 抽象方法
}
3.2、抽象方法
- 关键字:
abstract
- 用
abstract
修饰的方法叫抽象方法,又叫 纯虚方法 - 特点
- 只能在抽象类中声明
- 没有方法体
- 不能私有
- 抽象类继承后必须用
override
重写抽象方法
- 如何选择抽象类和普通类
- 不希望被实例化的类,选抽象类就完了
abstract class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
// 抽象方法
public abstract void Speak();
}
class Teacher : Person
{
public string Subject; // 教学科目
public Teacher(string name, int age, string subject) : base(name, age)
{
Subject = subject;
}
// 重写父类方法
public override void Speak()
{
Console.Write("我叫{0},今年{1}岁了。", Name, Age);
Console.WriteLine("我是你们的{0}老师,现在开始上课。", Subject);
}
}
class Student : Person
{
public string School;
public Student(string name, int age, string school) : base(name, age)
{
School = school;
}
public override void Speak()
{
Console.Write("我叫{0},今年{1}岁了。", Name, Age);
Console.WriteLine("我在{0}上学。", School);
}
}
// 某方法内↓↓↓
Person tec = new Teacher("peiqi", 24, "C/C++/Java/C#课");
tec.Speak(); // 我叫peiqi,今年24岁了。我是你们的C/C++/Java/C#课老师,现在开始上课。
Person stu = new Student("qiaozhi", 10, "宇宙佩奇小学");
stu.Speak(); // 我叫qiaozhi,今年10岁了。我在宇宙佩奇小学上学。
4、接口
4.1、概念
- 关键字:
interface
- 接口是行为的抽象规范,它是一种自定义类型
- 个人理解:接口就是定义一套规范、模板,谁继承这个接口,谁就要遵守这个规范套用这个模板
- 特点
- 和类声明类似
- 接口也是用来继承的
- 接口不能被实例化,但依然适用里氏替换原则
4.2、语法
- 不能有成员变量
- 只可有属性、方法、索引器、事件
- 成员不能被实现
- 成员可不写访问修饰符(不写默认public),不能是私有的,如果是protected则需要显示实现
- 接口不能继承类,只能继承另一个接口
- 接口名一般以
I
开头 + 帕斯卡命名
// 语法
interface 接口名
{
// 属性、方法、索引器、事件
}
// 栗子
interface IFly
{
// 属性
int Height
{
get; // get、set也不能包含方法体
set; // get、set也不可能全省略不写
}
// 方法
void Fly();
// 索引
int this[int index]
{
get; // get、set使用与属性的类似
set;
}
// 事件
event Action doSomething;
}
4.3、使用
- 类可以多实现接口
- 类继承接口必须实现其所有成员
- 接口可以继承接口,不需要实现,待普通类继承之后实现所有成员
- 实现的接口函数可以加
virtual
再在子类重写 - 接口不能被实例化,但依然适用里氏替换原则
// 接口
interface IFly
{
// 属性
int Height
{
get; // get、set也不能包含方法体
set; // get、set也不可能全省略不写
}
// 方法
void Fly();
// 索引
int this[int index]
{
get; // get、set使用与属性的类似
set;
}
// 事件
event Action doSomething;
}
// 接口继承接口小栗子
interface IXxx : IFly // 接口是可以多继承接口的,继续加,就完了
{
// 这里写IXxx的成员,不需要实现IFly成员,实现只留给普通类
}
// 一个无关紧要的基类
abstract class Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
public virtual void Speak()
{
Console.Write("我叫{0},今年{1}岁了。", Name, Age);
}
}
// 接口的实现(掺杂继承。。。)
class Teacher : Person,IFly // 接口多继承后面可以无限,
{
public string Subject; // 教学科目
public Teacher(string name, int age, string subject) : base(name, age)
{
Subject = subject;
}
// 重写父类方法
public override void Speak()
{
base.Speak();
Console.WriteLine("我是你们的{0}老师,现在开始上课。", Subject);
}
// 以下为实现接口的成员
int IFly.Height
{
get;
set;
}
public int this[int index]
{
get { return 0; }
set { }
}
public event Action doSomething;
public void Fly()
{
Speak();
Console.WriteLine("老师可以借助飞机飞行。");
}
}
// 某方法内↓↓↓
IFly fly = new Teacher("佩奇", 10, "幼儿园老师");
fly.Fly(); // 我叫佩奇,今年10岁了。我是你们的幼儿园老师老师,现在开始上课。\n老师可以借助飞机飞行。
4.4、显式实现接口
4.4.1、需要使用显式实现接口的两种情况
- ==接口里成员的访问修饰符是
protected
== - 继承多个接口,其中有同名方法
注!!!显式实现接口不能写访问修饰符
4.4.2、栗子
- 两个包含同名方法的接口
// 能吃接口
interface IEat
{
void Eat();
}
// 超能吃接口
interface ISuperEat
{
void Eat();
}
- 普通的实现会导致两个行为变成了同一种表现形式
- 语法上没问题,但是别忘了接口设计的初衷
class MyEat : IEat, ISuperEat
{
public void Eat()
{
Console.WriteLine("我能吃,而且超能吃!");
}
}
// 某方法内↓↓↓
IEat eat = new MyEat();
eat.Eat(); // 我能吃,而且超能吃!
ISuperEat superEat = new MyEat();
superEat.Eat(); // 我能吃,而且超能吃!
- 显示实现
- 就是实现的时候用
接口名.行为名
- 能过实现不同规范行为的不同表现形式
- 但是!实现类就不能自己点出实现的行为了,只能转成父类才能点对应的行为
- 当然实现类也可以有自己的
Eat
方法,这时MyEat
里就有 3 个Eat
方法了,此时MyEat
对象mine
就可以调(也只能调)自己的mine.Eat();
方法了 - 注意!上面说的 3 个同名方法不是重载哦!
- 当然实现类也可以有自己的
- 就是实现的时候用
class MyEat : IEat, ISuperEat
{
void IEat.Eat()
{
Console.WriteLine("我能吃!");
}
void ISuperEat.Eat()
{
Console.WriteLine("我超能吃!");
}
}
// 某方法内↓↓↓
IEat eat = new MyEat();
eat.Eat(); // 我能吃!
ISuperEat superEat = new MyEat();
superEat.Eat(); // 我超能吃!
// 此时实现类就不能自己点出实现的行为了,只能转成父类才能点对应的行为
MyEat mine = new MyEat();
(mine as IEat).Eat(); // 我能吃!
(mine as ISuperEat).Eat(); // 我超能吃!
5、密封方法
- 关键字:
sealed
- 作用:用
sealed
关键字 修饰的实现类中实现的 虚方法 或 抽象方法,其子类不可再重写此虚方法 或 抽象方法 - 特点:和
overide
一起出现
// 一个普通的抽象类
abstract class AbsTest
{
public string name;
public abstract void Eat();
public virtual void Say()
{
Console.WriteLine("阿巴阿巴阿巴~");
}
}
// 一个普通的类
class Test : AbsTest
{
public sealed override void Eat() // 我用 sealed 修饰吃了,后面的一个也别想吃了!嘿嘿!
{
Console.WriteLine("吃就完了!");
}
public override void Say()
{
// base.Say();
Console.WriteLine("嘎嘎嘎!");
}
}
// 一个更普通的类
class TestSon : Test
{
// 由于 Eat 被 Test 用 sealed 修饰了,我就啥也吃不到了
// 虚方法我也懒得实现了,毕竟那个也不是强制的
}
6、(+)运算符重载
- 语法:
public static T operator [symbol] (T t1, T t2) { return T; }
- demo:
class Box
{
public int length;
public int width;
public int height;
public Box(int length, int width, int height)
{
this.length = length;
this.width = width;
this.height = height;
}
public static Box operator +(Box a, Box b){
return new Box(a.length + b.length, a.width + b.width, a.height + b.height);
}
public override string ToString()
{
return new StringBuilder()
.Append("{ length=").Append(length)
.Append("; width=").Append(width)
.Append("; height=").Append(height)
.Append(" }").ToString();
}
}
// 测试代码
Console.WriteLine(new Box(1, 1, 1) + new Box(9, 9, 9)); // { length=10; width=10; height=10 }
- 可重载运算符和不可重载运算符: