C#学习笔记——基础知识点(叁)
一、命名法
-
驼峰命名法:myName(变量)
-
帕斯卡命名法:MyName (函数、类、属性等)
二、值类型和引用类型
1、分类
-
引用类型: string,数组,类
-
值类型: 其他类型,结构体,枚举
2、区别
-
值类型在相互赋值时把内容拷贝给对方,它变我不变
-
引用类型相互赋值时是让两者指向同一个值,它变我也变
3、原因
值类型存储在栈空间 ——系统分配,自动回收,小而快
引用类型存储在堆空间 ——手动申请和释放,大而慢,new就是在堆中开辟新空间
4、特殊引用类型string
-
string的它变我不变:具备值类型的特点
-
原因:c#进行了特殊处理,比如将str1赋值给str2,此时str1和str2指向堆中同一个区域,当str2重新赋值时,会为str2的值在堆中开辟一个新空间,此时str1和str2指向的就不是同一个区域。
-
缺点:频繁重新赋值会产生内存垃圾,优化替代方案下面的stringBuilder章节中
补充知识点在进阶知识点的最后一部分
三、ref和out
1、用ref和out的原因
可以解决函数内部改变外部传入的内容的问题
2、使用
返回类型 函数名(ref 参数类型 参数名1,......)
函数名(ref 参数)
返回类型 函数名(ref 参数类型 参数名1,......)
函数名(out 参数)
3、ref和out的区别
-
ref传入的变量必须初始化,out不用
-
out传入的变量必须在内部赋值,ref不用
4、对比表
| 对比维度 | ref | out |
|---|---|---|
| 调用前是否初始化 | 必须 | 不必 |
| 方法内是否必须赋值 | 否 | 必须 |
| 数据流方向 | 传入+传出 | 仅传出 |
| 语义侧重点 | 修改已有值 | 生成新值 |
四、面向对象三大特性
1、封装(Encapsulation)
- 核心思想:将对象的状态(属性)和行为(方法)包装在一起,对外隐藏内部实现细节,仅暴露必要的交互接口。
- 意义
- 保护数据安全,避免外部直接修改对象内部状态。
- 降低代码耦合度,修改内部实现时不影响外部调用。
- 提升代码可维护性,统一的接口让使用更清晰。
2、继承(Inheritance)
- 核心思想:让一个类(子类)可以继承另一个类(父类)的属性和方法,实现代码复用与拓展。
- 意义
- 复用父类的代码,减少重复编写。
- 建立类的层级关系,让代码结构更清晰。
- 为多态提供基础。
3、多态(Polymorphism)
-
核心思想:同一个行为在不同对象上可以有不同的表现形式,分为编译时多态和运行时多态。
-
分类
-
编译时多态(静态多态):通过方法重载、运算符重载实现,在编译阶段就确定调用哪个方法。
-
运行时多态(动态多态):通过继承 + 虚方法重写实现,在运行阶段根据对象的实际类型确定调用哪个方法。
-
-
意义
- 提升代码的灵活性和可扩展性,新增子类时无需修改原有调用逻辑。
- 符合开闭原则,拓展新功能时不影响旧代码。
4、三大特性关系总结
-
封装是基础:隐藏内部细节,保障数据安全,为继承和多态提供稳定的基础。
-
继承是手段:复用代码,建立类的层级关系,是实现多态的前提。
-
多态是目的:让代码更灵活、可扩展,是面向对象设计的核心价值体现。
五、万物之父与装箱拆箱
1、万物之父
-
关键字:
object -
概念:object是所有类型的基类,可以使用里氏替换原则,用object装载所有类型的对象。
-
使用:例子如下
// string
object obj = "123456";
string str = obj as string;
// 值类型
object obj2 = 1f;
float f1 = (float)o2;
2、装箱拆箱
(1)概念
- 装箱:用object存值类型,把值类型用引用类型存储,栈内存会迁移到堆内存中
- 拆箱:将object转换为值类型,把引用类型存储的值取出,堆内存会迁移到栈内存
(2)优缺点
- 优点:不确定类型时方便参数存储和传递
- 缺点:内存迁移增加性能消耗
3、万物之父中的方法
- Equals: 判断两个对象是否相等,按照左侧对象Equals方法的规则进行比较
- ReferenceEquals: 判断两个对象是否是相同的引用,值对象始终是false
- GetType: 反射中的重要方法,返回对象运行时的类型Type
- MemberwiseClone: 获取对象的浅拷贝
- GetHashCode: 获取对象的哈希码(一种通过算法算出的表示对象的唯一编码,不同对象哈希码可能一样,具体由哈希算法决定)
- ToString: 返回当前对象代表的字符串
六、垃圾回收机制
1、概念
垃圾回收(Garbage Collector,GC): 垃圾回收的过程是遍历堆上动态分配的所有对象,通过识别他们是否被引用来确定对象是否是垃圾。所谓垃圾就是没有被任何变量、对象引用的内容,而垃圾就需要被回收释放。
垃圾回收有很多算法,比如:
-
引用计数(Reference Counting)
-
标记清除(Mark Sweep)
-
标记整理(Mark Compact)
-
复制集合(Copy Collection)
C#的GC使用的是标记清除算法和分代回收算法
注意:
- GC只负责堆内存的垃圾回收
- 引用类型都是在堆中的,所以它的分配释放都通过垃圾回收机制管理
- 栈上的内存有系统自动管理,值类型在栈中分配内存,有自己的生命周期,因此无需对他们进行管理,会自动分配和释放
2、原理
C#的GC采用分代回收策略,将堆划分为不同的代,通常有3代,如0代内存、1代内存、2代内存
代的概念(Generations): 代是垃圾回收机制使用的一种算法(分代算法),新分配的对象通常放在第0代,每次分配都可能会进行垃圾回收以释放内存(如0代内存满时)。
在一次内存回收的过程开始时,垃圾回收器会进行以下两步:
- 标记对象(标记清除算法),标记后的为可达对象,未被标记的就是不可达对象,即垃圾。
- 搬迁对象,压缩堆(内存压缩):释放未标记对象的内存,搬迁可达对象,修改引用地址。
3、标记清除算法
主要分为2个阶段:
- 标记阶段
- 根对象:GC会从一组根对象开始,根对象指始终可达的对象(全局变量、静态变量、当前执行方法的局部变量等)
- 标记过程:GC会遍历所有根对象,递归的标记所有从根对象可达的对象,表明他们仍被使用,不能回收
- 清除阶段
- 清除过程:标记阶段完成后,GC会遍历整个托管堆,将所有未被标记的对象视为垃圾,并回收他们所占的内存空间
托管堆:
不同于堆,它由CLR(Common Language Runtime,公共语言运行库)管理,当堆中内存满时,会自动清理堆中的垃圾
4、分代回收算法
C#的GC采用分代回收策略,将堆划分为不同的代,通常有3代,如0代内存、1代内存、2代内存
代的划分原则:
- 第0代:新创建的对象通常分配到第0代。这一代的对象生命周期短,大部分创建后很快就不再使用
- 第1代:经过一次0代垃圾回收后仍然存活的对象会晋升到第1代。这一代的对象生命周期相对较长
- 第2代:经过多次1代垃圾回收后仍然存活的对象会晋升到第2代。这一代的包含一些长期存活的对象,如静态对象等
分代回收的优势:
- 提高回收效率: 大部分新创建的对象很快变成垃圾,频繁对第0代进行垃圾回收可以快速回收大量内存空间,减少垃圾回收的时间开销
- 减少内存碎片: 分代回收将生命周期不同的对象分开处理,减少内存碎片的产生
5、内存压缩
某些情况下,GC可能会执行内存压缩操作,将活动对象移动到堆的连续区域,消除内部碎片,提高内存利用率和分配性能
6、触发垃圾回收的条件
- 内存不足
- 特定代达到阈值:某一代的对象数量达到一定阈值,就会触发该代的垃圾回收
- 手动调用
GC.Collect方法
七、string和StringBuilder
1、string
-
特点:string变量只要修改就会分配新的空间,string本质是char数组
-
常用方法:
1.字符串指定位置获取,字符串长度
string str;
str[0]
str.Length
2.字符串拼接
string.Format("{0}",str);
3.正向查找字符位置(没找到默认返回-1)
str = "唐老狮";
str.indexOf("唐");
4.反向查找字符位置
str.LastIndexOf();
5.移除指定位置后的字符(返回新字符串,不改变原字符串)
str = str.Remove(4); // 把索引4后的字符全移除
str = str.Remove(开始位置,字符个数);
6.替换指定字符串
str = str.Replace(老字符串,新字符串);
7.大小写转换
str = str.ToUpper();
str = str.ToLower();
8.字符串截取(截取从指定位置开始之后的字符串)
str = str.Substring(2);
str = str.Substring(开始位置,指定个数);
9.字符串切割
str = "1,2,3,5,6,7,8";
string[] strs = str.Split(',');
2、StringBuilder
- 概念:C#提供的用于处理字符串的公共类,修改字符串而不创建新的对象,适用于频繁修改和拼接的字符串,提升性能
- 使用:
using System.Text
StringBuilder str = new StringBuilder("123456");
- 常用方法:
1、容量
str.Capacity;
2、字符串长度
str.Length;
3、增加
str.Append(增加内容);
str.AppendFormat("{0}",100);
4、插入
str.Insert(位置,内容);
5、删除
str.Remove(位置,个数);
6、清空
str.Clear();
7、查找
str[0];
8、修改
str[0] = 'A'; // string是不能这样改的
9、替换
str.Repalce(原字符串,新字符串);
3、string和stringBuilder区别
- string相对StringBuilder更容易产生垃圾,每次修改拼接都会产生垃圾
- string相对StringBuilder更加灵活,提供了更多的方法供使用
4、如何优化内存
- 少new对象、少产生垃圾
- 合理使用static
- 合理使用string和StringBuilder
八、结构体和类的区别
1、区别概述
结构体和类最大区别是在存储空间上的,结构体是值类型,类是引用类型。结构体存储在栈上,类存储在堆上。
结构体和类在使用上很相似,并且结构体具备面向对象的封装特性,但不具备继承和多态特性。因为不具备继承特性,因此结构体无法使用protected修饰符。但结构体可以实现接口,因为接口是行为的抽象
2、细节区别
- 结构体是值类型,类是引用类型
- 结构体存在栈中,类存在堆中(具体点就是:结构体局部变量在栈、作为类字段时在堆;类实例数据在堆、引用本身可在栈/堆)
- 结构体成员无法使用
protected修饰符 - 结构体成员变量声明无法指定初始值(静态字段除外),类可以
- 结构体不能声明无参构造函数,类可以(C#10 及以上结构体可以声明无参构造函数,此时会覆盖默认的)
- 结构体声明有参构造函数后,无参构造不会被顶掉;类声明有参构造后,默认无参构造会被顶掉
- 结构体不能声明析构函数,类可以
- 结构体不能继承,类可以
- 结构体需要在构造函数中初始化所有成员变量,类随意
- 结构体本身不能用static修饰(没有静态结构体),类可以
- 结构体不能在内部声明和自己一样的结构体变量,类可以
3、如何选择
- 想要继承和多态,选择类,如玩家、怪物等等
- 对象是数据集合时,优先考虑结构体,如位置、坐标、颜色等等
- 从值类型和引用类型的区别去考虑,比如经常被赋值、传递的对象,并且修改副本不想影响原对象,就用结构体,如坐标、向量、旋转等等
九、抽象类和接口的区别
1、共同点
- 都可以被继承
- 都不能直接实例化
- 都可以包含方法声明
- 子类必须实现未实现的方法
- 遵循里氏替换原则
2、区别
- 抽象类可以有构造函数;接口不能
- 抽象类只能单继承;接口可以被继承多个
- 抽象类可以有成员变量;接口不能
- 抽象类可以声明成员方法、虚方法、抽象方法、静态方法;接口只能声明没有实现的抽象方法
- 抽象类方法可以使用访问修饰符;接口建议不写,默认
public
3、如何选择
- 表示对象的用抽象类,表示行为拓展的用接口
- 不同对象拥有相同行为,往往可以用接口实现
十、面向对象的七大原则
总体目标: 高内聚低耦合,使程序模块的可重用性、移植性增强
1、单一职责原则(Single Responsibility Principle, SRP)
- 核心思想:一个类只应该负责一项功能,专注于单一职责。
- 意义:减少类被修改的频率,避免功能间的关联耦合,防止修改一个功能时影响其他功能。
- 举例:程序、策划、美术类应各司其职,只处理自身职责范围内的内容。
2、开闭原则(Open-Closed Principle, OCP)
- 核心思想:对拓展开放,对修改关闭。
- 意义:新增功能时优先通过拓展(如新增子类、实现新接口)完成,而非修改原有代码,以此保障系统稳定性。
- 举例:通过继承扩展功能,添加新的子类并重写父类方法来实现新需求。
3、里氏替换原则(Liskov Substitution Principle, LSP)
- 核心思想:任何父类出现的地方,子类都可以替换,且不会破坏原有系统的逻辑。
- 意义:保障继承体系的正确性,让父类容器可以安全地装载子类对象。
- 举例:父类的容器可以直接装入子类对象,因为子类包含了父类的所有内容。
4、依赖倒转原则(Dependence Inversion Principle, DIP)
- 核心思想:依赖于抽象,不要依赖于具体实现。
- 意义:通过抽象(接口 / 抽象类)解耦模块间的依赖,降低耦合度,提升系统的可扩展性。
- 举例:依赖 “开枪接口” 这个抽象,而非具体的手枪、步枪类,间接实现不同枪械的开火功能。
5、迪米特法则(Law of Demeter, LoP)
- 核心思想:一个对象应当对其他对象尽可能少的了解,即 “不要和陌生人说话”。
- 意义:降低对象间的耦合度,减少不必要的直接关联。
- 举例:一个类的成员应尽量少与其他类直接建立关系,仅与 “朋友”(直接关联的对象)交互。
6、接口隔离原则(Interface Segregation Principle, ISP)
- 核心思想:不应该强迫别人依赖他们不需要使用的方法,一个接口只提供单一对外功能。
- 意义:避免臃肿的大接口,让使用者可以按需选择实现对应的接口。
- 举例:将移动行为拆分为
飞行接口、走路接口、跑步接口等,而非封装到一个大接口中。
7、合成复用原则(Composite Reuse Principle, CRP)
- 核心思想:尽量使用对象组合,而不是继承来达到复用的目的。
- 意义:继承是强耦合关系,组合是低耦合关系,优先组合可提升系统灵活性。
- 举例:脸是眼镜、鼻子、嘴巴的组合,角色与装备也是组合关系,而非继承关系。
十一、控制台相关
1、输入输出
Console.WriteLine()
Console.Write()
Console.ReadLine()
Console.ReadKey(true) // 若括号中为true,则不会把输入的内容显示在控制台上
Console.ReadKey(true).Key // 返回枚举类型
2、清空
Console.Clear()
3、设置控制台大小
Console.SetWindowSize(宽,高)
4、缓冲区大小
Console.SetBufferSize(宽,高)
5、设置光标的位置
Tip:
-
左上角为0,0 右侧x轴正方向,下方y轴正方向
-
边界问题
-
横纵距离单位不同 1y = 2x (视觉上)
Console.SetCursorPosition(x,y)
6、设置文字颜色
Console.ForegroundColor = ConsoleColor.Red
7、设置背景颜色
Console.BackgroundColor = ConsoleColor.White;
Console.Clear();
8、光标显隐
Console.CursorVisible = true;
9、关闭控制台
Environment.Exit(0);
10、随机数
Random ran = new Random();
ran.Next(5,100) // 5~99的随机数
11、检测按键按下
Console.KeyAvailable // 键盘按下键时变为true,否则为false