封装
构造函数
基本概念:
在实例化对象时,会调用的用于初始化的函数,如果不写,则默认存在一个无参构造函数。
写法:
1、没有返回值
2、函数名和类名必须相同
3、没有特殊需求时,一般是 public
4、构造函数可以被重载
5、this代表当前调用该函数的对象自己
特殊写法
可以通过this 重用构造函数代码
格式:访问修饰符 构造函数名(参数列表):this(参数1,参数2……)
class Person
{
public string name;
public int age;
public Person()//无参构造函数
{
name = "chx";
age = 23;
}
//特殊写法
public Person(int age,string name):this()//先调用this这个无参构造函数,也可以调用含参构造函数
{
//this代表当前调用该函数的对象自己
this.age = age;
this.name = name;
}
}
注意:
如果不自己实现无参构造函数而实现了有参构造函数,会失去默认的无参构造函数
析构函数
1、当引用类型的堆内存被回收时,会调用该函数
2、对于需要手动管理内存的语言(如c++),需要在析构函数中做一些内存回收处理
3、但c#中存在自动垃圾回收机制GC,所以在c#中几乎不会怎么使用析构函数,除非你想在某一个对象被垃圾回收时,做一些特殊处理
基本语法
~类名()
{
}
垃圾回收机制(GC,Garbage Collector)
1、垃圾回收的过程是在遍历堆上动态分配的所有对象,通过识别他们是否被引用来确定哪些对象是垃圾,哪些对象仍然要被使用。所谓的垃圾就是没有被任何变量,对象引用的内容,垃圾就需要被回收释放。
2、GC只负责堆内存的垃圾回收,引用类型都是存在堆中的,所以它的分配和释放都通过垃圾回收机制来管理。
3、栈上的内存是由系统自动管理的,值类型在栈中分配内存的,他们有自己的生命周期,不用对他们进行管理,会自动分配和释放
回收算法
1、引用计数(Reference Counting)
2、标记清除(Mark Sweep)
3、标记整理(Mark Compact)
4、复制集合(Copy Collection)
C# 中内存回收原理
代的概念:代是垃圾回收机制使用的一种算法(分代算法),0代内存,1代内存,2代内存。新分配的对象会被配置在第0代内存中,当0代内存满时,会进行垃圾回收以释放内存。
自动回收步骤
1、标记对象,从根(静态字段,方法参数)开始检查引用对象,标记后为可达对象,未标记为不可达对象,不可达对象则被认为是垃圾。
2、搬迁对象压缩堆(挂起执行托管代码线程):
①释放未标记的对象
②搬迁可达对象
③修改引用地址
注意
1、大对象总被认为是第二代内存,目的是减少性能损耗,提高性能
2、不会对大对象进行搬迁压缩,85000字节以上的对象为大对象
手动回收:
1、一般情况下不会频繁调用
2、都是在Loeding过场景时才调用
GC.Collect();
成员属性
基本概念
1、用于保护成员变量
2、为成员属性的获取和赋值添加逻辑处理
3、打破public,private,protected的局限性:
public–内外访问
private–内部访问
protected–内部和子类访问
①成员属性可以让成员变量在外部
②只能获取不能修改
③只能修改不能获取
基本语法
访问修饰符 属性类型 属性名
{
get
{
//可以在返回之前添加一些逻辑规则
//这个属性可以获取的内容
return 变量名;
}
set
{
//可以在设置之前添加一些逻辑规则
//value关键字 用于表示外部传入的值,即当调用属性被赋值时所得到的值即为value
变量名 = value;
}
}
注意:
get和set前可以加访问修饰符
1、默认不加,会使用属性声明时的访问权限
2、加的访问修饰符要低于属性的访问权限
3、不能让get和set的访问权限都低于属性的权限
4、get和set可以只有一个,只有一个时,没必要在前面加访问修饰符。一般情况下,只会出现只有get的情况,基本不会出现只有set的情况。
5、自动属性:如果类中有一个特征是只希望外部能得不能改的,又没什么特殊处理,那么可以直接使用自动属性,例如:
public float Height
{
get;
set;
}
索引器
基本概念
让对象可以像数组一样通过索引访问其中元素,使程序看起来更直观,更容易编写
①索引器可以写逻辑
②索引器可以重载
索引器语法
访问修饰符 返回值 this[参数类型 参数名,参数类型 参数名……]
{
get{};
set{};
}
例如:
private Person[] friends;
public Person this[int index]
{
get
{
return friends[index];
}
set
{
friends[index] = value;
}
}
静态成员
基本概念
①静态关键字 static,用static修饰的成员变量,方法,属性等,成为静态成员。
②静态成员的特点是:直接用类名点出使用
特点
1、程序开始运行时,就会分配内存空间,所以我们能直接使用
2、静态成员和程序同生共死,只要使用静态成员,知道程序结束时内存空间才会被结束释放
3、因此一个静态成员就会有自己唯一的一个“内存小房间”,这使得静态成员就有了唯一性,在任何地方使用的都是用的小房间里的内容,改变了静态成员也就是改变了小房间里的内容
4、静态函数中不能直接使用非静态成员,必须将对象实例化出来后,才能点出来使用
5、非静态函数可以使用静态成员
作用
1、静态变量
①常用唯一变量的声明
②方便别人获取的对象申明
2、静态方法
常用的唯一的方法声明,如:相同规则的数学计算相关函数
常量和静态变量
常量(const)可以理解为特殊的static(静态)
相同点
都可以通过类名点出使用
不同点
1、const必须初始化,不能修改,static则没有
2、const只能修饰变量,static可以修饰很多
3、const一定是写在访问修饰符后面的,static没有
静态类
概念
用static修饰的类
特点
只能包含静态成员,不能被实例化
作用
1、将常用的静态成员写在静态类中,方便使用
2、静态类不能被实例化,更能体现工具类的唯一性
静态构造函数
概念
在构造函数加上static修饰
特点
1、静态类和普通类都可以有
2、不能使用访问修饰符
3、不能有参数
4、只会自动调用一次
作用
在静态构造函数中初始化静态变量
使用
静态类中的静态构造函数
拓展方法
概念
为现有非静态变量类型添加新方法
作用
1、提升程序拓展性
2、不需要在对象中重新写方法
3、不需要继承来添加方法
4、为别人封装的类型写额外的方法
特点
1、一定是写在静态类中
2、一定是个静态函数
3、第一个参数为拓展目标
4、第一个参数用this修饰
基本语法
访问修饰符 static 返回值 函数名(this 拓展类名 参数名,参数类型 参数名,参数类型 参数名……)
运算符重载
基本概念
让自定义类和结构体,能够使用运算符。
1、一定是一个公共的静态方法
2、返回值写在operator前
3、逻辑处理自定义
基本语法
public static 返回类型 operator 运算符(参数列表)
作用
让自定义类和结构对象可以进行运算
注意
1、条件运算符需要成对实现(如实现>重载,则必须实现<重载)
2、一个符号可以多个重载
3、不能使用ref和out
不可重载的运算符
1、逻辑与(&&) 逻辑或(||)
2、索引符 [ ]
3、强转运算符()
4、特殊运算符:点. 、三目运算符?:赋值符号=
内部类
在一个类中再声明一个类,使用时要用包裹者点出自己,亲密关系的变现,访问修饰符作用很大
分部类
把一个类分成几部分声明(partial),分部描述一个类,增加程序的拓展性。
1、分部类可以写在多个脚本文件中
2、分部类的访问修饰符要一致
3、分部类中不能有重复成员
分部方法
将方法的声明和实现分离
1、不能加访问修饰符,默认私有
2、只能在分部类中声明
3、返回值只能是void
4、可以有参数但不用out关键字
继承
基本概念
- 一个类A继承一个类B,类A将会继承类B的所有成员,A类将拥有B类的所有特征和行为
- 被继承的类,称为父类,基类,超类
- 继承的类,称为子类,派生类
- 子类可以有自己的特征和行为
特点
1、单根性:子类只能有一个父类
2、传递性:子类可以间接继承父类的父类
基本语法
class 类名:被继承的类名
{
}
注意
1、访问修饰符:使用protected使得内部和子类可以使用
2、子类和父类的同名成员:c#中允许存在和父类同名的成员,但极不建议使用
里氏替换原则
里氏替换原则是面向对象七大原则中最重要的原则,任何父类出现的地方,子类都可以替代,在语法表现上,父类容器装子类对象,因为子类对象包含了父类的所有内容,方便进行对象存储和管理。
基本实现
namespace learn
{
class GameObject
{
}
class Player:GameObject
{
public void PlayerAtk()
{
Console.WriteLine("玩家攻击");
}
}
class Monster:GameObject
{
public void MonsterAtk()
{
Console.WriteLine("怪物攻击");
}
}
class Boss : GameObject
{
public void BossAtk()
{
Console.WriteLine("Boss攻击");
}
}
class Program
{
static void Main(string[] args)
{
//用父类容器装载子类对象
GameObject player = new Player();
GameObject monster = new Monster();
GameObject boss = new Boss();
GameObject[] objects = new GameObject[] {new Player(),new Monster(),new Boss()};
}
}
}
is和as
1、is:判断一个对象是否是指定类对象
返回值:bool
if(player is Player)
{
}
2、as:将一个对象转换为指定类对象
返回值:指定类型对象
成功返回指定类对象,失败返回null
Player p = player as Player;//成功
Player p = monster as Player;//失败
继承中的构造函数
基本概念
当声明一个子类对象时,先执行父类的构造函数,再执行子类的构造函数
注意
1、父类的无参构造很重要
2、子类可以通过base关键字,代表父类调用父类构造
通过base调用指定父类构造
namespace learn
{
class Father
{
public Father(int i)
{
Console.WriteLine("Father构造");
}
}
class Son:Father
{
//通过base调用指定父类构造
public Son(int i) : base(i)
{
Console.WriteLine("Son的一个参数的构造");
}
}
万物之父(object)
object 是所有类型的基类,它是一个类(引用类型),可以利用里氏替换原则,用object容器装载所有对象。可以用来表示不确定类型,作为函数参数类型。
1、利用里氏替换原则中is和as转换类型
object o = new Son();
if(o is Son)
{
(o as Son).Speak();
}
2、利用强转转换类型
object o2 = 1f;//值类型
float fl = (float)o2;//强制转换
装箱拆箱
装箱
1、用object存值类型(装箱)
2、把值类型用引用类型存储
3、栈内存会迁移到堆内存中
拆箱
1、把object转为值类型(拆箱)
2、把引用类型存储的值类型取出来
3、堆内存会迁移到栈内存中
优劣
好处:不确定类型时可以方便参数的存储和传递
坏处:存在内存迁移,增加性能消耗
密封类
是使用sealed密封关键字修饰的类,让类无法再被继承
在面向对象程序的设计中,密封类的主要作用就是不允许最底层子类被继承,可以保证程序的规范性、安全性,以后制作复杂系统或者程序框架时,便能慢慢体会到密封的作用。
多态
基本概念(virtual,override,base)
按字面的意思就是“多种状态”,让继承同一父类的子类们在执行相同方法时有不同的表现(状态),让同一个对象有唯一行为的特征。
实现
namespace learn
{
class GameObject
{
public string name;
public GameObject(string name)
{
this.name = name;
}
//虚函数
public virtual void Atk()
{
Console.WriteLine("游戏对象进行攻击");
}
}
class Player:GameObject
{
public Player(string name):base(name)
{
}
//重写虚函数
public override void Atk()
{
//base的作用
//代表父类可以通过base来保留父类的行为
base.Atk();
Console.WriteLine("玩家对象进行攻击");
}
}
class Program
{
static void Main(string[] args)
{
GameObject p = new Player("chx");//父类容器装载子类对象
p.Atk();//父类调用虚函数,虚函数被子类重写,但由于base的作用,父类的功能也会被保留
}
}
}
抽象类
被抽象关键字abstract修饰的类
特点
1、不能被实例化的类
2、可以包含抽象方法
3、继承抽象类必须重写其抽象方法
abstract class Thing
{
public string name;
}
class water:Thing { }
抽象函数(纯虚方法)
用abstract关键字修饰的方法
特点
1、只能在抽象类中声明
2、没有方法体
3、不能是私有的
4、继承后必须实现,用override重写
abstract class Fruits
{
public string name;
public abstract void Bad();//无方法体
}
class Apple : Fruits
{
public override void Bad() { }//被继承时必须被重写
}
接口
基本概念
接口(interface)时行为的抽象规范,它也是一种自定义类型
接口声明的规范
1、不包含成员变量
2、只包含方法、属性、索引器、事件
3、成员不能被实现
4、成员可以不用写访问修饰符,不能是私有的(此处不写访问修饰符,默认public)
5、接口不能继承类,但是可以继承另一个接口
接口时抽象行为的“基类”
命名规范:帕斯卡前面加个I
interface 接口名
{
}
接口的使用规范
1、类可以继承多个接口
2、类继承接口后,必须实现接口中所有成员
3、接口继承接口后,不需要实现
namespace learn
{
interface IFly
{
void Fly();
}
class Animal { }
//类可以继承一个类,n个接口
class Person : Animal, IFly
{
public void Fly() { }//这里要加public,若原先是protected,则需要显示实现接口
}
class Program
{
static void Main(string[] args)
{
//里氏替换原则
IFly fly = new Person();
}
}
}
显式使用接口
namespace learn
{
interface IAtk
{
void Atk() { }
}
interface ISuperAtk
{
void Atk() { }
}
class Player : IAtk, ISuperAtk
{
//显式实现接口:接口名.行为名
void IAtk.Atk() { }
void ISuperAtk.Atk() { }
}
class Program
{
static void Main(string[] args)
{
Player p = new Player();
(p as IAtk).Atk();
(p as ISuperAtk).Atk();
}
}
}
特点
1、它和类的声明类似
2、接口是用来继承的
3、接口不能被实例化,但是可以作为容器存储对象
密封方法
用密封关键字sealed修饰重写函数,让虚方法或者抽象方法之后不能再被重写,和override一起出现
面向对象关联知识点
命名空间
基本概念
命名空间是用来组织和重用代码的,就像是一个工具包,类就像是一件一件的工具,都是申明再命名空间中的
1、不同命名空间中相互使用,需要引用命名空间或指明出处
using 命名空间名
或
命名空间名.类名
2、不同命名空间中允许有同名类
3、命名空间可以包裹命名空间
4、命名空间中的类默认为internal(只能在该程序集中使用)
基本语法
namespace 命名空间名
{
类
类
}
万物之父中的方法
object中的静态方法
Equals: 判断两个对象是否相等,最终的判断权交给左侧对象的Equals方法,不管值类型还是引用类型都会按照左侧对象Equals方法的规则来进行比较
public static bool Equals(Object? objA, Object? objB);
ReferenceEquals: 比较两个对象是否是相同的引用,主要是用来比较引用类型的对象。值类型对象返回值始终是false。
public static bool ReferenceEquals(Object? objA, Object? objB);
object中的成员方法
GetType: 该方法在反射相关知识点中是非常重要的方法,其主要作用就是获取对象运行时的类型Type,通过Type结合反射相关知识点可以做很多关于对象的操作
public Type GetType();
MemberwiseClone: 该方法用于获取对象的浅拷贝对象,口语化的意思是会返回一个新的对象,但是新对象中的引用变量会和老对象中一致。
protected Object MemberwiseClone();
object中的虚方法
Equals: 默认实现还是比较两者是否为同一个引用,即相当于ReferenceEquals,但是微软在所有值类型的基类System。ValueType中重写了该方法,用来比较值类型,我们也可以重写该方法,定义自己的比较相等的规则
public virtual bool Equals(Object? obj);
GetHashCode: 该方法是获取对象的哈希码(一种通过算法算出的,表示对象的唯一编码,不同对象哈希码可能一样,具体值根据哈希算法决定),我们可以通过重写该函数来自己定义对象的哈希码算法,正常情况下,我们使用的极少,基本不用。
public virtual int GetHashCode();
ToString: 该方法用于返回当前对象代表的字符串,我们可以重写它定义我们自己的对象转字符串规则,该方法非常常用。当我们调用打印方法时,默认使用的就是对象的ToString方法后打印出来的内容。
public virtual string? ToString();
string
1、字符串本质是char数组,转为char数组
str.ToCharArray();
2、字符串拼接
str = string.Format("{0}{1}",1,333);
3、正向查找字符位置
str = "我是chx";
int index = str.IndexOf("我");
4、反向查找指定字符串位置
str.LastIndexOf("我");
5、移除指定位置后的字符
//删除该位置上的元素
str = str.Remove(4);
//执行两个参数进行移除,开始位置 字符个数
str = str.Remove(1,1);
6、替换指定字符
str = str.Replace("chx","syx");//将1替换成2
7、大小写转换
str = str.ToUpper();
str = str.ToLower();
8、字符串截取
//获取从指定位置开始之后的字符串
str = str.Substring(2);
//开始位置 指定个数(需要自己判断是否越界)
str = str.Substring(2,3);
9、字符串切割
//以逗号分隔字符串进行切割
string[] strs = str.Split(',');
StringBuilder
C#提供的一个用于处理字符串的公共类,修改字符串而不创建新的对象,需要频繁修改和拼接的字符串可以使用它,可以提升性能,使用前需要引用命名空间。
初始化
StringBuilder str = new StringBuilder("chx");
容量
StringBuilder存在一个容量的问题,每次往里面增加时,会自动扩容。
获得容量:
str.Capacity
获得字符串长度:
str.Length
增删查改替换
增:
str.Append("abc");
str.AppendFormat("{0}{1}",100,999);
插入:
str.Insert(0,"123");
删:
str.Remove(0,10);//从0位置开始删除10个
清空:
str.Clear();
查:
str[1];
改:
str[1] = 'A';
替换:
str.Replace("1","陈");//将1改成“陈”
判断字符串是否相等:
str.Equals("123");
结构体和类的区别
1、存储空间上,结构体是值,类是引用,结构体存在栈上,类在堆上。
2、结构体具备面向对象思想中封装的特性,但是它不具备继承和多态的特性,因此大大减少了它的使用频率。由于结构体不具备继承的特性,所以它不能够使用protected保护访问修饰符。
3、结构体成员变量声明不能指定初始值,而类可以
4、结构体不能声明无参的构造函数,而类可以
5、结构体声明有参构造函数后,无参构造不会被顶掉
6、结构体不能声明析构函数,而类可以
7、结构体不能被继承,而类可以
8、结构体需要在构造函数中初始化所有成员变量,而类随意
9、结构体不能被静态static修饰(不存在静态结构体),而类可以
10、结构体不能在自己内部声明和自己一样的结构体变量,而类可以
结构体的特别之处
结构体可以继承接口,因为接口是行为的抽象
如何选择类和结构体
1、想要用继承和多态时,直接淘汰结构体,如玩家,怪物等。
2、对象是数据集合时,优先考虑结构体,如位置、坐标等。
3、从值类型和引用类型赋值时的区别上去考虑,如经常赋值传递的对象,并且改变赋值对象,原对象不想跟着变化时,就用结构体,如坐标、向量、旋转等。
抽象类和接口的区别
相同点
1、都可以被继承
2、都不能直接实例化
3、都可以包含方法声明
4、子类必须实现未实现的方法
5、都遵循里氏替换原则
区别
1、抽象类中可以有构造函数;接口中不能
2、抽象类只能别单一继承;接口可以被继承多个
3、抽象类中可以有成员变量;接口中不能
4、抽象类中可以声明成员方法、虚方法、抽象方法、静态方法;接口中只能声明没有实现的抽象方法
5、抽象类方法可以使用访问修饰符;接口中建议不写,默认public
如何选择
表示对象的用抽象类,表示行为拓展的用接口
不同对象拥有的共同行为,我们往往可以使用接口来实现。例如:动物是一类对象,我们自然会选择抽象类;而飞翔是一个行为,我们自然会选择接口。