- A+
C#核心
面向对象--封装
用程序来抽象现实世界,(万物皆对象)来编程实现功能。
三大特性:封装、继承、多态。
类与对象
声明位置:namespace
中
样式:class 类名{}
命名:帕斯卡命名法(首字母大写)
实例化对象:根据类来新建一个对象。Person p=new Person();
成员变量
- 声明在类语句块中
- 用来描述对象的特征
- 可以是任意变量类型
- 数量不做限制
- 是否赋值根据需求决定
enum E_SexType { Man, Woman } struct Position{}//位置结构体 class Pet{}//宠物类 //类中的成员变量 class Person { public string name="TonyChang";//区别于结构体--可以默认赋初始值 public int age=21; public E_SexType sex; public Person bestFriend;//区别于结构体---类中可以有同类的成员类型(本质是因为类属于引用类型,但不可以实例化,防止反复new,陷入死循环) public Position pos; public Pet pet; }
成员类型的默认值:
值类型的:数字的为0,bool
类型的false
引用类型:null
查看(int类型)默认值:default(int)
补充:class属于引用类型,其中的值类型也放置在堆中。
成员方法
- 声明在类语句块中
- 用来描述对象行为
- 其返回值参数不做限制
- 数量不做限制
- 帕斯卡命名法(首字母大写)
成员方法只有在实例化之后才可以使用调用。具体的一个对象的行为(方法),必须具体的对象调用。
//成员方法 class Person { public string name; public int age; public void Speak() { Console.WriteLine("你好!"); } } //成员方法的使用 Person p=new Person; p.Speak();
构造函数和析构函数
默认有一个无参构造函数,而类中可以允许自己声明无参构造函数,而结构体不行。
一旦有自定义的构造函数,默认的无参构造函数则失效!
构造函数:
- public修饰
- 无返回值,名字和类名相同
class Person { public string name; public int age; //构造函数 public Person() { name="TonyChang"; age=21; } //此时先调用age参数的构造函数 然后再调用两个参数的构造函数 public Person(string name,int age):this(age) { this.name=name; this.age=age; } public Person(string name) { this.name=name; } public Person(int age) { this.age=age; } }
特殊的构造函数,在调用该函数之前先调用this的无参构造函数。
public Person(int age):this()
{
this.age=age;
}
析构函数:
由于C#中有自动的垃圾回收机制,一般不使用析构函数。
析构函数是当垃圾真正被回收时候才会调用。
~Person(){}
//析构函数
成员属性:
用于保护成员变量,为成员属性的获取和赋值添加逻辑处理。
//成员属性 帕斯卡命名法 class Person { private string name; public string Name { get{ return name; } set{ name=value; } } private int age; public int Age { //不可以获得年龄(或者删除set设置则可表明也无法获取age) private get=>age; //可以设置年龄 set { age=value; } } //额外: //自动成员属性 (对于没有特殊需要的成员) public float Height { get; set; } }
索引器
可以让对象像数组一样通过索引来访问其中的元素。
注意:结构体中也支持索引器。
//索引器 class Person { private string name; private int age; private Person[] friends; private int[,] arry; //索引器 public Person this[int index] { get { return friends[index]; } set { //此处可以写一些控制逻辑 friends[index]=value; } } //索引器的重载 public int this[int i,int j] { get { return array[i,j]; } set { array[i,j]=value; } } } //使用 Person p=new Person(); p[0]=new Person();//可以像数组一样进行访问
静态成员
static
修饰的成员变量/方法为静态成员。
静态成员归属为类,即不用初始化就可以使用的类的成员。
静态成员函数中不可以使用非静态成员,非静态成员函数中可以使用静态成员函数。
本质:在程序运行开始时,检查到静态成员,会再特定的区域为其开辟内存空间来存储。所以说静态成员与程序共生死。因为生命周期不同,所以静态成员函数中不可以使用非静态成员。
使用:全局性,稳定不变的常量。例如固定的数值 Π,重力加速度g等包括固定的计算方法,可以供全局成员来访问使用。但是静态过多会占用内存,引发GC。
常量与静态成员
相同点:都可以通过类名点来使用
不同点:
const
必须初始化,不能修改,而static可以const
只能修饰变量,而static可以修饰很多const
一定是写在访问修饰符的后面,static则无此要求
静态类与静态构造函数
用 static修饰的类为静态类,往往来作为工具类。例如System中的Console类。只能包含静态成员,不能实例化。
静态构造函数 :在静态构造函数中初始化静态成员变量。
- 静态类和普通类中均可以有
- 不能使用访问修饰符
- 不能有参数
- 只会自动调用一次
//静态构造函数 static class Test { public static int testInt=100; public static float testFloat=20.5f; static Test() { //静态构造函数 Console.WriteLine("自动调用了!"); } } class NormalTest { public static int i=5; //首次使用静态成员时候 自动调用一次 //静态成员函数 static NormalTest() { Console.WriteLine("静态构造函数"); } public NormalTest() { Console.WriteLine("非静态成员函数"); } }
拓展方法
拓展方法为现有的非静态变量类型添加新方法。
作用:
- 提升程序的拓展性
- 不需要再对对象中重新写方法
- 不需要继承来添加方法
- 为别人封装的类型添加额外的方法
特点:
- 一定写在静态类中
- 一定是个静态函数
- 第一个参数为拓展目标(为谁而拓展)
- 第一个参数用this修饰
//拓展方法 static class expendTool { //为int添加拓展方法 public static void SpeakValue(this int value) { Console.WriteLine("这是int的拓展方法,int的数值为{0}",value); } //为string拓展方法 public static void SpeakStringInfo(this string str,string name,string info) { Console.WriteLine("这是string的拓展方法,string的数值为{0},该拓展方法由{1}编写,拓展内容为{2}",str,name,info); } } class Program { static void Main(string[] args) { int i = 5; i.SpeakValue(); string ss = "原始字符串"; ss.SpeakStringInfo("TonyChang", "附加字符串"); } }
注意:如果拓展方法名称与自身现有的方法名称相同,则只会调用自身的方法,不会调用拓展的方法。
运算符重载
关键字 operator
特点:1. 一定是一个公共的静态成员方法
- 返回值写在operator前
- 逻辑处理自定义
作用:可以使自定义的数据类型来完成后相同意义的运算。
注意:
- 条件运算符要成对实现(有>必须有<)
- 一个符号可以有多个重载
- 不能使用ref与out
//运算符重载 class Point { public int x; public int y; public Point(int x,int y) { this.x=x; this.y=y; } //重载+运算符 //参数列表中必须要有自己的类别出现 public static Point operator +(Point p1,Point p2) { Point sum=new Point(); sum.x=p1.x+p2.x; sum.y=p1.y+p2.y; return sum; } } class Program { static void Main(string[] args) { Point p1=new Point(1,1); Point p2=new Point(2,2); Point p3=P1+p2; Console.WriteLine(p3.x); } }
补充:大部运算符可以重载,逻辑运算符中只可以允许重载 逻辑非!
不能重载的运算符有:
&& || 索引符[] 强制转换符号() 特殊运算符 点. 三目运算符?:
*内部类和分部类(了解)
//内部类 class Person { public class Body { class Arm { } } } class Program { static void Main(string[] args) { Person.Body body=new Person.Body(); } } //分布类 //分布类可以分布在不同的脚本文件中 //分布类的访问修饰符要一致 partial class Student { public string name; public bool sex; partial void Speak(); } partial class Student { public int age; public string stuId; partial void Speak() { Console.WriteLine("分布方法的具体实现"); } }
垃圾回收机制
垃圾回收,英文简称GC(Garbage Collector)
垃圾回收过程:遍历堆(Heap)上的动态分配的所有对象
通过识别它们是否被引用来确定其是否是垃圾。垃圾是指没有引用所指引的对象、变量,需要被回收释放掉占用的内存空间。
垃圾回收算法:
引用计数、标记清除、标记整理、复制集合。
注意:垃圾回收只回收heap堆上的 栈中的内存由系统管理
回收机制: 三代内存
0代内存 1代内存 2代内存
-
每一代内存满掉之后便会清理垃圾
-
高代连锁:1代清理会连带0代清理,2代清理连带0代和1代
-
清理完垃圾之后,非垃圾内容搬迁到下一代中(0代将非垃圾转移到1代内存,
1代内存将非垃圾转移到2代内存)所以2代内存存储的较为老的对象实例,还包括大的对象
一般是85kb以上的对象
-
0代1代的读取速度要高于1代,分配内存位置优先0代>1代>2代
手动GC:
GC.Collect();
一般在场景加载时候进行GC。
面向对象--继承
继承者(子类)继承父类(基类、超类)的特性,同时也可以有自己独特的方法性质。
只能单继承。子类只能由一个父类。
继承特性:单根性、传递性。
//继承 //老师类 class Teacher { //姓名 public string name; //职工号 protected int number; //介绍名字 public void SpeakName() { number = 10; Console.WriteLine(name); } } //教学老师继承老师类 class TeachingTeacher : Teacher { //科目 public string subject; //介绍科目 public void SpeakSubject() { number = 11; Console.WriteLine(subject + "老师"); } } //语文老师继承教学老师类 class ChineseTeacher:TeachingTeacher { public void Skill() { Console.WriteLine("一行白鹭上青天"); } } class Program { static void Main(string[] args) { TeachingTeacher tt = new TeachingTeacher(); tt.name = "汪老师"; //tt.number = 1; tt.SpeakName(); tt.subject = "Unity"; tt.SpeakSubject(); ChineseTeacher ct = new ChineseTeacher(); ct.name = "张老师"; //ct.number = 2; ct.subject = "语文"; ct.SpeakName(); ct.SpeakSubject(); ct.Skill(); } }
里氏替换原则
父类容器装在子类对象。(任何父类出现的地方,子类都可以替代)
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) { //里氏替换原则 Gameobjet player=new Player(); Gameobjet monster=new Monster(); Gameobjet boss=new Boss(); //is 和 as if(player is Player) { (player as Player).PlayerAtk(); } } }
is和as
is是判断一个对象是否为指定类型对象,返回值为true则为真,不是则为false
as用来将一个对象转换为指定类型对象,返回值为指定类型对象,若转换失败则返回null
继承中的构造函数
子类构造函数调用之前,先执行父类的构造函数。(爷爷-->父亲-->子类)
所以要保证父类的构造函数(尤其为无参构造函数)
- 保证父类的无参构造函数
- 通过base调用指定的有参构造函数
//继承中的构造函数 class Father { //父类的无参构造函数很重要! public Father() { } public Father(int i) { Console.WriteLine("Father的有参构造"); } } class Son:Father { public Son(int i):base(i) { //构造函数 } }
万物之父--装箱和拆箱
object 是所有类型的基类,
作用:
- 可以利用里氏替换原则,用父类装子类
- 可以用来表示不确定类型,作为函数的参数类型
装箱:
用object存值类型。本该在栈中数值转移到堆上
拆箱
将object转换为值类型,将堆上的值类型转移到栈上(配合 is和as 使用)
优点:统一对象类型(里氏替换原则),方便对不同类型对象数值的管理
缺点:消耗性能,
//装箱拆箱 int i=5; object obj=i;//装箱 i=(int)obj;//拆箱
*密封类(了解)
使用sealed关键字修饰的类,不可以被派生。(结扎了!)
面向对象--多态
V: virtual(虚函数)
O: override(重写)
B: base(父类)
让继承同一父类的子类们在执行相同方法有不同的表现与状态。
就是说,继承是一脉相承父类的品质,而多态是由自己的个性,尽管做的和父辈的事情相同。
解决的问题:
class Father { public void SpeakName() { Console.WriteLine("Father的方法"); } } class Son:Father { public new void SpeakName() { Console.WriteLine("Son的方法"); } } class Program { static void Main(string[] args) { #region 解决的问题 Father f = new Son(); f.SpeakName();//调用的是父亲的方法 (f as Son).SpeakName();//调用的是儿子的方法 #endregion } }
使用多态来保证(继承类)一个类方法的独立性
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("玩家对象进行攻击"); } }
抽象类与抽象方法
抽象类不可以被实例化
abstract class Thing{
public string name;
}
抽象方法:没有方法体的纯虚方法,继承的子类必须实现纯虚方法。(子类必须重写该方法,子类的子类不必强制实行,但也可以继续重写。)
抽象方法与virtual(虚函数)方法区别:
- 抽象方法只能在抽象类中出现,没有方法体,子类必须重写实现
- 虚函数则有方法体,可在普通类中出现,由子类选择性的实现
abstract class Fruits { public string name; //抽象方法 是一定不能有函数体的 public abstract void Bad(); public virtual void Test() { //可以选择是否写逻辑 } } class Apple : Fruits { public override void Bad() { } //虚方法是可以由我们子类选择性来实现的 //抽象方法必须要实现 }
接口(重要)
概念:接口是行为的抽象规范
关键字:interface
声明规范:
- 不能包含成员变量
- 只能包含方法、属性、索引器、事件
- 成员不能被实现
- 成员可以不用写访问修饰符,默认为public,不能是private
- 接口不能继承类,但是可以继承另一个接口
使用规范:
- 类可以继承多个接口
- 类继承接口,必须实现接口中所有成员
特点:
- 和类的声明相似
- 接口就是用来继承的
- 接口不能被实例化,可以作为容器存储对象(里氏替换原则,父类装子类)
接口是抽象行为的”基类“
//接口的声明 //命名规范 I+帕斯卡命名法 interface IFly { void Fly();//方法 string Name//属性 { get; set; } int this[int index]//索引器 { get; set; } event Action doSomthing;//事件委托 }
接口的使用---类的继承
- 一个类只能继承一个基类,但是可以继承多个接口
- 继承接口之后,必须实现其中的内容
//接口的使用 class Animal { } class Person:Animal,IFly { //实现接口方法也可以加virtual来实现 public virtual void Fly() { } public string Name { set; get; } public int this[int index] { get { return 0; } set { } } public event Action doSomething; }
接口的使用---接口的继承
接口继承基类接口之后,不需要实现接口中的内容(抽象继承抽象,还是抽象)
等到最后类来具体实现
//接口继承接口 interface IWalk { void Walk(); } interface IMove:IFly,IMove { } //必须实现所有相关的 //继承来的抽象内容(接口,接口的父接口中的成员) class Test:IMove { public int this[int index] { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public string Name { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } public event Action doSomthing; public void Fly() { throw new NotImplementedException(); } public void Walk() { throw new NotImplementedException(); } }
显示实现接口
//接口的使用--当作容器 父类装子类 interface IAtk { void Atk(); } interface ISuperAtk { void Atk(); } //显示实现接口 class Player : IAtk, ISuperAtk { //遇到相同方法名字 //显示实现接口 就是用 接口名.行为名 去实现 void IAtk.Atk() { } void ISuperAtk.Atk() { } public void Atk() { } } class Progarm { static void Main(string[] args) { IFly f = new Person(); //里氏替换原则 IMove im = new Test(); IFly ifly = new Test(); IWalk iw = new Test(); IAtk ia = new Player(); ISuperAtk isa = new Player(); ia.Atk(); isa.Atk(); Player p = new Player(); (p as IAtk).Atk();//IAtk的 (p as ISuperAtk).Atk();//ISuperAtk的Atk p.Atk();//自己的Atk } }
*密封方法(了解)
sealed 修饰的重写方法,子类不会被重写方法
其它关联知识点
命名空间namespace
- 命名空间是个工具包,用来管理类
- 不同命名空间中允许由同名类
- 不同命名空间相互使用时,using引用命名空间 或者指明出处
- 命名空间可以包裹命名空间
万物之父Object中的方法
string
-
string 本质是char[]数组 可以有
string ss="Tony",char[0]='T'
-
字符串的拼接
-
正向查找字符的位置 IndexOf()
-
反向查找字符串的位置 LastIndexOf()
-
移除指定位置后的字符 Remove(index)//注意接受返回值
-
字符串的替换
-
大小写转换
-
字符串的截取
-
字符串的切割 str.Split(',');按照,切割
StringBuilder
字符串频繁拼接使用StringBuilder,不会再次频繁的创建新的对象,减少垃圾产生。
容量问题:初始时候本身有一定容量,在容量允许范围内,直接存储。
超过容量之后,会以2倍大小扩容。相对于string每一次更改便会新建对象,可减少垃圾产生。
//StringBuilder StringBuilder str=new StringBuilder("My Name is Tony"); //获取容量 str.Capacity; //增加 str.Append("Chang"); str.AppendFormat("{0}{1}",123,456); //插入 str.Insert(0,"Hello"); //删除 str.Remove(0,10); //清空 str.Clear(); //查 str[1]; //替换 str.Replace("Name"."name"); //重新赋值 str.Clear(); str.Append("Hello World"); //equals if(str.Equals("123456")) { }
String 还是StringBuilder?
String的方法种类较多,使用更加方便和灵活,但性能上不如StringBuilder,不如StringBuilder产生垃圾少
需要频繁修改的字符串选用StringBuilder更好些。
如何优化内存?
- 节约内存
- 少new对象 少产生垃圾
- 合理使用static
- 合理使用String与StringBuilder
- 减少GC产生
结构体与类的区别
- 存储位置 结构体是值类型,存储在栈中 类是引用类型,存储在堆中
- 结构体中的成员变量不可以赋初始值,类中可以
- 结构体具备封装特性但是不具备继承和多态 而类都具有
- 结构体不具备继承特性,所以不可以使用protected保护修饰符修饰
- 结构体声明有参构造之后,无参构造不会被顶掉
- 结构体不能声明析构函数,而类可以
- 结构体需要在构造函数中初始化所有成员变量,而类随意
- 结构体不能被static修饰,不存在静态的结构体,而类随意
- 结构体不能在内部声明与自己一样的结构体变量(会无限创建...栈溢出),类可以(因为是引用)
- 结构体可以继承接口(不可以继承类、结构体)
如何选择结构体和类:
- 如果想要用继承和多态时,直接淘汰结构体,如玩家、怪物
- 存储的对象是数据的集合时候,优先考虑结构体,如向量、坐标等
- 从本质上考虑,如果经常要改变赋值对象,原有的数值不想跟着改变的,选用结构体(值类型,复制拷贝,不影响本身),如坐标、向量、旋转角等
抽象类与接口的区别
相同点:
- 都可以被继承
- 都不可以直接实例化
- 都可以包含方法的声明
- 子类必须实现未实现的方法
- 都遵循里氏替换原则(父类装子类)
区别:
- 抽象类中可以有构造函数,而接口不可以
- 抽象类只能被单一继承,接口可以被继承多个
- 抽象类中可以有成员变量,接口中不能
- 抽象类中可以有声明成员方法,虚方法,抽象方法,静态方法而接口中只能声明没有实现的抽象方法
- 抽象类方法可以使用访问修饰符;接口中建议不写,默认为public
如何选择抽象类与接口
表示对象的选用抽象类,表示行为拓展的用接口。不同对象的相同行为,我们可以抽象出行为,用接口来实现。
面向对象的七大原则
总目标:高内聚、低耦合
减少类内部对其它类的调用,减少模块与模块之间的交互复杂度。
- 单一职责原则(一个类专注于一个功能)
- 里氏替换原则(父类可以装子类)
- 开闭原则(对拓展开放,对修改关闭,要保持开放和扩展,减少修改)
- 依赖倒转原则(依赖于抽象,不依赖于抽象具体)
- 迪米特原则(最少知识原则,不要和陌生人说话)
- 接口隔离原则(一个接口不应该提供过多的功能,)
- 合成复用原则(尽量使用组合复用实现功能,减少继承高耦合行为)