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

目录

  • 前言
  • 类的六个默认构造函数
  • 构造函数
    • 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;  //constA _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.rhkb.cn/news/338660.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MyBatis】零基础从入门到进阶(源码级深入详解)

1 MyBatis概述 1.1 框架 ● 在⽂献中看到的framework被翻译为框架 ● Java常⽤框架&#xff1a; ○ SSM三⼤框架&#xff1a;Spring SpringMVC MyBatis ○ SpringBoot ○ SpringCloud ○ 等。。 ● 框架其实就是对通用代码的封装&#xff0c;提前写好了⼀堆通用…

数据库系统概论(个人笔记)(第三部分)

数据库系统概论&#xff08;个人笔记&#xff09; 文章目录 数据库系统概论&#xff08;个人笔记&#xff09;3、SQL介绍3.1 SQL查询语言概述3.2 SQL数据定义3.3 SQL查询的基本查询结构3.4 其他基本操作3.5 设置操作3.6 空值3.7 聚合函数3.8 嵌套子查询3.9 数据库的修改 3、SQL…

看车牌识别API如何应用到实际

车牌识别技术作为一种先进的识别系统&#xff0c;在现代城市的交通管理和安全领域扮演着日益重要的角色。本文将深入探讨车牌识别API 接口在智能停车、安全监控以及数据分析等方面的具体应用。通过详细研究这些应用场景&#xff0c;我们可以更好地理解这项技术如何提升交通流畅…

Laravel和ThinkPHP框架比较

一、开发体验与易用性比较 1. 代码可读性&#xff1a; - Laravel以其优雅的语法和良好的代码结构著称&#xff0c;使得代码更加易读易懂。 - 相比之下&#xff0c;ThinkPHP的代码可读性较为一般&#xff0c;在一些复杂业务场景下&#xff0c;可能会稍显混乱。 让您能够一站式…

WordPress中借助Table of Contents Plus+Widget Options插件,实现仅在文章侧边栏显示文章目录的功能

本文转自博主的个人博客&#xff1a;https://blog.zhumengmeng.work,欢迎大家前往查看。 原文链接&#xff1a;点我访问 序言&#xff1a;今天心血来潮&#xff0c;写了一篇文章&#xff0c;忽然发现自己的文章极少有目录&#xff0c;这对于长文章的阅读来说是十分不利的&#…

Day 10:100322. 删除星号以后字典序最小的字符串

Leetcode 100322. 删除星号以后字典序最小的字符串 给你一个字符串 s 。它可能包含任意数量的 ‘’ 字符。你的任务是删除所有的 ’ 字符。 当字符串还存在至少一个 ‘*’ 字符时&#xff0c;你可以执行以下操作&#xff1a; 删除最左边的 ‘*’ 字符&#xff0c;同时删除该星号…

STM32(十):SPI (标准库函数)

前言 上一篇文章已经介绍了如何用STM32单片机中USART通信协议来串口通信&#xff0c;并向XCOM串口助手发送信息。这篇文章我们来介绍一下如何用STM32单片机中SPI接口来实现LED的闪亮并玩转WS2812B灯带。 一、实验原理 串行通信之前的博客里有所介绍&#xff0c;可以查看以下…

python中利用cartopy库绘制SST图像

1. Cartopy简介 Cartopy 是一个开源的 Python 库&#xff0c;用于绘制地图和地理数据分析。它结合了 matplotlib 的绘图功能和 shapely、pyproj 等库的地理空间数据处理能力&#xff0c;为用户提供了在地图上可视化数据的强大工具。 以下是 Cartopy 的一些主要特点和功能&#…

2、浮动的用法特点,解决父元素高度塌陷解决

一、浮动 用法&#xff1a;浮动就是使用float样式&#xff0c;使元素脱离文档流。属性值有三个&#xff1a;none默认left right 特点&#xff1a; 常用于文字环绕图片浮动的元素脱离文档流影响其他元素排列造成父元素高度塌陷 1、一旦元素设置了浮动&#xff0c;元素就会脱离…

Python知识点14---被规定的资源

提前说一点&#xff1a;如果你是专注于Python开发&#xff0c;那么本系列知识点只是带你入个门再详细的开发点就要去看其他资料了&#xff0c;而如果你和作者一样只是操作其他技术的Python API那就足够了。 在Python中被规定的东西不止有常识中的那些关键字、构造器等编程语言…

汇编原理 | 二进制、跳转指令、算数运算、

一.二进制 two complement reprentation&#xff08;补码&#xff09; 二进制的运算&#xff1a; 6的二进制 0110 -6的二进制 如何表示&#xff1f; 四个bit的第一个bit表示符号&#xff1a;1负0正 -6表示为1010 解释&#xff1a; 0 0000 1 0001 -1 1111&#xff08;由 …

自然语言处理(NLP)—— 置信度(Confidence)

1. 置信度&#xff08;Confidence&#xff09;的概念 置信度&#xff08;Confidence&#xff09;在机器学习和统计中通常指一个模型对其做出的预测是正确的确信程度。在分类任务中&#xff0c;置信度通常由模型赋予特定类别的概率值来表示。例如&#xff0c;在文本分类或实体识…

dm8 什么时候视图中统计的内存会超过OS

v$bufferpool和v$mem_pool视图记录着DMSERVER各组件的内存占用量。理论上跟OS看到的保持一致。但实际大多数场景下&#xff0c;OS中看到的数据远大于视图中的统计。这里面可能有内存泄漏的原因。不过也有的时候视图中的统计数据超过OS。下面就是这种情况&#xff1a; 上图中红线…

Vue插槽与作用域插槽

title: Vue插槽与作用域插槽 date: 2024/6/1 下午9:07:52 updated: 2024/6/1 下午9:07:52 categories: 前端开发 tags:VueSlotScopeSlot组件通信Vue2/3插槽作用域API动态插槽插槽优化 第1章&#xff1a;插槽的概念与原理 插槽的定义 在Vue.js中&#xff0c;插槽&#xff08;…

【OpenHarmony】TypeScript 语法 ④ ( 函数 | TypeScript 具名函数和匿名函数 | 可选参数 | 剩余参数 | 箭头参数 )

文章目录 一、TypeScript 函数1、TypeScript 具名函数和匿名函数2、TypeScript 函数 与 JavaScript 函数对比3、TypeScript 函数 可选参数4、TypeScript 函数 剩余参数5、TypeScript 箭头函数 参考文档 : <HarmonyOS第一课>ArkTS开发语言介绍 一、TypeScript 函数 1、Typ…

【Hive SQL 每日一题】统计指定范围内的有效下单用户

文章目录 测试数据需求说明需求实现 前言&#xff1a;本题制作参考牛客网进阶题目 —— SQL128 未完成试卷数大于1的有效用户 测试数据 -- 创建用户表 DROP TABLE IF EXISTS users; CREATE TABLE users (user_id INT,name STRING,age INT,gender STRING,register_date STRING…

LLM背后的基础模型2:Transformer的组成模块

Transformer是一种先进的语言模型&#xff0c;它在预测下一个单词或标记方面与传统的语言模型有所不同&#xff0c;但仍然遵循相同的基本原理。Transformer通过一系列复杂的步骤&#xff0c;将输入的标记序列转换为能够进行预测的丰富向量序列。 在Transformer中&#xff0c;输…

MySQL8找不到my.ini配置文件以及报sql_mode=only_full_group_by解决方案

一、找不到my.ini配置文件 MySQL 8 安装或启动过程中&#xff0c;如果系统找不到my.ini文件&#xff0c;通常意味着 MySQL服务器没有找到其配置文件。在Windows系统上&#xff0c;MySQL 8 预期使用my.ini作为配置文件&#xff0c;而不是在某些情况下用到的my.cnf文件。 通过 …

极简网络用户手册(1)

极简网络系统处理流程 模块位置&#xff1a;参数平台--专题分析--极简网络分析 步骤&#xff1a; 步骤一&#xff1a;创建精细化场景策略 步骤二&#xff1a;创建任务&#xff0c;主要选择策略&#xff08;包括√配置和距离配置&#xff09;和需要处理的小区清单&#xff08;源…

曲面细分技术在AI去衣中的创新应用

引言&#xff1a; 随着人工智能技术的飞速发展&#xff0c;其在图像处理领域的应用日益广泛。其中&#xff0c;AI去衣技术因其独特的应用场景而备受瞩目。在这一技术的发展过程中&#xff0c;曲面细分技术发挥了至关重要的作用。本文将深入探讨曲面细分技术在AI去衣中的作用及其…