多态vob
让继承同一父类的子类们,在执行相同方法有不同表现
**主要目的:**同一父类的对象,执行相同行为有不同表现
解决问题:让同一个对象有唯一行为的特征
- virtual:父类声明可重写的方法;
- override:子类重写该方法;
- base:(可选)在重写中调用父类实现。
如果想要父类中的Atk方法保留执行,则在子类中base.Atk()
如果不需要保留,则不需要base.Atk()。
上述两种情况都是调用子类的对应函数Atk
cs
class GameObeject
{
//虚函数 virtual
public virtual void Atk()
{
Console.WriteLine("游戏对象进行攻击");
}
}
class Player : GameObeject
{
//重写
public override void Atk()
{
//base可以保留父类的方法
//base.Atk();
Console.WriteLine("玩家对象进行攻击");
}
}
class Monster : GameObeject
{
//重写
public override void Atk()
{
//base可以保留父类的方法
//base.Atk();
Console.WriteLine("怪物对象进行攻击");
}
}
internal static class Program
{
private static void Main(string[] args)
{
Console.WriteLine("多态 vob");
#region 多态的使用
GameObject p = new Player();
p.Atk();
GameObject p2 = new Monster();
p2.Atk();
#endregion
}
}
抽象类和抽象方法
特点:
1、不能被实例化, 可以包含抽象方法
2、继承抽象类必须重写抽象方法
抽象函数
特点:
1. 只能在抽象类中声明
2. 没有方法体
3. 不能是私有的
4. 继承后必须要override重写
cs
abstract class Fruits
{
public string name;
// 抽象方法 是一定不能有函数体的------即不能有大括号
public abstract void Bad();
//虚方法可以由子类选择性实现
public virtue void Test(){}
}
class Apple : Fruits
{
public override void Bad ()//如果没有重写这个Bad函数会报错
{
Console.WriteLine("苹果坏了");
}
//这里没实现父类虚方法依旧可以执行
}
接口
基本概念
cs
#region 知识点二 接口的申明
// 接口关键字: interface
// 语法:
// interface 接口名
// {
// }
// 一句话记忆:接口是抽象行为的"基类"
// 接口命名规范:帕斯卡前面加个I
#endregion
接口的使用
类可以继承一个类,n个接口
继承了接口后,必须实现其中内容
cs
class Animal{}
interface IFly
{
//方法
void Fly();
//属性
string Name
{
get;
set;
}
//索引器
int this[int index]
{
get;
set;
}
//事件 c#进阶讲
event Action doSomthing;
}
cs
//接口遵循里氏替换 父类装子类(Person类继承IFly接口)
IFly f = new Person();
//接口不能被实例化
IFly f = new IFly();
接口类与实现实现接口类的默认访问权限
接口类默认成员变量和方法是public
cs
interface IFly
{
//方法
void Fly();
//属性
string Name
{
get;
set;
}
//索引器
int this[int index]
{
get;
set;
}
//事件 c#进阶讲
event Action doSomthing;
}
继承类默认访问权限是private
cs
class Person : Animal, IFly
{
public void Fly(){}
public virtue void Fly(){}//这里使用虚函数,Person的子类可以重写该方法
public string Name
{
get;
set;
}
}
接口可以继承接口
相当于行为合并
接口继承接口时 不需要实现
待类继承接口后 类去实现所有内容
cs
interface IWalk
{
void Walk();
}
interface IMove : IFly, IWalk
{
}
显式实现接口
但是接口中存在同名方法时
注意:显示实现接口时,不能写访问修饰符
cs
class Player : IAtk, ISuperAtk
{
// 显示实现接口:就是用 接口名.行为名 去实现
void IAtk.Atk()
{
throw new NotImplementedException();
}
void ISuperAtk.Atk()
{
throw new NotImplementedException();
}
}
static void Main(string[] args)
{
IAtk ia = new Player();
ISuperAtk isa = new Player();
ia.Atk();//调用IAtk.Atk()
isa.Atk();//调用ISuperAtk.Atk()
}
密封函数
关键字:sealed 修饰重写函数
让虚方法或者抽象方法不能再被重写 结扎
和override一同出现(写在override前面)
cs
class Person : Animal
{
public sealed override void Eat()
{
}
}
class WhitePerson : Person
{
public override void Eat()//这报错,因为被密封
{
}
}
命名空间
基本语法
是用来组织和复用代码的,像一个工具包 类就是工具都声明在命名空间中 (文件夹装文件)
cs
// 基本语法
namespace 命名空间名
{
类
类
}
命名空间中的类的使用
cs
using Mygame;
using System; 1、使用该命名空间中的类
如果注释掉命名空间,其中的类会失效
cs
using Mygame;
using System;//注释掉
namespace Lesson21_面向对象相关_命名空间
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("命名空间");//报错!因为Console是System中的类
}
}
}
你还可以使用指明出处的方式创建
MyGame.GameObject g = new MyGame.GameObject();
命名空间可以分开
命名空间可以分开写,同命名空间中类的名字不可以重复,不同命名空间的类可以同名
cs
namespace Mygame
{
class Gameobject
{
}
}
//命名空间可以分开写
namespace Mygame
{
//属于同一命名空间 可以正常继承
class Player:Gameobject
{
}
}
cs
namespace MyGame
{
namespace UI
{
class Image
{
}
}
namespace Game
{
class Image
{
}
}
}
因为是两个不同命名空间,所以Image可以重复,如果你现在要使用UI中的Image而不是Game
cs
使用哪个就调用哪个
MyGame.UI.Image img = new MyGame.UI.Image();
MyGame.Game.Image img2 = new MyGame.Game.Image();
万物之父的方法
cs
public class Object
{
public Object();
~Object();
public static bool Equals(object? objA, object? objB);
public static bool ReferenceEquals(object? objA, object? objB);
public virtual bool Equals(object? obj);
public virtual int GetHashCode();
public virtual string? ToString();
protected Object MemberwiseClone();
}
静态方法:Equals函数
值类型判断 对比两个对象是否相等
引用类型判断 对比的是两个对象 是否指向同一内存地址,并不是判断是否是相同类型
cs
// 静态方法 Equals 判断两个对象是否相等
// 最终的判断权,交给左侧对象的Equals方法
// 不管值类型引用类型都会按照左侧对象Equals方法的规则来进行比较
Console.WriteLine(Object.Equals(1, 1));
Test t = new Test();
Test t2 = new Test();
Console.WriteLine(Object.Equals(t, t2));
静态方法:ReferenceEquals
比较两个对象是否相同的引用,主要是用来比较引用类型的对象。
值类型对象返回值始终是 false。
cs
Test t = new Test();
Test t2 = new Test();
Console.WriteLine(Object.ReferenceEquals(t, t2));
成员方法:GetType
反射相关知识,c#进阶讲
成员方法:MemberwiseClone
浅拷贝:值类型直接复制过来,引用类型复制的是内存地址。
所以改变拷贝后的值类型变量与拷贝前的值类型变量无关,改变拷贝后的引用类型变量 拷贝前的引用类型变量也会跟着改变
虚方法:GetHashCode
极少用
虚方法:Tostring
cs
class Test
{
public override string ToString()
{
return "苏老师声明的Test类"
}
}
//*************************************
Test t = new Test();
Console.WriteLine(t.ToString);
//打印结果:命名空间+类名------面向对象测试(这是命名空间名字).Test
Console.WriteLine(t.ToString);
//打印结果为"苏老师声明的Test类"
string字符串
字符串指定位置
字符串的本质是char数组,所以指定位置应该依靠索引器
cs
String中的元数据
public char this[int index]
cs
//字符串本质是char数组
string str = "苏同学";
Console.WriteLine(str[0]);
//打印结果为"苏"
//⭕转为char数组
char[] chars = str.ToCharArray();
字符串拼接
1. 加法拼接(+)
string str = "Hello" + " " + "World"; // Hello World
string result = "Value: " + 42; // Value: 42(自动调用 ToString())
2. string.Format()
对应字符串输出到对应数组位置,
cs
string str = string.Format("{0} {1}", "Hello", "World"); // Hello World
string result = string.Format("Value: {0}", 42); // Value: 42
string str = string.Format("{0}我的{1}", "Hello", "World"); // Hello我的World
正向查找字符位置
cs
//⭕正向查找字符位置
str = "我是苏同学?";
int index = str.IndexOf("苏");
//返回 2 字符串的索引 , 找不到就会返回-1
反向查找字符位置
一般是顺序查找,顺序查找苏同学就返回索引值2
但如果要先找后面的苏同学,就用该方法
cs
//⭕反向查找字符位置
str = "我是苏同学苏同学?";
index = str.LastIndexOf("苏同学");
//返回 5 从后面开始查找词组就返回第一个字的索引,找不到就返回-1
移除指定位置后的字符
cs
//⭕移除指定位置后的字符
str = "我是苏同学苏同学";
str = str.Remove(4);
//返回 "我是苏同"
//⭕执行两个参数进行移除 参数1开始的位置 参数2字符个数
str = "我是苏同学陈同学";
str = str.Remove(3,3);//删去苏同学三个字
//返回"我是陈同学"
大小写转换
cs
//⭕大小写转换
str = "abcdefg";
str = str.ToUpper();
//返回"ABCDEFG"
str = str.ToLower();
//返回"abcdefg"
字符串截取
cs
//⭕字符串截取 截取从指定位置开始之后的字符串
str = "苏同学陈同学";
str = str.Substring(3);
//返回 "陈同学"
//重载 参数1开始位置 参数2指定个数
str = "苏同学陈同学苏同学";
str = str.Substring(3,3);
//返回 "陈同学"
字符串切割
cs
//字符串切割 指定切割符号来切割字符串
str = "1|2|3|4|5|6|7|8";
string[] strs = str.Split("|");
//返回 string[]{1,2,3,4,5,6,7,8}
StringBuilder
C#提供的一个用于处理字符串的公共类
主要解决的问题是: 修改字符串而不创建新的对象,需要频繁修改和拼接的字符串可以使用它,可以提升性能
使用前 需要引用命名空间
cs
System.Text
为什么需要引入这种字符串处理类
cs
String 拼接操作
▶ 每一次修改都需要申请内存空间
▶ 每一次修改都需复制原字符串到新的空间
▶ 每一次修改后都需要销毁原有空间
因为String拼接操作过于冗杂,消耗性能
二者的使用区间
|-------------------|------------------------------------|
| 拼接次数 ≤ 3 次? | → 用 String(+ 或插值 $"") |
| 在循环/递归中拼接? | → 用 StringBuilder |
| 字符串内容几乎不变? | → 用 String |
| 需要高性能生成大文本?(如配置表) | → 用 StringBuilder |
| 要做字典的 key? | → 必须用 String |
| 多线程共享修改? | → 用 String,或对 StringBuilder 加锁 |
StringBuilder中的容量
容量问题 每次增加时都会自动扩容


StringBuilder使用增删查改
cs
StringBuilder strBui = new StringBuilder("123123123");
//⭕增
strBui.Append("444");
//结果为 "123123123444"
strBui.AppendFormat("{0}{1}",555,666);
//结果为 "123123123444555666"
//⭕插入 参数1插入的位置 参数2插入的内容
strBui.Insert(0,"苏同学");
//结果为 "苏同学123123123444555666"
//⭕删 参数1删除开始的位置 参数2删除的个数
strBui.Remove(0,3);
//结果为 "123123123444555666"
//⭕清空
strBui.Clear();
//结果为 ""
//⭕重新赋值 先清空再增加
strBui.Clear();
strBui.Append("苏同学");
//⭕查 和数组一样
strBui[1];
//结果为 "同"
//⭕改 和数组一样
strBui[0]='李';
//strBui结果为 "李同学"
//⭕替换 参数1被替换的字符 参数2要替换的内容
strBui.Replace("同学","老师");
//strBui结果为 "李老师"
//⭕判断是否相等
strBui.Equals("李老师");
//返回为 true
结构体和类的区别
cs
结构体是值类型,类是引用类型
结构体存在栈中,类存在堆中
结构体成员不能使用 protected,类可以
结构体成员变量声明时不能指定初始值,类可以
结构体不能声明无参构造函数,类可以
结构体声明有参构造后,无参构造仍存在
结构体不能声明析构函数,类可以
结构体不能被继承,类可以
结构体需在构造函数中初始化所有成员,类随意
结构体不能被 static 修饰,类可以
结构体不能在内部声明自身类型变量,类可以
**结构体的特别之处:**结构体可以继承接口(因为接口是行为的抽象),只是不能继承类和结构体
cs
public interface IDrawable
{
void Draw();
}
public struct Point : IDrawable
{
public int X { get; set; }
public int Y { get; set; }
// 实现接口方法
public void Draw()
{
Console.WriteLine($"绘制点 ({X}, {Y})");
}
}
两者如何选择
- 想要用继承来动态化,直接淘汰结构体,比如玩家、怪物等等
- 对象为数据集合时,优先考虑结构体,比如位置、坐标等等
- 从值类型和引用类型传递的区别上去考虑,比如经常被赋值传递的对象,并且改变值后,原对象不想跟着变化时,就用结构体。比如坐标、向量、旋转等等
抽象类和接口的区别
知识点一 相同点
cs
都不能被直接实例化
都不能被直接实例化
都可以(包含方法申明)
子类必须实现未实现的方法
都遵循里氏替换原则
知识点二 区别
cs
抽象类中可以有构造函数;接口中不能
抽象类只能被单一继承;接口可以被继承多个
抽象类中可以有成员变量;接口中不能
抽象类中可以申明成员方法,虚方法,抽象方法,静态方法;接口中只能申明没有实现的抽象方法
抽象类方法可以使用访问修饰符;接口中建议不写,默认public
如何选择抽象类和接口:
cs
表示对象的用抽象类,表示行为拓展的用接口
不同对象拥有的共同行为,我们往往可以使用接口来实现
举个例子:
动物是一类对象,我们自然会选择抽象类;而飞翔是一个行为,我们自然会选择接口。