C#自学笔记:继承与多态
继承
实例
class Teacher
{
public string name;
public int number;
public void SpeakName()
{
Console.WriteLine(name);
}
}
class TeachingTeacher : Teacher
{
public string subject;
public void SpeakSubject()
{
Console.WriteLine(subject + "老师");
}
}
//可以连续继承,有传递性,拥有所有父类的属性
class ChineseTeacher : TeachingTeacher
{
public void Skill()
{
Console.WriteLine("一行白鹭上青天");
}
}

子类也能继承父类的private属性,只是在子类中无法访问。
子类和父类的同名成员
概念
C#中允许子类存在和父类同名的成员
但是:极不建议使用
会冒波浪线,提醒你是否有意隐藏父类的同名成员,意思是会隐藏父类的同名成员,使用的时候是子类成员,如果是有意的话请用new,意思是故意要隐藏父类的同名成员的话最好用new,但是加不加都是默认使用子类成员。
class Teacher
{
public string name;
}
class TeachingTeacher : Teacher
{
public new string name;
}
里氏替换原则
里氏替换原则是面向对象七大原则中最重要的原则概念:
- 任何父类出现的地方,子类都可以替代
重点:
- 语法表现——父类容器装子类对象,因为子类对象包含了父类的所有内容
作用:
- 方便进行对象存储和管理
基本实现
GameObject player = new Player();
GameObejct monster = new Monster();
GameObject boss = new Boss();
GameObject[] objects = new GameObject[] {new Player(), new Monster(), new Boss()};
is和as
基本概念
is
- is:判断一个对象是否是指定类对象
- 返回值:bool,是为true,不是为false
as
- as:将一个对象转换为指定类对象
- 返回值:指定类型对象
成功返回指定类型对象,失败返回null
基本语法
if (player is Player) //即使是父类引用指向子类对象,可以判断指向的对象
{
//Player p = player as Player;
//p.PlayerAtk();
(player as Player).PlayerAtk();
//目的是为了调用子类的特有方法,原来是GameObject类,现在转换成了Player类
}
目的:对单个类影响不大,主要是用于对象数组。
继承中的构造函数
基本概念
特点
- 当声明一个子类对象时,先执行父类的构造函数,再执行子类的构造函数
注意:
- 父类的无参构造很重要
- 子类可以通过base关键字代表父类来调用父类构造
继承中构造函数的执行顺序
父类的父类构造->父类构造->子类构造
如果父类的无参构造被有参构造顶掉的话,子类继承时会报错,因为子类实例化时默认自动调用的是父类的无参构造,所以父类的无参构造很重要
通过base调用指定父类构造
此时没有无参构造就不会报错了,因为指定了一个有参构造,不会默认调用无参构造class Father
{
public Father(int i)
{
}
}
class Son : Father
{
public Son(int i) : base(i)
{
}
}
装箱拆箱
发生条件
用object存值类型(装箱)再把object转为值类型(拆箱)
装箱
把值类型用引用类型存储
栈内存会迁移到堆内存中
拆箱
把引用类型存储的值类型取出来
堆内存会迁移到栈内存中
好处:不确定类型时可以方便参数的存储和传递
坏处:存在内存迁移,增加性能消耗
总结:
- 万物之父:object
- 装箱拆箱是基于里氏替换原则的,可以用object容器装载一切类型的变量
- object是所有类型的基类
- 装箱拆箱不是不能用,而是尽量少用,因为存在内存的迁移,增加了性能消耗
密封类
基本概念
是使用sealed密封关键字修饰的类
作用:让类无法再被继承
有人说是太监类、结扎类、绝育类
作用
- 在面向对象程序的设计中,密封类的主要作用就是不允许最底层子类被继承
- 可以保证程序的规范性、安全性
- 目前对于大家来说,可能用处不大
- 随着大家的成长,以后制作复杂系统或者程序框架时,便能慢慢体会到密封的作用
多态vob
基本概念
多态按字面意思就是“多种状态”
让继承同一父类的子类们,在执行相同方法时有不同的表现(状态)
目的
- 同一个父类的对象执行相同行为(方法)时有不同的表现
解决的问题
- 让同一个对象有唯一行为的特征
vob
v:virtual(虚函数)o:override(重写)
b:base(父类)
实现
public GameObject
{
public string name;
public GameObject(string name)
{
this.name = name;
}
public virtual void Atk()
{
Console.WriteLine("游戏对象进行攻击");
}
}
public Player : GameObject
{
public Player(string name) : base(name)
{
}
public override void Atk()
{
Console.WriteLine("玩家对象进行攻击");
}
}
public Main
{
public static void main(String[] args)
{
GameObject p = new Player("柠凉");
p.Atk();//玩家对象进行攻击
}
}
抽象类
概念
被抽象关键字abstract修饰的类
特点:
- 不能被实例化的类
- 可以包含抽象方法
- 继承抽象类必须重写其抽象方法
抽象函数
又叫纯虚方法用abstract关键字修饰的方法
特点:
- 只能在抽象类中声明
- 没有方法体
- 不能是私有的
- 继承后必须实现,用override重写
如何选择普通类还是抽象类?
- 不希望被实例化的对象,相对比较抽象的类可以使用抽象类
- 父类中的行为不太需要被实现的,只希望子类去定义具体的规则的,可以选择抽象类然后使用其中的抽象方法来定义规则。
接口
它也是一种自定义类型关键字:interface
接口声明的规范
- 不包含成员变量
- 只包含方法、属性、索引器、事件
- 成员不能被实现
- 成员可以不用写访问修饰符,不能是私有的
- 接口不能继承类,但是可以继承另一个接口
接口的使用规范
- 类可以继承多个接口
- 类继承接口后,必须实现接口中所有成员
特点:
- 它和类的声明类似
- 接口是用来继承的
- 接口不能被实例化,但是可以作为容器存储对象
接口的声明
接口是抽象行为的“基类”接口命名规范:帕斯卡前面加个I
语法:
interface 接口名
{
}
举例:
interface IFly
{
void Fly();
string Name
{
get;
set;
}
int this[int index]
{
get;
set;
}
event Action doSomething;
}
接口的使用
- 类可以继承1个类,n个接口
- 继承了接口后,必须实现其中的内容,并且必须是public的
- 实现的接口函数,可以加v再在子类重写,意思是可以在实现了接口的类当中的函数方法前面加virtual,这样子类继承这个方法就可以重写
- 接口也遵循里氏替换原则
接口可以继承接口
接口继承接口时,不需要实现待类继承接口后,类自己去实现所有内容
interface IWalk
{
void Walk();
}
interface IMove : IFly, IWalk
{
}
显示实现接口
当一个类继承两个接口但是接口中存在着同名方法时
注意:显示实现接口时,不能写访问修饰符
class IAtk
{
void Atk();
}
class ISuperAtk
{
void Atk();
}
//显示实现接口,就是用接口名.行为名,去实现
class Player : IAtk, ISuperAtk
{
void IAtk.Atk()
{
}
void ISuperAtk.Atk()
{
}
public void Atk()
{
}
}
//使用
class Main
{
static void main(String[] args)
{
IAtk ia = new Player();
ISuperAtk isa = new Player();
Player p = new Player();
ia.Atk();//调用的是IAtk.Atk()
isa.Atk();//调用的是ISuperAtk.Atk()
p.Atk();//调用的是Player自己的Atk()
}
}
总结
继承类:是对象间的继承,包括特征行为等等继承接口:是行为间的实现,继承接口的行为规范,按照规范去实现内容
由于接口也是遵循里氏替换原则,所以可以用接口容器装对象
那么就可以实现装载各种毫无关系但是却有相同行为的对象
密封方法
基本概念
用密封关键字sealed修饰的重写函数
作用:让虚方法或者抽象方法之后不能再被重写
特点:和override一起出现
class Person : Animal
{
public sealed override void Eat()
{
base.Eat();
}
}
Person的子类将无法再重写Person的Eat()方法
命名空间
概念
命名空间是用来组织和重用代码的
作用
就像是一个工具包,类就像是一件一件的工具,都是声明在命名空间中的
基本语法
namespace 命名空间名
{
class 类名
...
}
- 不同命名空间中相互使用时,需要引用命名空间或指明出处
using 命名空间名或命名空间名.方法名()
命名空间可以包裹命名空间
namespace MyGame
{
namespace UI
{
class Image
{
}
}
namespace Game
{
class Image
{
}
}
}
命名空间中的类默认为internal(访问修饰符),只能在同一个工程里面调用,不同工程不能相互调用
万物之父中的方法
object中的静态方法
Equals:判断两个对象是否相等
- 最终的判断权,交给左侧对象的Equals方法,不管值类型引用类型都会按照左侧对象Equals方法的规则来进行比较。
Object.Equals(1, 1);//返回true或false
ReferenceEquals
- 比较两个对象是否是相同的引用,主要是用来比较引用类型的对象
- 值类型对象返回值始终是false
Object.ReferenceEquals(t1, t2);
object中的成员方法
GetType
- 该方法在反射相关知识点中是非常重要的方法
- 该方法的主要作用就是获取对象运行时的类型Type
- 通过Type结合反射相关知识点可以做很多关于对象的操作
Test t = new Test();
Type type = t.GetType();
MemberwiseClone
- 该方法用于获取对象的浅拷贝对象,口语化的意思就是会返回一个新的对象,但是新对象中的引用变量会和老对象中一致。(弹幕说值类型是独立的,引用类型是共用的)
- 浅拷贝:就是简单的把地址复制了一份,堆里面如果有成员变量是引用变量不变。
- 深拷贝:自己创建了新地址,把旧地址数据拷贝过来,之后和旧地址无关。
object中的虚方法
Equals
- 默认实现还是比较两者是否为同一个引用,即相当于ReferenceEquals
- 但是微软在所有值类型的基类System.ValueType中重写了该方法,用来比较值相等
- 我们也可以重写该方法,定义自己的比较相等的规则
GetHashCode
- 该方法是获取对象的哈希码
- (一种通过算法算出的,表示对象的唯一编码,不同对象哈希码有可能一样,具体值根据哈希算法决定),我们可以通过重写该函数来自己定义对象的哈希码算法,正常情况下,我们使用的极少,基本不用。
ToString
- 该方法用于返回当前对象代表的字符串,我们可以重写它定义我们自己的对象转字符串规则
- 该方法非常常用。当我们调用打印方法时,默认使用的就是对象的ToString方法后打印出来的内容。
String
字符串指定位置获取
//字符串本质是char数组
string str = "柠凉";
Console.WriteLine(str[0]);//本质上是个索引器
//转为char数组
char[] chars = str.ToCharArray();
字符串拼接
str = string.Format("{0}{1}", 1, 3333);
Console.WriteLine(str);//13333
//第二种方法是用+拼接
正向查找指定字符串位置
str = "我是柠凉!"
int index = str.IndexOf("柠");
Console.WriteLine(index);//2,如果没找到会返回-1
反向查找指定字符串位置
str = "我是柠凉柠凉";
int index = str.LastIndexOf("柠凉");
Console.WriteLine(index);//4
index = str.LastIndexOf("唐老师");//打印出来是-1
移除指定位置后的字符
str = "我是柠凉柠凉";
str.Remove(3);
Console.WriteLine(str);//打印出来不变,注意,这个方法不会改变原字符串,只会返回一个新字符串
str = str.Remove(3);
Console.WriteLine(str);//我是柠
//指定两个参数进行移除
//参数1:开始位置
//参数2:字符个数
str = str.Remove(1, 1);
Console.WriteLine(str);//我柠
替换指定字符串
str = "我是柠凉柠凉";
str = str.Replace("柠凉", "程序");
Console.WriteLine(str);//我是程序程序
大小写转换
str = "abcdefg";
str = str.ToUpper();
Console.WriteLine(str);//ABCDEFG
str = str.ToLower();
Console.WriteLine(str);//abcdefg
字符串截取
str = "柠凉柠凉";
str = str.Substring(1);
Console.WriteLine(str);//凉柠凉
//参数1:开始位置
//参数2:指定个数
//不会自动的帮你判断是否越界,需要自己去判断
str = str.Substring(1, 2);
Console.WriteLine(str);//凉柠
字符串切割(非常重要)
str = "1,2,3,4,5,6,7,8";
string[] strs = str.Split(',');
for (int i = 0; i < strs.Length; i++)
{
Console.WriteLine(strs);
}
//1
//2
//...
StringBuilder
C#提供的一个用于处理字符串的公共类主要解决的问题是:
修改字符串而不创建新的对象,需要频繁修改和拼接的字符串可以使用它,可以提升性能
本质上是个数组
初始化
using System.Text;
StringBuilder str = new StringBuilder("123123");
容量
StringBuider存在一个容量,每次往里面添加时,如果超出容量,则会自动扩容Console.WriteLine(str.Capacity);//容量
Console.WriteLine(str.Length); //现在的长度
增删改查替换
//增
str.Append("4444");
str.AppendFormat("{0}{1}", 100, 999);
//插入
str.Insert(0, "柠凉");
//删除
//第一个参数为开始位置
//第二个参数为个数
str.Remove(0, 10);
//清空
str.Clear();
//查找
Console.WriteLine(str[0]);
//修改
str[0] = 'A';
//替换
//替换指定字符
str.Replace("1", "柠");
//判断是否相等
//不能使用==
if (str.Equals("123123"))
return true;
结构体和类的区别
最大的区别是在存储空间里,因为结构体是值,类是引用,因此它们一个存储在栈上,一个存储在堆上。- 结构体和类在使用上类似,结构体甚至可以使用面向对象的思想来形容一类对象。
- 结构体具备面向对象思想中的封装特性,但是它不具备继承和多态的特性,因此大大减少了它的使用频率
- 由于结构体不具备继承的特性,所以它不能够使用protected保护访问修饰符
细节区别
- 结构体是值类型,类是引用类型
- 结构体存在栈中,类存在堆中
- 结构体成员不能使用protected访问修饰符,而类可以
- 结构体成员变量声明不能指定初始值,而类可以
- 结构体不能声明无参的构造函数,而类可以
- 结构体声明有参构造函数后,无参构造不会被顶掉,而类会被顶掉
- 结构体不能声明析构函数,而类可以
- 结构体不能被继承,而类可以
- 结构体需要在构造函数中初始化所有成员变量,而类都行
- 结构体不能被静态static修饰(不存在静态结构体),而类可以
- 结构体不能在自己内部声明和自己一样的结构体变量,而类可以
- 特点:结构体可以继承接口,因为接口是行为的抽象
如何选择结构体和类
- 想要用继承和多态时,直接淘汰结构体,比如:玩家、怪物等等。
- 对象是数据集合时,优先考虑结构体,比如:位置、坐标等等。
- 从值类型和引用类型赋值时的区别上考虑,比如经常被赋值传递的对象,并且改变赋值对象,原对象不想跟着变化时,就用结构体。比如:坐标、向量、旋转等等
抽象类和接口的区别
相同点
- 都可以被继承
- 都不能直接示例化
- 都可以包含方法声明
- 子类必须实现未实现的方法
- 都遵循里氏替换原则
不同点
- 抽象类中可以有构造函数,接口中不能
- 抽象类只能被单一继承,接口可以被继承多个
- 抽象类中可以有成员变量,接口中不能
- 抽象类中可以声明成员方法:虚方法、抽象方法、静态方法,接口中只能声明没有实现的抽象方法
- 抽象类方法可以使用访问修饰符,接口中不建议写,默认public
如何选择抽象类和接口
- 表示对象的用抽象类
- 表示行为拓展的用接口
举例:动物是一类对象,我们选择抽象类。飞翔是一个行为,我们选择接口。

浙公网安备 33010602011771号