猿创征文| Unity高级开发面向对象编程知识总结
@作者 : SYFStrive
@博客首页 : HomePage
📜: C#面向对象
📌:个人社区(欢迎大佬们加入) 👉:社区链接🔗
📌:觉得文章不错可以点点关注 👉:UnityC#编程干货🔗
目录
- 面向对象的三大特性
- 面向对象的七大原则
- 封装✔
- 类和对象基本概念
- 类声明的语法
- 类实例化相关语法
- 类成员变量和访问修饰符
- 成员方法
- 构造函数
- 垃圾回收机制 ★(重点)
- 小总结
- 索引器✔
- 重载索引器✔
- 拓展方法✔
- 继承✔
- 基本概念
- 基本语法
- 访问修饰符
- 七大原则之里氏替换原则(最重要的原则)
- 继承中的构造函数
- 万物之父&装箱拆箱✔
- 基本概念
- 装箱拆箱
- 密封类
- 多态✔
- VOB理解
- 抽象类与抽象方法
- 抽象类
- 抽象方法
- 接口✔
- 接口可以继承接口
- 显示实现接口(了解)
- 接口的作用和总结
- 密封方法(了解)
- 命名空间✔
- 使用命名空间
- 命名空间可以包裹命名空间
- 万物之父中的方法✔
- 静态方法:Equals
- 静态方法:ReferenceEquals
- 成员方法:GetType
- 成员方法:MemberwiseClone
- 虚方法:Equals
- 虚方法:Tostring
- string 的常用方法✔
- StringBuilder✔
- 结构体和类的区别✔
- 如何选择结构体和类
- 细节区别
- 抽象类和接口的区别`✔`
- 相同点
- 区別
- 如何选择抽象类和接口
- 最后
提示:以下是本篇文章正文内容
面向对象的三大特性
- 封装 : 用程序语言来形容对象
- 继承 :复用封装对象的代码;儿子继承父亲,复用现成代码
- 多态:同样行为的不同表现,儿子继承父亲基因但是有不同的行为表现
面向对象的七大原则
① 单一职责原则(Single Responsibility Principle)
👉单一职责原则又称单一功能原则,它规定一个类应该只有一个发生变化的原因。
② 里氏替换原则(Liskov Substitution Principle)
👉氏替换原则的意思是,所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则是与面向对象语言的继承特性密切相关的。
③ 依赖倒置原则(Dependence Inversion Principle)
👉程序要依赖于抽象接口,不要依赖于具体实现
④ 接口隔离原则(Interface Segregation Principle)
👉客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
⑤ 迪米特法则(Law Of Demeter)
👉迪米特法则又叫作最少知识原则,一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。
⑥ 开闭原则(Open Close Principle)
👉软件实现应该对扩展开放,对修改关闭,其含义是说一个软件应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化的。
⑥ 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)
👉组合/聚合复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
封装✔
类和对象基本概念
- 一般在namespace中声明
- 具相同特征,相同行为,一类事物的抽象,类是对象的模板,可以通过类创建对象。
- 声明类的关键词:Class
- 类的声明 和 类对象声明是两个概念
- 类的声明: 👉 类(类是少的数引用类型)似枚举和结构体(值类型) 的声明 👉 类的声明相当于是声明了一个自定义的变量类型,用来抽象现实实物的
- 类对象的声明: 👉 相当于声明一个指定类的变量
- 对象是类创建出来的 是用来表象现实中的对象个体
- 类创建对象的过程 👉 称之为实例化对象
- 类、对象都是引用类型的
- null :空引用类型为null的时候指的是内存堆没有分配
类声明的语法
- 声明类类名使用 👉 怕斯卡命名法(简单理解首字母大写)
- 相关语法如 👇:
class 类名
{
//特征 👉 成员变量
//行为 👉 成员方法
//保护特征 👉 成员属性
//构造函数 和 析构函数
//索引器
//运算符重载
//静态成员
ヾ(@⌒ー⌒@)ノ……
}
类实例化相关语法
// 引用类型
//类名 变量名; (栈指向堆地址值是空的没有分配堆内存)
//类名 变量名 = null; (栈指向堆地址值是空的没有分配堆内存)
//类名 变量名 = new 类名; (存在栈指向堆的内存空间使其在堆中新开了个房间(这时是空的))
// 使用new时(相当于开辟一个新的空间):相当于创建一个新对象
面向对象 👉 new……;
类成员变量和访问修饰符
- 成员变量特点如 👇:
- 用于描述对象的特征
- 可以是任意变量类型(枚举,结构体,类对象)
- 是否初始化根据需求而定
- 声音一个Person类 如 👇
class Person
{
public string name;
public int age;
public E_Sex sex;
public Person[] boyFriend; (类名一样的变量类型 👉 初始值为空)
public Position Pos;(Position结构体)
注意:如果要声明一个和自己相同类型的成员变量时,不能对他进行实例化/或设置为空(会死循环!!)
……
}
- 三个访问修饰符
- public: 公开的,所有对象都能访问和使用
- private: 私有的,只有自己内部才能访问和使用, 变量前不写默认为该状态
- protected: 只有自己内部和子类才能访问和使用,继承的时候用到
class Person
{
public int a;
private int b;
protected int c;
}
-
检查默认值
- Default()
成员方法
- 和结构体中函数的声明使用差不多
- 用来描述对象的行为,在类中声明
- 受访问修饰符的影响
- 不需要加static关键字
- 成员方法需要实例化才能使用
class Person
{
//特征——成员变量
public bool sex;
public string nanme;
public float height;
public int age;
public Person[] friend;
/// <summary>
/// 添加朋友扩容数组
/// </summary>
/// <param name="p">添加朋友P</param>
public void AddFriend(Person p)
{
if (friend ==null)
{
friend = new Person[] { p };
}
else
{
//数组扩容+1
Person[] newFriend = new Person[friend.Length + 1];
for (int i = 0; i < friend.Length; i++)
{
newFriend[i] = friend[i];
}
//将新成员p存在新数组的最后一个索引
newFriend[newFriend.Length - 1] = p;
}
}
}
构造函数
- 在实例化对象时会调用的用于初始化的函数,如果不写就默认存在一个无参构造函数
- 和结构体中构造函数的写法一致,(类允许自己申明一个无参构造函数 结构体是不允许的)
- 无返回值,函数名和类名必须相同,一般都是public,可以重载构造函数
- 声明有参构造函数之前最好一起声明一个无参构造函数,声明有参构造时默认的无参构造就不存在了,要手动声明
class Person
{
//特征——成员变量
public string nanme;
public float height;
public int age;
public Person[] friend;
//构造函数 实现对象实例化时 初始化
//构造函数可以重载
//无参构造函数
public Person()
{
nanme = "郭先生";
age = 18;
0 = true;
height = 180;
}
//有参构造函数
public Person(string name, int age,float height) :this()
{
this.name = name;
this.age = age;
this.height = height;
}
}
Person p = new Person("郭先生"",18,120f);
垃圾回收机制 ★(重点)
-
垃圾回收,英文简写GC(Garbage Collector)
-
垃圾回收的过程是在遍历堆(Heap)上动态分配的所有对象
-
通过识别它们是否被引用来确定哪些对象是垃圾,哪些对象仍要被使用
-
所谓的垃圾就是没有被任何变量,对象引用的内容
-
垃圾就需要被回收释放
-
垃圾回收有很多种算法,比如 :引用计数(Reference Counting) 👉 标记清除(Mark Sweep)👉 标记整理(Mark Compact) 👉 复制集合(Copy Collection)
-
注意:
1、GC只负责堆(Heap)内存的垃圾回收
2、引用类型都是存在堆(Heap)中的,所以它的分配和释放都通过垃圾回收机制来管理 -
栈(Stack)上的内存是由系统自动管理的
1、值类型在栈(Stack)中分配内存的,他们有自己的生命周期,不用对他们进行管理,会自动分配和释放
-
C#中内存回收机制的大概原理
0代内存 👉 1代内存 👉 2代内存
代的概念:👇
- 代是垃圾回收机制使用的一种算法(分代算法)
- 新分配的对象都会被配置在第0代内存中
- 每次分配都可能会进行垃圾回收以释放内存(0代内存满时)
-
在一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,会进行以下两步
1、标记对象 从根(静态字段、方法参数)开始检查引用对象,标记后为可达对象,未标记为不可达对象 👉 不可达对象就认为是垃圾(💡离开树的叶子)。
2、搬迁对象压缩堆(挂起执行托管代码线程)释放未标记的对象 搬迁可达对象 修改引用地址
-
垃圾回收文字说明:
如 : A 、B、C、D (引用类型)
0代、1代、2代 (空间)
A、B、C(引用类型……) 进入0代 :这时D想进0代(0代满了) 👉 0代进行判断不可达对象(释放未标记的对象)然后把A、B、C(引用类型……) 搬迁到1代(放到一个连续的空间)同时改变引用地址值 👉 这时0代释放了内容有了空位 D就可以在空位开一个空间 👉 如果1代满了这时会触发1代的内存释放(0代、1代内存都会释放),然后把1代的可达对象移到2代内存(0代、1代速度比2代速度快);
触发了垃圾回收(GC)
使用语法 GC.Collect() (静态方法)
总结 🤳大对象:85000字节(83kb)以上的对象为大对象(默认存在2代内存)
小总结
new 垃圾回收就是一个生命周期生命🛸
索引器✔
作用:可以以中括号的形式范围自定义类中的元素 规则自定义 访问时和数组一样适用于 在类中有数组变量时使用 索引器可以重载锦上添花的作用,功能和成员属性相同,可以不写
语法如 👇 :
class Person
{
public string name;
public Person friends[ ];
public Person this[int index]
{
get
{
if(friends==null || friends.length-1<index){
return null;
}
//返回想要的索引
return friends[index];
}
set
{
if (friends==null)
{
//判断为空时实例化
friends = new Person[]{value};
}else if (index > friends.length-1){
//把最后的顶掉(自己写的 👉 可能会丢朋友)
friends[friends.length-1]=value;
}
//赋值
friends[index] = value
}
}
}
使用如 👇 :
Person p =new Person()
p[0] = new Person();
重载索引器✔
class Person
{
public string name;
public Person friends[ ];
public string this[string str]{
get
{
switch(str)
{
case "name":
return this.name;
case "age"
return age.ToString()
}
return "";
}
}
public Person this[int index]
{
get
{
if(friends==null || friends.length-1<index){
return null;
}
//返回想要的索引
return friends[index];
}
set
{
if (friends==null)
{
//判断为空时实例化
friends = new Person[]{value};
}else if (index > friends.length-1){
//把最后的顶掉(自己写的 👉 可能会丢朋友)
friends[friends.length-1]=value;
}
//赋值
friends[index] = value
}
}
}
使用如 👇 :
Person p =new Person()
p[0] = p.name;
拓展方法✔
相关语法:访问修饰符 static 返回值 函数名(this 拓展方法 参数名 ,参数类型 参数名,参数类型,参数名…)
-
概念:为现有非静态,变量类型添加新方法
-
作用如 👇
- 提升程序拓展性
- 不需要再对象中重新写方法
- 不需要继承来添加方法
- 为别人封装的类型写额外的方法
-
特点如 👇
- 一定是写在静态类中的静态方法
- 第一个参数为拓展目标
- 第一个参数用this修饰
//为int拓展了一个成员方法
//成员方法 是需要 实例化对象后 才 能使用的
//value 代表 使用该方法的 实例化对象
0个引用
public static void SpeakValue(this int value)
{
//拓展的方法的逻辑
Console.WriteLine("int拓展的方法"+ value);
}
调用如下:
int i=10;
i.SpeakValue()
继承✔
基本概念
- 一个类A继承一个类B
- 类A将会继承类B的所有成员
- A类将拥有B类的所有特征和行为
-
被继承的类
- 称为 父类、基类、超类
-
继承的类
- 称为子类、派生类
- 子类可以有自己的特征和行为
-
特点
- 单根性 子类只能有一个父类
- 传递性 子类可以间接继承父类的父类
-
作用
- 用于保护成员变量
- 为成员属性的获取和赋值添加逻辑处理
- 解决3p局限性问题
- get,set可以写一个(起到保护作用)
基本语法
- 基本语法
class Person
{
//名字
public string name;
//工号
public int number;
//介绍名字
public void SpeakName()
{
Console.WriteLine(name);
}
}
//继承Person类
class Studio: Person
{
//科目
public string subject;
//介绍科目
public void SpeakSubject()
{
Console.WriteLine(subject+"老师");
}
}
访问修饰符
关键字:protected
不希望外部访问,子类可以访问
七大原则之里氏替换原则(最重要的原则)
★ 里氏替换原则是面向对象七大原则中最重要的原则
基本概念:
- 父类容器转载子类对象
- 方便进行对象存储和管理
- 任何父类出现的地方,子类都可以替代
-
重点:
语法表现父类容器装子类对象 ,因为子类对象包含了父类的所有内容
-
作用:
方便进行对象存储和管理
代码如 👇
class GameObject
{
}
class Player : GameObject
{
public void PlayerAtk()
{
Console.WriteLine("玩家攻击");
}
}
class Moster : GameObject
{
public void MosterAtk()
{
Console.WriteLine("怪物攻击");
}
}
class Boss : GameObject
{
public void BossAtk()
{
Console.WriteLine("Boss攻击");
}
}
//注意两点
//里氏替换原则,用父类容器转载子类对象
//但是Player类的功能无法使用要用is as转换
//子类进父类
GameObject player = new Player();
//✅is和as
//is判断一个对象是否是指定的对象
//返回值bool
if (player is Player)
{
//as:将一个对象转换为指定类对象
//返回值:指定类型对象
//成功返回指定类对象 失败返回null
Player p = player as Player;
}
//可以正常使用Player类的功能了
p.PlayerAtk();
//数组
GameObject[] objects = new GameObject[] { new Player(), new Moster(), new Boss() };
//遍历objects数组 来判断类和执行类
for (int i = 0; i < objects.Length; i++)
{
if (objects[i] is Player)
{
(player as Player).PlayerAtk();
}
else if (objects[i] is Moster)
{
(player as Moster).MosterAtk();
}
else if (objects[i] is Boss)
{
(player as Boss).BossAtk();
}
}
继承中的构造函数
- 基本概念
-
先执行父类的构造函数再执行子类构造函数
-
子类实例化时 👉 默认调用无参 👉 父类没有无参构造就会报错
1.始终保持申明一个无参构造
2.通过base调用指定父类构造调用父类的构造函数 (注意和this的区别)
执行顺序 (父类有多个子类):但子类调用构造时会先找到基类调用基类的构造函数然后依次往下执行子类的构造函数
代码如 👇
class Father
{
int a ;
public Father (int a)
{
this a = a;
}
}
class Son : Father
{
public Son (int a) : base(a)
{
}
}
Son s = new Son(999);
万物之父&装箱拆箱✔
基本概念
关键字:object 是一个基类 可以装任何东西
概念:Object是所有类型的基类,它是一个类(引用类型)
作用如 👇
- 可以利用里氏替换原则,用object容器装所有对象
- 可以用来表示不确定类型,作为函数参数类型
使用方式如 👇
- 引用类型和里氏替换原则一样用is和as
- 值类型使用括号强转
class Person
{
public Speak()
{
Console.WriteLine("好人卡!");
}
}
//装引用类型 和使用——里氏替换
object o = new Person();
if(o is Person)
{
(o as Person).Speak;
}
//装值类型 和 使用——括号强转
object o2 = 10;
int a = (int)o2;
//装特殊类型 string
object o3 = "你好呀";
string str = o3.ToString;//也可以使用引用类型的 o3 as string
//装特殊类型 数组
object o4 = new int[10];
int[] arr = (int[])o4;//也可以使用引用类型的 o4 as int[]
装箱拆箱
值类型和object 简单如 👇
装箱:把值类型用引用类型存储 👉 栈内存移到堆内存中
拆箱:把引用类型存储的值类型取出来 👉 堆内存移到栈内存中
-
用object存值类型(装箱)
-
再把object转为值类型(拆箱)
-
装箱
把值类型用引用类型存储 👉 栈内存会迁移到堆内存中 -
拆箱
把引用类型存储的值类型取出来 👉 堆内存会迁移到栈内存中 -
好处:不确定类型时可以方便参数的存储和传递
-
坏处:存在内存迁移,增加性能消耗
int a =10;
//装箱
object o = a;
//拆箱
int b = (int)o;
密封类
关键字:sealed
作用:让类无法被继承
意义:保证程序的规范性,安全性
多态✔
概念:多态可以简单地理解为同一条函数调用语句能调用不同的函数;或者说,对不同对象发送同一消息,使得不同对象有各自不同的行为。
VOB理解
VOB : virtual(虚函数) 👉 override(重写) 👉 base(父类);
- 在执行同一方法时有不同的表现,重写的方法 输入参数的类型和数量要一致(也复合面向对象)
- 多层继承中也可以使用层层重写回到父类
作用:其实多态的作用就是把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出更通用的程序。
主要目的如 👇
-
同一父类的对象,执行相同行为(方法)有不同的表现
-
解决的问题
2.1 让同一个对象有唯一行为的特征
小总结如 👇
-
多态按字面的意思就是“多种状态”
-
让继承同一父类的子类们 在执行相同方法时有不同的表现(状态)
抽象类与抽象方法
抽象类
- 关键字:abstract
- 不能被实例化 可以包含抽象方法 遵循里氏替换
代码如 👇
abstract class Fruits
{
}
- 如何选择普通类还是抽象类
- 不希望实例化的对象 👉 相对抽象的类可以使用 如 :人Person 水果fruit
- 父类中的行为不太需要被实现 👉 只希望子类去定义具体的规则整体框架设计时会使用 👉 让基类更安全
抽象方法
-
抽象函数又叫纯虚方法
关键字:abstract
特点如 👇
1.只能在抽象类中声明
2.没有方法体
3.不能是私有的
4.继承后必须要override重写
代码如 👇
abstract class Fruits
{
public string name;
public abstract void Bad ();
}
class Apple : Fruits
{
public override void Bad ()
{
Console.WriteLine("苹果有虫子了");
}
}
//遵循里氏替换 父类装子类
Fruits f = new Apple();
f.Bad();
虚方法(vritual) 对比 纯虚方法(abstract)的区别 如 👇
- 虚方法可以在抽象类和非抽象类中声明
- 纯虚方法只能在抽象类中声明
- 虚方法可以由子类选择性实现
- 纯虚方法必须实现重写。
- 虚方法有方法体可实现逻辑他们都可以被子类用override重写 可以多层重写 子子类重写子类重写父类
小总结:搭框架使用
接口✔
- 基本概念
接口是行为的抽象规范也是一种自定义类型
关键字:interface
接口申明的规范
1.不包含成员变量
2.只包含方法、属性、索引器、事件
3.成员不能被实现
4.成员可以不用写访问修饰符,不能是私有的
5.接口不能继承类,但是可以继承另一个接口
接口的使用规范
1.类可以继承多个接口
2.类继承接口后,必须实现接口中所有成员
特点 如 👇
1.它和类的申明类似
2.接口是用来继承的
3.接口不能被实例化,但是可以作为容器存储对象
- 基本语法
接口关键字:interface
语法:interface 接口名
记忆:接口是抽象行为的“基类” 👉 接口命名规范 帕斯卡前面加个I
interface IFly
{
//方法
void Fly();
//属性
string Name
{
get;
set;
}
//索引器
int this[int index]
{
get;
set;
}
//系统事件
event Action doSomthing;
}
- 接口的使用
接口用来继承
1.类可以继承1个类,n个接口
2.继承了接口后 必须实现其中的内容 并且必须是public的
3.实现的接口函数,可以加v再在子类重写
4.接口也遵循里氏替换原则
代码如 👇
//声明接口
interface IFly
{
//方法
void Fly();
//属性
string Name
{
get;
set;
}
//索引器
int this[int index]
{
get;
set;
}
//事件 c#进阶讲
event Action doSomthing;
}
class Animal{}
//继承类 && 实现接口
class Persom : Animal,IFly
{
//实现的接口函数 可以加virtual再在子类中重写
public virtual void Fly()
{
}
public string Name
{
get;
set;
}
public int this[int index]
{
get
{
return 0 ;
}
set;
}
public event Action doSomthing;
}
//接口遵循里氏替换 父类装子类
IFly f = new Persom();
接口可以继承接口
- 相当于行为合并
- 接口继承接口时 👉 不需要实现
- 待类继承接口后 👉 类去实现所有内容
interface IWalk
interface IMove: IFly, IWalk{}
显示实现接口(了解)
- 当一个类继承两个接口
- 但是接口中存在着同名方法时
注意❗:显示实现接口时 不能写访问修饰符
class Player:IAtk,ISuperAtk {
//显示实现接口 就是用 接口名.行为名 去实现2个引用
void IAtk.Atk() {}
void ISuperAtk.Atk() {}
接口的作用和总结
作用如 👇
- 把行为抽象出来供子类继承。个别功能独立在基类外,子类需要时继承。
- 提高程序复用性
总结如 👇
- 继承类:是对象间的继承,包括特征行为……
- 接口继承:是行为间的继承,继承接口的行为规范,按照规范去实现内容。
- 接口也是遵循里氏替换,所以可以用接口容器装对象,
- 实现装载毫无关系但是却有相同行为的对象。
注意如 👇
- 接口包含:成员方法,属性,索引器,事件,且都不实现 都没有访问修饰符
- 可以继承多个接口,但是只能继承一个类
- 接口可以继承接口,相当于在进行行为合并,待子类继承时再去实现具体的行为
- 接口可以被显示实现 主要用于实现不同接口中的同名函数的不同表现
- 实现的接口方法 可以加 virtual关键字之后子类再重写
密封方法(了解)
- 关键字:sealed 修饰重写函数
- 让虚方法或者抽象方法不能再被重写(劫杀)
- 和override一同出现
命名空间✔
基本概念
- 是用来组织和复用代码,像一个工具包 👉 类就是工具都声明在命名空间中 (文件夹装文件)
- 命名空间可以分开写 👉 同命名空间中类的名字不可以重复,不同命名空间的类可以同名
代码如 👇
namespace Mygame
{
class Gameobject
{
}
}
//命名空间可以分开写
namespace Mygame
{
//属于同一命名空间 可以正常继承
class Player:Gameobject
{
}
}
使用命名空间
- using关键字
namespace Mygame
{
class Gameobject
{
}
}
//命名空间可以分开写
namespace Mygame
{
//属于同一命名空间 可以正常继承
class Player:Gameobject
{
}
}
using System
using Mygame
//使用Mygame命名空间的Gameobject类
Gameobject g = new Gameobject();
- 点出来引用
//使用Mygame命名空间的Gameobject类
Mygame.Gameobject g = new Mygame.Gameobject();
//也可以应对不同命名空间中 同名类的引用标识
using Mygame
using Mygame2
Mygame.Gameobject g = new Mygame.Gameobject();
Mygame2.Gameobject g2 = new Mygame2.Gameobject();
命名空间可以包裹命名空间
代码如 👇
namespace MyGame
{
namespace UI
{
class Image
{
}
}
namespace Game
{
class Image
{
}
}
}
方法一:通过命名空间依次点出其中的类来使用
MyGame.UI.Image img = new MyGame.UI.Image();
MyGame.Game.Image img2 = new MyGame.Game.Image();
方法二:引入命名空间
using MyGame.UI;
using MyGame.Game;
万物之父中的方法✔
静态方法:Equals
- 静态方法 Equals 判断两个对象是否相等
- 最终的判断权,交给左侧对象的Equals方法,
- 不管值类型引用类型都会按照左侧对象Equals方法的规则来进行比较 如 👇
Console.WriteLine(Object.Equals(1, 1)); //true
Test t = new Test();
Test t2 = new Test();
Console.WriteLine(object.Equals(t,t2)); false
静态方法:ReferenceEquals
- 比较两个对象是否相同的引用,主要是用来比较引用类型的对象。不能用来比值。
成员方法:GetType
- 反射相关知识,c#进阶
- 该方法在反射相关知识点中是非常重要的方法
- 该方法的主要作用就是获取对象运行时的类型Type,通过Type结合反射相关知识点可以做很多关于对象的操作。
成员方法:MemberwiseClone
- 浅拷贝:值类型直接复制过来,引用类型复制的是内存地址。所以改变浅拷贝后的值类型变量与拷贝前的值类型变量无关,浅拷贝后的引用类型变量与拷贝前的引用类型变量也会跟着改变
虚方法:Equals
- 可以重写该方法定义自己想要的规则 👉 比较虚方法Equals默认实现还是比较两者是否为同一个引用,即相当于ReferenceEquals。
- 但是微软在所有值类型的基类system.ValueType中重写了该方法,用来比较值相等。
- 我们也可以重写该方法,定义自己想要的规则
虚方法:Tostring
- 虚方法ToString
-
该方法用于返回当前对象代表的字符串,我们可以重写它定义我们自己的对象转字符串规则,
-
该方法非常常用。当我们调用打印方法时,默认使用的就是对象的ToString方法后打印出来的内容。
修改ToString默认内容值如 👇
class Test
{
public override string ToString()
{
return "Test类"
}
}
Test t = new Test();
Console.WriteLine(t.ToString);
//打印结果为"Test类"
string 的常用方法✔
//字符串本质是Char数组
string str = "郭同学";
Console.WriteLine(str[0]);
//打印结果为"郭"
//🚀转为char数组
char[] chars = str.ToCharArray();
//🚀获取字符长度
str.Length
//🚀字符串拼接
str = string.Format("{0}{1}",1,222);
//🚀正向查找字符位置
str = "我是郭同学?";
int index = str.IndexOf("郭");
//返回 2 字符串的索引 , 找不到就会返回-1
//🚀反向查找字符位置
str = "我是郭同学?";
index = str.LastIndexOf("郭同学");
//返回 3 从后面开始查找词组就返回第一个字的索引,找不到就返回-1
//🚀移除指定位置后的字符
str = "郭同学";
str = str.Remove(0);
//返回 "同学"
//🚀执行两个参数进行移除 👉 参数1开始的位置 👉 参数2字符个数
str = "郭同学";
str = str.Remove(0,1);
//返回"同学"
//🚀替换指定字符串
str = "阿里巴巴";
str = str.Replace("阿里","阿里巴巴");
//返回"阿里巴巴巴巴"
//🚀大小写转换
str = "abcd";
str = str.ToUpper();
//返回"ABCD"
str = str.ToLower();
//返回"ABCD"
//🚀字符串截取 👉 截取从指定位置开始之后的字符串
str = "郭同学A";
str = str.Substring(3);
//返回 "郭同学"
//重载 参数1开始位置 参数2指定个数
str = "郭同学郭同学";
str = str.Substring(3,1);
//返回 "郭"
//🚀字符串切割 👉 指定切割符号来切割字符串
str = "1|2|3|4|5|6|7|8";
string[] strs = str.Split("|");
//返回 string[]{1,2,3,4,5,6,7,8}
StringBuilder✔
-
解决多次修改string性能消耗问题
- string是特殊的引用
- 每次重新赋值或者拼接时会分配新的内存空间
- 如果一个字符串经常改变会非常浪费空间
- c#提供的一个用于处理字符串的公共类
- 主要解决的问题是:
- 修改字符串而不创建新的对象,需要频繁修改和拼接字符串可以使用StringBuilder,可以提升性能。
-
使用 StringBuilder 步骤如 👇
- 引用命名空间:System.Text
- 关键字:StringBuilder
- 容量问题 每次增加时都会自动扩容
- 无法直接重新赋值 要先清空再增加
- 完全具有引用类型的特征,没有值类型特征了
StringBuilder strBui = new StringBuilder("StringBuilder");
//获得容量
int a = strBui.Capacity;
//默认为16现在已经用了9 自动扩容会x2 变成32 64 128....
- 增删查改替换
StringBuilder strBui = new StringBuilder("1314");
//🚀增
strBui.Append("159");
//结果为 "1314159"
strBui.AppendFormat("{0}{1}",555,666);
//结果为 "1314159555666"
//🚀插入 参数1插入的位置 参数2插入的内容
strBui.Insert(0,"阿里巴巴");
//结果为 "阿里巴巴1314159555666"
//🚀删 参数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
结构体和类的区别✔
-
结构体和类最大的区别是在存储空间上的,因为结构体是值,类是引用,因此他们的存储位置一个在栈上,一个在堆上
-
结构体和类在使用上很类似,结构体甚至可以用面向对像的思想来形容一类对像。
-
结构体具备着面向对象思想中封装的特性,但是它不具备继承和多态的特性,因此大大减少了它的使用频率。
-
由于结构体不具备继承的特性,所以它不能够使用protected保护访问修饰符。
-
结构体可以继承接口,因为接口是行为的抽象
如何选择结构体和类
- 想要用继承和多态时,直接淘汰结构体,比如玩家、怪物等等
- 对象数据集合时,优先考虑结构体,比如位置、坐标等等
- 从值类型和引用类型赋值时的区别上去考虑,比如经常被赋值传递的对像,并且改变赋值对象,原对象不想跟着变化时,就用结构体。
如坐标、向量、旋转……
细节区别
- 结构体是值类型,类是引用类型
- 结构体存在栈中,类存在堆中
- 结构体成员不能使用protectedi访问修饰符,而类可以
- 结构体成员变量申明不能指定初始值,而类可以
- 结构体不能申明无参的构造函数,而类可以
- 结构体申明有参构造函数后,无参构造不会被顶掉
- 结构体不能申明析构函数,而类可以
- 结构体不能被继承,而类可以
- 结构体需要在构造函数中初始化所有成员变量,而类随意
- 结构体不能被静态static修饰(不存在静态结构体),而类可以
- 结构体不能在自己内部申明和自已一样的结构体变量,而类可以
抽象类和接口的区别✔
相同点
- 都可以继承
- 都不能直接实例化
- 都可以包含方法
- 子类必须实现未实现的方法
- 都遵循里氏替換原则
区別
- 抽象类中可以有构造函数;接口中不能
- 抽象类只能被单一继承;接囗可以被继承多个
- 抽象类中可以有成员变量;接口中不能
- 抽象类中可以申明成员方法虚方法,抽象方法,静态方法;接口中只能申明没有实现的抽象方法
- 抽象类方法可以使用坊问修饰符;接口中建议不与,默认public
如何选择抽象类和接口
- 表示对象的用抽象类表示行为拓展的用接口
- 不同対象拥有的共同行为,往往可以使用接口来实现
★ 举个例子
动物是1类对象,自然会选择抽象类 👉 而飞翔是1个行为,我们自然会选择接口。
最后
本文到这里就结束了,大佬们的支持是我持续更新的最大动力,希望这篇文章能帮到大家💪相关专栏连接🔗
下篇文章再见ヾ( ̄▽ ̄)ByeBye