当前位置: 首页 > article >正文

C#核心(5)构造,析构,垃圾回收

前言

也是有很长一段时间没有更新相关教程了,是因为十月份的taptap聚光灯比赛的缘故,作者几乎把精力全都放到了自己开发的项目上面,所以才没有更新博客,并不是不更新了。

上一节我们进行了对成员变量的讲解,既然有成员变量,那其实必然就会有成员方法,一个人他不可能只有属性,他还一定有技能。

在C#中,构造和析构函数以及垃圾回收起着重要的作用,它们有助于管理和释放内存资源。以下是它们的作用的解释:

  1. 构造函数(Constructor):构造函数是在创建类的实例(对象)时自动调用的特殊方法。它的主要作用是初始化对象的成员变量和执行其他必要的初始化操作。构造函数可以有参数,也可以是无参数的。它们通常被用来设置对象的初始状态,确保对象在被使用之前是可用的。

  2. 析构函数(Destructor):析构函数是类的特殊方法,用于在对象被销毁之前执行清理操作。它与构造函数相反,用于释放对象所占用的资源,如文件句柄、数据库连接、网络连接等。析构函数没有参数,且只能有一个。它在对象被垃圾回收之前被自动调用。

  3. 垃圾回收(Garbage Collection):垃圾回收是.NET Framework提供的一种自动内存管理机制。它的主要作用是自动释放不再使用的内存资源,避免内存泄漏和垃圾堆积的问题。垃圾回收器会周期性地扫描程序的堆内存,标记不再被引用的对象,并进行回收和整理。这种机制使得程序员不必手动释放内存,避免了常见的错误和内存泄漏的风险。

嗯,很抽象,没事,我慢慢带你了解。

构造函数

基本概念

在实例化对象时,调用来初始化的函数,你可以理解成,在对象生成时,这个函数会最先执行,将你想要执行的操作赋予给对象。

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

写法

  1. 没有返回值
  2. 函数名必须和类名相同
  3. 没有特殊情况,一般默认为public 
  4. 构造函数可以被重载
  5. this代表当前调用该函数自己

    tips:如果自己不实现无参构造函数而实现有参,默认失去无参构造

例子 

public class Person {
    private int age;
    private String name;

    public Person()//类中允许声明无参构造函数,结构体不允许
    {
        this.age ="ling1s";
        this.name = 18;
    }

   
}
 

构造函数特殊写法

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

示例:

public class MyClass
{
    private int value1;
    private int value2;

    // 构造函数1
    public MyClass() : this(0)
    {
    }

    // 构造函数2,调用构造函数3
    public MyClass(int value) : this(value, 0)
    {
    }

    // 构造函数3
    public MyClass(int value1, int value2)
    {
        this.value1 = value1;
        this.value2 = value2;
    }
}
 

在上面的示例中,我们定义了三个构造函数:MyClass()MyClass(int value)MyClass(int value1, int value2)。构造函数1和构造函数2都通过使用this关键字调用了构造函数3,以避免重复的代码。

使用构造函数链可以创建多个不同参数的构造函数,以便在不同的情况下初始化对象的属性。也就是说你可以通过传不同的参数构造不同的属性。

析构函数

C#中存在垃圾回收机制GC,除非想在某一对象被垃圾回收时,做一些特殊处理,才会使用,但在unity开发中一般是不会使用的。

语法
~类名()
{
}

下面是一个示例代码,展示了C#中的析构函数的用法:

using System;

public class MyClass
{
    private string name;

    public MyClass(string n)
    {
        name = n;
        Console.WriteLine("对象 {0} 被创建。", name);
    }

    ~MyClass()
    {
        Console.WriteLine("对象 {0} 被销毁。", name);
    }
}

public class Program
{
    public static void Main()
    {
        MyClass obj1 = new MyClass("对象1");
        MyClass obj2 = new MyClass("对象2");

        obj1 = null;
        obj2 = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
    }
}

在上面的代码中,我们创建了一个名为MyClass的类,它有一个构造函数和一个析构函数。构造函数用来初始化对象,而析构函数在对象被销毁时执行一些清理工作。

Program类的Main方法中,我们创建了两个MyClass对象obj1obj2。然后,我们将这些对象设置为null,这样它们就不再被引用了,并且可以被垃圾回收器回收。

最后,我们调用GC.Collect()方法和GC.WaitForPendingFinalizers()方法,以确保垃圾回收器在我们退出程序之前运行,并且调用所有未调用的析构函数。在控制台输出中,我们可以看到析构函数被调用的消息。你可以自己尝试去试一下,因为在unity中几乎不使用,这里就不重点讲解了。

垃圾回收机制(GC)

垃圾回收机制的过程是在遍历堆(Heap)上动态分配的所有对象通过识别他们是否被引用来确定他们是不是垃圾,如果没被引用了,那就是垃圾,就应该释放。

tips:GC只负责Heap内存的垃圾回收,值类型在栈(stack)中分配,有自己的生命周期(到点就会噶),不用管理,会自动分配释放

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

在C#中,内存回收机制是由垃圾回收器(Garbage Collector)负责管理和释放不再使用的内存。垃圾回收器会自动扫描程序中的对象,找出不再被引用的对象,并将其标记为垃圾。随后,垃圾回收器会回收这些垃圾对象所占用的内存空间,并将其重新分配给新的对象。

为了更高效地管理内存,垃圾回收器将内存分为三代:0代内存、1代内存和2代内存。每个代表了对象的存活时间和垃圾回收的频率。

0代内存:当一个对象被创建时,它会被分配到0代内存中。0代内存是垃圾回收器会直接回收的内存区域,回收频率较高。通常情况下,如果一个对象在0代内存中存活一段时间,并且没有变成垃圾,它将被提升到1代内存。

1代内存:1代内存是相对于0代内存而言的。当一个对象在0代内存中存活一段时间后,垃圾回收器将会将其提升到1代内存中。1代内存的回收频率相对较低,因为在这里的对象通常能够更长时间地存在。

2代内存:2代内存是相对于1代内存而言的。当一个对象在1代内存中存活一段时间后,垃圾回收器将会将其提升到2代内存中。2代内存的回收频率最低,因为这里的对象通常是程序中存活时间最长的对象。

通过将内存分为不同的代,垃圾回收器能够更加有效地管理内存,提高程序的性能和效率。不同代的内存回收频率也是动态调整的,垃圾回收器会根据不同代的内存状态和使用情况,动态调整垃圾回收的频率和策略。

代的概念

代是垃圾回收机制使用的一个算法(分代算法,感兴趣可以了解一下),新分配的对象都会被配置在第0代内存中,每次分配都可能先进行GC,当0代内存存满了后。

C#中垃圾回收过程大概如下

博主自行整理如下:

  1. 标记对象,从跟(静态字段,方法参数)开始检查,标记可达对象,不可达就是垃圾

  2. 搬迁对象,压缩堆

  3. 释放未标记对象,修改引用地址(85000字节以上)

    tips:大对象被认为是二代内存,是为了提高性能。

gpt解释如下:

C#中的内存回收机制基于垃圾回收器(Garbage Collector, GC)。

  1. 对象创建:当程序创建一个对象时,垃圾回收器会在托管堆(Managed Heap)中为其分配内存空间。

  2. 引用跟踪:垃圾回收器会跟踪所有的引用类型对象,并在程序执行时检查这些对象是否仍然被活动的引用所使用。如果一个对象没有被任何活动的引用所引用,则被判定为垃圾对象。

  3. 垃圾标记:在托管堆中,垃圾回收器会标记所有的活动对象,并将它们标记为“非垃圾”。同时,未标记的对象则被认为是垃圾对象。

  4. 垃圾回收:一旦垃圾回收器完成了垃圾标记,它会在堆上进行垃圾回收操作。在回收过程中,垃圾回收器会从堆中删除垃圾对象,并释放它们所占用的内存空间。

  5. 内存整理:垃圾回收器还会执行内存整理操作,以消除堆中的内存碎片。内存整理可以提高内存的利用率,减少内存分配的开销。

需要注意的是,垃圾回收器的具体实现会因不同版本的.NET框架而有所差异。有时候,开发人员也可以手动控制垃圾回收的行为,例如通过调用GC.Collect()方法来强制进行垃圾回收。

手动方法

GC.Collect();//手动触发,一般不会频繁调用,都在游戏loading时调用

总结

这一节的内容比较干,理解起来比较困难,主要掌握构造函数的使用,对于析构和垃圾回收,做了解就好,如果是面向unity方向的话,这两个的使用频率不高,一般不是主要的知识点,但如果是面向.NET的工作,建议还是仔细翻看其他博主的文章或者相关教学视频。

另外,还是希望你多敲代码,不要只是看。

至此,与诸君共勉!

请期待我的下一篇博客!


http://www.kler.cn/a/377454.html

相关文章:

  • 【RK3588 Linux 5.x 内核编程】-等待队列(WaitQueue)
  • 嵌入式Linux入门具备:C语言基础与基本驱动学习(2):Linux GIibc IO基础
  • 黑马官网2024最新前端就业课V8.5笔记---HTML篇
  • Python学习笔记-生成器的应用与原理
  • Spring Boot 注解大全:全面解析 Spring Boot 常用注解及其应用场景
  • mysql 查看数据库、表的基本命令
  • 开源模型应用落地-qwen模型小试-Qwen2.5-7B-Instruct-玩转ollama-Modelfile文件(二)
  • 光耦合器的关键作用和创新---腾恩科技
  • 力扣1658.将x减到0的最小操作数
  • vue-verify-plugin。vue项目表单验证插件
  • Unity3D学习FPS游戏(8)装弹和弹夹UI显示
  • Android开发之——SOLID基础设计原则(掌握23种设计模式)其实开发之路如此简单
  • 苹果转向 Apple Silicon,Intel Mac 的支持时限倒计时
  • 通用型函数——冒泡排序
  • 商务英语学习柯桥学外语到泓畅-老外说“go easy on me”是什么意思?
  • spring-解析Scope注解
  • golang switch v := data.(type)
  • Flarum:简洁而强大的开源论坛软件
  • 活动回顾丨艾体宝《开源软件供应链安全的最佳实践》线下研讨会圆满落幕!
  • 五、SpringBoot3实战(1)
  • docker对nginx.conf进行修改后页面无变化或页面报错
  • 【运动的&足球】足球场地区域图像分割系统源码&数据集全套:改进yolo11-RFAConv
  • 提高交换网络可靠性之端口安全配置
  • 项目自动化构建工具——make与Makefile详解
  • 高效实现SCRM用户管理的最佳实践与策略
  • DB-GPT系列(三):底层大模型设置(开源模型、在线模型)