【C++】--类和对象(2)

👌个人主页: 起名字真南
👆个人专栏:【数据结构初阶】 【C语言】 【C++】

请添加图片描述

目录

  • 1 类的默认成员函数
  • 2 构造函数
  • 3 析构函数
  • 4 拷贝构造
  • 5 赋值运算符重载
    • 5.1 运算符重载
    • 5.2 赋值运算符的重载

1 类的默认成员函数

默认成员函数就是用户没有显示实现,编译器自动生成的的成员函数称为默认成员函数

一个类我们不写的情况下会默认生成六个成员函数,分别是构造函数,析构函数,拷贝构造,赋值重载以及关于取地址对普通对象和const对象的重载。
在这里插入图片描述

2 构造函数

构造函数是特殊的成员函数,构造函数的主要任务是在对象实例化时对其进行初始化对象。他的本质就是Stack和Date类中Init函数的功能,而且构造函数可以自动调用的特点就完美的替代了Init函数。

构造函数的特点 :

  1. 函数名和类型名一致
  2. 无返回值(不需要写void )
  3. 对象实例化时系统会自动调用对应的构造函数。
  4. 构造函数可以重载
  5. 如果类中没有定义构造函数那么编译器会自动生成一个无参的构造函数,如果显示定义了就不会自己生成
  6. 无参构造函数,全缺省构造函数,以及我们不写编译器自动生成的构造函数都叫做默认构造函数,并且这三个函数不能同时存在,同时存在虽然无参和全缺省构成了函数的重载但是调用函数时会存在歧义。总结不传实参就可以调用的构造函数统称为默认构造。
  7. 我们不写编译器默认生成的构造函数对内置类型没有影响,具体怎么初始化取决于编译器怎么初始化,但是对于我们用class/struct来创建的自定义类型成员变量就需要调用他的构造函数进行初始化,如果没有构造函数就会报错,我们要初始化成员变量就需要用到初始化列表(后面会解释)
#include<iostream>using namespace std;
class Date 
{
public://无参构造函数/*Date(){_year = 1;_month = 1;_day = 1;}*///带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}//全缺省构造函数/*Date(int year = 1, int month = 1, int day = 1){_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.Print();Date d2(2024, 10, 6); //调用带参的构造函数d2.Print();return 0;
}

在这里插入图片描述
如果我们将第一个无参构造函数和第三个全缺省构造函数注释掉编译器就会报错显示没有可用的构造函数。

在这里插入图片描述
当我们使用无参的构造函数进行初始化时后面不需要加()否则编译器无法区分这里是函数声明还是实例化对象。

typedef int STLDateType;
class Stack
{
public:Stack(int n = 4){_a = (STLDateType*)malloc(sizeof(STLDateType) * n);if (_a == nullptr){perror("malloc fail!");return;}_top = 0;_capacity = n;}
private:STLDateType* _a;size_t _top;size_t _capacity;
};//两个Stack实现队列
class MyQueue
{
public://编译器默认生成MyQueue的构造函数调用了Stack的构造函数,完成了两个成员的初始化
private:Stack _STPush;Stack _STPop;
};
int main()
{MyQueue mq;return 0;
}

当一个类类型的成员变量是自定义类型的时候例如上面的代码MyQueue类的成员变量是自定义类型Stack类,所以编译器不会生成MyQueue的默认构造函数需要我们自己来实现,但是为什么我们明明没有写但是还是可以运行,因为编译器默认生成MyQueue的构造函数调用了Stack的构造函数,完成了两个成员的初始化。如果我们将Stack的构造函数注释掉就会报错。出现下面这种情况
在这里插入图片描述

3 析构函数

析构函数与构造函数的功能相反,析构函数不是完成对对象本身的销毁比如局部对象创立是在栈帧,如果函数结束那么栈帧就会被销毁,不需要我们去管。C++规定对象在销毁时会自动调用析构函数完成对对象中资源的清理和释放工作。析构函数的功能就类似与我们Stack类中Destory函数的功能,而向Date类没有Destory就不需要析构函数。

析构函数的特点 :

  1. 析构函数的函数名是在类名的前面加上‘~’。
  2. 无参数无返回值和构造函数类似
  3. 一个类只能有一个析构函数,如果未显示定义编译器就会自动生成一个默认析构函数
  4. 函数声明周期结束会自动调用析构函数
  5. 跟构造函数类似,我们不写编译器自动生成的析构函数对内置类型的成员变量不做处理,自定义类型成员会调用他自己的析构函数
  6. 需要注意的是如果我们写了析构函数但是对于自定义类型还是会调用他自己的析构函数,也就是说自定义类型成员变量无论什么时候都会调用自己的析构函数
  7. 一个类中如果没有资源申请那么使用编译器自己生成的析构函数就可以比如Date类,如果有资源申请像Stack/MyQueue类都需要自己写析构函数,否则会造成资源泄露。
  8. 一个局部域有多个对象,C++规定后定义的先析构。
#include<iostream>using namespace std;
typedef int STLDateType;
class Stack
{
public:Stack(int n = 4){_a = (STLDateType*)malloc(sizeof(STLDateType) * n);if (_a == nullptr){perror("malloc fail!");return;}_top = 0;_capacity = n;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}
private:STLDateType* _a;size_t _top;size_t _capacity;
};//两个Stack实现队列
class MyQueue
{
public://编译器默认生成MyQueue的构造函数调用了Stack的构造函数,完成了两个成员的初始化
private:Stack _STPush;Stack _STPop;
};
int main()
{Stack st;MyQueue mq;return 0;
}

在这里插入图片描述
从运行结果我们可以看到一共调用了三次析构函数,分别是st一次和mq内的两次

4 拷贝构造

如果说构造函数的第一个参数是自身类类型的引用,并且额外的参数都有默认值,那么这个构造函数可以叫做拷贝构造函数,拷贝构造也是一种特殊的构造函数。

拷贝构造的特点 :

  1. 拷贝构造函数是构造函数的重载
  2. 拷贝构造的函数参数有且必须只有一个就是自身类类型的引用,使用传值的方式编译器会报错,因为会引发无穷递归。
  3. C++规定自定义类型对象进行拷贝行为必须调用拷贝构造,所以这里自定义类型传值传参和传值返回都会调用拷贝构造来完成
  4. 若未显示定义拷贝构造,编译器会自动生成拷贝构造函数,自动生成的内置类型成员变量会进行值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员会调用他自己的拷贝构造
  5. 像Date类型的成员变量都是内置类型可以不写拷贝构造直接使用编译器自己生成的拷贝构造函数,但是如果是Stack类型虽然内部的成员变量都是内置类型但是其中的_a涉及到了资源的指向一起开辟,如果使用浅拷贝会发生冲突,所以需要我们自己实现并且将指向的资源同样进行拷贝。像MyQueue类型的虽然他的成员变量都是自定义类型但是在调用拷贝构造的时候也会调用Stack的拷贝构造所以也不用自己去实现。
  6. 传值返回会产生一个临时对象调用拷贝构造,传引用返回,返回的是对象的别名(引用),没有产生拷贝。但是如果返回对象是当前局部域的临时对象当前函数结束时该局部对象也会销毁就会出现野引用的问题,类似于野指针,传引用返回可以减少拷贝,但是一定要确定返回的对象不会因为当前函数结束而消失才可以使用。
#include<iostream>using namespace std;
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//编译报错 Date类的复制构造函数不能有Date类型的参数,但是可以有 Date& 以及 Date* 类型//Date(Date d)Date(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;
};void func1(Date d)
{cout << &d << endl;d.print();
}
Date& func2()
{Date tmp(2024, 10, 01);tmp.print();return tmp;
}
int main()
{//C++规定传值传参必须调用拷贝构造,所以这里调用了拷贝构造Date d1(2024,10,07);//传值传参给d会调用拷贝构造,使用传引用传参可以减少拷贝func1(d1);cout << &d1 << endl;//传引用传参,这里不是拷贝构造只是单纯的拷贝Date d2(&d1);d1.print();d2.print();//这里也是拷贝构造Date d3 = d1;d3.print();//拷贝构造用同类型的对象初始化而不是指针Date d4(d1);d4.print();//传引用返回的tmp在函数结束的时候就已经被销毁了,所以返回的是野引用Date ret = func2();ret.print();return 0;
}
class Stack
{
public:Stack(int n = 4){_a = (STDataType*)malloc(sizeof(STDataType) * n);if (_a == nullptr){perror("malloc fail");}_capacity = n;_top = 0;}Stack(const Stack& st){//需要对_a指向的资源同样进行拷贝_a = (STDataType*)malloc(sizeof(STDataType) * st._capacity);if (_a == nullptr){perror("malloc fail");}memcpy(_a, st._a, sizeof(STDataType) * st._top);_capacity = st._capacity;_top = st._top;}void Push(STDataType data){if (_top == _capacity){int newcapacity = _capacity * 2;STDataType* tmp = (STDataType*)realloc(_a, sizeof(STDataType) * newcapacity);if (tmp == nullptr){perror("realloc fail");}_a = tmp;_capacity = newcapacity;}_a[_top++] = data;}~Stack(){cout << "~Stack()" << endl;free(_a);_a = nullptr;_top = _capacity = 0;}
private:STDataType* _a;size_t _capacity;size_t _top;
};
//用两个栈实现队列
class MyQueue
{
private:Stack push;Stack pop;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);MyQueue m1;MyQueue m2 = m1;return 0;
}

5 赋值运算符重载

5.1 运算符重载

  • 当运算符被用于类类型对象时,C++允许我们通过运算符重载的方式指定新的含义
  • 重载运算符的参数个数和运算符本来的操作对象数量一样多一元操作符有一个参数,二元运算符有两个参数。
  • 如果一个重载运算符函数是成员函数那么他的第一个参数是隐式指针this,因此运算符重载作为成员函数时会少一个参数。
  • . :: ?: . sizeof* 这五个运算符不能重载
  • 运算符重载是具有图书名字的函数,他的名字是由operator加上后面定义的运算符共同构成。和其他函数一样他也有返回类型 函数体和参数列表
  • 重载++运算符要注意后置++多一个int类型的形参用来区分两个运算符
  • 重载‘<<’ '>>'两个运算符的时候需要重载为全局函数,如果是成员函数他的第一个默认类型是隐式类型this作为他的左操作数调用时就变成了 ‘ 输出的对象<<cout ’ 不符合使用习惯和可读性,重载为全局函数只需要第一个参数类型时ostream/istream就可以了,第二个参数位置为该类类型的对象。
  • 运算符重载后优先级和结合性要保持一致
  • 重载操作符必须有一个类类型的参数,不能改变其原有的含义。

在这里插入图片描述

class A
{
public:void func(){cout << "A::func()" << endl;}
};
typedef void(A::* PF)(); //成员函数指针类型int main()
{//C++规定成员函数要加&才可以取到函数指针PF pf = &A::func;A obj;(obj.*pf)();//对象调用成员函数指针使用.*运算符return 0;
}
//重载为全局函数会面临的问题
// 无法访问私有变量
// 解决办法
// 1 用public
// 2 提供get函数
// 3 友元函数
// 4 重载为成员函数//public成员变量
bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
int main()
{Date d1(2024, 10, 06);Date d2(2024, 10, 07);//显示调用operator==(d1, d2);//编译器会转换成operator==(d1, d2)d1 == d2;return 0;
}

5.2 赋值运算符的重载

赋值运算符重载是一个默认成员函数,用于完成两个已经存在的的对象直接拷贝赋值,但这里和拷贝构造的区分,拷贝构造用于一个对象拷贝初始化给另一个创建的对象。

赋值运算符重载的特点 :

  1. 赋值运算符重载是一个运算符重载,必须重载为成员函数,他的参数必须是当前的类类型建议加上const,否则传值传参会有拷贝。
  2. 有返回值,建议写成当前类类型的引用,引用返回可以提高效率,有返回值的母体是为了可以连续赋值
  3. 没有显式实现时,编译器会⾃动⽣成⼀个默认赋值运算符重载,默认赋值运算符重载⾏为跟默认构造函数类似,对内置类型成员变量会完成值拷⻉/浅拷⻉(⼀个字节⼀个字节的拷⻉),对⾃定义类型成员变量会调⽤他的拷⻉构造
#include<iostream>using namespace std;
typedef int STDataType;
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//编译报错 Date类的复制构造函数不能有Date类型的参数,但是可以有 Date& 以及 Date* 类型//Date(Date d)Date(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;}Date& operator=(const Date& d1){if (this != &d1){_year = d1._year;_month = d1._month;_day = d1._day;}return *this;}
//private:int _year;int _month;int _day;
};
int main()
{Date d1(2024, 10, 06);Date d2(2024, 10, 07);//显示调用operator==(d1, d2);//编译器会转换成operator==(d1, d2)d1 == d2;d2.print();d2 = d1;d2.print();//注意这里不是赋值重载,而是拷贝构造,赋值重载只用于两个已经存在的对象,// 而拷贝构造用于一个对象初始化另一个对象Date d3 = d1;return 0;
}

在这里插入图片描述

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

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

相关文章

【Arduino IDE安装】Arduino IDE的简介和安装详情

目录 &#x1f31e;1. Arduino IDE概述 &#x1f31e;2. Arduino IDE安装详情 &#x1f30d;2.1 获取安装包 &#x1f30d;2.2 安装详情 &#x1f30d;2.3 配置中文 &#x1f30d;2.4 其他配置 &#x1f31e;1. Arduino IDE概述 Arduino IDE&#xff08;Integrated Deve…

黑神话:仙童,数据库自动反射魔法棒

黑神话&#xff1a;仙童&#xff0c;数据库自动反射魔法棒 Golang 通用代码生成器仙童发布了最新版本电音仙女尝鲜版十一及其介绍视频&#xff0c;视频请见&#xff1a;https://www.bilibili.com/video/BV1ET4wecEBk/ 此视频介绍了使用最新版的仙童代码生成器&#xff0c;将 …

论文阅读:InternVL v1.5| How Far Are We to GPT-4V? 通过开源模型缩小与商业多模式模型的差距

论文地址&#xff1a;https://arxiv.org/abs/2404.16821 Demo&#xff1a; https://internvl.opengvlab.com Model&#xff1a;https://huggingface.co/OpenGVLab/InternVL-Chat-V1-5 公开时间&#xff1a;2024年4月29日 InternVL1.5&#xff0c;是一个开源的多模态大型语言模…

mac配置python出现DataDirError: Valid PROJ data directory not found错误的解决

最近在利用python下载SWOT数据时出现以下的问题&#xff1a; import xarray as xr import s3fs import cartopy.crs as ccrs from matplotlib import pyplot as plt import earthaccess from earthaccess import Auth, DataCollections, DataGranules, Store import os os.env…

C语言初步介绍(初学者,大学生)【上】

1.C语⾔是什么&#xff1f; ⼈和⼈交流使⽤的是⾃然语⾔&#xff0c;如&#xff1a;汉语、英语、⽇语 那⼈和计算机是怎么交流的呢&#xff1f;使⽤ 计算机语⾔ 。 ⽬前已知已经有上千种计算机语⾔&#xff0c;⼈们是通过计算机语⾔写的程序&#xff0c;给计算机下达指令&am…

Ubuntu 安装RUST

官方给的是这样如下脚本 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh 太慢了 curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh -x 执行这个脚本后会给出对应的下载链接 如下图 我直接给出来 大多数应该都是这个 https://static.rust-…

【EXCEL数据处理】000013 案例 EXCEL筛选与高级筛选。

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录 【EXCEL数据处理】000013 案例 EXCEL筛选与高级筛选。使用的软件&#…

【动态规划-最长公共子序列(LCS)】力扣1035. 不相交的线

在两条独立的水平线上按给定的顺序写下 nums1 和 nums2 中的整数。 现在&#xff0c;可以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线&#xff0c;这些直线需要同时满足&#xff1a; nums1[i] nums2[j] 且绘制的直线不与任何其他连线&#xff08;非水平线&#xff09…

提升开机速度:有效管理Windows电脑自启动项,打开、关闭自启动项教程分享

日常使用Windows电脑时&#xff0c;总会需要下载各种各样的办公软件。部分软件会默认开机自启功能&#xff0c;开机启动项是指那些在电脑启动时自动运行的程序和服务。电脑开机自启太多的情况下会导致电脑卡顿&#xff0c;开机慢&#xff0c;运行不流畅的情况出现&#xff0c;而…

[C++11] lambda表达式

文章目录 Lambda表达式简介捕获列表的常见写法&#xff1a; Qt中的connect和Lambda常规的 connect() 方式&#xff1a;使用Lambda表达式的 connect()&#xff1a;代码示例&#xff1a; 捕获外部变量在 Qt 信号槽中的应用Lambda在Qt中的使用优势总结参考代码总结&#xff1a; La…

目标检测与图像分类:有什么区别?各自的使用场景是什么?

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

Android阶段学习思维导图

前言 记录下自己做的一个对Android原生应用层的思维导图&#xff0c;方便个人记忆扩展&#xff1b;这里只露出二级标题。 后语 虽然有些内容只是初步了解&#xff0c;但还是记录了下来&#xff1b;算是对过去一段学习的告别。

EtherNet/IP 转 EtherNet/IP, EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关

EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关https://item.taobao.com/item.htm?ftt&id822721028899 协议转换通信网关 EtherNet/IP 转 EtherNet/IP GW系列型号 MS-GW22 概述 简介 MS-GW22 是 EtherNet/IP 和 EtherNet/IP 协议转换网关&#xff0c;…

ThreeJS入门(092):THREE.Curve 知识详解,示例代码

作者&#xff1a; 还是大剑师兰特 &#xff0c;曾为美国某知名大学计算机专业研究生&#xff0c;现为国内GIS领域高级前端工程师&#xff0c;CSDN知名博主&#xff0c;深耕openlayers、leaflet、mapbox、cesium&#xff0c;webgl&#xff0c;ThreeJS&#xff0c;canvas&#xf…

厂商资源分享网站

新华三&#xff08;H3C&#xff09;是一家中国知名的网络设备供应商&#xff0c;提供网络设备、网络解决方案和云计算服务。公司成立于2003年&#xff0c;是华为公司和惠普公司合资的企业&#xff0c;总部位于中国深圳。 华为&#xff08;Huawei&#xff09;是一家全球知名的电…

使用rsync+jenkins实现服务自动部署全流程

项目背景&#xff1a;城市政务云服务器没有上k8s&#xff0c;所有后端服务都是原始方式部署启动 &#xff08;java -jar xxx.jar&#xff09;&#xff0c;那么有没有方式简化部署难度&#xff0c;实现自动部署&#xff1f;当然是有的&#xff0c;下面详细介绍&#xff08;以Cen…

前端工程化 - Vue

环境准备 Vue-cli是Vue官方提供的一个脚手架&#xff0c;用户快速生成一个Vue的项目模板。 Vue-cli提供了如下功能&#xff1a; 统一的目录结构本地调试热部署单元测试集成打包上线 需要安装Node.js 安装Vue-cli npm install -g vue/cli通过vue --version指令查看是否安装成…

vite学习教程03、vite+vue2打包配置

文章目录 前言一、修改vite.config.js二、配置文件资源/路径提示三、测试打包参考文章资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝3W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客之星、阿里云平台优质作者、专注于Java后端技术领域。 涵盖技术内容&…

数学概念算法-打印100以内的素/质数

素数&#xff1a;只能被1和自己整除的数 暴力破解 埃氏筛选 找到第一个数字&#xff0c;如果它是素数&#xff0c;则把它的倍数全部划掉 比如数字2是素数&#xff0c;那么 4,6,8,10,12。这些数字肯定不是素数&#xff0c;所以不用再考虑&#xff0c;直接划掉即可 第二步&#…

20年408数据结构

第一题&#xff1a; 解析&#xff1a;这种题可以先画个草图分析一下&#xff0c;一下就看出来了。 这里的m(7,2)对应的是这图里的m(2,7),第一列存1个元素&#xff0c;第二列存2个元素&#xff0c;第三列存3个元素&#xff0c;第四列存4个元素&#xff0c;第五列存5个元素&#…