C++ 类和对象(中篇)

类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情 况下,都会自动生成下面6个默认成员函数。

构造函数:

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

特性:

1.不是开空间创建对象,而是初始化对象。

2. 函数名与类名相同。

3. 无返回值。

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

5. 构造函数可以重载


#include <iostream>
#include <assert.h>using namespace std;class Date
{
private:int _year;int _month;int _day;public://1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}
};int main()
{Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明// 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象Date d3();return 0;
}

根据不同的初始化需求,去选择构造函数:

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

class Date
{
private:int _year;int _month;int _day;void MPrintf(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
};int main()
{
// 没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数Date d1;d1.MPrintf();return 0;
}

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

默认成员函数分为:

为什么默认构造函数只能有一个?

回答:

当同时有无参构造函数和全缺省构造函数时,在实例化过程中编译器无法判断选择哪一种构造函数。

//为什么默认构造函数只能有一个?
class Date
{
private:int _year;int _month;int _day;public://1.无参构造函数Date(){}// 2.全缺省构造函数Date(int year=2018, int month=1, int day=1){_year = year;_month = month;_day = day;}void MPrintf(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
};int main()
{   // 调用无参构造函数Date d1; //全缺省构造函数--注释1.无参构造函数Date d2(1111);d2.MPrintf();Date d3(2222, 2);d3.MPrintf();Date d4(3333, 3, 3);d4.MPrintf();return 0;
}

此时编译器是无法确定选择哪一种默认构造函数;

全缺省构造函数结果:

8.默认构造函数多用于自定义类型

对于自定义类型(复杂情况)我们会用构造函数的默认生成,更方便

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;
}

调试结果:

对于内置类型,默认构造函数时不进行内容的改变,只保留随机值。

对于自定义类型,我们设置了他的默认成员函数,随机值进行了改变,在以后多次使用tmie类型是,他的初始化内容都会改变成自己一开始设置的初始化内容。

内置和自定义混合的,可以给内置缺省

此处不是初始化(空间没有创造就不能算是初始化,只能算是声明缺省值)

解释:默认构造没有参数传递,使用其原本的缺省值

由此得出:

默认生成构造函数:

1.内置类型成员不做处理

2.自定义类型成员,会去调用它的默认构造(不用传参数的构造)

建议:每个类都提供一个默认构造函数(内置类型->缺省构造)

析构函数:

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

特征:

1. 析构函数名是在类名前加上字符 ~

2. 无参数无返回值。

3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

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

拷贝构造函数:
 

定义:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。(将已近实例化好的类型对象拷贝给将要实例化的新的对象的构造函数

特征:

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个必须使用引用传参,使用传值方式会引发无穷递归调用。

为什么只传一个参数?

回答:其在结构体内部,本身还有一个隐形的This指针。

必须使用引用的原因?

回答:避免出现使用传值方式会引发无穷递归调用。--在拷贝时先准备启用拷贝构造函数,传值时会调用拷贝构造函数。这样会形成无穷递归调用。(不理解直接记成:拷贝构造函数必用引用


使用const的原因?

回答:在传递过程中,将可读可写的修改成const(只读)模式。将其权限缩小,避免因为自己思路的问题而导致原本值的改变。

3. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。

代码:

#include <iostream>using namespace std;class Date
{
public://构造函数-全缺省Date(int year = 2018, int month = 1, int day = 1){_year = year;_month = month;_day = day;}拷贝构造函数//Date(const Date& d)//{//	_year = d._year;//	_month = d._month;//	_day = d._day;//}void Mprintf(){cout << _year << "/" << _month<<"/" << _day << endl;}
private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Mprintf();Date d2(d1);d2.Mprintf();return 0;
}

结果:

通过两次运行结果对比发现貌似这个拷贝构造函数有无都一样?

其实不然,C语言本身是对一些内置类型(int char等)在编译器的底层是有类似拷贝构造函数的结构的。

但是,面对一些自定义类型,或者申请有申请空间的情况,C语言不能满足需求

那就需要我们自己写专门针对自己的类的类型的拷贝构造函数

如以下代码:

//开辟一个字符串空间
class String
{
public:String(const char* str = "jack"){_str = (char*)malloc(strlen(str) + 1);strcpy(_str, str);}//析构函数~String(){cout << "~String()" << endl;free(_str);}
private:char* _str;
};
int main()
{String s1("hello");String s2(s1);
}

此时拷贝的运行结果就是错误。

什么情况下用拷贝构造函数:
自己实现了析构函数释放空间,就需要实现拷贝构造函数;

拷贝构造应用场景:

获取x天后的日期:

//拷贝构造应用场景
class Date
{
public://构造函数--全缺省Date(int year = 2018, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//打印函数void Mprintf(){cout << _year << "/" << _month << "/" << _day << endl;}int GetMonthDay(int year,int month){assert(month > 0 && month < 13);//正常情况下当前月数返回的天数,月从1开始,所以多加一位占位int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//闰年2月天数情况if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400) == 0)){return 29;}else{return monthArray[month];}}//x天后的日期Date GetAfterXDay(int x){//因为不想改变初始日期所以使用拷贝Date tmp = *this;tmp._day += x;while (tmp._day > GetMonthDay(tmp._year, tmp._month)){tmp._day -= GetMonthDay(tmp._year, tmp._month);++tmp._month;if (tmp._month == 13){tmp._year++;tmp._month = 1;}}return tmp;}private:int _year;int _month;int _day;
};int main()
{Date d1(2222, 2, 2);//d1.GetAfterXDay(100)调用类d1里的GetAfterXDay函数//返回一个Date类型的对象再拷贝到(Date d2=返回值)d2中Date d2 = d1.GetAfterXDay(100);d1.Mprintf();d2.Mprintf();return 0;
}

赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类 型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)(对于比较类,不需要更改值,常用const修饰)

用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义

.* 、:: 、sizeof 、?: (三目运算符)、. 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

公共成员:

要在类外调用还得将声明对象的private改成public

class Date
{
public:Date(int year = 1111, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};
//d1与d2位置分别对应==的两端,不能修改
//参数和操作数(函数形参位置)是成正比的
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(2018, 9, 26);Date d2(2018, 9, 27);cout << (d1 == d2) << endl;//d1==d2会自动call对于函数地址//改变成->operator==(d1,d2)进行比较
}

为什么要在输出是加括号:

回答:因为<< 和== 优先级不同cout << d1 == d2 << endl;只会先识别cout << d1

成员函数:

class Date
{
public:Date(int year = 1111, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
//因为是成员函数,所以有其自身的this
// 完全展开后:bool operator==(Date* this, const Date& d2)bool operator==(const Date& d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};int main()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);d1 == d2;//因为是成员函数,其内部自身有隐藏this所以值传递一个值d1.operator==(d2);cout << (d1 == d2) << endl;cout << d1.operator==(d2) << endl;
}

并不局限于==除了.*    、   ::     、    sizeof     、   ?: (三目运算符)   、  .  这5个外都可以用operator(运算符)更改

运算符重载的复用:

代码:

//运算符重载的复用
class Date
{
public:Date(int year = 1111, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//因为是成员函数,所以有其自身的this// 完全展开后:bool operator==(Date* this, const Date& d2)bool operator==(const Date& d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}//复用!!//由于已知了==,根据==就能写出!=(> < >= <=(小于或等于)同理)bool operator!=(const Date& d){return !(*this == d);//相等1再!->false//不等0再!->true}
private:int _year;int _month;int _day;
};int main()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);cout << (d1!=d2) << endl;//显示1,truereturn 0;
}

赋值运算符重载 =

主要特点:

1. 参数类型

2. 返回值

3. 检测是否自己给自己赋值

4. 返回*this

5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。

赋值运算使用:

常用域自己实现了析构函数释放空间,就需要使用自己编写的赋值运算符重载;

class Date
{
public:Date(int year = 1111, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Mprintf(){cout << _year << "/" << _month<<"/" << _day << endl;}
private:int _year;int _month;int _day;
};class String
{
public://拷贝String(const char* str = ""){_str = (char*)malloc(strlen(str) + 1);strcpy(_str, str);}//析构~String(){cout << "~String()" << endl;free(_str);}void Mprintf(){cout << _str << endl;}
private:char* _str;
};int main()
{Date d1(2222, 9, 26);Date d2(3333, 9, 27);String s1("hello");String s2("world");d1.Mprintf();d2.Mprintf();s1.Mprintf();s2.Mprintf();//编译器生成的默认赋值重载函数已经可以完成字节序的值拷贝了d1 = d2;s1 = s2;//报错//此时,自己没有写赋值重载函数,当s2赋值给s1后//s1和s2同时指向相同的一块空间(s2开出的)//后序清理资源,清理s2的时候,s1所指向的空间也会改变//所以错误,编译器不通过d1.Mprintf();s1.Mprintf();return 0;
}

连续赋值

有返回值用于支持这里的连续赋值,保持运算符的特性

eg.i=j=k;连续赋值的顺序是先j=k后返回j再i=j;

所以由于以上连续赋值的可能性,也要考虑赋值运算符重载的连续赋值的可能。


class Date
{
public:Date(int year = 1111, int month = 1, int day = 1){_year = year;_month = month;_day = day;}void Mprintf(){cout << _year << "/" << _month<<"/" << _day << endl;}//d3=d2=d1//返回的是赋值的左操作数所以返回*this//返回后该操作数的生命周期还存在所以用引用返回Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}
private:int _year;int _month;int _day;
};int main()
{Date d1(1111, 1, 1);Date d2(2222, 2, 2);Date d3(3333, 3, 3);d1.Mprintf();//1111,1,1d1 = d2 = d3;d1.Mprintf();//3333,3,3return 0;
}

自己给自己赋值

有时不小心的操作可能会造成自己给自己赋值的情况

所以还需要在自己写的赋值情况中加入if判断

总结:

赋值重载和拷贝构造区别:

赋值重载是多个已近定义出来的对象

拷贝构造是一个已经实例化的对象初始化另一个未实例化的对象

在使用引用返回时,不能盲目的为了追求使用引用返回而使用静态变量static

静态变量static在整体函数调用中,只会初始化他的第一次,再次走到static内一步时,不会再初始化,而是使用其之前的值;

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

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

相关文章

【C语言】猜数字小游戏(并讲解随机数相关知识)

前言 一、游戏菜单 二、游戏逻辑 1.用户选择 2.开始游戏 2.1 生成1~100的随机数 总结 前言 本文讲解使用C语言写一个猜数字小游戏(1~100)&#xff0c;涉及到的语法为&#xff1a;循环、分支、随机数、函数 一、游戏菜单 一个游戏的最开始&#xff0c;往往是一个菜单&…

Jetpack Bluetooth——更优雅地使用蓝牙

Jetpack Bluetooth——更优雅地使用蓝牙 蓝牙是安卓开发中非常常用的操作&#xff0c;但安卓经过这么多年的迭代&#xff0c;蓝牙的相关接口都经过了很多修改需要适配&#xff0c;还有的接口需要实现一堆函数。。。整套操作虽说不算复杂&#xff0c;但难免感觉不太舒服。 之前…

专题【双指针】【学习题】刷题日记

题目列表 11. 盛最多水的容器 42. 接雨水 15. 三数之和 16. 最接近的三数之和 18. 四数之和 26. 删除有序数组中的重复项 27. 移除元素 75. 颜色分类 167. 两数之和 II - 输入有序数组 2024.04.06 11. 盛最多水的容器 题目 给定一个长度为 n 的整数数组 height 。有 n 条垂…

PHP实现网站微信扫码关注公众号后自动注册登陆实现方法及代码【关注收藏】

在网站注册登陆这环节&#xff0c;增加微信扫码注册登陆&#xff0c;普通的方法需要开通微信开发者平台&#xff0c;生成二维码扫码后才能获取用户的uinonid或openid&#xff0c;实现注册登陆&#xff0c;但这样比较麻烦还要企业认证交费开发者平台&#xff0c;而且没有和公众号…

【SQL】1890. 2020年最后一次登录(简单写法;窗口函数写法)

前述 sql 中 between 的边界问题 ---- between 边界&#xff1a;闭区间&#xff0c;not between 边界&#xff1a;开区间 在 sql 中&#xff0c; between 边界&#xff1a;闭区间not between 边界&#xff1a;开区间 题目描述 leetcode题目&#xff1a;1890. 2020年最后一…

【leetcode面试经典150题】16.接雨水(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

aardio教程五) 写Python风格的aardio代码(字符串篇)

前言 熟悉一个新的语言最麻烦的就是需要了解一些库的使用&#xff0c;特别是基础库的使用。 所以我想给aardio封装一个Python风格的库&#xff0c;Python里的基础库是什么方法名&#xff0c;aardio里也封装同样的方法名。 这样就不需要单独去了解aardio里一些方法的使用细节…

Lanelets_ 高效的自动驾驶地图表达方式

Lanelets: 高效的自动驾驶地图表达方式 附赠自动驾驶学习资料和量产经验&#xff1a;链接 LaneLets是自动驾驶领域高精度地图的一种高效表达方式&#xff0c;它以彼此相互连接的LaneLets来描述自动驾驶可行驶区域&#xff0c;不仅可以表达车道几何&#xff0c;也可以完整表述车…

AIGC实战——ProGAN(Progressive Growing Generative Adversarial Network)

AIGC实战——ProGAN 0. 前言1. ProGAN2. 渐进式训练3. 其他技术3.1 小批标准差3.2 均等学习率3.3 逐像素归一化 4. 图像生成小结系列链接 0. 前言 我们已经学习了使用生成对抗网络 (Generative Adversarial Network, GAN) 解决各种图像生成任务。GAN 的模型架构和训练过程具有…

真实的招生办对话邮件及美国高校官网更新的反 AI 政策

这两年 ChatGPT 的热度水涨船高&#xff0c;其编写功能强大&#xff0c;且具备强大的信息整合效果&#xff0c;所以呈现的内容在一定程度上具备可读性。 那么&#xff0c;美国留学文书可以用 ChatGPT 写吗&#xff1f;使用是否有风险&#xff1f;外网博主 Kushi Uppu 在这个申…

基于卷积神经网络的天气识别系统(pytorch框架)【python源码+UI界面+前端界面+功能源码详解】

功能演示&#xff1a; 天气识别系统&#xff0c;vgg16&#xff0c;mobilenet卷积神经网络&#xff08;pytorch框架&#xff09;_哔哩哔哩_bilibili &#xff08;一&#xff09;简介 基于卷积神经网络的天气识别系统是在pytorch框架下实现的&#xff0c;系统中有两个模型可选…

vue+elementUI实现表格组件的封装

效果图&#xff1a; 在父组件使用表格组件 <table-listref"table":stripe"true":loading"loading":set-table-h"slotProps.setMainCardBodyH":table-data"tableData":columns"columns.tableList || []":ra…

基于Springboot的Java学习平台

采用技术 基于Springbootjava学习平台的设计与实现~ 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBootMyBatis 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 页面展示效果 系统功能模块 后台管理 用户注册 课程信息 作业信息 资料信息…

电工技术学习笔记——正弦交流电路

一、正弦交流电路 1. 正弦量的向量表示法 向量表示方法&#xff1a;正弦交流电路中&#xff0c;相量表示法是一种常用的方法&#xff0c;用于描述电压、电流及其相位关系。相量表示法将正弦交流信号表示为复数&#xff0c;通过复数的运算来描述电路中各种参数的相互关系 …

java中的正则表达式和异常

正则表达式&#xff1a; 作用一&#xff1a;用来校验数据格式是否合法 作用二&#xff1a;在文本中查找满足要求的内容 不用正则表达式&#xff1a;检验QQ号是否合法&#xff0c;要求全部是数字&#xff0c;长度在6-20&#xff0c;不能以0开头 public class test {public stat…

【Linux实践室】Linux高级用户管理实战指南:创建与删除用户组操作详解

&#x1f308;个人主页&#xff1a;聆风吟_ &#x1f525;系列专栏&#xff1a;Linux实践室、网络奇遇记 &#x1f516;少年有梦不应止于心动&#xff0c;更要付诸行动。 文章目录 一. ⛳️任务描述二. ⛳️相关知识2.1 &#x1f514;Linux创建用户组命令2.1.1 知识点讲解2.1.2…

亲手开发全国海域潮汐表查询微信小程序详情教程及代码

最近在做一个全国海域潮汐表查询&#xff0c;可以为赶海钓鱼爱好者提供涨潮退潮时间表及潮高信息。 下面教大家怎么做一个这样的小程序。 主要功能&#xff0c;根据IP定位地理位置&#xff0c;自动查询出省份或城市的港口&#xff0c;进入后预测7天内港口潮汐表查询。 步骤&…

全坚固笔记本丨工业笔记本丨三防笔记本相较于普通笔记本有哪些优势?

三防笔记本和普通笔记本在设计和性能方面存在显著差异&#xff0c;三防笔记本相较于普通笔记本具备以下优势&#xff1a; 三防笔记本通常采用耐磨、耐摔的材料&#xff0c;并具有坚固的外壳设计&#xff0c;能够承受恶劣环境和意外碰撞&#xff0c;有效保护内部组件不受损坏。相…

【Linux】进程初步理解

个人主页 &#xff1a; zxctscl 如有转载请先通知 文章目录 1. 冯诺依曼体系结构1.1 认识冯诺依曼体系结构1.2 存储金字塔 2. 操作系统2.1 概念2.2 结构2.3 操作系统的管理 3. 进程3.1 进程描述3.2 Linux下的PCB 4. task_struct本身内部属性4.1 启动4.2 进程的创建方式4.2.1 父…

C/C++预处理过程

目录 前言&#xff1a; 1. 预定义符号 2. #define定义常量 3. #define定义宏 4. 带有副作用的宏参数 5. 宏替换的规则 6. 宏和函数的对比 7. #和## 8. 命名约定 9. #undef 10. 命令行定义 11. 条件编译 12. 头文件的包含 13. 其他预处理指令 总结&#x…