- 一、GC 回收机制
- 二、成员属性
- 三、索引器
- 四、静态成员
- 五、静态类和静态构造函数
- 六、拓展方法
- 七、运算符重载
- 七、内部类和分部类
- 八、继承
- 九、里氏替换原则
- 十、继承中的构造函数
- 十一、万物之父和装箱拆箱
- 十二、密封类
- 十三、多态 vob
- 十四、抽象类和抽象方法
- 十五、接口
- 十六、密封方法
- 十七、命名空间
- 十八、万物之父中的方法
- 十九、string
- 二十、StringBuilder
- 二十一、结构体和类的区别
- 二十二、抽象类和接口的区别
垃圾回收,英文简写 GC(Garbage Collector) 垃圾回收的过程是在遍历堆(Heap)上动态分配的所有对象,通过识别它们是否被引用来确定哪些对象是垃圾,哪些对象仍要被使用 所谓的垃圾就是没有被任何变量,对象引用的内容 垃圾就需要被回收释放
垃圾回收有很多种算法,比如: 引用计数(Reference Counting) 标记清除(Mark Sweep) 标记整理(Mark Compact) 复制集合(Copy Collection)
注意: GC只负责堆(Heap)内存的垃圾回收 引用类型都是存在堆(Heap)中的,所以它的分配和释放都通过垃圾回收机制来管理
栈(Stack)上的内存是由系统自动管理的 值类型在栈(Stack)中分配内存的,他们有自己的生命周期,不用对他们进行管理,会自动分配和释放
C# 中内存回收机制的大概原理 0代内存 1代内存 2代内存 代的概念: 代是垃圾回收机制使用的一种算法(分代算法) 新分配的对象都会被配置在第0代内存中 每次分配都可能会进行垃圾回收以释放内存(0代内存满时)
在一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,会进行以下两步 1.标记对象 从根(静态字段、方法参数)开始检查引用对象,标记后为可达对象,未标记为不可达对象,不可达对象就认为是垃圾 2.搬迁对象压缩堆 (挂起执行托管代码线程) 释放未标记的对象 搬迁可达对象 修改引用地址
大对象总被认为是第二代内存 目的是减少性能损耗,提高性能 不会对大对象进行搬迁压缩 85000字节(83kb)以上的对象为大对象
二、成员属性 基本概念
-
用于保护成员变量
-
为成员属性的获取和赋值添加逻辑处理
-
解决 3P (Private、Public、Protected)的局限性
public——内外访问
private——内部访问
protected——内部和子类访问
属性可以让成员变量在外部只能获取、不能修改,或者,只能修改、不能获取
// 访问修饰符 属性类型 属性名
// {
// get{}
// set{}
// }
internal class Person
{
private int age;
private int money;
// 属性的命名一般使用 帕斯卡命名法
public string Name { get; set; }
public int Money {
get =>
//解密处理
money - 5;
set =>
//加密处理
money = value + 5;
}
// get和set可以只有一个
// 注意:
// 只有一个时 没必要在前面加访问修饰符
// 一般情况下 只会出现 只有 get的情况 基本不会出现只有set
public bool Sex { get; }
// set
// {
// sex = value;
// }
// 作用:外部能得不能改的特征
// 如果类中有一个特征是只希望外部能得不能改的 又没什么特殊处理
// 那么可以直接使用自动属性
public float Height {
// 没有再get和set中写逻辑的需求或者想法
get;
private set;
}
// 成员属性中,get和set前可以加访问修饰符
// 注意
// 1.默认不加 会使用属性申明时的访问权限
// 2.加的访问修饰符要低于属性的访问权限
// 3.不能让get和set的访问权限都低于属性的权限
}
1、成员属性概念:一般是用来保护成员变量的 2、成员属性的使用和变量一样 外部用对象点出 3、get 中需要 return 内容 ; set 中用 value 表示传入的内容 4、get 和 set 语句块中可以加逻辑处理 5、get 和 set 可以加访问修饰符,但是要按照一定的规则进行添加 6、get 和 set 可以只有一个 7、自动属性是属性语句块中只有 get 和 set,一般用于 外部能得不能改这种情况
三、索引器 索引器:让对象可以像数组一样通过索引访问其中元素,使程序看起来更直观,更容易编写
// 访问修饰符 返回值 this[参数类型 参数名, 参数类型 参数名.....]
// {
// 内部的写法和规则和属性相同
// get{}
// set{}
// }
internal class Person
{
private int age;
private int[,] array;
private Person[] friends;
private string name;
public Person this[int index] {
get {
// 可以写逻辑的 根据需求来处理这里面的内容
// 索引器中可以写逻辑
if (friends == null ||
friends.Length - 1 friends.Length - 1)
// 自己定了一个规则 如果索引越界 就默认把最后一个朋友顶掉
friends[friends.Length - 1] = value;
friends[index] = value;
}
}
// 索引器可以重载
// 重载的概念是——函数名相同 参数类型、数量、顺序不同
public int this[int i, int j] {
get => array[i, j];
set => array[i, j] = value;
}
public string this[string str] {
get {
switch (str)
{
case "name":
return name;
case "age":
return age.ToString();
}
return "";
}
}
}
注意:结构体里面也支持索引器
四、静态成员 概念:用 static 修饰的成员变量、成员方法、成员属性等 就称为静态成员 特点:直接用类名点出来使用(全局性) 生命周期:和程序同生共死 程序运行后就会一直存在内存中,知道程序结束后才会释放,因此静态成员具有唯一性 注意: 1、静态函数中不能直接使用非静态成员 2、非静态函数中可以直接使用静态成员
常量和静态变量 常量是特殊的静态变量 相同点: 他们都可以通过类名点出来使用 不同点: 1、const 必须初始化不能被修改 static 没有这个规则 2、const 只能修饰变量,static 可以修饰很多 3、const 不能写在访问修饰符前面,一定是写在变量申明前面 static 没有这个规则
五、静态类和静态构造函数(一)静态类
概念:用 static 修饰的类
特点:
1、只能包含静态成员
2、不能被实例化
作用:
1、将常用的静态成员写在静态类中,方便使用
2、静态类不能被实例化,更能体现工具类的唯一性 比如 Console 就是一个静态类
(二)静态构造函数
概念:在构造函数加上 static 修饰
特点: 1、静态类和普通类都可以有
2、不能使用访问修饰符
3、不能有参数
4、只会自动调用一次
作用:在静态构造函数中初始化静态变量
// 1.静态类中的静态构造函数
internal static class StaticClass
{
public static int testInt = 100;
public static int testInt2 = 100;
static StaticClass()
{
Console.WriteLine("静态构造函数");
testInt = 200;
testInt2 = 300;
}
}
// 2.普通类中的静态构造函数
internal class Test
{
public static int testInt = 200;
static Test()
{
Console.WriteLine("静态构造");
}
public Test()
{
Console.WriteLine("普通构造");
}
}
六、拓展方法
概念:为现有非静态变量类型添加新方法 作用: 1、提升程序拓展性 2、不需要再对象中重新写方法 3、不需要继承来添加方法 4、为别人封装的类型写额外的方法 特点: 1、一定是写在静态类中 2、一定是个静态函数 3、第一个参数为拓展目标 4、第一个参数用 this 修饰
语法:
访问修饰符 static 返回值 函数名(this 拓展类名 参数名, 参数类型 参数名,参数类型 参数名....)
概念:让自定义类和结构体能够使用运算符
使用关键字 operator
特点: 1、一定是一个公共的静态方法 2、返回值写在 operator 前 3、逻辑处理自定义
作用:让自定义类和结构体对象可以进行运算 注意: 1、条件运算符需要成对实现 2、一个符号可以多个重载 3、不能使用 ref 和 out
基本语法:
public static 返回类型 operator 运算符(参数列表)
internal class Point
{
public int x;
public int y;
public static Point operator +(Point p1, Point p2)
{
var p = new Point();
p.x = p1.x + p2.x;
p.y = p1.y + p2.y;
return p;
}
public static Point operator +(Point p1, int value)
{
var p = new Point();
p.x = p1.x + value;
p.y = p1.y + value;
return p;
}
public static Point operator +(int value, Point p1)
{
var p = new Point();
p.x = p1.x + value;
p.y = p1.y + value;
return p;
}
// 注意 符号需要两个参数还是一个参数
public static Point operator -(Point p1, Point P2)
{
return null;
}
public static Point operator *(Point p1, Point P2)
{
return null;
}
public static Point operator /(Point p1, Point P2)
{
return null;
}
public static Point operator %(Point p1, Point P2)
{
return null;
}
public static Point operator ++(Point p1)
{
return null;
}
public static Point operator --(Point p1)
{
return null;
}
// 注意 符号需要两个参数还是一个参数
public static bool operator !(Point p1)
{
return false;
}
// 注意 符号需要两个参数还是一个参数
public static Point operator |(Point p1, Point p2)
{
return null;
}
public static Point operator &(Point p1, Point p2)
{
return null;
}
public static Point operator ^(Point p1, Point p2)
{
return null;
}
public static Point operator ~(Point p1)
{
return null;
}
public static Point operator (Point p1, int num)
{
return null;
}
// 1.返回值一般是 bool 值 也可以是其它的
// 2.相关符号必须配对实现
public static bool operator >(Point p1, Point p2)
{
return false;
}
public static bool operator =(Point p1, Point p2)
{
return false;
}
public static bool operator 。。。——>子类构造
internal class GameObject
{
public GameObject()
{
Console.WriteLine("GameObject的构造函数");
}
}
internal class Player : GameObject
{
public Player()
{
Console.WriteLine("Player的构造函数");
}
}
internal class MainPlayer : Player
{
public MainPlayer()
{
Console.WriteLine("MainPlayer的构造函数");
}
}
// 父类的无参构造函重要
// 子类实例化时 默认自动调用的 是父类的无参构造 所以如果父类无参构造被顶掉 会报错
internal class Father
{
// public Father()
// {
// }
public Father(int i)
{
Console.WriteLine("Father构造");
}
}
internal class Son : Father
{
// 通过base调用指定父类构造
public Son(int i) : base(i)
{
Console.WriteLine("Son的一个参数的构造");
}
public Son(int i, string str) : this(i)
{
Console.WriteLine("Son的两个参数的构造");
}
}
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("继承中的构造函数");
var mp = new MainPlayer();
var s = new Son(1, "123");
}
}
特点:执行顺序 是先执行父类的构造函数 再执行子类的 从老祖宗开始 依次一代一代向下执行
父类中的无参构造函数很重要,如果被顶掉 子类中就无法默认调用无参构造了 解决方法: 1、始终保持申明一个无参构造 2、通过 base 关键字,调用指定父类的构造 注意: 区分 this 和 base 的区别
十一、万物之父和装箱拆箱(一)万物之父
关键字:object 概念:object 是所有类型的基类,它是一个类(引用类型) 作用: 1、可以利用里氏替换原则,用 object 容器装所有对象 2、可以用来表示不确定类型,作为函数参数类型
(二)装箱拆箱
发生条件:用 object 存值类型(装箱),再把 object 转为值类型(拆箱)
装箱:把值类型用引用类型存储,栈内存会迁移到堆内存中
拆箱:把引用类型存储的值类型取出来,堆内存会迁移到栈内存中
好处:不确定类型时可以方便参数的存储和传递 坏处:存在内存迁移,增加性能消耗
// 装箱
object v = 3;
// 拆箱
var intValue = (int) v;
十二、密封类
使用 sealed 密封关键字修饰的类 作用:让类无法再被继承
意义:加强面向对象程序设计的规范性、结构性、安全性
十三、多态 vob(一)多态的概念
多态按字面的意思就是“多种状态” 让继承同一父类的子类们 在执行相同方法时有不同的表现(状态) 主要目的:同一父类的对象 执行相同行为(方法)有不同的表现 解决的问题:让同一个对象有唯一行为的特征
(二)vob 的使用
// 运行时多态( vob、抽象函数、接口 )
// v: virtual(虚函数)
// o: override(重写)
// b: base(父类)
internal class GameObject
{
public string name;
public GameObject(string name)
{
this.name = name;
}
// 虚函数 可以被子类重写
public virtual void Atk()
{
Console.WriteLine("游戏对象进行攻击");
}
}
internal class Player : GameObject
{
public Player(string name) : base(name) { }
// 重写虚函数
public override void Atk()
{
// base 的作用
// 代表父类 可以通过base来保留父类的行为
base.Atk();
Console.WriteLine("玩家对象进行攻击");
}
}
internal class Monster : GameObject
{
public Monster(string name) : base(name) { }
public override void Atk()
{
Console.WriteLine("怪物对象进行攻击");
}
}
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("多态vob");
Father f = new Son();
f.SpeakName();
(f as Son).SpeakName();
// 多态的使用
GameObject p = new Player("xxx");
p.Atk();
(p as Player).Atk();
GameObject m = new Monster("xxx");
m.Atk();
(m as Monster).Atk();
}
}
多态:让同一类型的对象,执行相同行为时有 解决的问题: 让同一对象有唯一的行为特征 vob: v:virtual 虚函数 o:override 重写 b:base 父类 v 和 o 一定是结合使用的,来实现多态 b 是否使用根据实际需求,保留父类行为
十四、抽象类和抽象方法(一)抽象类
概念:被抽象关键字abstract修饰的类 特点: 1、不能被实例化的类 2、可以包含抽象方法 3、继承抽象类必须重写其抽象方法
(二)抽象方法
又叫纯虚方法,用 abstract 关键字修饰的方法 特点: 1、只能在抽象类中申明 2、没有方法体 3、不能是私有的 4、继承后必须实现,用 override 重写
internal abstract class Fruits
{
public string name;
// 抽象方法 是一定不能有函数体的
public abstract void Bad();
public virtual void Test()
{
// 可以选择是否写逻辑
}
}
internal class Apple : Fruits
{
public override void Bad() { }
// 虚方法是可以由我们子类选择性来实现的
// 抽象方法必须要实现
}
internal class SuperApple : Apple
{
// 虚方法和抽象方法 都可以被子类无限的 去重写
public override void Bad()
{
base.Bad();
}
public override void Test()
{
base.Test();
}
}
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("抽象类和抽象方法");
// 抽象不能被实例化
// Thing t = new Thing();
// 但是 可以遵循里氏替换原则 用父类容器装子类
Thing t = new Water();
}
}
十五、接口
接口是行为的抽象规范,它也是一种自定义类型 关键字 :interface
接口申明的规范 1、不包含成员变量 2、只包含方法、属性、索引器、事件 3、成员不能被实现 4、成员可以不用写访问修饰符,不能是私有的 5、接口不能继承类,但是可以继承另一个接口
接口的使用规范 1、类可以继承多个接口 2、类继承接口后,必须实现接口中所有成员
特点: 1、它和类的申明类似 2、接口是用来继承的 3、接口不能被实例化,但是可以作为容器存储对象
// 接口关键字:interface
// 语法:
// interface 接口名
// {
// }
// 一句话记忆:接口是抽象行为的“基类”
// 接口命名规范 帕斯卡前面加个 I
internal interface IFly
{
string Name { get; set; }
int this[int index] { get; set; }
void Fly();
event Action doSomthing;
}
// 接口用来继承
internal class Animal { }
// 1.类可以继承1个类,n个接口
// 2.继承了接口后 必须实现其中的内容 并且必须是public的
internal class Person : Animal, IFly
{
// 3.实现的接口函数,可以加virtual再在子类重写
public virtual void Fly() { }
public string Name { get; set; }
public int this[int index] {
get => 0;
set { }
}
public event Action doSomthing;
}
// 接口可以继承接口
// 接口继承接口时 不需要实现
// 待类继承接口后 类自己去实现所有内容
internal interface IWalk
{
void Walk();
}
internal interface IMove : IFly, IWalk { }
internal 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();
}
}
// 当一个类继承两个接口
// 但是接口中存在着同名方法时
// 注意:显示实现接口时 不能写访问修饰符
internal interface IAtk
{
void Atk();
}
internal interface ISuperAtk
{
void Atk();
}
internal class Player : IAtk, ISuperAtk
{
// 显示实现接口 就是用 接口名.行为名 去实现
void IAtk.Atk() { }
void ISuperAtk.Atk() { }
public void Atk() { }
}
internal class Program
{
private static void Main(string[] args)
{
Console.WriteLine("接口");
// 4.接口也遵循里氏替换原则
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();
var p = new Player();
(p as IAtk).Atk();
(p as ISuperAtk).Atk();
p.Atk();
}
}
十六、密封方法
用密封关键字 sealed 修饰的重写函数 作用:让虚方法或者抽象方法之后不能再被重写 特点:和 override 一起出现
十七、命名空间 概念:命名空间是用来组织和重用代码的 作用:就像是一个工具包,类就像是一件一件的工具,都是申明在命名空间中的
// 基本语法
// namespace 命名空间名
// {
// 类
// 类
// }
namespace MyGame
{
internal class GameObject { }
}
namespace MyGame
{
internal class Player : GameObject { }
}
// 不同命名空间中允许有同名类
namespace MyGame2
{
// 在不同的命名空间中 是可以有同名类的
internal class GameObject { }
}
// 命名空间可以包裹命名空间
namespace MyGame
{
namespace UI
{
internal class Image { }
}
namespace Game
{
internal class Image { }
}
}
十八、万物之父中的方法
(一)静态方法
public static bool Equals(object? objA, object? objB);
// 静态方法 Equals 判断两个对象是否相等
// 最终的判断权,交给左侧对象的Equals方法,
// 不管值类型引用类型都会按照左侧对象Equals方法的规则来进行比较
Console.WriteLine(Equals(1, 1));
Test t = new Test();
Test t2 = new Test();
Console.WriteLine(Object.Equals(t, t2));
public static bool ReferenceEquals(object? objA, object? objB);
// 静态方法 ReferenceEquals
// 比较两个对象是否是相同的引用,主要是用来比较引用类型的对象。
// 值类型对象返回值始终是false。
Console.WriteLine(Object.ReferenceEquals(t, t2));
(二)成员方法
public Type GetType();
// 普通方法GetType
// 该方法在反射相关知识点中是非常重要的方法,之后我们会具体的讲解这里返回的Type类型。
// 该方法的主要作用就是获取对象运行时的类型Type,
// 通过Type结合反射相关知识点可以做很多关于对象的操作。
var t = new Test();
var type = t.GetType();
protected object MemberwiseClone ();
// 普通方法 MemberwiseClone
// 该方法用于获取对象的浅拷贝对象,口语化的意思就是会返回一个新的对象,
// 但是新对象中的引用变量会和老对象中一致。
var t2 = t.MemberwiseClone();
(三)虚方法
public virtual bool Equals(object? obj);
// 虚方法Equals
// 默认实现还是比较两者是否为同一个引用,即相当于ReferenceEquals。
// 但是微软在所有值类型的基类System.ValueType中重写了该方法,用来比较值相等。
// 我们也可以重写该方法,定义自己的比较相等的规则
public virtual int GetHashCode();
// 虚方法GetHashCode
// 该方法是获取对象的哈希码
// (一种通过算法算出的,表示对象的唯一编码,不同对象哈希码有可能一样,具体值根据哈希算法决定),
// 我们可以通过重写该函数来自己定义对象的哈希码算法,正常情况下,我们使用的极少,基本不用。
public virtual string? ToString();
// 虚方法ToString
// 该方法用于返回当前对象代表的字符串,我们可以重写它定义我们自己的对象转字符串规则,
// 该方法非常常用。当我们调用打印方法时,默认使用的就是对象的ToString方法后打印出来的内容。
Console.WriteLine(t);
十九、string
// 1.字符串指定位置获取
// 字符串本质是char数组
var str = "xxx";
Console.WriteLine(str[0]);
// 转为char数组
var chars = str.ToCharArray();
Console.WriteLine(chars[1]);
for (var i = 0; i
关注
打赏
最近更新
- 深拷贝和浅拷贝的区别(重点)
- 【Vue】走进Vue框架世界
- 【云服务器】项目部署—搭建网站—vue电商后台管理系统
- 【React介绍】 一文带你深入React
- 【React】React组件实例的三大属性之state,props,refs(你学废了吗)
- 【脚手架VueCLI】从零开始,创建一个VUE项目
- 【React】深入理解React组件生命周期----图文详解(含代码)
- 【React】DOM的Diffing算法是什么?以及DOM中key的作用----经典面试题
- 【React】1_使用React脚手架创建项目步骤--------详解(含项目结构说明)
- 【React】2_如何使用react脚手架写一个简单的页面?