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,一经查实,立即删除!

相关文章

js的some函数

在JavaScript中&#xff0c;some() 是一个数组的方法&#xff0c;用于测试数组中是否至少有一个元素满足提供的函数。如果数组中有至少一个元素通过由提供的函数实现的测试&#xff0c;则它返回 true&#xff1b;否则返回 false。 以下是 some() 函数的基本语法&#xff1a; j…

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

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

面试反问环节

目录 hr面反问技术面反问 一般反问3、4个问题 hr面反问 部门的工作氛围和领导风格是怎样的&#xff08;还有上下班作息时间等&#xff09;&#xff1f;部门对这个职位的期望是什么&#xff1f;部门的转正率大概有多少&#xff1f;实习生的薪资有多少&#xff1f;有房补吗&…

金融数据_Scikit-Learn决策树(DecisionTreeClassifier)实例

金融数据_Scikit-Learn决策树(DecisionTreeClassifier)实例 逻辑回归: 逻辑回归常被用于二分类问题, 比如涨跌预测。你可以将涨跌标记为类别, 然后使用逻辑回归进行训练。 决策树和随机森林: 决策树和随机森林是用于分类问题的强大模型。它们能够处理非线性关系, 并且对于特征…

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 条垂…

阿里云服务器 篇二:搭建静态网站

文章目录 系列文章获取静态网站模板应用静态网站模板解压zip文件SCP命令上传文件其他上传文件的方法 系列文章 阿里云服务器 篇一&#xff1a;申请和初始化 阿里云服务器 篇二&#xff1a;搭建静态网站 获取静态网站模板 站长素材&#xff1a;网站中包括大量的免费模板&…

上传视频的核心代码

/*** 上传学习视频信息*/Log(title "上传学习视频信息", businessType BusinessType.INSERT)PostMapping("/uploadVideo")public AjaxResult add(HttpServletRequest request) {return toAjax(videoInfoService.insertVideoInfo(request));}/*** 上传学习…

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

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

如何控制Docker容器退出后的自动重启行为?

在Docker中&#xff0c;可以通过以下两种方式来控制容器退出后的自动重启行为&#xff1a; 使用docker run命令时&#xff0c;通过设置--restart参数来指定容器退出后的重启策略。可以使用以下值之一&#xff1a; no: 默认值&#xff0c;容器退出后不会自动重启。always: 容器…

什么是EL表达式?怎么使用?

文章目录 一、什么是EL表达式1、命令格式&#xff1a;${作用域对象别名.共享数据} 二、EL表达式与作用域对象别名1、JSP文件可以使用的作用域对象2、EL表达式提供作用域对象别名3、EL表达式将引用对象属性写入到响应体4、EL表达式简化版 三、EL表达式与运算表达式四、EL表达式提…

【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;也可以完整表述车…

.NET9 PreView2+.AOT ILC 的重大变化

RyuJIT 增强功能 1. 环路优化 (循环优化) 这种优化实际上是一种 for 循环叠加态的优化&#xff0c;for 循环叠加计算的过程中&#xff0c;会对其中部分变量进行感应。比如循环中放置 0 扩展 (第一个索引为 0)&#xff0c;这种优化灵感来源于 LLVM 标量演化。下面看例子&#…

每天一个数据分析题(二百五十四)

在大数据时代背景下&#xff0c;我们使用的数据主要包含两种类别&#xff0c;一种称为结构化数据&#xff0c;另一种称为非结构化数据。请问以下哪个选项属于非结构化数据&#xff1f; A. 利润表 B. 短视频 C. 产品库存表 D. 产品进货表 题目来源于CDA模拟题库 点击此处获…

LeetCode 每日一题 2024/4/1-2024/4/7

记录了初步解题思路 以及本地实现代码&#xff1b;并不一定为最优 也希望大家能一起探讨 一起进步 目录 4/1 2810. 故障键盘4/2 894. 所有可能的真二叉树4/3 1379. 找出克隆二叉树中的相同节点4/4 2192. 有向无环图中一个节点的所有祖先4/5 1026. 节点与其祖先之间的最大差值4/…

8种专坑运维的 SQL 写法,性能降低100倍,您不来看看?

1、LIMIT 语句 分页查询是最常用的场景之一&#xff0c;但也通常也是最容易出问题的地方。比如对于下面简单的语句&#xff0c;一般 DBA 想到的办法是在 type&#xff0c;name&#xff0c; create_time 字段上加组合索引。这样条件排序都能有效的利用到索引&#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 的模型架构和训练过程具有…