面向对象之封装四

构造函数和析构函数

一.构造函数

基本概念:

在实例化对象时,会调用的用于初始化的函数。

如果不写,默认存在一个无参构造函数。

构造函数的写法:

1.没有返回值

2.函数名和类名必须相同

3.没有特殊需求时,一般都是public

4.构造函数可以重载

5.this代表当前调用该函数的对象自己。

注意:

如果不自己实现无参构造函数而实现了有参构造函数,会失去默认的无参构造。(默认的无参构造函数会丢失)

(感觉和结构体好像哦)

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
namespace 语法知识
{
class Person{
public string name;
public int age;
//类中是允许自己申明无参构造函数的
//但是结构体是不允许的
public Person(){
name="大帅哥";
age=18;
//开始构造函数嘞。
}
//允许有重载,可以再写一个。
public Person(int age,string name){
//this 代表当前该调用函数的对象自己。
this.age=age;
//this代表类里面的age,另一个代表外面传进来的age(即形式参数)
this.name=name;
}
}
internal class Program
{
static void Main(string[] args){
Person p=new Person();
//这个时候其实调用的就是一个无参的构造函数了。
//在调用那个new Person时,其实就会执行上面那个无参构造函数。在执行时就会把我们的成员变量进行初始化了。
//和结构体高度相似了。
Console.WriteLine(p.age);
//这个时候age就是我们在构造函数时赋的值,age会打印出18.
Person p2=new Person(18,"大帅哥");
//默认调用另一个重载函数。

}
}
}

二.构造函数的特殊写法

可以通过this重用构造函数代码

访问修饰符 构造函数名(参数列表):this(参数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
namespace 语法知识
{
class Person{
public string name;
public int age;
//类中是允许自己申明无参构造函数的
//但是结构体是不允许的
public Person(){
name="大帅哥";
age=18;
//开始构造函数嘞。
}
//允许有重载,可以再写一个。
public Person(int age,string name):this(){
//注意,在这个构造函数中,参数列表就没有意义了。会首先调用this()(this()就代表那个无参的构造函数)所指的函数,之后再调用当前的这个函数的逻辑。
Console.WriteLine("两个");
}
}
internal class Program
{
static void Main(string[] args){
Person p=new Person(18,"哈哈");
Console.WriteLine(p.age);
}
//最后会先输出“两个”,再输出18.因为先执行了this函数指向的无参函数,让age等于18,之后再执行有参函数,输出“两个”这一字符串,之后再进到主函数中打印出18.
}
}

有参重构有参函数

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
namespace 语法知识
{
class Person{
public string name;
public int age;
//类中是允许自己申明无参构造函数的
//但是结构体是不允许的
public Person(){
name="大帅哥";
age=18;
//开始构造函数嘞。
}
//允许有重载,可以再写一个。
public Person(int age,string name):this(name){
Console.WriteLine("两个");
}
//传参传谁的?传你的那个构造函数,(就是this前面的函数),意思为:当执行到this前面那个函数时,它会把那个有两个参数的函数里面的name参数先传进去this所指的构造函数中,再进到this函数中进行逻辑执行。
//总结:你传的参数类型是什么,它this调用的构造函数就是什么。
//再构造一个有一个参数的有参函数
public Person(string name){
this.name=name;
}
}
internal class Program
{
static void Main(string[] args){
Person p=new Person(18,"哈哈");
Console.WriteLine(p.age);
}
}
}

无参重构有参函数

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
namespace 语法知识
{
class Person{
public string name;
public int age;
public Person()this(18){
name="大帅哥";
age=18;
}
//在这种情况下,this前面的函数括号里面没有参数形式了,也没有东西可以传进this所指的构造函数了,但是还是可以调用this函数的。可以在this里面写常量,会自动判别常量的类型,从而调用相应的this函数。
//可以看出,只要this()内的结果是参数类型,不一定要this前面函数的参数名,你也可以自己写一个变量。
public Person(int age,string name):{
Console.WriteLine("两个");
}
public Person(int age){
this.age=age;
}
}
internal class Program
{
static void Main(string[] args){
Person p=new Person(18,"哈哈");
Console.WriteLine(p.age);
}
}
}

三.析构函数(了解即可)

基本概念

当引用类型的堆内存被回收时,会调用该函数

对于需要手动管理内存的语言(比如C++),需要在析构函数中做一些内存回收处理。

但是C#中存在自动垃圾回收机制GC

所以我们几乎不会怎么使用析构函数,除非你想在某一个对象被垃圾回收时,做一些特殊处理。

注意:在Unity开发中,析构函数几乎不会使用,所以该知识点只做了解即可。

基本语法

~类名()

{

}

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
namespace 语法知识
{
class Person{
public string name;
public int age;
public Person()this(18){
name="大帅哥";
age=18;
}
public Person(int age,string name):{
Console.WriteLine("两个");
}
public Person(int age){
this.age=age;
}
//当引用类型的堆内存被回收时,会调用该函数
//但是,在C#中这一般不用我们去处理,C#有自动回收的机制,所以我们一般不用写这个函数,写一下只是为了方便看清楚罢了。
//析构函数是当你的的内存垃圾真正被回收时,才会调用的函数。
~Person(){

}
}
internal class Program
{
static void Main(string[] args){
Person p=new Person(18,"哈哈");
Console.WriteLine(p.age);
}
}
}

四.垃圾回收机制

垃圾回收,英文简写GC(Garbage Collector)

垃圾回收的过程是在遍历堆(Heap)上动态分配的所有对象

通过识别它们是否被引用来确定哪些对象是垃圾,哪些对象仍要被使用。

所谓垃圾就是没有被任何变量,对象引用的内容

垃圾就需要被回收释放

垃圾回收有很多种算法,比如:

引用计数(Reference Counting)

标记清理(Mark Sweep)

标记整理(Mark Compact)

复制集合(Copy Collection)

注意:

GC只负责堆(Heap)内存的垃圾回收

引用类型都是存在堆(Heap)中的,所以它的分配和释放都通过垃圾回收机制来管理

栈(Stack)上的内存是由系统自动管理的

值类型在栈(Stack)中分配内存的,他们有自己的生命周期,不用对他们进行管理,会自动分配和释放。

C#中内存回收机制的大概原理

0代内存 1代内存 2代内存

代的概念:

代是垃圾回收机制使用的一种算法(分代算法)

新分配的对象都会被配置在第0代内存中

每次分配都可能会进行垃圾回收以释放内存(0代内存满时)

在一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,会进行以下两步:

1.标记对象从根(静态字段、方法参数)开始检查引用对象,标记后为可达对象,未标记为不可达对象,不可达对象就认为是垃圾。

2.搬迁对象压缩堆 (挂起执行托管代码线程) 释放未标记的对象,搬迁可达对象,修改引用地址。

大对象总被认为是第二代内存,目的是减少性能损耗,提高性能

不会对大对象进行搬迁压缩 (85000字节(83kb)以上的对象为大对象)

我们也可以手动进行GC

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
namespace 语法知识
{
class Person{
public string name;
public int age;
public Person()this(18){
name="大帅哥";
age=18;
}
public Person(int age,string name):{
Console.WriteLine("两个");
}
public Person(int age){
this.age=age;
}
internal class Program
{
static void Main(string[] args){
Person p=new Person(18,"哈哈");
Console.WriteLine(p.age);
GC.Collect();
//手动触发垃圾回收的方法
//一般情况下我们不会频繁调用,都是在Loading过场景时才调用。
}
}
}

面向对象之封装四
https://gaster44.github.io/2023/12/03/面向对象之封装四/
作者
huangjinhong
发布于
2023年12月3日
许可协议