【C++】类与对象——六个默认成员函数、构造函数的概念和特征,析构函数的概念和特征

news/2025/3/19 12:07:23/

文章目录

  • 1.类的六个默认成员函数
  • 2.构造函数
    • 2.1构造函数的概念
    • 2.2构造函数的特性
  • 3.析构函数
    • 3.1析构函数的概念
    • 3.2析构函数的特征

1.类的六个默认成员函数

  如果一个类中什么成员都没有,简称为空类。
  空类中真的什么都没有吗?

  并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。

class Date {};

默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。

在这里插入图片描述

2.构造函数

2.1构造函数的概念

  构造函数是C++中的一个特殊函数,用于初始化类对象的数据成员,为对象分配内存并完成一些初始化工作。一个类可以有多个构造函数,但必须满足函数名相同、参数列表不同的条件,称为函数重载
  构造函数有以下特点:

(1)构造函数的函数名与类名相同,并且不需要返回类型的声明,在函数体中也不需要指定 return 语句。

(2)构造函数可以具有参数,用于传递初始值给对象的数据成员

(3)构造函数可以进行重载,支持多个构造函数的存在。

(4)如果一个类没有定义自己的构造函数,编译器会自动生成一个默认的构造函数,该函数不带任何参数并且什么也不做,它会自动初始化类的成员变量并分配内存。

(5) 构造函数可以使用初始化列表进行初始化,这种方式可以提高效率。初始化列表是用冒号:跟在构造函数名后的成员初始化语句,以逗号隔开数据成员的名称和初始值。初始化列表的执行顺序与成员在类中声明的顺序一致。

(6) 如果一个类需要在离开作用域时,自动释放在堆内存上分配的资源,必须定义类的析构函数(Destructor)

总结:构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。

例子一(Person类):

class Person {
public:string name;int age;Person(string n, int a) { // 带参数的构造函数name = n;age = a;}
};int main() {Person p1("Alice", 20); // 使用构造函数创建对象cout << p1.name << ", " << p1.age << endl; // 输出对象的成员变量return 0;
}

  我们定义了一个 Person 类,并创建了一个带有参数的构造函数来初始化对象的成员变量。
  在 main() 函数中,我们使用类定义了一个 Person 对象,编译器在创建对象时会自动调用构造函数,初始化对象并输出该对象的成员变量值。

例子二(Date类):

class Date
{
public:
void Init(int year, int month, int day)
{_year = year;_month = month;_day = day;
}void Print()
{cout << _year << "-" << _month << "-" << _day << endl;
}private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Init(2023, 5, 29);d1.Print();Date d2;d2.Init(2023, 5, 29);d2.Print();return 0;
}

  这段代码定义了一个名为 Date 的类。Date 类包含了三个私有的数据成员:_year(年份)、_month(月份)和 _day(日期)。该类提供了两个公有的成员函数: Init 和 Print。
  Init 函数用于初始化 Date 对象的年、月、日信息,Print 函数则用于打印出 Date 对象的年、月、日信息。在主函数中,创建了两个 Date 对象,并通过 Init 函数初始化了其年、月、日的信息。

  我们可以使用构造函数来代替Init函数的作用,使创建的数据成员初始化。这两段代码实现的功能完全一样。

#include <iostream>
using namespace std;class Date
{
public:// 默认构造函数Date() {_year = 1949;_month = 10;_day = 1;}// 带参数的构造函数Date(int year, int month, int day) {_year = year;_month = month;_day = day;}// 输出日期方法void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{// 使用构造函数初始化对象Date d1(2023, 5, 29);d1.Print();Date d2(2023, 5, 29);d2.Print();return 0;
}

2.2构造函数的特性

  综上所述:构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

  其特征如下:

(1)函数名与类名相同。

(2)无返回值。

(3)对象实例化时编译器自动调用对应的构造函数。

(4)构造函数可以重载。

  在函数 TestDate 中,分别通过无参构造函数和带参构造函数创建了三个 Date 类型的对象(d1、d2 和 d3)。其中,d1 使用了无参构造函数,d2 使用了带参构造函数,并传入年月日参数进行初始化。而 d3 也是通过无参构造函数创建的对象,但使用了错误的语法,即在对象后添加了一对空括号,则编译器会将其解析为函数声明,而不是对象的创建;正确的写法应该是 Date d3;

class Date
{
public:
// 1.无参构造函数
Date()
{}// 2.带参构造函数
Date(int year, int month, int day)
{_year = year;_month = month;_day = day;
}private:int _year;int _month;int _day;
};void TestDate()
{Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象// warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)Date d3();
}


(5)如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

  在这段代码中,如果将 Date 类中的构造函数屏蔽掉,那么编译器将会自动生成一个默认的无参构造函数,此时主函数中的代码可以正常编译。但如果取消屏蔽,那么编译器将不再生成默认构造函数,主函数中的代码将会编译失败。
  所以这个构造函数Date是错误的,因为在使用默认参数时,必须在函数声明或定义中为这些参数提供默认值,否则编译器会报错。

class Date
{
public:
/*
// 如果用户显式定义了构造函数,编译器将不再生成
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
*/
void Print()
{cout << _year << "-" << _month << "-" << _day << endl;
}private:int _year;int _month;int _day;
};int main()
{// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函数// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再生// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return 0;
}

(6)C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int / char …,自定义类型就是我们使用 class / struct /union 等自己定义的类型,编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

  Date 类包含了三个整型类型的基本数据成员 _year、_month、_day 和一个 Time 类型的私有数据成员 _t,其中 Time 类由于没有显式定义构造函数,因此会有一个默认的无参构造函数。

  在主函数中,创建对象 d 时将调用 Date 类的默认构造函数,并在其中对其基本数据成员 _year、_month、_day 进行了初始化,Time 类型的成员 _t 也会调用其默认构造函数,将 _hour、 _minute、 _second 分别初始化为 0。

class Time
{
public:
Time()
{cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;
}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year;int _month;int _day;// 自定义类型Time _t;
};int main()
{Date d;return 0;
}

  注意:C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。

class Time
{
public:
Time()
{cout << "Time()" << endl;_hour = 0;_minute = 0;_second = 0;
}
private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};int main()
{Date d;return 0;
}

(7)无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。

  在这段代码中,Date 类显式声明了两个构造函数——其中一个是默认构造函数,另一个是带有三个参数的构造函数。在 Test() 函数中,没有提供任何参数来创建 Date 类型的对象 d1,因此将会使用默认构造函数进行初始化。

  所有这个测试函数能够通过编译,且会成功创建一个 Date 类型的对象 d1,该对象的年月日属性分别为默认值 1900 年 1 月 1 日。

class Date
{
public:
Date()
{_year = 1900;_month = 1;_day = 1;
}Date(int year = 1900, int month = 1, int day = 1)
{_year = year;_month = month;_day = day;
}private:int _year;int _month;int _day;
};void Test()
{Date d1;
}

3.析构函数

3.1析构函数的概念

  在 C++ 中,析构函数(Destructor)是一种特殊的函数,其名称与类名称相同,但在名称前面加上一个波浪号(~)。析构函数与构造函数一样,也是一种特殊的成员函数,但它是在对象生命周期结束时自动调用的,并且只能有一个析构函数,且不能带有参数。

  析构函数通常用于清理对象所占用的资源,比如释放动态申请的内存、关闭打开的文件等。具体来说,析构函数的工作包括释放内存或资源、删除临时文件、清理诸如打开的文件和数据库连接等。

析构函数有一下特点:

(1)在析构函数中,一般要释放对象所使用的内存或资源,防止内存泄漏。

(2)在堆上分配内存的对象,必须在析构函数中释放。

(3)调用析构函数的顺序与调用构造函数的顺序相反,即先析构派生类对象,再析构基类对象。

(4) 如果一个类有成员变量是指针类型或者是其他类的对象,需要在析构函数中先释放这些成员变量占用的内存,然后再释放自己的内存。

(5) 如果一个类没有显示地声明析构函数,则编译器会为该类生成一个默认的析构函数。默认的析构函数什么也不做。

总结:析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

简单的析构函数使用例子:

#include<iostream>
using namespace std;class A
{
public:A(){cout << "A constructor" << endl;}~A(){cout << "A destructor" << endl;}
};int main()
{A a;return 0;
}

3.2析构函数的特征

析构函数是特殊的成员函数,其特征如下:

(1)析构函数名是在类名前加上字符 ~。

(2)无参数无返回值类型。

(3)一个类只能有一个析构函数。 若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载。

(4)对象生命周期结束时,C++编译系统系统自动调用析构函数。


以栈的实现为例:

typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = capacity;_size = 0;
}void Push(DataType data)
{// CheckCapacity();_array[_size] = data;_size++;
}// 其他方法...
~Stack()
{if (_array){free(_array);_array = NULL;_capacity = 0;_size = 0;}
}private:DataType* _array;int _capacity;int _size;
};void TestStack()
{Stack s;s.Push(1);s.Push(2);
}

(5)编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

  在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?

  因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, _day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。

  但是:main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数。

注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数。

class Time
{
public:
~Time()
{cout << "~Time()" << endl;
}private:int _hour;int _minute;int _second;
};class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};int main()
{Date d;return 0;
}
//程序运行结束后输出:~Time()

(6)如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。


这些就是C++中类和对象中构造函数和析构函数的简单介绍了😉
如有错误❌望指正,最后祝大家学习进步✊天天开心✨🎉


http://www.ppmy.cn/news/103644.html

相关文章

json和pickle模块

目录 ❤ json和pickle模块 序列化 json pickle python从小白到总裁完整教程目录:https://blog.csdn.net/weixin_67859959/article/details/129328397?spm1001.2014.3001.5502 ❤ json和pickle模块 序列化 把对象(变量)从内存中变成可存储或传输的过程称之为序列化&am…

Java并发体系-第二阶段-锁与同步-[3](仅做了解吧不好理解)

synchronized保证三大特性 synchronized保证原子性的原理 对num;增加同步代码块后&#xff0c;保证同一时间只有一个线程操作num;。就不会出现安全问题。 synchronized保证可见性的原理 synchronized保证可见性的原理&#xff0c;执行synchronized时&#xff0c;会对应lock…

热力学统计物理专题:德拜模型

简正振动的固体模型 由于固体晶格结点上相邻原子之间距离很小&#xff08;约为 米量级&#xff09;&#xff0c;故原子间存在强烈的相互作用&#xff0c;而非相互独立。在温度不高时&#xff0c;原子只在其平衡位置附近作微振动&#xff0c;且具有不同的振动频率。设系统有…

算法27:从暴力递归到动态规划(2)

上一题比较简单&#xff0c;下面来一道比较难的题目。 假设有排成一行的N个位置&#xff0c;记为1~N&#xff0c;N 一定大于或等于 2 开始时机器人在其中的M位置上(M 一定是 1~N 中的一个) 如果机器人来到1位置&#xff0c;那么下一步只能往右来到2位置&#xff1b; 如果机…

linux环境下安装gitlab

前几天跟朋友聊天时说到gitlab版本控制。其实&#xff0c;之前也对它只是知道有这个东西&#xff0c;也会用。只是对于它的安装和配置&#xff0c;那我还是没整过。这两天&#xff0c;我找了一下网上的资料&#xff0c;还是写下吧。 一安装&#xff1a; 按网上所说&#xff0c;…

【系统工具】Rundll32:Windows系统中的神奇工具,你知道吗?

▒ 目录 ▒ &#x1f6eb; 问题描述环境 1️⃣ Rundll32的使用使用方法 - cmd使用方法 - 运行窗口/任务管理器/资源管理器 2️⃣ 常见应用场景运行js或vbs的脚本代码执行命令绕过杀毒软件的作法&#xff1f;修改注册表增加一个服务修复Internet Explorer其它常见命令 3️⃣ 原理…

Kubernetes入门指南

Kubernetes是一个用于容器编排和管理的开源平台。它可以帮助您简化容器化应用程序的部署、扩展和管理。本指南将引导您完成Kubernetes的基本概念和入门操作。 1. 安装Kubernetes集群 首先&#xff0c;您需要设置Kubernetes集群。以下是一些常见的安装选项&#xff1a; Minik…

IIC接口

一、IIC总线简介 IIC总线是由飞利浦公司推出的一种串行、同步、半双工通信协议。它由两条线组成&#xff0c;时钟线&#xff08;SCL&#xff09;和数据线&#xff08;SDA&#xff09;。主机产生通信用的时钟&#xff0c;可以产生起始信号和结束信号来开始或者结束一次通信。 …