6.构造函数、析构函数和垃圾回收

6.面向对象-封装-构造函数和析构函数


6.1 知识点

构造函数

基本概念

  • 在实例化对象时,会调用用于初始化的函数。
  • 如果不写,默认存在一个无参构造函数。

构造函数的写法

  • 没有返回值。
  • 函数名和类名必须相同。
  • 一般为 public,除非有特殊需求。
  • 构造函数可以被重载。
  • this 代表当前调用该函数的对象自己。

注意:

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

声明人类

class Person
{
    public string name;
    public int age;
    // 人类内部
}

人类内部

public Person()
{
    name = "小林";
    age = 18;
}

public Person(int personAge)
{
    age = personAge;
}

public Person(string name)
{
    // this 代表当前调用该函数的对象自己 
    // 有参构造函数参数名和自己成员变量名一样时使用 this
    this.name = name;
}

主函数内

Person p1 = new Person();        // 无参构造函数
Person p2 = new Person(20);      // 有参构造函数
Person p3 = new Person("林浩僧");// 有参构造函数重载

构造函数特殊写法

  • 可以通过 this 重用构造函数代码。
  • 访问修饰符 构造函数名(参数列表):this(参数1,参数2….)。
  • 这样的话会先调用 this 后面重载的构造函数,再返回当前的构造函数。
  • 后面重载的构造函数的参数可以传任意对应的类型。

人类内部

public Person(int age, string name):this(age + 10)
{
    Console.WriteLine("Person两个参数构造函数调用");
}

主函数内

Person p4 = new Person(18, "林文韬");
Console.WriteLine(p4.age); // 28 
// 通过 public Person(int age, string name): this(age + 10)
// 重用构造函数代码,age比传进去的年龄+了10

析构函数

基本概念

  • 当引用类型的堆内存被回收时,会调用该函数。
  • 在C#中,由于存在自动垃圾回收机制(GC),极少需要使用析构函数。

注意:

  • 在Unity开发中析构函数几乎不会使用,因此了解即可。

基本语法

// ~类名()
// {
// }

人类内部

// 析构函数是当引用类型的堆内存(垃圾)真正被回收时才会调用的函数
~Person()
{

}

主函数内

p4 = null;
// 设置为 null 后,引用类型的堆内存虽然变成了垃圾,但是没有立即触发垃圾回收

垃圾回收机制

基本概念

  • 垃圾回收,英文简写GC(Garbage Collector)。
  • 过程是在遍历堆(Heap)上动态分配的所有对象,通过识别是否被引用来确定哪些对象是垃圾,哪些对象仍需被使用。
  • 垃圾即没有被任何变量或对象引用的内容,需要被回收释放。

垃圾回收算法

  • 引用计数(Reference Counting)
  • 标记清除(Mark Sweep)
  • 标记整理(Mark Compact)
  • 复制集合(Copy Collection)

注意:

  • GC只负责堆(Heap)内存的垃圾回收。
  • 引用类型都存在堆(Heap)中,因此它们的分配和释放都通过垃圾回收机制来管理。
  • 栈(Stack)上的内存是由系统自动管理的。
  • 值类型在栈(Stack)中分配内存,有自己的生命周期,不需要手动管理,会自动分配和释放。

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

  • 垃圾会被分为 0代内存、1代内存、2代内存。
  • 代是垃圾回收机制使用的一种算法(分代算法)。
  • 新分配的对象都会被配置在第0代内存中。
  • 在一次内存回收过程开始时,垃圾回收器会认为堆中全是垃圾,进行以下两步:
    1. 标记对象:从根(静态字段、方法参数)开始检查引用对象,标记为可达对象,未标记为不可达对象,不可达对象即为垃圾。
    2. 搬迁对象压缩堆:释放未标记的对象,搬迁可达对象,并修改引用地址。
  • 大对象总被认为是第二代内存,目的是减少性能损耗,提高性能。
  • 不会对大对象进行搬迁压缩,85000字节(83kb)以上的对象为大对象。

主函数内

// 手动触发垃圾回收的方法 
// 一般情况下,不会频繁调用
// 通常在 Loading 过场景时才调用
GC.Collect();

总结

构造函数

  • 实例化对象时调用的函数,主要用于初始化成员变量。
    • 基本语法:
      • 不写返回值。
      • 函数名和类名相同。
      • 访问修饰符根据需求而定,一般为 public
    • 注意:
      • 可以重载构造函数。
      • 可以用 this 语法重用代码。
      • 可以在函数中用 this 区分同名参数和成员变量。
      • 有参构造会顶掉默认的无参构造。

析构函数

  • 当对象被垃圾回收时调用,主要用于回收资源或特殊处理内存。
    • 基本语法:
      • 不写返回值。
      • 不写修饰符。
      • 不能有参数。
      • 函数名和类名相同,前面加 ~.

垃圾回收机制

  • 垃圾回收的过程是在遍历堆上动态分配的所有对象,通过识别是否被引用来确定哪些对象是垃圾。
  • 引用类型的分配和释放通过垃圾回收机制管理。
  • C#中的垃圾回收算法有多种,通常采用分代算法。
  • GC只负责堆内存的垃圾回收,栈上的内存由系统自动管理。
  • 值类型在栈上分配内存,有自己的生命周期,不需要手动管理。

6.2 知识点代码

using System;

namespace Lesson4_封装_构造函数和析构函数
{
    #region 人 类

    //声明 人 类
    class Person
    {
        public string name;
        public int age;

        //人 类 内部

        #region 知识点一 构造函数

        //类中是允许自己申明无参构造函数的
        //结构体是不允许 必须申明有参构造函数的

        //无参构造函数
        //不声明任何构造函数的话 默认就有无参构造函数
        //声明有参构造函数但是不声明无参构造函数的话 默认的无参构造函数会被代替掉
        public Person()
        {
            name = "小林";
            age = 18;
        }

        //有参构造函数
        public Person(int personAge)
        {
            age = personAge;
        }

        //有参构造函数
        public Person(string name)
        {
            //this代表当前调用该函数的对象自己 
            //有参构造函数参数名和自己成员变量名一样时 使用this
            this.name = name;
        }

        #endregion

        #region 知识点二 构造函数特殊写法

        //可以通过this 重用构造函数代码
        //访问修饰符 构造函数名(参数列表):this(参数1,参数2....)
        //这样的话会先调用this后面重载的构造函数 在返回当前的构造函数
        //后面重载的构造函数的参数可以传任意对应的类型,写死也行
        public Person(int age, string name) : this(age + 10)
        {
            Console.WriteLine("Person两个参数构造函数调用");
        }

        #endregion

        #region 知识点三 析构函数

        //析构函数 是当引用类型的堆内存(垃圾)真正被回收时 才会调用的函数
        ~Person()
        {

        }

        #endregion
    }

    #endregion

    #region 知识点一 构造函数
    //基本概念
    //在实例化对象时 会调用的用于初始化的函数
    //如果不写 默认存在一个无参构造函数

    //构造函数的写法
    //1.没有返回值
    //2.函数名和类名必须相同
    //3.没有特殊需求时 一般都是public的
    //4.构造函数可以被重载
    //5.this代表当前调用该函数的对象自己

    //注意:
    // 如果不自己实现无参构造函数而实现了有参构造函数
    // 会失去默认的无参构造
    #endregion

    #region 知识点二 构造函数特殊写法
    //可以通过this 重用构造函数代码
    //访问修饰符 构造函数名(参数列表):this(参数1,参数2....)
    //这样的话会先调用this后面重载的构造函数 在返回当前的构造函数
    //后面重载的构造函数的参数可以传任意对应的类型,写死也行
    #endregion

    #region 知识点三 析构函数
    //基本概念
    //当引用类型的堆内存被回收时,会调用该函数
    //对于需要手动管理内存的语言(比如C++),需要在析构函数中做一些内存回收处理
    //但是C#中存在自动垃圾回收机制GC
    //所以我们几乎不会怎么使用析构函数。除非你想在某一个对象被垃圾回收时,做一些特殊处理

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

    //基本语法
    // ~类名()
    // {
    // }


    #endregion

    #region 知识点四 垃圾回收机制

    //基本概念
    //垃圾回收,英文简写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.搬迁对象压缩堆(比如不是垃圾的0代会搬到1代去)  (挂起执行托管代码线程) 释放未标记的对象 搬迁可达对象 修改引用地址

    //大对象总被认为是第二代内存  目的是减少性能损耗,提高性能
    //不会对大对象进行搬迁压缩  85000字节(83kb)以上的对象为大对象
    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("构造函数和析构函数");

            //主函数内

            #region 知识点一 构造函数
            Person p1 = new Person();//无参构造函数
            Person p2 = new Person(20);//有参构造函数
            Person p3 = new Person("林浩僧");//有参构造函数重载
            #endregion

            #region 知识点二 构造函数特殊写法
            Person p4 = new Person(18,"林文韬");
            Console.WriteLine(p4.age);//28 
            //通过public Person(int age, string name) : this(age + 10)
            //重用构造函数代码 age比传进去的年龄+了10
            #endregion

            #region 知识点三 析构函数
            p4 = null;
            //设置为null后 引用类型的堆内存虽然变成了垃圾 但是没有立即触发垃圾回收
            #endregion

            #region 知识点四 垃圾回收机制
            //手动触发垃圾回收的方法 
            //一般情况下 我们不会频繁调用
            //都是在 Loading过场景时 才调用
            GC.Collect();
            #endregion
        }
    }

    //总结

    //构造函数
    //实例化对象时 调用的函数
    //主要是用来初始化成员变量的

    //基本语法
    //不写返回值
    //函数名和类名相同
    //访问修饰符根据需求而定
    //一般为public

    //注意
    //可以重载构造函数
    //可以用this语法重用代码
    //可以在函数中用this区分同名参数和成员变量
    //有参构造会顶掉默认的无参构造

    //析构函数
    //当对象呗垃圾回收时 调用的,主要是用来回收资源或者特殊处理内存的

    //基本语法
    //不写返回值
    //不写修饰符
    //不能有参数
    //函数名和类名相同
    //前面加~
}

6.3 练习题

基于成员方法练习题,对人类的构造函数进行重载,用人类创建若干个对象

class语句块外 namespace语句块内

// class语句块外 namespace语句块内
// 人 类
class Person
{
    public string name;
    public float height;
    public int age;
    public string homeAddress;

    //有参数构造函数
    public Person(string name, float height)
    {
        this.name = name;
        this.height = height;
    }

    //有参数构造函数 特殊写法
    public Person(string name, float height, int age, string homeAddress) : this(name, height)
    {
        //this
        this.age = age;
        this.homeAddress = homeAddress;
    }

    //成员方法
    public void Speak(string str)
    {
        Console.WriteLine("{0}说{1}", name, str);
    }

    public void Walk()
    {
        Console.WriteLine("{0}开始走路了", name);
    }

    public void Eat(Food f)
    {
        Console.WriteLine("{0}吃了{1}", name, f.name);
    }
}

// 食物 类
class Food
{
    public string name;
    public int kaluli;
}

主函数内

// 主函数内
Person p1 = new Person("林文韬", 177.5f, 18, "成都");
Person p2 = new Person("小火", 184.5f);
//Person p3 = new Person();//写了有参构造函数 默认无参构造函数被顶掉 会报错

基于成员变量练习题,对班级类的构造函数进行重载,用班级类创建若干个对象

class语句块外 namespace语句块内

// class语句块外 namespace语句块内
// 班级 类
class Class
{
    public string name;
    public int capacity;
    public Student[] students;

    //有参数构造函数
    public Class(string name, int capacity)
    {
        this.name = name;
        this.capacity = capacity;
    }

    //有参数构造函数 特殊写法
    public Class(string name, int capacity, Student[] students) : this(name, capacity)
    {
        this.students = students;
    }
}

// 学生 类
class Student
{

}

主函数内

// 主函数内
Class c1 = new Class("Unity", 9999999);
Class c2 = new Class("C#", 9999999, new Student[] { new Student(), new Student() });

写一个Ticket类,有一个距离变量(在构造对象时赋值,不能为负数),有一个价格特征,有一个方法GetPrice可以读取到价格,并且根据距离distance计算价格price(1元/公里)

  • 0~100公里 不打折
  • 101~200公里 大9.5折
  • 201~300公里 打9折
  • 300公里以上 打8折
  • 有一个显示方法,可以显示这张票的信息。
  • 例如:100公里100块钱

class语句块外 namespace语句块内

// class语句块外 namespace语句块内
// 票 类
class Ticket
{
    public uint distance;
    public float price;

    //有参数构造函数
    public Ticket(uint distance)
    {
        this.distance = distance;
        price = GetPrice();
    }

    //成员方法 

    //获得价格
    private float GetPrice()
    {
        //0~100公里 不打折
        //101~200公里 打9.5折
        //201~300公里 打9折
        //300公里以上 打8折
        if (distance > 300)
        {
            return distance * 0.8f;
        }
        else if (distance >= 201 && distance <= 300)
        {
            return distance * 0.9f;
        }
        else if (distance >= 101 && distance <= 200)
        {
            return distance * 0.95f;
        }
        else
        {
            return distance;
        }
    }

    //展示信息
    public void ShowInfo()
    {
        float perKMprice = price / distance;
        Console.WriteLine("{0}公里{1}块钱", distance, price);
        Console.WriteLine("平均每公里价格是" + perKMprice + "块钱");
    }
}

主函数内

// 主函数内
Ticket t = new Ticket(1000);
t.ShowInfo();
//1000公里800块钱
//平均每公里价格是0.8块钱

6.4 练习题代码

using System;

namespace Lesson4_练习题
{
    //class语句块外 namespace语句块内

    #region 练习题一
    //基于成员方法练习题
    //对人类的构造函数进行重载,用人类创建若干个对象

    // 人 类
    class Person
    {
        public string name;
        public float height;
        public int age;
        public string homeAddress;

        //有参数构造函数
        public Person(string name, float height)
        {
            this.name = name;
            this.height = height;
        }

        //有参数构造函数 特殊写法
        public Person(string name, float height, int age, string homeAddress) : this(name, height)
        {
            //this
            this.age = age;
            this.homeAddress = homeAddress;
        }

        //成员方法

        public void Speak(string str)
        {
            Console.WriteLine("{0}说{1}", name, str);
        }

        public void Walk()
        {
            Console.WriteLine("{0}开始走路了", name);
        }

        public void Eat(Food f)
        {
            Console.WriteLine("{0}吃了{1}", name, f.name);
        }
    }

    // 食物 类
    class Food
    {
        public string name;
        public int kaluli;
    }

    #endregion

    #region 练习题二
    //基于成员变量练习题
    //对班级类的构造函数进行重载,用班级类创建若干个对象

    // 班级 类
    class Class
    {
        public string name;
        public int capacity;
        public Student[] students;

        //有参数构造函数
        public Class(string name, int capacity)
        {
            this.name = name;
            this.capacity = capacity;
        }

        //有参数构造函数 特殊写法
        public Class(string name, int capacity, Student[] students):this(name, capacity)
        {
            this.students = students;
        }
    }

    // 学生 类
    class Student
    {

    }
    #endregion

    #region 练习题三
    //写一个Ticket类,有一个距离变量(在构造对象时赋值,不能为负数),有一个价格特征,
    //有一个方法GetPrice可以读取到价格,并且根据距离distance计算价格price(1元/公里)
    //0~100公里 不打折
    //101~200公里 打9.5折
    //201~300公里 打9折
    //300公里以上 打8折
    //有一个显示方法,可以显示这张票的信息。
    //例如:100公里100块钱

    // 票 类
    class Ticket
    {
        public uint distance;
        public float price;

        //有参数构造函数
        public Ticket(uint distance)
        {
            this.distance = distance;
            price = GetPrice();
        }

        //成员方法 

        //获得价格
        private float GetPrice()
        {
            //0~100公里 不打折
            //101~200公里 打9.5折
            //201~300公里 打9折
            //300公里以上 打8折
            if ( distance > 300 )
            {
                return distance * 0.8f;
            }
            else if( distance >= 201 && distance <= 300 )
            {
                return distance * 0.9f;
            }
            else if (distance >= 101 && distance <= 200)
            {
                return distance * 0.95f;
            }
            else
            {
                return distance;
            }
        }

        //展示信息
        public void ShowInfo()
        {
            float perKMprice = price / distance;
            Console.WriteLine("{0}公里{1}块钱", distance, price);
            Console.WriteLine("平均每公里价格是"+perKMprice+"块钱");
        }
    }
    #endregion

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("构造函数析构函数练习题");

            //主函数内

            #region 练习题一
            Person p1 = new Person("林文韬", 177.5f, 18, "成都");
            Person p2 = new Person("小火", 184.5f);
            //Person p3 = new Person();//写了有参构造函数 默认无参构造函数被顶掉 会报错
            #endregion

            #region 练习题二

            Class c1 = new Class("Unity", 9999999);
            Class c2 = new Class("C#", 9999999, new Student[] { new Student(), new Student() });

            #endregion

            #region 练习题三

            Ticket t = new Ticket(1000);
            t.ShowInfo();
            //1000公里800块钱
            //平均每公里价格是0.8块钱

            #endregion
        }
    }
}


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏