写在最前

  • 这篇笔记对应的是唐老狮的C#四部曲──C#核心课程地址

一、面向对象的概念

  1. 面向对象是一种对现实世界理解和抽象的编程方法,把相关的数据和方法组织为一个整体来看待,从更高的层次来进行程序开发,更贴近事物的自然运行模式。
  2. 人话:万物皆对象,用程序来抽象对象。
  3. 套路:用中文去形容一类对象,把对象的共同点提取出来,然后用程序语言把它翻译过来,带着对象的概念在程序中去使用它们。

二、面向对象-封装

类和对象

  1. 基本概念
    1. 具有相同特性、相同行为的一类事物的抽象
    2. 类是对象的模板,可以通过类创建出对象
  2. 类声明语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//一般声明在namespace中
//帕斯卡命名法
//一个语句块中的类名不能相同
class MyClass
{
//特性──成员变量
//行为──成员方法
//保护特征──成员属性

//构造函数和析构函数
//索引器
//运算符重载
//静态成员
}

3、类对象声明

1
2
3
4
5
6
//类是自定义变量类型,类对象是一个变量。
//对象是类创建的,一般称为实例化对象
//类对象都是引用类型
//类名 变量名;
//类名 变量名 = null;null为空,不会分配堆内存
//类名 变量名 = new 类名();

成员变量和访问修饰符

  1. 成员变量基本规则
    1. 声明在类语句块中,用来描述对象的特征
    2. 可以是任意类型
    3. 数量不做限制
    4. 是否赋值根据需求来定
    5. 可以在类中声明和自己相同类型的成员变量,但是不能实例化(可以使用null)
  2. 访问修饰符
    1. public 公共的,外部可以用
    2. private 私有的,自己内部可以用
    3. protected 保护的,自己内部和子类可以用
  3. 成员变量的初始值
    1. 值类型
      1. 数字类型:0
      2. bool类型:false
    2. 引用类型
      都为null
    3. 小技巧:default(类型)会返回默认值

成员方法

  1. 声明
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
//1. 声明在类语句块中
//2. 用来描述对象的行为的
//3. 规则和函数声明规则相同
//4. 受到访问修饰符规则影响
//5. 返回值不受限制
//6. 方法数量不受限制
//7. 成员方法不要加static关键字
//8. 必须实例化对象,再通过对象使用s
class People
{
public bool IsAdult()
{
return age>=18;
}
int age;
string name;
}
class program
{
static void Main(string[] args)
{
People me = new People();
me.age = 24;
me.name = "chengjun";
Console.WriteLine(me.IsAdult());
}
}

构造函数和析构函数

  1. 构造函数
    1. 实例化对象时会调用的用于初始化的函数
    2. 如果不写,默认存在一个无参构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//没有返回值
//函数名必须和类名相同
//没有特殊需求的时候,一般是public
//构造函数可以被重载
//this代表当前函数的对象自己
//结构体必须在构造函数中初始化全部字段,类不用
//注意:如果自己不去实现无参构造函数而实现了有参构造函数,会失去默认的无参构造函数(结构体中不会失去,也不能写无参构造函数)
using System;

class Person
{
// 类的成员变量
public string Name;
public int Age;

// 无参构造函数
public Person()
{
Name = "Unknown";
Age = 0;
}

// 有参构造函数
public Person(string name, int age)
{
Name = name;
Age = age;
}
//特殊写法
//使用 this() 的好处:代码复用:可以避免在多个构造函数中重复相同的初始化代码,将公共的初始化逻辑放在一个构造函数中,其他构造函数通过 this() 调用该构造函数,从而使代码更加简洁和易于维护。
//this()括号中可以传参数去调用有参构造函数,传的参数是this前面的括号里的参数
public Person(string name, int age):this()
{
Name = name;
Age = age;
}
}

class Program
{
static void Main()
{
// 使用无参构造函数创建 Person 实例
Person person1 = new Person();
Console.WriteLine($"Person 1: Name = {person1.Name}, Age = {person1.Age}");

// 使用有参构造函数创建 Person 实例
Person person2 = new Person("Alice", 30);
Console.WriteLine($"Person 2: Name = {person2.Name}, Age = {person2.Age}");
}
}
  1. 析构函数
    1. 当引用类型的堆内存回收时,会调用该函数
    2. 对于需要手动管理内存的语言(c++),需要在析构函数中做内存回收的处理
    3. c#中存在垃圾回收机制(GC),基本不会使用这个函数
    4. 析构函数是垃圾真正被回收的时候调用,而不是产生垃圾就调用
1
2
3
~类名()
{
}
  1. 垃圾回收
    1. 垃圾回收的过程是遍历堆(heap)上动态分配的所有对象,通过识别它们是否被引用来确定哪些对象是垃圾,哪些对象仍然在是被使用。
    2. 所谓的垃圾就是没有被任何变量,对象引用的内容
    3. GC只负责堆内存的垃圾回收,引用类型的分配和释放都是垃圾回收机制管理的。
    4. 具体原理:
      • C# 的垃圾回收器主要采用分代式垃圾回收算法,它将堆分为三代:①第 0 代:新创建的对象通常会被分配到第 0 代。当第 0 代满时,垃圾回收器会执行一次回收操作。②第 1 代:一些在第 0 代中经过一次垃圾回收后仍然存活的对象会被提升到第 1 代。③第 2 代:存活时间更长的对象会从第 1 代提升到第 2 代。大对象(通常大于 85,000 字节)通常直接分配到第 2 代。
      • 垃圾回收过程中,从根对象开始,遍历对象图,标记所有可达对象。清除阶段会回收所有未标记的对象,释放它们占用的内存。然后将所有可达对象进行搬迁,集中,并修改引用地址。
    5. 手动触发垃圾回收,较少使用,一般在loading时使用,消耗较大。GC.Collect();

成员属性

  1. 基本概念
    1. 用于保护成员变量
    2. 为成员属性的获取和赋值添加逻辑处理
    3. 解决3P的局限性──将读和写解绑
  2. 基本语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
//访问修饰符 属性类型 属性名
//{
// get{}
// set{}
//}
class Person
{
string name;
int age;
int money;
bool sex;
//属性命名一般用帕斯卡命名法
public string Name
{
//1. 可以在get和set前加访问修饰符,默认不加,会使用属性声明时的访问权限
//2. 加的访问修饰符的权限低于属性的访问修饰符
//3. 不能让set和get的访问权限都低于属性的权限
//4. public > private
//5. get和set可以只有一个,且一般为get,没必要加访问修饰符
get
{
//获取
return name;
}
set
{
//赋值
//value代表外部传入的值
name = value;
}
}
//自动属性
//外部能得不能改,并且无特殊处理
//这种写法可以直接不写成员变量
public float Height
{
get;
private set;
}
}

索引器

  1. 基本概念
    1. 让对象可以像数组一样通过索引访问其中元素,使程序直观。结构体里面也支持索引器
  2. 语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//访问修饰符 返回值 this[参数类型 参数名,参数类型 参数名,……]
//{
// 内部的写法和规则和属性相同
// get{}
// set{}
//}
class Person
{
private string name;
private int age;
private Person[] friends;
//索引器可以重载
public Person this[int index]
{
//同属性一样,索引器里是可以写逻辑的
get
{
return friends[index];
}
set
{
friends[index] = value;
}
}
}
class Program
{
static void Main(string[] args)
{
Person p = new Person();
p[0] = new Person();
}
}

静态成员

  1. 静态关键字static修饰的成员,直接使用类名点出使用
  2. 原理
    1. 当程序运行时,对于包含静态成员的类型,CLR(公共语言运行时)会在加载该类型时为其静态成员分配专门的内存区域。这个内存区域是全局唯一的,无论创建多少个该类型的实例,静态成员都只有一份内存空间。
    2. 在类的内部,静态成员可以直接访问其他静态成员,无需通过类名。但在访问非静态成员时,会引发编译错误,因为非静态成员依赖于具体的对象实例,而静态成员在没有实例的情况下也可能被调用。
    3. 静态成员的生命周期从类型被加载到 CLR 中开始,直到程序域(AppDomain)卸载时结束。在整个程序运行期间,静态成员始终存在于内存中,它的状态会一直保持,除非被显式修改或程序结束。
    4. 静态成员可以在声明时进行初始化,也可以在静态构造函数中进行初始化。静态构造函数是一种特殊的构造函数,它只在类型第一次被使用之前自动调用一次,用于对静态成员进行初始化操作。
  3. 常量和静态成员的区别
    1. 相同:都可以通过类名点出使用
    2. 不同:const必须初始化,不能修改,static无这个限制
    3. 不同:const只能修饰变量,static可以修饰其它
    4. 不同:const不惜写在访问修饰符后面,static无这个限制

静态类和静态构造函数

  1. 静态类
    1. 用static修饰的类,只能包含静态成员,不能被实例化
    2. 一般是工具类,唯一性
  2. 静态构造函数
    1. 用static修饰的构造函数
    2. 静态类和普通类都可以使用
    3. 不能有访问修饰符和参数
    4. 只会调用一次
    5. 作用是在静态构造函数中初始化静态变量

拓展方法

  1. 基本概念
    1. 为现有的非静态的变量类型添加方法
    2. 提升程序拓展性,不需要再在对象中重新写方法,不需要继承来添加方法
    3. 为别人封装的类型写额外的方法
    4. 一定写在静态类中,一定是个静态函数,第一个参数为拓展目标且用this修饰
  2. 基本语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//访问修饰符 static 返回值 函数名(this 拓展类名 参数名,参数类型 参数名……)
static class Tools
{
//为int拓展了一个成员方法
//成员方法是实例化对象后才能使用的
//value代表使用该方法的实例化对象
public static void SpeakValue(this int value)
{
//拓展的方法的逻辑
Console.WriteLine("为int拓展的方法"+value)
}
//Test为自定义类
public static void Fun3(this Test t)
{
//为test拓展的方法
}
}
class program
{
static void Main(string[] args)
{
int i = 10;
i.SpeakValue();
}
}

运算符重载

  1. 基本概念
    1. 让自定义类和结构体使用运算符,使用关键字operator
    2. 一定是一个公共的静态方法,返回值写在operator前,逻辑处理自定义
    3. 条件运算符需要成对实现,一个符号可以多个重载,不能使用ref和out
  2. 基本用法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//public static 返回类型 operator 运算符(参数列表)
class Point
{
public int x;
public int y;

public static Point operator +(Point p1,Point p2)
{
Point p = new Point();
p.x = p1.x + p2.x;
p.y = p1.y + p2.y;
return p;
}
}
  1. 可重载和不可重载的运算符
    1. 可重载
      ①算数运算符
      ②逻辑运算符中的逻辑非( ! )
      ③位运算符
      ④条件运算符
    2. 不可重载
      ①逻辑运算符中的逻辑与( && )和逻辑或( || )
      ②索引符 [ ]
      ③强转运算符 ( )
      ④特殊运算符 点 . 三目运算符? : 赋值符号 =

内部类和分部类

  1. 内部类
    1. 在一个类中再声明一个类
    2. 使用时需要用包裹者点出自己
    3. 亲密关系的变现
    4. 访问修饰符作用很大
  2. 分部类
    1. 把一个类分成几个部分声明,关键字:partial
    2. 分部描述一个类,增加程序的拓展性
    3. 分部类可以写在多个脚本文件中,分部类的访问修饰符要一致且写在关键字前面,分部类中不能有重复成员
  3. 分部方法
    1. 将方法的声明和实现分离,在方法前面加分部关键字partial
    2. 不能加访问修饰符,默认私有
    3. 只能在分部类中声明,返回值只能是void
    4. 可以有参数,但不能用out关键字

三、面向对象-继承

继承的基本规则

  1. 概念
    1. 一个类A继承一个类B,类A会有类B的所有成员、所有特征和行为
    2. 被继承的类:父类、基类、超类;继承的类:子类、派生类;
    3. 子类可以有自己的特征和行为
    4. 单根性:子类只能有一个父类
    5. 传递性:子类可以间接继承父类的父类
  2. 语法
1
2
3
4
class MyClass:FatherClass
{
//code
}
  1. 父类和子类同名成员
    1. 极不建议父类和子类都存在同名成员,在子类的实例中,该字段会隐藏父类字段,从而访问到子类的对应字段。
    2. 可以用new关键字在子类同名字段声明时显示隐藏父类同名字段。

里氏替换原则

  1. 概念
    1. 任何父类出现的地方都可以用子类代替
    2. 父类容器装子类对象,因为子类对象包含了父类的所有内容(不能用子类容器装父类对象)
    3. 方便进行对象存储和管理
  2. 语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class GameObject
{

}
class Player
{
public void PlayerAtk()
{
Console.WriteLine("玩家攻击");
}
}
class program
{
static void Main(string[] args)
{
GameObject player = new Player();
}
if(player is Player)
{
Player p = player as Player;
p.PlayerAtk();
//简洁写法: (player as Player).PlayerAtk();
}
}
  1. is 和 as
    1. is
      ①判断一个对象是否是指定类对象
      ②返回值:bool,是为真,不是为假
    2. as
      ①将一个对象转换为指定类对象
      ②返回值:指定类型对象,成功返回指定类型对象,失败返回null

继承中的构造函数

  1. 概念

万物之父和装箱拆箱

密封类

四、面向对象-多态

五、面向对象关联知识点