类和对象(中篇)

类的六个默认成员函数

如果一个类中什么成员都没有,简称为空类。
空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下6个默认成员函数。
默认成员函数:
用户没有显式实现,编译器会生成的成员函数称为默认成员函数

我们可以把它们看作是从娘胎里带出来的东西,是与生俱来的,就算我们在类里面不写编译器也会自动生成

而这六个默认成员函数的主要功能分别如下:
在这里插入图片描述
下面我们就对几个默认成员函数进行更加深入的学习

构造函数

概念

我们首先给出一个日期类:

class Date
{public:void Init(int year, int month, int day){_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.Init(2022, 7, 5);d1.Print();Date d2;d2.Init(2022, 7, 6);d2.Print();return 0;}

对于这个日期类,我们想要给一个日期初始化,我们首先想到的就是写一个init的初始化函数,但如果每次创建对象时都调用该方法设置信息,就会有点麻烦,那能否在对象创建时,就将信息设置进去呢?

我们C++的祖师爷就想到了一个办法:

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

而这个函数就是我们所说的构造函数

特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

构造函数具有以下特性:

  1. 函数名与类名相同。
  2. 无返回值。
  3. 对象实例化时编译器自动调用对应的构造函数。
  4. 构造函数可以重载。

下面给出一段代码进行参考
本段代码中有两个与类名相同的函数,但是一个有参数,一个无参数,但是他们两个都是date类的构造函数
也就是说,一个类里面可以有多个构造函数

 class Date{public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;};

这里还需要注意,调用无参构造函数时不要加上括号,否则就成了函数声明

void TestDate(){Date d1; // 调用无参构造函数Date d2(2015, 1, 1); // 调用带参的构造函数//这里会报错Date d3();}
  1. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

请看一段代码:
我们首先是将我们自己定义的构造函数注释掉的,编译器就自动生成了构造函数,但是如果取消注释的话,就会编译错误
因为代码中调用了无参的构造函数,但是我们已经定义了有参数的构造函数,编译器不在自动生成无参构造函数,所以就会报错

class Date{public:/*// 如果用户显式定义了构造函数,编译器将不再生成Date(int year, int month, int day){_year = year;_month = month;_day = day;}*/void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;};int main(){// 将Date类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函
数// 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再
生成// 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用Date d1;return 0;}
  1. 关于编译器生成的默认成员函数,很多童鞋会有疑惑:不实现构造函数的情况下,编译器会生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的
    默认构造函数并没有什么用??
    解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。

什么意思呢?请看一段代码

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

我们用调试来看看d的内容是否被初始化:
我们可以看到d的内容都是随机值,并且编译器爆出警告未初始化,因为_year等是内置类型,编译器自动生成的默认成员函数不对其起作用,只对自定义类型起作用
在这里插入图片描述
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
例如:

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 = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}
  1. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
    注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
    请看下面这段代码:
    这段代码是不能编译通过的,因为Date()是无参构造函数,Date(int year = 1900, int month = 1, int day = 1)是全缺省构造函数,他们两个都被认作是默认构造函数,但是默认生成的构造函数只能有一个,所以随便注释掉其中一个即可
class Date
{
public:Date(){_year = 1900;_month = 1;_day = 1;}Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year;int _month;int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{Date d1;
}

析构函数

概念

其实析构函数和构造函数的很多特性都相同

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

特性

析构函数是特殊的成员函数,其特征如下:

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

  2. 无参数无返回值类型。

  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载(就因为析构函数没有参数)

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

  5. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

请看代码:

class Time
{
public:~Time(){cout << "~Time()" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d;return 0;
}

运行结果:
在这里插入图片描述
这就表明了程序在清理d的时候调用了time类的析构函数,因为d的的成员变量中就包括了一个time类的对象,而其他三个成员变量都是内置类型,由系统直接回收,但是销毁的过程不是我们想的这么简单的

// 程序运行结束后输出:~Time()
// 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
// 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month, 
//_day三个是内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对
//象,所以在d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:
//main函数中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date
//类的析构函数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部
//调用Time类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
//main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析构函数
// 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
  1. 如果类中没有申请资源时,析构函数可以不写(自动回收),直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

拷贝构造函数

概念

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

特征

拷贝构造函数也是特殊的成员函数,其特征如下:

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

写法如下:

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// Date(const Date d)   // 正确写法Date(const Date& d)   // 错误写法:编译报错,会引发无穷递归{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(d1);return 0;
}

这里需要注意:
传参也需要调用拷贝构造!
所以不能使用传值传参的方式,否则就会无穷递归
在这里插入图片描述
3. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。
4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类这样的类是没必要的。那么下面的类呢?验证一下试试?
请看代码:
这段代码跑起来就会奔溃:

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。
在这里插入图片描述
5. 拷贝构造函数典型调用场景:
1 使用已存在对象创建新对象
2 函数参数类型为类类型对象
3 函数返回值类型为类类型对象

注意:
为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。

赋值运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
1 不能通过连接其他符号来创建新的操作符:比如operator@
2 重载操作符必须有一个类类型参数
3 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
4 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

.* :: sizeof ?: .

注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

具体操作请看代码:

// 全局的operator==
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//private:int _year;int _month;int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。或者直接放类里面
bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
void Test()
{Date d1(2018, 9, 26);Date d2(2018, 9, 27);cout << (d1 == d2) << endl;
}

这里还需要注意隐含参数this

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// bool operator==(Date* this, const Date& d2)// 这里需要注意的是,左操作数是this,指向调用函数的对象bool operator==(const Date & d2){return _year == d2._year;&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};
赋值运算符重载
  1. 赋值运算符重载格式
    1 参数类型:const T&,传递引用可以提高传参效率
    2 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
    3 检测是否自己给自己赋值
    4 返回*this :要复合连续赋值的含义
    例如:
    这段代码的return this 的操作就是为了能够连续赋值
class Date
{
public:Date(int year = 1900, 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;}Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}
private:int _year;int _month;int _day;
};
  1. 赋值运算符只能重载成类的成员函数不能重载成全局函数
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员
  1. 用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。
    注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
前置++和后置++重载

对于前置++
我们直接写成:

Date& operator++()

而后置的++
C++固定写成
在调用是编译器会自动识别你的前置++后后置++来分别调用两个函数

Date& operator++(int)

请看代码:
对于函数结束后不会销毁的变量我们就用引用来提高效率

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 前置++:返回+1之后的结果// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率Date& operator++(){_day += 1;return *this;}// 后置++:// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1// 而temp是临时对象,因此只能以值的方式返回,不能返回引用Date operator++(int){Date temp(*this);_day += 1;return temp;}
private:int _year;int _month;int _day;
};
int main()
{Date d;Date d1(2022, 1, 13);d = d1++;    // d: 2022,1,13   d1:2022,1,14d = ++d1;    // d: 2022,1,15   d1:2022,1,15return 0;
}

好了,今天的分享d到这里就结束了,感谢大家的支持!

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

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

相关文章

网易面试:亿级用户,如何做微服务底层架构?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业网易、美团、字节、如阿里、滴滴、极兔、有赞、希音、百度、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 微服务改造&#xff0c;你是怎么做的&#xff1…

网络世界的黑暗角落:常见漏洞攻防大揭秘

网络世界的黑暗角落&#xff1a;常见漏洞攻防大揭秘 今天带来了网站常见的漏洞总结,大家在自己的服务器上也需要好好进行防护,密码不要过于简单.不然非常容易遭到攻击,最终达到不可挽回的损失.很多黑客想网络乞丐一样将你服务器打宕机,然后要求你进行付费.不知道大家有没有遇到…

【PIE-Engine 数据资源】全球2.5分分辨率累积降水量数据集

文章目录 一、 简介二、描述三、波段四、示例代码参考资料 一、 简介 数据名称全球2.5分分辨率累积降水量数据集时间范围1961年- 2018年空间范围全球数据来源worldclim代码片段var images pie.lmageCollection(WORLDCLIME/GLOBAL_PREC_MONTH") 二、描述 全球2.5分分率果…

【Python炫酷系列】祝考研的友友们金榜题名吖(完整代码)

文章目录 环境需求完整代码详细分析系列文章环境需求 python3.11.4及以上版本PyCharm Community Edition 2023.2.5pyinstaller6.2.0(可选,这个库用于打包,使程序没有python环境也可以运行,如果想发给好朋友的话需要这个库哦~)【注】 python环境搭建请见:https://want595.…

计算机硬件 4.1声卡与音响

第四章 输入/输出系统 第一节 声卡与音响 一、认识声卡 1.定义&#xff1a;采用脉冲编码调制技术&#xff0c;实现音频和数字信号相互转换的硬件电路。 2.功能&#xff1a;对音频输入设备进行数模或模数转换&#xff0c;将数据存入计算机。 3.组成&#xff1a;数字信号处理芯…

10、Qt:对数据进行加密、解密

一、说明 在Qt项目中简单的对数据进行加密解密&#xff0c;有如下两种方式 1、QCryptographicHash Qt提供了用于加密的类QCryptographicHash&#xff0c;但是QCryptographicHash类只有加密功能&#xff0c;没有解密功能 2、Qt-AES 使用第三方AES库&#xff0c;对数据进行加密解…

【动态规划】08路径问题_下降路径最小和_C++(medium)

题目链接&#xff1a;leetcode下降路径最小和 目录 题目解析&#xff1a; 算法原理 1.状态表示 2.状态转移方程 3.初始化 4.填表顺序 5.返回值 编写代码 题目解析&#xff1a; 题目让我们求通过 matrix 的下降路径 的 最小和 由题可得&#xff1a; 在下一行选择的元…

用户管理第2节课-idea 2023.2 后端--删除表,从零开始

一、鱼皮清空model文件夹下 二、鱼皮清空mapper文件夹下 三、删除 test 测试类下的部分代码 3.1删除SampleTest 3.2删除部分代码 UserCenterApplicationTests

Android ViewModel的简单应用

Android ViewModel 是一种设计模式&#xff0c;用于在应用程序组件之间存储和管理UI相关的数据。下面是使用Android ViewModel的基本步骤&#xff1a; 添加 ViewModel 依赖&#xff1a;在项目的 build.gradle 文件中添加以下依赖&#xff1a; implementation "androidx.…

【SQL】根据年份,查询每个月的数据量

根据年份&#xff0c;查询每个月的数据量 一种 WITH Months AS (SELECT 1 AS Month UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9 UNION ALL SELECT 10 UNION…

计算型服务器和通用型服务器的区别_Maizyun

计算型服务器和通用型服务器的区别 随着云计算技术的不断发展&#xff0c;服务器作为云计算的核心基础设施&#xff0c;其类型和功能也在不断丰富。其中&#xff0c;计算型服务器和通用型服务器是两种常见的服务器类型。本文将详细介绍这两种服务器之间的区别。 一、计算型服…

B : DS二叉排序树之创建和插入

Description 给出一个数据序列&#xff0c;建立二叉排序树&#xff0c;并实现插入功能 对二叉排序树进行中序遍历&#xff0c;可以得到有序的数据序列 Input 第一行输入t&#xff0c;表示有t个数据序列 第二行输入n&#xff0c;表示首个序列包含n个数据 第三行输入n个数据…

Axure中如何使用交互样式交互事件交互动作情形

&#x1f3ac; 艳艳耶✌️&#xff1a;个人主页 &#x1f525; 个人专栏 &#xff1a;《产品经理如何画泳道图&流程图》 ⛺️ 越努力 &#xff0c;越幸运 目录 一、Axure中交互样式 1、什么是交互样式&#xff1f; 2、交互样式的作用&#xff1f; 3、Axure中如何…

【Jenkins】Pipeline 简单使用

什么是Jenkins pipeline Jenkins Pipeline是Jenkins的一个插件&#xff0c;它允许你以代码的方式定义和管理持续集成和交付流水线。通过Jenkins Pipeline&#xff0c;你可以将整个软件交付过程定义为一个可重复、可扩展和可管理的流水线。这个流水线可以包括构建、测试、部署和…

DRF从入门到精通二(Request源码分析、DRF之序列化组件)

文章目录 一、Request对象源码分析区分原生request和新生request新的request还能像原来的reqeust一样使用吗源码片段分析总结&#xff1a; 二、DRF之序列化组件序列化介绍序列化步骤序列化组件的基本使用反序列化基本使用反序列化的新增反序列化的新增删除单条 反序列化的校验 …

mangokit:golang web项目管理工具,使用proto定义http路由和错误

文章目录 前言1、mangokit介绍1.1 根据proto文件生成http路由1.2 根据proto文件生成响应码1.3 使用wire来管理依赖注入 2、mangokit实现2.1 protobuf插件开发2.2 mangokit工具 3、使用示例3.1 创建新项目3.2 添加新的proto文件3.3 代码生成 前言 在使用gin框架开发web应用时&a…

51单片机基于时间片轮转的简单rtos

早就想写写这个了&#xff0c;正好赶上有点时间&#xff0c;写了一下基于51单片机的时间片轮转调度系统&#xff0c;简单的rtos&#xff0c;呵呵。直接上代码。 //基于51单片机时间片轮转的简单rtos。 #include"reg52.h" sbit led1 P2^7; sbit led2 P2^0; sbit key…

Linux学习(1)——初识Linux

目录 一、Linux的哲学思想 1.1 基础知识 1.2 根目录下的文件夹 二、Shell 1、Shell的定义 2、Shell的作用 三、Linux命令行 1、Linux通用命令行使用格式 四、Linux命令的分类 1、内部命令和外部命令的理解 2、内部命令和外部命令的区别 3、命令的执行过程 五、编辑…

[每周一更]-(第31期):Mysql安装汇总

写自&#xff1a;20230204 23:25 一. mysql rpm二进制包 rpm -Uvh http://repo.mysql.com/mysql-community-release-el6-5.noarch.rpm yum install mysql-community-server service mysqld start set password password(“123456”)二. mysql yum安装 1、安装查看有没有安装…

面试题总结(十二)【Qt】【华清远见西安中心】

Qt是什么&#xff1f; Qt是一个跨平台的应用程序开发框架&#xff0c;最初由挪威的Trolltech公司开发。它提供了一套丰富的工具和类库&#xff0c;用于开发图形用户界面&#xff08;GUI&#xff09;应用程序、网络应用程序和嵌入式应用程序等。 Qt框架基于C语言编写&#xff0c…