C++类和对象——第二关

目录

类的默认成员函数:

(一)构造函数

(二)析构函数

(三)拷贝构造函数


类的默认成员函数:

类里面有6个特殊的成员函数分别包揽不同的功能;

(一)构造函数

说明:C++把类型分成内置类型(基本类型)和⾃定义类型。内置类型就是语⾔提供的原⽣数据类型,如:int/char/double/指针等,⾃定义类型就是我们使⽤class/struct等关键字⾃⼰定义的类型。

构造函数的特点:

(1)自动调用,在类实例化对象的时候会自动的调用构造函数。

(2)构造函数没有返回值(啥都不写,而不是说写void),名字和当前类的名字相同。

(3)构造函数可以分为三种,1.没有形参的构造,2.全缺省参数的构造,3.自动生成的构造,编译器默认生成的是无参构造。他们三个只能存在一个。如果类中没有显式定义构造函数,则C++编译器会⾃动⽣成⼀个⽆参的默认构造函数,⼀旦⽤⼾显式定义编译器将不再⽣成。

因为在调用的时候不需要传递任何的参数,所以构造函数总结下来可以称之为0实参构造函数。

自己写了构造函数,(编译器不再生成)。

class Date {
private:int _year;int _month;int _day;public:Date(int year = 1999, int month = 01, int day = 01) // 自己定义的全缺省构造函数{_year = year;_month = month;_day = day;}void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}};int main()
{Date d1(2024, 9, 23);Date d2;cout << "传参了:" << endl;d1.Print();cout << "没有传参:" << endl;d2.Print();return 0;
}

去掉自己写的构造函数的时候(使用的是编译器的默认构造):

class Date {
private:int _year;int _month;int _day;public://Date(int year = 1999, int month = 01, int day = 01) // 自己定义的全缺省构造函数//{//	_year = year;//	_month = month;//	_day = day;//}void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}};int main()
{//Date d1(2024, 9, 23);Date d1; Date d2;cout << "传参了:" << endl;d1.Print();cout << "没有传参:" << endl;d2.Print();return 0;
}

这里可以看到,编译器初始化成员变量是随机值。

因为c++没有明确规定构造函数的初始化规则,所以不同的编译器会有不同的初始化规则,在vs上可能会初始化为随机值,但是在其他编译器上可能就不一样,构造函数中编译器对内置类型是没有确定处理的。

同样一段代码,我们放到devc++中执行的结果:编译器的初始化结果和vs是不一样的。

tips:正因为默认的构造函数初始化打击不确定的,所以大多数构造函数都是需要自己写的。

ps:想要redpanda dev编译器的可以私我给发。

(4)构造函数支持函数重载

我们可以写着两个构造函数,无参构造和全缺省构造函数,但是会产生调用歧义。

class Date {
private:int _year;int _month;int _day;public:Date(int year = 1999, int month = 01, int day = 01) // 自己定义的全缺省构造函数{_year = year;_month = month;_day = day;}// 自己定义的无参数的默认构造函数//Date() //{//	_year = 1999;//	_month = 07;//	_day = 12;//}// 自己定义的带参默认构造//Date(int year, int month, int day)//{//	_year = year;//	_month = month;//	_day = day;//}void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}};int main()
{Date d1; Date d2(2024,2,3);cout << "传参了:" << endl;cout << "没有传参:" << endl;d2.Print();return 0;
}

还有就是:在创建对象调用的时候,是无参构造就不需要给对象传参了,是有参数构造的必须要传参,不然编译器会报错的。

(5)为了更深层次的理解构造函数,我们使用C++来实现一个栈的初始化和压栈操作:

#include <stdlib.h>
#include <iostream>
using namespace std;
class stack {
private:int _top;// 栈顶int _capacity; // 数组空间的大小int* _arr; // 使用动态数组来实现栈public:stack(int n = 4) // 实现构造函数,和init函数差不多{_top = 0;_capacity = 4;_arr = (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 = 16个字节的空间}// 压栈入数据void push(int x){// 判断栈是否满了if (_top == _capacity) // 如果栈满了,就重新开空间{int newcapacity = _capacity * 2;int* tmp = (int*)realloc(_arr, sizeof(int) * newcapacity);if (tmp == nullptr){cout << "realloc fail!" << endl;return;}_capacity = newcapacity;_arr = tmp;}_arr[_top] = x;_top++;}
};int main()
{stack st;st.push(1);st.push(2);st.push(3);st.push(4);st.push(5);return 0;
}

可以看到我们写的栈的初始化函数就是构造函数我们没有手动调用,它在程序执行过程中自动调用了。

(二)析构函数

和构造函数相反对应的是析构函数,它们可以说是相辅相成。

C++规定对象在销毁时会⾃动调⽤析构函数,完成对象中资源的清理释放⼯作。

析构函数的特性:

(1)函数名和对象名一样,没有返回值,没有参数,定义时在函数名前面加~:

(2)同构造函数一样,一个类只能存在一个析构函数,构造函数在创建对象时调用,析构函数在对象生命周期结束的时候调用。

class Date {
private:int _year;int _month;int _day;public:Date(int year = 1999, int month = 01, int day = 01) // 自己定义的全缺省构造函数{_year = year;_month = month;_day = day;}// 自己写的析构函数~Date(){cout << "这里调用了析构函数:~Date()" << endl;}void Print(){cout << this->_year << "/" << this->_month << "/" << this->_day << endl;}};int main()
{Date d1;// 这里定义的两个对象没有显示的调用析构函数如:d1.Date().Date d2;return 0;
}

step1:d1和d2两个对象当代码执行到return 0;这句代码的时候生命周期结束,我们在这句代码处打一个断点进行调试。

step2:我们接下来对代码进行逐步调试,发现代码直接跳到了我们刚自己实现的析构函数的位置

step3:程序继续执行,执行完析构函数内的程序代码之后打印结束,程序结束。

在调用析构函数的时候,有几个对象就析构几次,可以看到我们创建了两个析构函数,打印了两次。因为在main的函数栈帧中,执行规则和数据结构的栈的执行规则差不多,都是后进先出,所以在析构多个对象的时候,遵循先定义的后析构,也可以理解为生命周期先结束的最后析构。

(3)跟构造函数类似,编译器默认⽣成的析构函数对内置类型成员不做处理,自定义类型成员会调⽤他的析构函数。

(4)还需要注意的是我们显⽰写析构函数,对于⾃定义类型成员也会调⽤他的析构,也就是说⾃定义类型成员⽆论什么情况都会⾃动调⽤析构函数

比如说:我们定义了一个类,里面存在另外一个类实例化的对象,我们在另一个类中实现了析构函数,在定义这个类实例化的对象的时候,这个实例化的对象生命周期结束的时候会去调用另一个类的析构函数。

(5)没有申请资源时,析构函数可以不写,有资源申请时,⼀定要⾃⼰写析构,否则会造成资源泄漏。

        在上面我面我们定义了一个栈的类做构造函数的例子,我们在写的时候申请了空间,但是我们没有将空间释放掉,也没有写析构函数,而编译器自己默认生成的析构函数只会清理内置类型的空间,所以其实已经造成了内存泄漏了,最终泄露的这块内存由操作系统回收。

        我们给它加上析构函数将开辟在堆上的空间释放掉。

#include <cstdlib>
#include <stdlib.h>
#include <iostream>using namespace std;class stack {
private:int _top;// 栈顶int _capacity; // 数组空间的大小int* _arr; // 使用动态数组来实现栈public:stack(int n = 4) // 实现构造函数,和init函数差不多{_top = 0;_capacity = 4;_arr = (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 = 16个字节的空间}// 压栈入数据void push(int x){// 判断栈是否满了if (_top == _capacity) // 如果栈满了,就重新开空间{int newcapacity = _capacity * 2;int* tmp = (int*)realloc(_arr, sizeof(int) * newcapacity);if (tmp == nullptr){cout << "realloc fail!" << endl;return;}_capacity = newcapacity;_arr = tmp;}_arr[_top] = x;_top++;}// 自己写的析构函数~stack(){free(_arr);_arr = nullptr;_capacity = _top = 0;}
};int main()
{stack st;return 0;}

当然,如果可以也可以在析构函数里面申请空间,除非你有病。

(三)拷贝构造函数

需要理解的几个点:

(1)拷贝构造是构造函数的重载,他和构造函数一样,都是为了初始化一个对象。构造函数是初始化当前对象,拷贝构造是使用另一个函数来初始化当前对象。

(2)拷⻉构造函数的第⼀个参数必须是类类型对象的引⽤,使⽤传值⽅式编译器直接报错,因为语法逻辑上会引发⽆穷递归调⽤。拷⻉构造函数也可以多个参数,但是第⼀个参数必须是类类型对象的引⽤,后⾯的参数必须有缺省值。

假设不是类类型的对象引用——>引发无穷递归:

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;} // 编译报错:error C2652 : “Date”: ⾮法的复制构造函数: 第⼀个参数不应是“Date”//Date(Date d)Date(const Date & d){_year = d._year;_month = d._month;_day = d._day;} Date(Date  d) // 这里语法上是不允许的,只是为了举例。{_year = d._year;_month = d._month;_day = d._day;} void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2(d1);return 0;
}

tips:为了避免发生被拷贝对象成员被修改的情况,在拷贝构造函数的参数前面加一个const限定。如在上面的日期类的拷贝构造函数中:

Date(const Date&  d) //这里加一个const修饰能够避免源对象(d1)被修改
{_year = d._year;_month = d._month;_day = d._day;
}int main()
{Date d1;Date d2(d1);return 0;
}

(3)C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,⾃定义类型传值传参和传值返回都会调⽤拷⻉构造完成

class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;} Date(const Date&  d) //这里加一个const修饰能够避免源对象{_year = d._year;_month = d._month;_day = d._day;} void Print(){cout << _year << "-" << _month << "-" << _day << endl;}
private:int _year;int _month;int _day;
};void func(Date d)
{d.Print();
}int main()
{Date d1;Date d2(d1);func(d1); // 这里传值传参会调用拷贝构造return 0;
}

对代码进行调试运行过程如图:

(4)若未显式定义拷⻉构造,编译器会⽣成⾃动⽣成拷⻉构造函数。⾃动⽣成的拷⻉构造对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉)类似于memcpy函数,对⾃定义类型成员变量会调⽤他的拷⻉构造

(5)如果在定义对象(类)的时候产生了资源的申请,就需要自己写拷贝构造函数,同析构函数一样,是否有资源的申请决定是否需要自己写拷贝构造函数,一般需要自己写析构函数的话就需要自己写拷贝构造函数。

(4)(5)两点且看下面代码:

class stack {
private:int _top;// 栈顶int _capacity; // 数组空间的大小int* _arr; // 使用动态数组来实现栈public:stack(int n = 4) // 实现构造函数,和init函数差不多{_top = 0;_capacity = 4;_arr = (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 = 16个字节的空间}// 压栈入数据void push(int x){// 判断栈是否满了if (_top == _capacity) // 如果栈满了,就重新开空间{int newcapacity = _capacity * 2;int* tmp = (int*)realloc(_arr, sizeof(int) * newcapacity);if (tmp == nullptr){cout << "realloc fail!" << endl;return;}_capacity = newcapacity;_arr = tmp;}_arr[_top] = x;_top++;}// 自己写的析构函数~stack(){free(_arr);_arr = nullptr;_capacity = _top = 0;}
};int main()
{stack st1;stack st2(st1);//使用st1来初始化st2return 0;}

当我们执行这段代码的时候会发生下列情况:

程序挂掉了,为什么?

我们来调试看一下:

对于这种情况我们就需要自己写一个拷贝构造函数了。

class stack {
private:int _top;// 栈顶int _capacity; // 数组空间的大小int* _arr; // 使用动态数组来实现栈public:stack(int n = 4) // 实现构造函数,和init函数差不多{_top = 0;_capacity = 4;_arr = (int*)malloc(sizeof(int) * _capacity);//先预开辟4*4 = 16个字节的空间}// 压栈入数据void push(int x){// 判断栈是否满了if (_top == _capacity) // 如果栈满了,就重新开空间{int newcapacity = _capacity * 2;int* tmp = (int*)realloc(_arr, sizeof(int) * newcapacity);if (tmp == nullptr){cout << "realloc fail!" << endl;return;}_capacity = newcapacity;_arr = tmp;}_arr[_top] = x;_top++;}// 自己写的析构函数~stack(){free(_arr);_arr = nullptr;_capacity = _top = 0;}// 自己写的拷贝构造函数stack(const stack& st){_capacity = st._capacity;_top = st._top;int new_capacity = st._capacity;int* tmp = (int*)malloc(sizeof(int) * new_capacity);//重新申请一片空间if (tmp == nullptr){perror("malloc fail!");return;}_arr = tmp;}};

执行代码重新调试看一下:

地址不一样了,我们重新申请了空间。

        在上面的代码调试过程中,我们发现使用编译器默认生成的拷贝构造函数不会申请空间,它会一个个字节的拷贝,拷贝动态数组的时候它拷贝的值动态数组指针变量的地址,并没有申请空间这一操作,我们将这种拷贝方式称为浅拷贝。而我们自己实现的拷贝构造函数自己申请了空间,我们将这种拷贝称为深拷贝。

        也可以使用等号的方式调用拷贝构造函数初始化对象,在承接上面的代码,我们定义一个新的对象,如:

int main()
{stack st1;stack st2(st1);//使用st1来初始化st2// 也可以使用等号的方式调用拷贝构造函数初始化对象stack st3 = st1; return 0;}

这样写不会报错。

(6) 传值返回会产⽣⼀个临时对象调⽤拷⻉构造,产生拷贝,传值引⽤返回,返回的是返回对象的别名(引⽤),没有产⽣拷⻉。但是如果返回对象是⼀个当前函数局部域的局部对象,函数结束就销毁了,那么使⽤引⽤返回是有问题的,这时的引⽤相当于⼀个野引⽤,类似⼀个野指针⼀样。传引⽤返回可以减少拷⻉,但是⼀定要确保返回对象,在当前函数结束后还在,才能⽤引⽤返回。

不能返回临时对象,返回临时对象是会销毁的,里面的各种指针就成了野指针,返回的是一个被销毁了的空间,所以引用这时候是这块被销毁的空间的别名。

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

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

相关文章

极狐GitLab 17.4 升级指南

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab https://dl.gitlab.cn/6y2wxugm 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 本文分享极狐GitLab 17.4 升级…

老人跌倒扶不扶?涪城三职工给出响亮答案

一、关键时刻的选择 于绵阳市三江湖湿地公园&#xff0c;平凡午后&#xff0c;三名环卫人员刘后刚、严荣礼及杨树坤正紧张作业。突闻呼救声&#xff0c;一位老人在石阶上跌倒需援手。在紧急关头&#xff0c;他们果断抛却工具&#xff0c;疾速赶至老人身边。此举不仅展现了他们…

MySQL数据库进阶知识(四)《视图、存储过程、触发器》

学习目标&#xff1a; 掌握数据库视图基础知识 掌握数据库存储过程原理 掌握数据库触发器相关知识 学习内容&#xff1a; 一. 视图 介绍 视图&#xff08;View&#xff09;是一种虚拟存在的表。视图中的数据并不在数据库中实际存在&#xff0c;行和列数据来自定义视图的查询…

JPEG图像的DCT(Discrete Cosine Transform)变换公式代码详解

引 言 网络上图像在传输过程中为节省内存空间主要采用jpeg格式。jpeg图属于有损压缩图像的一种。在图像篡改检测过程中&#xff0c;可以利用jpeg图像的单双压缩伪影的不同而判别图像为伪造图并可以定位伪造区域。RGB图像变成jpeg图像过程中涉及从RGB图变成YCbCr图像&#xff0c…

FreeRTOS(四)FreeRTOS列表与列表项

目录 列表 列表项 迷你列表项 列表和列表项的关系 列表相关API函数 列表初始化 列表项初始化 列表项插入 列表项末尾插入 列表项删除 列表遍历 在 FreeRTOS 中&#xff0c;列表&#xff08;List&#xff09;和列表项&#xff08;ListItem&#xff09;是核心数据结构&…

Centos7系统根分区空间小home空间大如何增加分区

Centos7 默认安装&#xff0c;区划默认划分&#xff0c;用着怎么感觉有问题&#xff0c;根分区太小50G&#xff0c;而home分区太大。 如果处理&#xff0c;能扩大根分区呢&#xff1f;如果是新安装的&#xff0c;可以先删除home&#xff0c;然后再扩容 根分区。最后使其生效。…

计算机视觉硬件整理(四):相机与镜头参数介绍

文章目录 前言一、工业相机常用分类二、工业相机的基本参数三、工业相机的接口四、工业镜头的参数五、工业镜头的选择要点 前言 随着科技的飞速发展&#xff0c;工业自动化和智能制造在当今社会扮演着越来越重要的角色。在这个背景下&#xff0c;工业相机作为一种关键的视觉检…

Qualitor processVariavel.php 未授权命令注入漏洞复现(CVE-2023-47253)

0x01 漏洞概述 Qualitor 8.20及之前版本存在命令注入漏洞,远程攻击者可利用该漏洞通过PHP代码执行任意代码。 0x02 复现环境 FOFA&#xff1a;app"Qualitor-Web" 0x03 漏洞复现 PoC GET /html/ad/adpesquisasql/request/processVariavel.php?gridValoresPopHi…

【azure-openai】批量翻译demo【python】【gradio】

要求&#xff1a;拥有azure-openai-api&#xff0c;上传文件为csv格式&#xff0c;utf-8编码。 注意&#xff1a;如果出现乱码&#xff0c;重新运行&#xff0c;换种方式打开&#xff0c;有时候wps会自动改编码。 实现功能&#xff1a;选择语言&#xff0c;使用gpt4omini&…

使用docker形式部署prometheus+alertmanager+钉钉告警

一、拉取所需要的镜像 docker pull prom/node-exporter docker pull grafana/grafana docker pull prom/prometheus docker pull prom/alertmanager 其中 prom/node-exporter&#xff1a;用于收集主机系统信息和指标的 grafana/grafana&#xff1a;是一个用于可视化和分…

mac 上配置Jmeter代理进行web脚本录制过程容易踩坑的点

macOS 配置 Jmeter代理录制web脚本&容易踩坑的点 mac配置下载&#xff1a;前景提要&#xff1a;Jmeter中具体操作容易踩坑的点1、进入浏览器后&#xff0c;显示访问连接不安全。2、证书失效需要重新生成3、重新生成证书的方式4、没有生成新的证书5、jmeter安装路径找不到 m…

硬件设计很简单?合宙低功耗4G模组Air780E—开机启动及外围电路设计

Air780E是合宙低功耗4G-Cat.1模组经典型号之一&#xff0c;上期我们解答了大家关心的系列问题&#xff0c;并讲解了选型的注意要点。 有朋友问&#xff1a;能不能讲些硬件设计相关的内容&#xff1f; 模组的上电开机&#xff0c;是硬件设计调试的第一步。 本期特别分享——Ai…

MySQL数据库进阶知识(五)《锁》

学习目标&#xff1a; 一周掌握数据库锁相关知识 学习内容&#xff1a; 一. 概述 介绍 锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中&#xff0c;除传统的计算资源&#xff08;CPU、RAM、I/O&#xff09;的争用以外&#xff0c;数据也是一种供许多用户共…

将本地文件上传至虚拟机

1、查看虚拟机ip地址 ip addr 2、xshell连接上虚拟机 连接root连接不上的解决办法更改配置文件vim /etc/ssh/sshd_config 重启&#xff08;sudo service ssh restart&#xff09;并查看是否开启ssh服务&#xff08;sudo ps -e | grep ssh&#xff09; 即可连接成功 3、复制文…

在实时语音交互上超过GPT-4o,端到端语音模型Mini-Omni部署

Mini-Omni是清华大学开源的多模态大型语言模型&#xff0c;具备实时语音输入和流式音频输出的能力。 Mini-Omni模型能够一边听、一边说&#xff0c;一边思考&#xff0c;类似于ChatGPT的语言对话模式。 Mini-Omni模型的主要特点是能够直接通过音频模态进行推理&#xff0c;并…

Skywalking告警配置

背景 skywalking 9.7.0&#xff0c;地址&#xff1a;Backend setup | Apache SkyWalking helm&#xff1a;skywalking-helm:4.5.0&#xff0c;地址&#xff1a;skywalking-helm/chart/skywalking/values.yaml at v4.5.0 首先来说一下为什么使用skywalking告警&#xff1f; …

JS设计模式之组合模式:打造灵活高效的对象层次结构

引言 当我们构建复杂的应用程序时&#xff0c;经常会遇到处理对象层次结构的情况。这些层次结构通常是树形结构&#xff0c;由组合节点和叶子节点组成。在这样的情况下&#xff0c;JavaScript 设计模式之一的组合模式就能派上用场。 组合模式是一种结构型设计模式&#xff0c…

MySQL从入门到精通 - 基础篇

一、MySQL概述 1. 数据库相关概念 二、SQL &#xff08;1&#xff09;SQL通用语法 &#xff08;2&#xff09;SQL分类 &#xff08;3&#xff09;数据定义语言DDL 数据库操作 表操作 数据类型 1. 数值类型 2. 字符串类型 二进制数据&#xff1a;以二进制格式&#xff08;0和…

【JavaEE初阶】深入解析死锁的产生和避免以及内存不可见问题

前言&#xff1a; &#x1f308;上期博客&#xff1a;【后端开发】JavaEE初阶—线程安全问题与加锁原理&#xff08;超详解&#xff09;-CSDN博客 &#x1f525;感兴趣的小伙伴看一看小编主页&#xff1a;GGBondlctrl-CSDN博客 ⭐️小编会在后端开发的学习中不断更新~~~ &#…

C#图像处理学习笔记(屏幕截取,打开保存图像、旋转图像、黑白、马赛克、降低亮度、浮雕)

1、创建Form窗体应用程序 打开VS&#xff0c;创建新项目-语言选择C#-Window窗体应用&#xff08;.NET Framework) 如果找不到&#xff0c;检查一下有没有安装.NET 桌面开发模块&#xff0c;如果没有&#xff0c;需要下载&#xff0c;记得勾选相关开发工具 接上一步&#xff0c;…