【C++】类和对象——构造和析构函数

news2024/11/15 15:57:44

目录

  • 前言
  • 类的六个默认构造函数
  • 构造函数
    • 1.构造函数的概念
    • 2.构造函数的特性
  • 初始化列表
    • 1.构造函数整体赋值
    • 2.初始化列表
  • 析构函数
    • 1.析构函数的概念
    • 2.析构函数的特性

请添加图片描述

前言

  类和对象相关博客:【C++】类和对象
  我们前面一个内容已经讲了关于类好对象的初步的一些知识,下面我们来进阶的讲一讲如何使用类和对象。

类的六个默认构造函数

  上一节我们介绍类对象大小的时候,就有介绍过空类,空类的大小是一个字节

class A {}; //空类的形式

  我们所看到的空类,类体中为空,那空类中就什么都不存在吗?
  事实并不是这样的。
  任何类在什么都不写时,编译器会自动生成6个默认成员函数。

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

6个默认成员函数如下:

  1. 构造函数(主要完成初始化操作)
  2. 析构函数(主要完成清理操作)
  3. 拷贝构造(使用同类对象初始化创建对象)
  4. 赋值重载(把一个对象赋值给另一个对象)
  5. const成员函数
  6. 取地址及const取地址操作符重载

本节我们将会介绍一下构造函数和析构函数这两个最为重要的默认成员函数。

构造函数

1.构造函数的概念

  构造函数是一种特殊的成员函数,它可以用来处理对象的初始化,且不需要用户来调用,而是在建立对象时自动执行,且在对象生命周期内只调用一次。
构造函数虽然起名叫构造,但是构造函数的主要内容不是开空间,而是给对象初始化,就像栈所需要使用的Init函数(用Init函数初始化栈)。

2.构造函数的特性

构造函数是一种特殊的成员函数,所以它有一些独特的特性。

  1. 函数名与类名相同
  2. 无返回值(不用写void)
class Time //定义Time类
{
public:
    //void Time()   错误写法
    Time()//定义构造成员函数,函数名与类名相同
    {     //利用构造函数对对象中的数据成员赋值
        _hour = 0;
        _minute = 0;
        _sec = 0;
    }
private:
    int _hour;//时
    int _minute;//分
    int _sec;//秒
}

  1. 对象实例化时编译器会自动调用对应的构造函数
//接上段代码
int main()
{
    Time t1; 
    //Time类实例化了一个对象t1,同时调用构造函数t1.Time()
}

  构造函数不需要用户调用,也不能被用户调用

这种用法是错误的。
ti.Time();  //企图调用一般成员函数的方法来调用构造函数

  1. 构造函数可以重载

  我们可以在类里面写多个构造函数,可以有多种初始化方式,这些构造函数就构成函数重载。例如:

#include<iostream>
using namespace std;

class Time //定义Time类
{
public:
    //无参构造函数
    Time()
    {
        _hour = 0;
        _minute = 0;
        _sec = 0;
    }
    
    //带参构造函数
    Time(int hour, int minute, int sec)
    {
        _hour = hour;
        _minute = minute;
        _sec = sec;
    }

    void Print()
    {
        cout<<_hour<<":"<<_minute<<":"<<_sec<<endl;
    }
    
private:
    int _hour;//时
    int _minute;//分
    int _sec;//秒
};

int main()
{
    Time t1;//调用无参构造函数
    Time t2(10,30,55);//调用带参的构造函数
    t1.Print();
    t2.Print();
    return 0;
}

运行结果如下:
在这里插入图片描述
  上述代码只定义了两个同名的构造函数,根据重载函数的性质,还可以写出更多构造函数,如:

Time(int hour, int minute);  //有两个参数的构造函数
Time(int hour);  //有一个参数的构造函数

  在建立对象时给出参数个数,系统就会自动调用对应的构造函数。

需要注意的是:
我们在调用无参构造函数时不能写成:

Time t1(); //错误的调用无参构造函数写法

是因为它无法和函数声明区分开来。
这行代码就变成了:声明一个t1函数,该函数无参,且返回一个时间类型的对象。

当然,细心的朋友也发现了,它和我们平常调用函数的方式不太一样,正常调用时的方式是:函数名(参数列表)。而调用构造函数时变成了:对象名(参数列表)


  1. 如果类中没有定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义,编译器将不再生成。如:
class Time //定义Time类
{
public: 

 /* Time(int hour, int minute, int sec)
    {
        _hour = hour;
        _minute = minute;
        _sec = sec;
    }*/

    void Print()
    {
        cout<<_hour<<":"<<_minute<<":"<<_sec<<endl;
    }
    
private:
    int _hour;//时
    int _minute;//分
    int _sec;//秒
};

int main()
{
    Time t1;//调用无参构造函数
    t1.Print();
    return 0;
}

在这里插入图片描述

  通过运行结果我们发现,我们将带参的构造函数屏蔽掉了,但是依然能够正常运行出来,虽然结果是随机值,是因为编译器自动生成了一个无参的默认构造函数,且将其初始化为了随机值,即:

Time()
{}  //这就是编译器默认生成的无参构造函数

  如果将Time类中的构造函数放开,代码会编译失败,因为我们显示定义了构造函数后,编译器便不再生成。报错如下:
在这里插入图片描述


  1. C++把类型分为了内置类型和自定义类型,编译器自动生成的构造函数,对内置类型没有规定要不要处理(看编译器,有些编译器不做处理则初始化为随机值,有些则初始化为0),对自定义类型成员变量才会调用它的无参构造

内置类型就是语言提供的数据类型,如int/char/double……指针等
自定义类型就是我们用class/struct/union等自己定义的类型

class Date
{
public:
    Date()
    {
        _year = 2024;
        _month = 6;
        _day = 1;
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

class Time //定义Time类
{
public:
    void Print()
    {
        cout << _hour << ":" << _minute << ":" << _sec << endl;
    }

private:
    //内置类型
    int _hour;//时
    int _minute;//分
    int _sec;//秒

    //自定义类型
    Date _d1;
};

int main()
{
    Time t1;//调用无参构造函数
    t1.Print();
    return 0;
}

  结果如下:
在这里插入图片描述

  可以发现,编译器生成的构造函数对内置类型不做处理,生成随机值,对自定义类型调用它的默认构造函数。

注意:如果我们没有对自定义类型显示写构造函数,那么编译器在调用它的默认构造函数时也会和内置类型一样生成随机值。
如果自定义类型没有默认构造函数,则编译器会报错。

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

class Date
{
public:
    Date()
    {
        _year = 2024;
        _month = 6;
        _day = 1;
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};

class Time //定义Time类
{
public:
    void Print()
    {
        cout << _hour << ":" << _minute << ":" << _sec << endl;
    }

private:
    //内置类型
    int _hour = 12;//时
    int _minute = 10;//分
    int _sec = 30;//秒

    //自定义类型
    Date _d1;
};

int main()
{
    Time t1;//调用无参构造函数
    t1.Print();
    return 0;
}

结果如下:
在这里插入图片描述
  对内置类型声明的过程中,给一个缺省值,编译器就可以用缺省值来初始化。


  1. 默认构造函数包括:无参构造函数、全缺省构造函数、我们没有写编译器默认生成的构造函数。并且默认构造函数只能有一个。

简单点说就是不传参数就可以调用的就是默认构造函数。

class Time //定义Time类
{
public:
    //无参构造函数
    /*Time()
    {
        _hour = 0;
        _minute = 0;
        _sec = 0;
    }*/
    //全缺省构造函数
    Time(int hour = 0, int minute = 0, int sec = 0)
    {
        _hour = hour;
        _minute = minute;
        _sec = sec;
    }
    void Print()
    {
        cout << _hour << ":" << _minute << ":" << _sec << endl;
    }

private:
    int _hour;//时
    int _minute;//分
    int _sec;//秒
};

int main()
{
    Time t1;
    t1.Print();
    return 0;
}

无参构造函数和全缺省构造函数只能存在一个,否则会报错
在这里插入图片描述

初始化列表

1.构造函数整体赋值

  在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值。

class Time //定义Time类
{
public:
    Time(int hour, int minute, int sec)
    {
        _hour = hour;
        _minute = minute;
        _sec = sec;
    }
private:
    int _hour;//时
    int _minute;//分
    int _sec;//秒
};

  但是上述代码并不能将其称为成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化,因为初始化只能初始化一次,而构造函数体内可以多次赋值。如:

    Time(int hour, int minute, int sec)
    {
        _hour = hour;
        _minute = minute;
        _sec = sec;
        _sec = 11;
    }

  上述代码就对sec这个成员变量进行了两次赋值,则不能称为初始化。

2.初始化列表

  初始化列表本质上可以理解为每个对象中成员变量定义的地方。
  格式为:以一个冒号开始,接着以一个逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或者是表达式。
即:

构造函数名([参数表])
    :[成员初始化表]
    {
        [构造函数体]
    }  //其中,方括号[]中的内容可有可无

则Time类用初始化列表形式如下:

class Time //定义Time类
{
public:
    Time(int hour, int minute, int sec)
        :_hour(hour)
        ,_minute(minute)
        ,_sec(sec)
    {}
private:
    int _hour;//时
    int _minute;//分   //成员变量的声明
    int _sec;//秒
};

使用初始化列表时有以下几个需要注意的点:

  1. 每个成员变量在初始化表中只能出现一次(初始化只能初始化一次)
  2. 类中包括以下成员的,必须要在初始化列表初始化:
  • 引用成员变量
  • const成员变量
  • 自定义类型成员(且该类没有默认构造函数时)
class A
{
public:
	A(int a)
		:_a(a)
	{}
private:
	int _a;
};
class B
{
public:
	B(int x, int y, int z)
		:_x(x)
		,_y(y)
		,_z(z)
	{}
private:
	int& _x;  //引用
	const int _y;  //const
	A _z;  //自定义类型没有默认构造函数
};

  1. 如果数据成员时数组,则应该在构造函数的函数体中用语句对其赋值,而不能在初始参数列表初始化。 如:
class Student
{
public:
	Student(int num, char sex, const char name[])
		:_num(num)
		,_sex(sex)
	{
		strcpy_s(_name, name);
	}
private:
	int _num;  //序号
	char _sex;  //性别
	char _name[20];  //姓名
};

  1. 对于初始化列表,不管你写不写(如果不写,编译器会自动生成),对于自定义类型成员,一定会先试用初始化列表初始化。
  2. 成员变量在类中声明次序就是在其初始化列表中的初始化顺序,与其在初始化列表的先后次序无关。如:
class A
{
public:
	A(int a)
		:_a1(a)
		,_a2(_a1)
	{}
	void Print()
	{
		cout << "_a1 = " << _a1 << "     _a2 = " << _a2 << endl;
	}
private:
	int _a2;
	int _a1;
};
int main()
{
	A a(10);
	a.Print();
	return 0;
}

运行结果如下:
在这里插入图片描述
  我们发现,_a2是一个随机值,是因为_a2先声明,则它先初始化,它是用_a1进行初始化的,但是_a1并没有初始化,所以_a2是一个随机值,然后是_a1的声明,将1给了_a1进行初始化(类型转换,将整型转化为自定义类型),所以就得出_a1是1,_a2是随机值的结果。
  所以,我们在进行初始化时尽量按照先声明先定义的原则去进行初始化。

析构函数

1.析构函数的概念

  析构函数的作用与构造函数相反,但是析构函数并不是完成对对象本身的销毁,局部对象出了作用域会自行销毁。但是对象在销毁的时候会自动调用析构函数,完成对象中资源清理的工作。

2.析构函数的特性

析构函数同样是特殊的成员函数,所以它也有一些独特的特性

  1. 析构函数名是在类名前面加上字符~
  2. 无参数无返回值类型
  3. 一个类只能有一个析构函数,若未显示定义,编译器会自动生成默认的析构函数。注意:析构函数不能重载。
  4. 对象生命周期结束时,C++编译系统会自动调用析构函数
    如:
class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
	}
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _sec;
};

int main()
{
	Time t1;
	return 0;
}

  我们显示定义了一个构造函数和一个析构函数,但是我们并没有调用它,编译器会自动调用。
在这里插入图片描述

  1. 编译器自动生成的析构函数,对内置类型不做处理,对自定义类型调用它的析构函数。如:
class Date
{
public:
    ~Date()
    {
        cout << "~Date()" << endl;//证明调用了析构函数
    }
private:
    int _year;
    int _month;
    int _day;
};

class Time //定义Time类
{

private:
    //内置类型
    int _hour = 12;//时
    int _minute = 10;//分
    int _sec = 30;//秒

    //自定义类型
    Date _d1;
};

int main()
{
    Time t1;//调用无参构造函数
    return 0;
}

在这里插入图片描述
  要注意的是,虽然我们区分了编译器对两种类型的处理方式,但实际上,我们不需要担心内置类型是否被处理了,是因为内置类型在生命周期结束时会自动销毁,没有资源需要清理,但自定义类型则需要担心,因为它可能是malloc,new,open出来的,此时就必须要写析构函数去清理。如栈:

typedef int DataType;
class Stack
{
public:
	Stack(int n = 4)
	{
		_arr = (DataType*)malloc(n * sizeof(DataType));
		if (_arr == nullptr)
		{
			perror("malloc fail");
			return;
		}
		_top = 0;
		_capacity = 4;
	}
	void Push(DataType a)
	{
		_arr[_top++] = a;

	}
	DataType Top()
	{
		return _arr[_top-1];
	}
	//...
	~Stack()
	{
		free(_arr);
		_arr = nullptr;
		_top = 0;
		_capacity = 0;
		cout << "~Stack()" << endl;
	}
private:
	DataType* _arr;
	int _top;
	int _capacity;
};
int main()
{
	Stack st;
	st.Push(1);
	cout << st.Top() << endl;
	return 0;
}

在这里插入图片描述
  栈在初始化的时候要malloc一块空间,所以以前我们在写栈的时候需要手动给它destroy,但是我们往往会忽视这一个小细节,如果不把这些需要清理的东西清理掉,则会导致内存泄漏。而有了析构函数,我们不需要去调用,编译器也可以自行调用清理。但是,这需要我们显示定义才可以。
  总的来说:

  1. 资源需要清理的就要写析构,如:Stack,List。
  2. 有两种场景不需要写析构,编译器默认生成就可以了:
    a.没有资源需要处理,如Time类,Date类
    b.内置类型成员没有资源需要处理,剩下全是自定义类型,如MyQueue(两个栈实现一个队列)。

  今天的内容到此结束啦,感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1793762.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux——nginx部署

部署Nginx 构建Nginx服务器 &#xff08;实验需要DNS支持&#xff0c;或添加hosts条目&#xff0c;例如&#xff1a; &#xff09; 安装Nginx&#xff08;yum安装即可&#xff09; 安装依赖软件包&#xff1a; 重启、启用服务并查看服务状态&#xff1a; 默认页面&#xff0…

AIGC会带来失业潮吗?紧紧跟时代第一步,如何学习AIGC

会&#xff0c;但AI淘汰的始终是跟不上时代的人。 现在很多公司都有AI培训&#xff0c;不仅GPT&#xff0c;还有Midjourney、Stable DIffusion等一系列AI工具。 像我们公司虽然今年招的少&#xff0c;但也会对新招的应届生统一进行AI培训。 用任正非先生的话来说就是&#x…

ubuntu top命令的参数和快捷键

命令选项 -1 单个、所有cpu信息切换 top -1-b 批处理 top -b > top.txt这将保存top命令的输出到文件&#xff0c;直到手动终止或关机。所以使用这个命令要注意和其他命令配合&#xff0c;否则文件速度增长会很快。 在文件中&#xff0c;将会重复输入top命令。 -c 切换命…

干货!以医疗行业为例,讲解数据安全级别划分以及归纳敏感数据的处理策略

数据分类分级是一项基础工作&#xff0c;也是提供数据分级保护的基础措施之一&#xff0c;是企业长期的一项技术、管理措施。企业通过制定数据分类分级策略、模板、管理规范能够有助于帮助企业梳理清楚企业数据资产&#xff0c;在面向合规监管、内部数据安全控制时能够提供更完…

pyspark中使用mysql jdbc报错java.lang.ClassNotFoundException: com.mysql.jdbc.Driver解决

报错信息&#xff1a; py4j.protocol.Py4JJavaError: An error occurred while calling o33.load. : java.lang.ClassNotFoundException: com.mysql.jdbc.Driver 我的解决方法&#xff1a; 这个报错就是提示你找不到jar包&#xff0c;所以你需要去下载一个和你mysql版本匹配的j…

摸鱼大数据——Hive调优16-19

16、JVM 重用 此操作, 在hive2.x已经不需要配置了, 默认支持 jvm重用: 默认情况下, container资源容器 只能使用一次,不能重复使用, 开启JVM重用, 运行container容器可以被重复使用,在hive2.x已经默认支持了 17、推测执行 调优举例&#xff1a; 大数据小组&#xff0c;假设…

Android14 WMS-窗口绘制之relayoutWindow流程(二)-Server端

本文接着如下文章往下讲 Android14 WMS-窗口绘制之relayoutWindow流程(一)-Client端-CSDN博客 然后就到了Server端WMS的核心实现方法relayoutWindow里 WindowManagerService.java - OpenGrok cross reference for /frameworks/base/services/core/java/com/android/server…

QT系列教程(9) 主窗口学习

简介 任何界面应用都有一个主窗口&#xff0c;今天我们谈谈主窗口相关知识。一个主窗口包括菜单栏&#xff0c;工具栏&#xff0c;状态栏&#xff0c;以及中心区域等部分。我们先从菜单栏说起 菜单栏 我们创建一个主窗口应用程序, 在ui文件里的菜单栏里有“在这里输入”的一个…

四、Window整理右键扩展

一、桌面右键和文件右键扩展整理 简而言之整理下面图示内容 桌面右键&#xff1a; 对文件右键&#xff1a; 工具&#xff1a;火狐自带工具 二、新建扩展管理 简而言之整理下面图示内容 Win R输入regedit 寻找&#xff1a;计算机\HKEY_CURRENT_USER\Software\Microsof…

中科数安 | 透明加密防泄密系统!如何有效防止企业内部核心数据资料外泄?

中科数安提供的透明加密防泄密系统是一种专为企业设计的数据保护解决方案&#xff0c;它通过以下关键特性有效防止企业内部核心数据资料外泄&#xff1a; PC地址&#xff1a;——www.weaem.com 自动智能透明加密&#xff1a;系统能够在操作系统级别无缝集成&#xff0c;对指定类…

重生之 SpringBoot3 入门保姆级学习(15、内容协商返回不同数据类型)

重生之 SpringBoot3 入门保姆级学习&#xff08;15、内容协商返回不同数据类型&#xff09; 3.3.3 改变 Accept 实现内容协商3.3.4 改变 application.proerties 实现内容协商 3.3.3 改变 Accept 实现内容协商 添加支持写出 xml 内容的 Maven 依赖并刷新 Maven <!-- …

日语里「ずつ」和「づつ」有什么不同,柯桥成人日语培训

在生活中&#xff0c;我们常用「1人ずつ」「一歩ずつ」表示固定数量的重复出现&#xff0c;但有时候也会写作「1人づつ」「一歩づつ」&#xff0c;究竟哪种写法才是正确的呢&#xff1f;今天这篇文章将对「ずつ」和「づつ」的正确用法进行说明。 01 使用情况 根据日本文部科学…

RLHF(从人类反馈中进行强化学习)详解(三)

在经过了前两节的内容学习之后&#xff0c;我们对于RLHF&#xff08;从人类反馈中进行强化学习&#xff09;有了比较深入的认知&#xff0c;并且初步了解了RLHF中偏好数据集的引入&#xff0c;奖励模型的设置以及baseLLM的训练过程。在本节的学习中&#xff0c;我们将深入LLM的…

Visual Studio Installer 点击闪退

Visual Studio Installer 点击闪退问题 1. 问题描述2. 错误类型3. 解决方法4. 结果5. 说明6. 参考 1. 问题描述 重装了系统后&#xff08;系统版本&#xff1a;如下图所示&#xff09;&#xff0c;我从官方网站&#xff08;https://visualstudio.microsoft.com/ ) 下载了安装程…

如何把几个pdf文件合成在一个pdf文件

PDF合并&#xff0c;作为一种常见的文件处理方式&#xff0c;无论是在学术研究、工作汇报还是日常生活中&#xff0c;都有着广泛的应用。本文将详细介绍PDF合并的多种方法&#xff0c;帮助读者轻松掌握这一技能。 打开 “轻云处理pdf官网” 的网站&#xff0c;然后上传pdf。 pd…

入门Consul注册、配置中心(代码演示)

1、安装与使用 2、注册服务到consul 3、调用consul上注册的服务 4、配置中心 1、安装与使用 官网地址&#xff1a;Install | Consul | HashiCorp Developer 下载对应的系统 解压缩后文件 打开命令行运行 consul.exe agent -dev 即可运行 可以编写一个bat脚本每次运行ba…

C# MQTTNET 服务端+客户端 实现 源码示例

目录 1.演示效果 2.源码下载 3.服务端介绍 4.客户端介绍 1.演示效果 2.源码下载 下载地址&#xff1a;https://download.csdn.net/download/rotion135/89385802 3.服务端介绍 服务端用的控制台程序进行设计&#xff0c;实际使用可以套一层Windows服务的皮&#xff0c;进…

了解一下Ubuntu Linux

1.3.1 什么是Ubuntu Ubuntu这个名字非常神奇&#xff0c;它取自非洲南部祖鲁语的ubuntu&#xff0c;是一个哲学名称&#xff0c;其意思为“人性”或者“我的存在是因为大家的存在”。对于中国人来说&#xff0c;一般称呼它为乌班图。 Ubuntu是在Debian的基础上开发出来的&am…

在Win10安装MySQL环境以及更改相关配置---附带每一步截图

下载MySQL数据库 MySQL官网链接 选择合适自己的版本&#xff0c;这里我选择5.7.17&#xff0c;选择安装包大的那一个&#xff0c;这个是离线安装&#xff0c;下载到本地后进行安装。 选择“No thanks&#xff0c;just start my download.”即进入下载状态。 安装 运行安…

SpringBoot+Vue房产销售网站(前后端分离)

技术栈 JavaSpringBootMavenMySQLMyBatisVueShiroElement-UI 角色对应功能 用户销售经理管理员 功能截图