C#自学笔记:封装与类成员
核心
封装
类和对象
万物皆对象 用程序来抽象对象 用面向对象的思想来编程什么是类
类声明的语法
class 类名{
//特征——成员变量
//行为——成员方法
//保护特征——成员属性
//构造函数和析构函数
//索引器
//运算符重载
//静态成员
}
什么是(类)对象
- 类声明和类对象(变量)声明是两个概念
- 类声明类似于枚举和结构体的声明,类的声明相当于申明了一个自定义变量类型
- 而对象 是类创建出来的
- 相当于声明一个指定的类的变量
- 类创建对象的过程 一般称为实例化对象
- 类对象 都是引用类型的
实例化对象基本语法
类名 变量名 = new 类名();
成员变量和访问修饰符
成员变量
- 声明在类语句中
- 用来描述对象的特征
- 可以是任意变量类型
- 数量不做限制
- 是否赋值根据需求来定
- 注意:如果要在类中声明一个和自己相同类型的成员变量时 不能对它进行实例化
访问修饰符
public——公共的 自己(内部)和别人(外部)都能访问和使用private——私有的 自己(内部)才能访问和使用 不写 默认为private
protected——保护的 自己(内部) 和子类才能访问和使用
internal——只能在该程序集中使用
abstract——抽象类
sealed——密封类
partial——分部类
成员变量的使用和初始值
值类型来说 数字类型 默认值都是0bool类型 false
引用类型 null
看默认值的技巧:default(变量类型) 就能得到默认值
成员方法
成员方法声明
- 声明在类语句块中
- 是用来描述对象的行为的
- 规则和函数声明规则相同
- 受到访问修饰符规则影响
- 返回值参数不做限制
- 方法数量不做限制
注意:
- 成员方法不要加static关键字
- 成员方法必须实例化出对象,再通过对象来使用,相当于该对象执行了某个行为
例子:
class Person
{
//在C++中,方法一般写在变量的前面
//因为程序是从上往下读的,在main里面执行代码
//如果函数放在main后面,程序是不会读到该方法的,默认main函数后面的代码无效
//如果要使用写在main函数后面的方法,就要在main函数前面声明这个函数,算是多此一举了
//在C#中,方法一般写在变量的后面
public void Speak(string str)
{
Console.WriteLine("{0}说{1}", name, str);
}
public string name;
public int age;
}
成员方法的使用
Person p = new Person();
p.name = "柠凉";
p.age = 18;
p.Speak("游戏开发者们好");
构造函数和析构函数
构造函数
基本概念在实例化对象时会调用的用于初始化的函数
如果不写 默认存在一个无参构造函数
构造函数的写法
- 没有返回值
- 函数名和类名必须相同
- 没有特殊需求时 一般都是public的
- 构造函数可以被重载
- this代表当前调用该函数的对象自己
注意:
如果不自己实现无参构造函数而实现了有参构造函数,会失去默认的无参构造
意思就是实例化的时候不能空着,必须要传入参数,否则会报错
构造函数的特殊写法
可以通过this重用构造函数代码访问修饰符 构造函数名(参数列表):this(参数1, 参数2...)
意思是先调用自己的构造函数(参数1, 参数2...),包括空参构造函数,再执行当前的构造函数里面的代码
例子:
public Person(int age)
{
this.age = age;
}
public Person(int age, string name):this(age)//甚至(age + 10)也可以
{
Console.WriteLine("Person两个参数构造函数调用");
}
//先调用第一个构造函数,再进入执行打印代码
//当没有参数可以传进去时,可以写死,也可以写一个常量
public Person():this(18)
{
name = "柠凉";
age = 18;
}
析构函数
基本概念
当引用类型的堆内存被回收时,会调用该函数
对于需要手动管理内存的语言(比如C++),需要在析构函数中做一些内存回收处理
但是C#中存在自动垃圾回收机制GC
所以我们几乎不会怎么使用析构函数,除非你想在某一个对象被垃圾回收时,做一些特殊处理
注意:
在Unity开发中析构函数几乎不会使用,所以该知识点只做了解即可
析构函数用于在结束程序(比如关闭文件、释放内存等等)之前释放资源。析构函数不能继承或重载。
基本语法
不写返回值
不写修饰符
不能有参数
函数名和类名相同
~类名()
{
}
例子:
class Person()
{
//析构函数是当垃圾真正被回收的时候才会调用的函数
~Person()
{
}
}
垃圾回收机制
垃圾回收,英文简写GC(Garbage Collector)垃圾回收的过程是遍历在堆(Heap)上动态分配的所有对象
通过识别它们是否被引用来确定哪些对象是垃圾,哪些对象仍要被使用
所谓的垃圾就是没有被任何变量、对象引用的内容
垃圾就需要被回收释放
垃圾回收有很多种算法,比如:
- 引用计数(Reference Counting)
- 标记清除(Mark Sweep)
- 标记整理(Mark Compact)
- 复制集合(Copy Collection)
注意:
GC只负责堆(Heap)内存的垃圾回收
引用类型都是存在堆(Heap)中的,所以它的分配和释放都通过垃圾回收机制来管理
栈(Stack)上的内存是由系统自动管理的
值类型是在栈(Stack)中分配内存的,它们有自己的生命周期,不用对它们进行管理,会自动分配和释放
C#中内存回收机制的大致原理
分为:0代内存、1代内存、2代内存代的概念:代是垃圾回收机制使用的一种算法(分代算法)
- 新分配的对象都会被配置在第0代内存中
- 每次分配都可能会进行垃圾回收以释放内存(0代内存满时)
在一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,会进行以下两步
- 标记对象
- 从根(静态字段、方法参数)开始检查引用对象
- 标记后为可达对象
- 未标记为不可达对象
- 不可达对象就认为是垃圾
- 搬迁对象压缩堆
- (挂起执行托管代码线程)
- 释放未标记对象
- 搬迁可达对象
- 修改引用地址
大对象总被认为是第二代内存,目的是减少性能损耗,提高性能
不会对大对象进行搬迁压缩,85000字节(83kb)以上的对象为大对象
手动触发垃圾回收的方法
一般情况下,我们不会频繁调用都是在Loading过场景时才调用
GC.Collect();
成员属性
基本概念
- 用于保护成员变量
- 为成员属性的获取和赋值添加逻辑处理
- 解决3p的局限性
- public——内外访问
- private——内部访问
- protected——内部和子类访问
属性可以让成员变量在外部只能获取不能修改或者只能修改不能获取
基本语法
访问修饰符 属性类型 属性名
{
get{}
set{}
}
例子:
class Person
{
private string name;
private int age;
//属性的命名一般使用帕斯卡命名法
public string Name
{
get
{
//可以在返回之前添加一些逻辑规则、加密处理
//意味着这个属性可以获取的内容
return name;
}
set
{
//可以在返回之前添加一些逻辑规则、加密处理
//value关键字用于表示外部传入的值
name = value;
}
}
}
get和set前可以加访问修饰符
1. 默认不加,会使用属性声明时的访问权限 2. 加的访问修饰符要低于属性的访问权限 3. 不能让get和set的访问权限都低于属性的权限,要不然外面属性的访问修饰符就没用了get和set可以只有一个
只有一个时,不能在前面加访问修饰符,加了会报错
一般情况下只会出现只有get的情况,基本不会出现只有set
自动属性
作用:外部能得不能改的特征如果类中有一个特征是只希望外部能得不能改的,又没有什么特殊处理(加密解密、约束、判断)
那么可以直接使用自动属性
例子:
public float Height
{
get;
private set;
}
索引器
基本概念
为了可以访问类中的数组的元素,使程序看起来更直观,更容易编写
相当于C++中的重载[]运算符
索引器语法
访问修饰符 返回值 this[参数类型 参数名, 参数类型 参数名...]
{
//内部的写法和规则和属性相同
get{}
set{}
}
例子:
class Person
{
private string name;
private int age;
private Person[] friends;
public Person this[int index]
{
get
{
//可以写逻辑,根据需求来处理这里面的内容
if (friends == null || index > friends.Length - 1)
{
return null;
}
return friends[index];
}
set
{
//可以写逻辑,根据需求来处理这里面的内容
if (friends == null)
{
friends = new Person[] { value };
} else {
//value代表传入的值
friends[index] = value;
}
}
}
}
索引器的使用
Person p = new Person();
p[0] = new Person();//没有使用索引器时只能用p.friends[0]
索引器重载
class Person
{
private string name;
private int age;
private Person[] friends;
public Person this[int index]
{
get
{
return friends[index];
}
set
{
//value代表传入的值
friends[index] = value;
}
}
private int[,] array;
//正常重载
public int this[int i, int j]
{
get
{
return array[i, j];
}
set
{
array[i, j] = value;
}
}
//自己写的索引器
public string this[string str]
{
get
{
switch(str)
{
case "name":
return this.name;
case "age":
return age.ToString();
}
return "";
}
}
}
使用
Person p = new Person();
p[0, 0] = 10;
Console.WriteLine(p["name"]);
索引器适用场景
比较适用于在类中有数组变量时使用,可以方便的访问和进行逻辑处理
注意:结构体里面也是支持索引器的
静态成员
- 一旦使用static修饰成员方法,那么这就成为了静态方法。静态方法不属于对象,而是属于类的。
- 如果没有static关键字,那么必须首先创建对象,然后通过对象才能使用它。
如果有了static关键字,那么不需要创建对象,直接就能通过类名称来使用它。
静态关键字的使用
无论是成员变量,还是成员方法。如果有了static,都推荐使用类名称进行调用。 静态变量:类名称.静态变量 静态方法:类名称.静态方法()obj/*对象*/.methodStatic();//不推荐
Myclass/*类*/.methodStatic();//推荐
- 注意事项:
- 静态不能直接访问非静态。
原因:因为在内存当中是【先】有的静态内容,【后】有的非静态内容。
“先人不知道后人,但是后人知道先人。” - 静态方法当中不能用this。
原因:this代表当前对象,通过谁调用的方法,谁就是当前对象。
- 静态不能直接访问非静态。
class Myclass
{
int num;
static int numStatic;
public void method() {
Console.WriteLine("这是一个成员方法");
Console.WriteLine(num);
Console.WriteLine(numStatic);
}
public static void methodStatic() {
Console.WriteLine("这是一个静态方法");
Console.WriteLine(numStatic);
//Console.WriteLine(num);//错误写法,静态不能直接访问非静态
//Console.WriteLine(this);//静态方法不能使用this关键字
MyClass c = new MyClass();
Console.WriteLine(c.num);//这样就可以使用非静态成员变量
}
}
静态代码块的语法
public class 类名称 {
static {
// 静态代码块的内容
}
}
- 特点:当第一次用到本类时,静态代码块执行唯一的一次。
静态内容总是优先于非静态,所以静态代码块比构造方法先执行。 - 静态代码块的典型用途:
用来一次性地对静态成员变量进行赋值。
为什么可以直接使用的底层原理
程序是不能无中生有的我们要使用的对象,变量,函数都是要在内存中分配内存空间的
之所以要实例化对象,目的就是分配内存空间,在程序中产生一个抽象的对象
静态成员的特点
程序开始运行时,就会分配内存空间,所以可以直接使用
静态成员和程序同生共死
只要使用了它,直到程序结束时内存空间才会被释放
所以一个静态成员会有自己唯一的一个“内存小房间”
这让静态成员有了唯一性
在任何地方使用都是用的小房间里的内容,改变它也是改变小房间的内容
静态关键字的作用
- 常用唯一变量的声明
- 方便别人获取的对象声明
- 常用的唯一方法的声明,比如:相同规则的数学计算相关函数
缺点:
静态过多时,固定占用的空间大,不会被释放,所以要慎用
常量和静态变量的区别
const(常量)可以理解为特殊的static(静态)相同点
它们都可以通过类名.来使用
不同点
- const必须初始化,不能修改。static没有这个规则
- const只能修饰变量。static可以修饰很多
- const一定是写在访问修饰符后面的。static没有这个要求
静态类和静态构造函数
静态类
用static修饰的类特点
只能包含静态成员
不能被实例化
作用
- 将常用的静态成员写在静态类中,方便使用
- 静态类不能被实例化,更能体现工具类的唯一性
比如:Console就是一个静态类
静态构造函数(非常少用)
在构造函数加上static修饰特点
- 静态类和普通类都可以有
- 不能使用访问修饰符
- 不能有参数
- 只会自动调用一次
作用
在静态构造函数中初始化静态变量
使用
- 静态类中的静态构造函数
static class StaticClass
{
public static int testInt = 100;
public static int testInt2 = 100;
static StaticClass()
{
Console.WriteLine("静态构造函数");
testInt = 200;
testInt2 = 300;
}
}
- 普通类中的静态构造函数
class Test
{
public static int testInt = 200;
//静态构造函数不算重载
static Test()
{
Console.WriteLine("静态构造");
}
public Test()
{
Console.WriteLine("普通构造");
}
}
class Program
{
static void Main(string[] args)
{
Test t = new Test();
//"静态构造"
//"普通构造"
Test t2 = new Test();
//"普通构造"
Console.WriteLine(Test.testInt);
//假设这个打印在第一行时,会先打印"静态构造"
//200
}
}
第一次使用类时,会自动调用静态构造函数
拓展方法
概念
为现有非静态变量类型添加新方法
作用
- 提升程序拓展性
- 不需要在对象中重新写方法
- 不需要通过继承来添加方法
- 为别人封装的类型写额外的方法
特点
- 一定是写在静态类中
- 一定是个静态函数
- 第一个参数为拓展目标
- 第一个参数用this修饰
基本语法
访问修饰符 static 返回值 函数名(this 拓展类名 参数名, 参数类型 参数名, 参数类型 参数名...)
{
}
举例
static class Tools
{
//为int拓展了一个成员方法
//成员方法是需要实例化对象后才能使用的
//value代表使用该方法的实例化对象
public static void SpeakValue(this int value, int v1, int v2)
{
//拓展方法的逻辑
Console.WriteLine("柠凉为int拓展的方法" + value);
Console.WriteLine("传的参数" + v1 + v2);
}
}
使用
int i = 10;
i.SpeakValue(12, 13);
//柠凉为int拓展的方法10
//传的参数1213
注意:如果拓展方法名和原有的方法重名,则拓展的方法就没用了
运算符重载
概念:
让自定义类和结构体能够使用运算符+-等等
关键字
operator
特点
- 一定是一个公共的静态方法
- 返回值写在operator前
- 逻辑处理自定义
- 至少有一个参数是本类的
- 参数个数看运算符
作用
为了让自定义类和结构体对象可以进行运算
注意:
- 条件运算符需要成对实现(比如说重载了>符号,就要重载<符号,否则报错)
- 一个符号可以多个重载
- 不能使用ref和out
基本语法
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;
}
}
使用
Point p = new Point();
p.x = 1;
p.y = 1;
Point p2 = new Point();
p2.x = 2;
p2.y = 2;
Point p3 = p + p2;//p3.x = 3, p3.y = 3
可重载的运算符
- 算数运算符
1.-
2. *
3. /
4. %
5. ++
6. -- - 逻辑运算符
1.! - 位运算符
1.|
2. &
3. ^
4. ~
5. <<
6. >> - 条件运算符
1.>
2. <
3. >=
4. <=
5. ==
6. !=
不可重载的运算符
- 逻辑与&&和逻辑非||
- 索引符[]
- 强转运算符()
- 特殊运算符
- 点.
- 三目运算符? :
- 赋值符号=
内部类和分部类(了解即可)
内部类
- 概念
在一个类中再声明一个类
- 特点
使用时要用包裹者点出自己
- 作用
亲密关系的变现
- 注意
访问修饰符作用很大
class Person
{
public int age;
public string name;
public Body body;
public class Body
{
Arm leftArm;
Arm rightArm;
class Arm
{
}
}
}
class Program
{
static void Main(string[] args)
{
Person p = new Person();
Person.Body body = new Person.Body();
}
}
分部类
- 概念
把一个类分成几部分声明
- 关键字
partial
- 作用
分部描述一个类
增加程序的拓展性
- 注意
分部类可以写在多个脚本文件中
分部类的访问修饰符要一致
分部类中不能有重复成员
partial class Student
{
public bool sex;
public string name;
//分部方法
partial void Speak();
}
partial class Student
{
public int number;
//分部方法实现
partial void Speak()
{
//实现逻辑
//(下面是未写逻辑时自动抛出异常,写了逻辑后可以删除)
throw new NotImplementedException();
}
public void Speak(string str)
{
}
}
分部方法
- 概念
将方法的声明和实现分离
- 特点
- 不能加访问修饰符,默认私有
- 只能在分部类中声明
- 返回值只能是void
- 可以有参数但不能用out关键字
局限性大,了解即可

浙公网安备 33010602011771号