@作者 : SYFStrive
@博客首页 : HomePage
: C#面向对象
:个人社区(欢迎大佬们加入) :社区链接
:觉得文章不错可以点点关注 :UnityC#编程干货
提示:以下是本篇文章正文内容
① 单一职责原则(Single Responsibility Principle)
单一职责原则又称单一功能原则,它规定一个类应该只有一个发生变化的原因。
② 里氏替换原则(Liskov Substitution Principle)
氏替换原则的意思是,所有基类在的地方,都可以换成子类,程序还可以正常运行。这个原则是与面向对象语言的继承特性密切相关的。
③ 依赖倒置原则(Dependence Inversion Principle)
程序要依赖于抽象接口,不要依赖于具体实现
④ 接口隔离原则(Interface Segregation Principle)
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。
⑤ 迪米特法则(Law Of Demeter)
迪米特法则又叫作最少知识原则,一个类对于其他类知道的越少越好,就是说一个对象应当对其他对象有尽可能少的了解,只和朋友通信,不和陌生人说话。
⑥ 开闭原则(Open Close Principle)
软件实现应该对扩展开放,对修改关闭,其含义是说一个软件应该通过扩展来实现变化,而不是通过修改已有的代码来实现变化的。
⑥ 组合/聚合复用原则(Composite/Aggregate Reuse Principle CARP)
组合/聚合复用原则是通过将已有的对象纳入新对象中,作为新对象的成员对象来实现的,新对象可以调用已有对象的功能,从而达到复用。
class 类名
{
//特征 成员变量
//行为 成员方法
//保护特征 成员属性
//构造函数 和 析构函数
//索引器
//运算符重载
//静态成员
ヾ(@⌒ー⌒@)ノ……
}
// 引用类型
//类名 变量名; (栈指向堆地址值是空的没有分配堆内存)
//类名 变量名 = null; (栈指向堆地址值是空的没有分配堆内存)
//类名 变量名 = new 类名; (存在栈指向堆的内存空间使其在堆中新开了个房间(这时是空的))
// 使用new时(相当于开辟一个新的空间):相当于创建一个新对象
面向对象 new……;
class Person
{
public string name;
public int age;
public E_Sex sex;
public Person[] boyFriend; (类名一样的变量类型 初始值为空)
public Position Pos;(Position结构体)
注意:如果要声明一个和自己相同类型的成员变量时,不能对他进行实例化/或设置为空(会死循环!!)
……
}
class Person
{
public int a;
private int b;
protected int c;
}
检查默认值
class Person
{
//特征——成员变量
public bool sex;
public string nanme;
public float height;
public int age;
public Person[] friend;
///
/// 添加朋友扩容数组
///
/// 添加朋友P
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;
}
}
}
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代内存
代的概念:
在一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,会进行以下两步
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 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 friends.length-1){
//把最后的顶掉(自己写的 可能会丢朋友)
friends[friends.length-1]=value;
}
//赋值
friends[index] = value
}
}
}
使用如 :
Person p =new Person()
p[0] = p.name;
相关语法:访问修饰符 static 返回值 函数名(this 拓展方法 参数名 ,参数类型 参数名,参数类型,参数名…)
概念:为现有非静态,变量类型添加新方法
作用如
特点如
//为int拓展了一个成员方法
//成员方法 是需要 实例化对象后 才 能使用的
//value 代表 使用该方法的 实例化对象
0个引用
public static void SpeakValue(this int value)
{
//拓展的方法的逻辑
Console.WriteLine("int拓展的方法"+ value);
}
调用如下:
int i=10;
i.SpeakValue()
被继承的类
继承的类
特点
作用
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是所有类型的基类,它是一个类(引用类型)
作用如
使用方式如
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 : virtual(虚函数) override(重写) base(父类);
作用:其实多态的作用就是把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出更通用的程序。
主要目的如
同一父类的对象,执行相同行为(方法)有不同的表现
解决的问题
2.1 让同一个对象有唯一行为的特征
小总结如
多态按字面的意思就是“多种状态”
让继承同一父类的子类们 在执行相同方法时有不同的表现(状态)
代码如
abstract class Fruits
{
}
抽象函数又叫纯虚方法
关键字: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)的区别 如
小总结:搭框架使用
接口是行为的抽象规范也是一种自定义类型
关键字: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() {}
作用如
总结如
注意如
基本概念
代码如
namespace Mygame
{
class Gameobject
{
}
}
//命名空间可以分开写
namespace Mygame
{
//属于同一命名空间 可以正常继承
class Player:Gameobject
{
}
}
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;
Console.WriteLine(Object.Equals(1, 1)); //true
Test t = new Test();
Test t2 = new Test();
Console.WriteLine(object.Equals(t,t2)); false
该方法用于返回当前对象代表的字符串,我们可以重写它定义我们自己的对象转字符串规则,
该方法非常常用。当我们调用打印方法时,默认使用的就是对象的ToString方法后打印出来的内容。
修改ToString默认内容值如
class Test
{
public override string ToString()
{
return "Test类"
}
}
Test t = new Test();
Console.WriteLine(t.ToString);
//打印结果为"Test类"
//字符串本质是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}
解决多次修改string性能消耗问题
使用 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保护访问修饰符。
结构体可以继承接口,因为接口是行为的抽象
如坐标、向量、旋转……
✔
★ 举个例子
动物是1类对象,自然会选择抽象类 而飞翔是1个行为,我们自然会选择接口。
本文到这里就结束了,大佬们的支持是我持续更新的最大动力,希望这篇文章能帮到大家相关专栏连接
下篇文章再见ヾ( ̄▽ ̄)ByeBye