封装
封装定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中",这个包就是类。在面向对象程序设计方法论中,封装可以防止对实现细节的访问。
1 类和对象
1.1 什么是类
具有相同特征、行为,是一类事物的抽象
类是对象的模板,通过类创建对象
1.2 类声明语法
1.3 类对象
类声明和类对象声明是两个概念:
- 类声明相当于定义了一个变量类型;
- 类对象声明相当于声明一个类的变量,这个过程称为实例化对象;
- 类对象是引用类型。
| |
| |
| |
| Person p1; |
| Person p2 = null; |
| |
| |
| Person p3 = new Person(); |
| Person p4 = new Person(); |
2 成员变量和访问修饰符
2.1 成员变量
声明在类语句块中,来描述对象的特征,可以是任意的变量类型,数量无限制,是否赋值根据需求确定
| enum E_SexType |
| { |
| Man, |
| Woman, |
| } |
| |
| struct Position |
| { |
| |
| } |
| |
| class Pet//宠物类 |
| { |
| |
| } |
| |
| class Person |
| { |
| |
| public string name; |
| public int age; |
| public E_SexType sex; |
| |
| public Person grilFriend; |
| public Person[] friend; |
| public Position pos; |
| public Pet pet = new Pet(); |
| } |
2.2 访问修饰符
- public 公共的,自己(内部)和别人(外部)都能访问使用
- private 私有的,自己才能访问和使用,不写默认为私有
- protected 保护的,自己和子类才能访问使用
- internal 内部的,同一个程序集的对象可以访问,程序集就是命名空间
2.3 成员变量的使用和初始值
| Person p = new Person(); |
| |
| |
| |
| |
| |
| |
| Console.WriteLine(default(bool)); |
| p.age = 10; |
3 成员方法
3.1 成员方法声明
- 声明在类语句块中,来描述对象的行为
- 和函数声明规则相同,受访问限制符影响
- 不需要加static关键字
| class Person |
| { |
| public string name; |
| public int age; |
| public Person[] friends; |
| |
| |
| public void Speak(string str) |
| { |
| Console.WriteLine($"{name}说{str}"); |
| } |
| |
| public bool isAudlt() |
| { |
| return age >= 18; |
| } |
| |
| public void AddFriend(Person p) |
| { |
| if (friends == null) |
| { |
| friends = new Person[] { p }; |
| } |
| else |
| { |
| Person[] newFriends = new Person[friends.Length + 1]; |
| for(int i = 0; i < friends.Length; i++) |
| { |
| newFriends[i] = friends[i]; |
| } |
| newFriends[newFriends.Length - 1] = p; |
| friends = newFriends; |
| } |
| } |
3.2 成员方法的使用
必须实例化对象,再通过对象来使用,相当于对象执行了某个行为
| Person p = new Person(); |
| p.name = "abc"; |
| p.age = 18; |
| p.Speak("123"); |
| |
| if (p.isAudlt()) p.Speak("我成年了"); |
| |
| Person p2 = new Person(); |
| p2.name = "def"; |
| p2.age = 24; |
| |
| p.AddFriend(p2); |
| |
| foreach(Person f in p.friends) |
| { |
| Console.WriteLine(f.name); |
| } |
4 构造函数析构函数、垃圾回收
4.1 构造函数
- 用处:在实例化对象时,会调用的用于初始化的函数,如果没写,默认存在一个无参的构造函数
- 没有返回值,函数名和类名相同,访问权限一般都是public
- 如果实现了有参构造函数且没有写无参构造函数,那么默认的无参构造函数就没有了,实例化对象时只能有参实例化
| class Person |
| { |
| public string name; |
| public int age; |
| |
| |
| |
| public Person() |
| { |
| name = "tyy"; |
| age = 24; |
| } |
| |
| |
| public Person(string name, int age) |
| { |
| this.name = name; |
| this.age = age; |
| } |
| |
| |
| |
| |
| public Person(string name):this() |
| { |
| Console.WriteLine("两个构造函数调用"); |
| } |
| } |
4.2 析构函数
- 与内存垃圾回收有关,C#有自动回收垃圾,几乎不用析构函数
- 析构函数只能在类中定义,不能用于结构体;
- 一个类中只能定义一个析构函数;
- 析构函数不能继承或重载;
- 析构函数没有返回值;
- 析构函数是自动调用的,不能手动调用;
- 析构函数不能使用访问权限修饰符修饰,也不能包含参数。
| class Person |
| { |
| public Person() |
| { |
| |
| } |
| ~Person() |
| { |
| |
| } |
| } |
4.3 垃圾回收
- 垃圾就是内存中没有被任何变量、对象引用的内容;
- 垃圾回收(Garbage Collecyor GC)就是遍历堆(Heap)上动态分配的所有对象,识别它们是否被引用来确定哪些对象是垃圾,哪些对象有用;
- GC只负责Heap内存的垃圾回收,引用类型都是存放在Heap中,因此它们的分配释放都通过GC来处理;
- 系统自动管理栈(Stack)内存的垃圾回收,值类型通过Stack分配内存,它们有自己的生命周期,自动分配释放;
- 垃圾回收算法:引用计数(Reference Counting)、标记清除(Mark Sweep)、标记整理(Mark Compact)、复制集合(Copy Collection)。
GC基本原理:
- 分代算法:把Heap内存分为 0代内存 1代内存 2代内存
- 新分配的对象存储在0代内存里,每次分配或(0 1 2代内存满)可能进行垃圾回收释放内存,执行下列两步:
- 1、标记对象:从根(静态字段、方法参数)开始检测引用对象,检测到的为可达对象,未检测到的为不可达对象(垃圾)
- 2、搬迁对象压缩堆:释放不可达对象,搬迁可达对象到下一代,修改它们的引用地址
- 1代内存满时会触发GC释放0 1代内存空间,2代满了0 1 2代内存都释放
5 成员属性
- 用于保护成员变量;
- 为成员属性的获取和赋值添加逻辑处理;
- 解决3P(public、private、protected)的局限性;
- 成员属性可以设置成员变量在外部的权限为只能获取不能修改 或 只能修改不能获取。
5.1 成员属性的基本语法
| |
| |
| |
| |
| |
| |
| |
| |
| class Person |
| { |
| private string name; |
| private int age; |
| private int money; |
| private bool sex; |
| |
| |
| public string Name |
| { |
| get |
| { |
| |
| return name; |
| } |
| set |
| { |
| |
| |
| name = value; |
| } |
| } |
| |
| public int Money |
| { |
| get |
| { |
| |
| return money - 5; |
| } |
| set |
| { |
| |
| if(value < 0) |
| { |
| value = 0; |
| Console.WriteLine("钱不能为负数"); |
| } |
| money = value + 5; |
| } |
| } |
| } |
5.2 成员属性的使用
| Person p = new Person(); |
| p.Name = "abc"; |
| Console.WriteLine(p.Name); |
| |
| p.Money = 1000; |
| Console.WriteLine(p.Money); |
5.3 get和set的访问修饰符
- 默认会使用属性的访问修饰符;
- 如果要加,权限要低于属性的访问权限;
- 不能让get和set的权限都低于属性的权限,要满足 只读不写 或 只写不读,解决3P的局限性。
5.4 get和set可以只有一个
| class Person |
| { |
| private string name; |
| private int age; |
| private int money; |
| private bool sex; |
| |
| public bool Sex |
| { |
| get { return sex; } |
| } |
| } |
5.5 自动属性
- 作用:外部只读不写的特征
- 如果类中有一个特征是只希望外部只读不写的,又没什么特殊处理,那么可以直接使用自动属性
| class Person |
| { |
| private string name; |
| private int age; |
| private int money; |
| private bool sex; |
| |
| public float Height |
| { |
| get; |
| private set; |
| } |
| } |
6 索引器
- 索引器(Indexer) 允许一个对象可以像数组一样使用下标的方式来访问;
- 当为类定义一个索引器时,该类的行为就会像一个 虚拟数组(virtual array) 一样。可以使用数组访问运算符 [ ] 来访问该类的的成员。
6.1 基本语法
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| class Person |
| { |
| private string name; |
| private int age; |
| private Person[] friends; |
| |
| public Person this[int index] |
| { |
| |
| get |
| { |
| if(friends == null || friends.Length - 1 < index) |
| { |
| return null; |
| } |
| return friends[index]; |
| } |
| set |
| { |
| |
| if(friends == null) |
| { |
| friends = new Person[] { value }; |
| }else if(index > friends.Length - 1) |
| { |
| friends[friends.Length - 1] = value; |
| } |
| else friends[index] = value; |
| } |
| } |
| } |
6.2 使用
| Person p = new Person(); |
| p[0] = new Person(); |
| Console.WriteLine(p[0]); |
6.3 索引器重载
| class Person |
| { |
| public int[,] array; |
| |
| |
| public int this[int i, int j] |
| { |
| get |
| { |
| return array[i, j]; |
| } |
| set |
| { |
| array[i, j] = value; |
| } |
| } |
| } |
7 静态成员
- 关键字 static
- 用static修饰的 成员变量、方法、属性等 称为静态成员
- 特点:直接用类名+点 使用,如Console.XX
7.1 自定义静态成员
| class Test |
| { |
| |
| public static float PI = 3.121592654f; |
| |
| public int testInt = 100; |
| |
| |
| |
| public static float CalcCircle(float r) |
| { |
| |
| return PI * r * r; |
| } |
| |
| |
| |
| public void testFun() |
| { |
| Console.WriteLine("123"); |
| Console.WriteLine(PI); |
| } |
| } |
7.2 静态成员使用
| Console.WriteLine(Test.PI); |
| Console.WriteLine(Test.CalcCircle(2)); |
| |
| |
| Test t = new Test(); |
| Console.WriteLine(t.testInt); |
| t.testFun(); |
7.3 为什么能够不实例化对象直接使用
- 静态成员在程序开始运行时就会分配内存空间,和程序同生共死,只要使用了静态成员,直到程序结束了它的内存才会被释放;
- 每个静态成员在内存里都有唯一的一块空间,因此类中只有一个该静态成员的实例,在任何地方使用都是改变那一个。
7.4 常量const和静态变量static
相同点:都可以使用类名+点使用
不同点:
- const必须初始化且不能修改;
- const只修饰变量;
- const一定写在访问修饰符后面,static可前可后。
8 静态类和静态构造函数
8.1 静态类
用 static 修饰的类,只能包含静态成员,不能被实例化
作用:
- 将常用的静态成员写在静态类中,方便使用;
- 静态类不能被实例化,体现工具类的唯一性 如Console就是一个静态类,它写在命名空间System里面。
| static class TestClass |
| { |
| public static int testIndex = 0; |
| public static void testFun() |
| { |
| |
| } |
| } |
8.2 静态构造函数
- 在构造函数加上static修饰;
- 不能使用访问修饰符,不能有参数,只会自动调用一次;
- 作用:在静态构造函数中初始化静态变量。
8.2.1 静态类 中的 静态构造函数
| static class StaticClass |
| { |
| |
| public static int testInt = 100; |
| public static int testInt2 = 100; |
| |
| static StaticClass() |
| { |
| Console.WriteLine("静态构造函数"); |
| } |
| } |
8.2.2 普通类 中的 静态构造函数
| class Test |
| { |
| public static int testInt = 200; |
| static Test() |
| { |
| Console.WriteLine("静态构造"); |
| } |
| public Test() |
| { |
| Console.WriteLine("普通构造"); |
| } |
| } |
8.3 使用
| Console.WriteLine(StaticClass.testInt); |
| |
| |
| Console.WriteLine(Test.testInt); |
| |
| Test t = new Test(); |
9 拓展方法
为现有 非静态 的 <变量类型> 添加新方法
作用:
- 提升程序的拓展性;
- 不需要在对象中重写方法或通过继承来添加方法;
- 为别人封装的类型写额外的方法。
特点:
- 一定写在静态类中
- 一定是个静态函数
- 第一个参数为拓展目标
- 第一个参数用this修饰
9.1 基本语法
访问修饰符 static 返回值 函数名(this 要拓展的类名 参数名,参数类型 参数名,......)
| static class Tools |
| { |
| |
| |
| public static void SpeakValue(this int value) |
| { |
| |
| Console.WriteLine("为int拓展的方法" + value); |
| } |
| |
| public static void SpeakStringInfo(this string str, string str2, string str3) |
| { |
| Console.WriteLine("为string拓展的方法,调用方法的对象是:" + str); |
| Console.WriteLine("传的参数:" + str2 + str3); |
| } |
| |
| |
| public static void Fun3(this Test t) |
| { |
| Console.WriteLine("为Test拓展的方法"); |
| } |
| } |
| |
| class Test |
| { |
| public int i = 10; |
| public void Fun1() |
| { |
| Console.WriteLine("123"); |
| } |
| public void Fun2() |
| { |
| Console.WriteLine("456"); |
| } |
| } |
9.2 使用
| int i = 10; |
| i.SpeakValue(); |
| |
| string s = "ABC"; |
| s.SpeakStringInfo("a ", "b"); |
| |
| Test t = new Test(); |
| t.Fun3(); |
10 运算符重载
10.1 基本概念
作用:让自定义的类和结构体能够使用运算符进行运算,关键字 operator
特点:
- 一定是一个公共的静态方法;
- 返回值写在operator前;
- 逻辑处理自定义。
注意:
- 条件运算符需要成对实现;
- 一个符号可以多个重载;
- 不能使用ref和out。
10.2 可重载和不可重载的运算符
注意:运算符需要两个参数还是一个参数
可重载的:
- 算数运算符:+ - * / % ++ --
- 逻辑运算符:!
- 位运算符:| & ^ ~ << >>
- 条件运算符:> < >= <= == != ,需要成对出现:(大于,小于)(大于等于,小于等于)(等于,不等于)
不可重载的:
- 逻辑与(&&) 逻辑或(||)
- 索引符 []
- 强转运算符 ()
- 特殊运算符
- 点. 三目运算符
10.3 基本语法
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; |
| } |
| |
| |
| public static Point operator +(Point p1, int value) |
| { |
| Point p = new Point(); |
| p.x = p1.x + value; |
| p.y = p1.y + value; |
| return p; |
| } |
| } |
10.4 使用
| Point p1 = new Point(); |
| p1.x = 1; |
| p1.y = 1; |
| Point p2 = new Point(); |
| p2.x = 2; |
| p2.y = 2; |
| |
| Point p3 = p1 + p2; |
| |
| Point p4 = p3 + 2; |
11 内部类和分部类
11.1 内部类
- 在一个类中再申明一个类
- 使用时要用包裹者点出内部类
- 实现类之间的亲密关系
| class Person |
| { |
| public int age; |
| public string name; |
| public Body body; |
| public class Body |
| { |
| Arm leftArm; |
| Arm rightArm; |
| class Arm |
| { |
| |
| } |
| } |
| } |
| |
| |
| Person p = new Person(); |
| Person.Body body = new Person.Body(); |
11.2 分部类
把一个类分成几部分申明。
作用:分部描述一个类,增加程序的拓展性,关键字 partial
11.3 分部方法
将方法的声明和实现分离。
特点:
- 不能加访问修饰符,默认私有
- 只能在分部类中声明
- 返回值只能是void
- 可以有参数,但不用out关键字
| |
| partial class Student |
| { |
| public string name; |
| public bool sex; |
| |
| |
| partial void Move(); |
| } |
| |
| partial class Student |
| { |
| public int number; |
| |
| partial void Move() |
| { |
| |
| throw new NotImplementedException(); |
| } |
| |
| public void Speak(string str) |
| { |
| |
| } |
| } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步