C#面向对象核心-封装

封装

封装定义为"把一个或多个项目封闭在一个物理的或者逻辑的包中",这个包就是类。在面向对象程序设计方法论中,封装可以防止对实现细节的访问。

1 类和对象

1.1 什么是类

具有相同特征、行为,是一类事物的抽象.

类是对象的模板,通过类创建对象

1.2 类声明语法


 
  //声明在namespace中
  /*class 类名
  {
  //成员变量 表示特征
  //成员方法 表示行为
  //成员属性 保护成员变量
  //构造函数和析构函数 初始化和释放
  //索引器 像数组一样使用
  //运算符重载 自定义对象可计算
  //静态成员 类名.点出成员使用
  }*/
  class Person
  {
   
  }

1.3 类对象

类声明和类对象声明是两个概念:

  • 类声明相当于定义了一个变量类型;

  • 类对象声明相当于声明一个类的变量,这个过程称为实例化对象;

  • 类对象是引用类型。


 
  //类名 变量名;
  //类名 变量名 = null;
  //类名 变量名 = new 类名();
  Person p1;
  Person p2 = null;//空,不分配堆的内存空间
   
  //p3 p4虽然来自一个类的实例化对象,但它们的特征行为等都是分别独有的,不会相互共享
  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;
  //如果在类中声明和自己类型相同的成员变量时,不能对它实例化:Person grilFriend = new Person();
  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();
  /* 成员变量的使用和初始值
  * 值类型、数字类型:默认为0
  * bool类型:默认为false
  * 引用类型:默认为null
  * 用default(变量类型) 关键字可以看默认值
  */
  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;
  }
   
  //构造函数可以被重载,this代表当前调用该函数的对象本身
  public Person(string name, int age)
  {
  this.name = name;
  this.age = age;
  }
   
  //构造函数特殊写法:通过this 复用构造函数代码
  //访问修饰符 构造函数名(参数):this(参数1,参数2...)。先调用this()里面的构造函数重载,再执行后面花括号内容
  //先调用this()里面的构造函数重载,再执行后面花括号内容
  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代内存都释放


 
  //手动出发垃圾回收的方法,一般在Loading切换场景时才调用
  GC.Collect();

5 成员属性

  • 用于保护成员变量;

  • 为成员属性的获取和赋值添加逻辑处理;

  • 解决3P(public、private、protected)的局限性;

  • 成员属性可以设置成员变量在外部的权限为只能获取不能修改 或 只能修改不能获取。

5.1 成员属性的基本语法


 
  /*
  访问修饰符 属性类型 属性名
  {
  get{} 读,写了get就一定要有一个返回值
  set{} 写
  }
  */
   
  class Person
  {
  private string name;
  private int age;
  private int money;
  private bool sex;
   
  //属性的命名用帕斯卡命名法
  public string Name
  {
  get
  {
  //可以在返回之前添加一些逻辑条件
  return name;//虽然name是私有的,但是通过成员属性也能得到它
  }
  set
  {
  //可以在设置之前添加一些逻辑条件
  //value关键字表示外部传入的值
  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";//执行set{}
  Console.WriteLine(p.Name);//p.Name执行get{}
   
  p.Money = 1000;
  Console.WriteLine(p.Money);//外部看是1000元,但在内存里面是1005元 起到了加密的作用

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; }//创造只读属性,private类型的成员也能读
  }
  }

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 基本语法


 
  /*
  element-type this[int index]
  {
  // get 访问器
  get
  {
  // 返回 index 指定的值
  }
   
  // set 访问器
  set
  {
  // 设置 index 指定的值
  }
  }
  */
  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
  {
  //和成员属性类似,value代表传入的值
  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();//调用set 得到1个朋友
  Console.WriteLine(p[0]);//调用get

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)
  {
  //π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));
   
  //普通的只有new了对象之后才能使用那些变量方法等
  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);
  //普通构造在new的时候调用
  Test t = new Test();

9 拓展方法

为现有 非静态 的 <变量类型> 添加新方法

作用:

  • 提升程序的拓展性;

  • 不需要在对象中重写方法或通过继承来添加方法;

  • 为别人封装的类型写额外的方法。

特点:

  • 一定写在静态类中

  • 一定是个静态函数

  • 第一个参数为拓展目标

  • 第一个参数用this修饰

9.1 基本语法

访问修饰符 static 返回值 函数名(this 要拓展的类名 参数名,参数类型 参数名,......)


 
  static class Tools
  {
  //为int拓展了一个成员方法,int里面是没有SpeakValue方法的
  //value 代表使用该方法的 实例化对象
  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);
  }
   
  //为自定义的类型Test拓展方法
  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;
   
  //重载‘+’成为类Point的加法
  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)
  {
   
  }
  }