C#核心:多态

多态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})");
    }
}

两者如何选择

  1. 想要用继承来动态化,直接淘汰结构体,比如玩家、怪物等等
  2. 对象为数据集合时,优先考虑结构体,比如位置、坐标等等
  3. 从值类型和引用类型传递的区别上去考虑,比如经常被赋值传递的对象,并且改变值后,原对象不想跟着变化时,就用结构体。比如坐标、向量、旋转等等

抽象类和接口的区别

知识点一 相同点

cs 复制代码
都不能被直接实例化
都不能被直接实例化
都可以(包含方法申明)
子类必须实现未实现的方法
都遵循里氏替换原则

知识点二 区别

cs 复制代码
抽象类中可以有构造函数;接口中不能
抽象类只能被单一继承;接口可以被继承多个
抽象类中可以有成员变量;接口中不能
抽象类中可以申明成员方法,虚方法,抽象方法,静态方法;接口中只能申明没有实现的抽象方法
抽象类方法可以使用访问修饰符;接口中建议不写,默认public

如何选择抽象类和接口:

cs 复制代码
表示对象的用抽象类,表示行为拓展的用接口
不同对象拥有的共同行为,我们往往可以使用接口来实现
举个例子:
动物是一类对象,我们自然会选择抽象类;而飞翔是一个行为,我们自然会选择接口。
相关推荐
Howrun7772 小时前
C++标准线程库-全面讲解
开发语言·c++
浪扼飞舟2 小时前
C#(多线程和同步异步)
java·开发语言
万行2 小时前
机器人系统SLAM讲解
开发语言·python·决策树·机器学习·机器人
抬头望远方2 小时前
【无人机】无人机群在三维环境中的碰撞和静态避障仿真(Matlab代码实现)
开发语言·支持向量机·matlab·无人机
matlab科研助手2 小时前
【路径规划】基于遗传算法的农药无人机在多边形区域的路径规划研究附Matlab代码
开发语言·matlab·无人机
2301_780669862 小时前
字符集及其编码、解码操作、IO流分类
java·开发语言
无名的小三轮2 小时前
第三章 防火墙概述
开发语言·php
有梦想的攻城狮2 小时前
Java中的Double类型的存在精度丢失详解
java·开发语言·bigdecimal·double
一路往蓝-Anbo2 小时前
【第42期】调试进阶(一):IDE中的Register与Memory窗口
c语言·开发语言·ide·stm32·单片机·嵌入式硬件