c++类和对象三部曲【中】

前言

类与对象的重头戏,本章关键词 构造函数析构函数浅拷贝与深拷贝, 运算符重载 话不多说我们直接开始吧

类的六个默认成员函数

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

构造函数

什么是构造函数?

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

构造函数的特性

构造函数是特殊的成员函数,需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象。
如果没有写构造函数则编译器会默认生成一个无参的构造函数
注意:

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

下面这个类的构造函数怎么写呀?
在这里插入图片描述
根据要求 没有返回值,函数名与类名相同,可以没有参数,也可以有,下面就是个例子。验证了构造函数可以重载,可能有的同学忘了什么叫函数的重载、
在这里插入图片描述

函数重载

函数重载是指在同一个作用域内,有多个函数名相同,但参数不同的函数(参数不同包括参数的类型不同,参数的个数不同,参数的顺序不同),与返回值无关,我们将这种参数叫做函数重载。

构造函数的作用

不是创建而是初始化对象
很多童鞋会有疑惑:不实现构造函数的情况下,编译器会
生成默认的构造函数。但是看起来默认构造函数又没什么用?d对象调用了编译器生成的默认构造函数,但是d对象_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么用??
在这里插入图片描述C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语言提供的数据类型,如:int/char…,自定义类型就是我们使用class/struct/union等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认构造函数。`

#include<iostream>
using namespace std;
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;
}

在这里插入图片描述

如果没有-t的默认构造函数,则会报错。如果我们什么也不写则系统会生成一个默认的构造函数。看起来-t好像没有被处理一样。说到底所有的初始化都需要我们自己写构造函数,只是直接调用和间接调用的区别。

在这里插入图片描述

怎么解决用编译器的默认构造函数出现随机值的情况呢?

可以给成员变量缺省值
在这里插入图片描述

编译器写的构造函数的应用场景

对于一个MyQueue这样的类我们就不用专门的写构造函数了
在这里插入图片描述

默认构造函数

注意:1无参构造函数、2全缺省构造函数、3我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。总结来说不需要传参数的构造函数都统称为默认构造函数。
且只能同时存在一个,因为1和2不写参数调用的时候是一样的,编译器无法区分。然后写了构造函数编译器就不再提供构造函数了。所以三者只能存其一。

析构函数

析构函数完成的工作不是销毁对象本身而是清理/释放对象中的资源对象空间的开辟和销毁是伴随函数栈帧(函数作用域)的,随着栈帧的创建而创建随着栈帧的结束而销毁。而清理是什么呢?清理是destory比如下面代码如果没有desory,随着函数作用域的解释Stack 的数组指针被销毁了,但是没有释放数组指针指向的空间,会造成内存泄漏。

void StackDestory(ST* s)
{
	assert(s);
	s->top = 0;
	free(s->a);
	s->a = NULL;
	s->capacity = 0;
}

在这里插入图片描述
注意

编号内容
1析构函数名是在类名前加上字符 ~。
2无参数无返回值类型
3一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4对象生命周期结束时,C++编译系统系统自动调用析构函数。构造函数可以重载
5编译器生成的默认析构函数,对自定类型成员调用它的析构函数。

验证第四点
在这里插入图片描述

构造函数的调用顺序

对象在栈上,要满足后进先出,后定义的先析构,有static的最后销毁,如果两者都是静态的,先销毁局部的静态
这很好理解嘛因为static修饰的后生命周期变成了全局的了,析构函数只有在类的生命周期结束后才调用。
总结:局部对象(后定义的先析构)->局部的静态->全局(后定义的先析构)
在这里插入图片描述
在这里插入图片描述

拷贝构造函数

什么是拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它的作用是他通过传对象的引用构克隆出另外一个对象。
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

在这里插入图片描述
刚刚的const Date &a什么呢?

常引用

如以下代码
在这里插入图片描述
这样是可以,a的权限是可读可写,b的权限是只能读,大权限可以到小权限

在这里插入图片描述
引用就是a给取别名b,没有 const 修改b的时候同时也修改了a
加了const之后b就不能被修改了。
在这里插入图片描述

上面这个代码为什么会报错呢?因为const修饰了a,a的权限只能读不能写了,int&b =a 的意思是b既可以读又可以写。
小权限不能到大权限
还有一种小权限不能到小权限到大权限的情况

a+b会存储到一个新的变量中,这个变量是常量的,
在这里插入图片描述

总结常引用

常用修饰的值不能被修改,只能从大权限到小权限。

为什么要用const修饰

如果我们写着写着写昏,写出如下代码,咋一看还以为是对的
在这里插入图片描述
在这里插入图片描述
会出现赔了夫人又折兵的情况
const这个语法就是防呆设计的,当我们加一个const修饰d的时候再修改d的值编译就不会通过
在这里插入图片描述

为什么拷贝构造函数要传引用

传指针也行但是不能传值传参
C++规定自定义类型传值传参时都会调用自己的拷贝构造函数
会造成死递归
在这里插入图片描述

拷贝构造函数的特征

编号特征
1拷贝构造函数是构造函数的一个重载形式。(用拷贝构造函数之后编译器就不默认生成了)
2拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
3若未显式定义,编译器会生成默认的拷贝构造函数。`` 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

浅拷贝与深拷贝

浅拷贝只拷贝值
如编译器生成的拷贝构造函数
编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?当然像日期类这样的类是没必要的。但是下面这样的类呢?
在这里插入图片描述
我们运行程序的的时候会崩溃这是为什么呢?

因为是值传递,s1.a 的值赋值给s2.a所以s1.a和s2.a指向的是同一个空间。当程序结束时,s2先调用析构函数释放了开辟的空间,然后s1再调用析构函数释放同一个空间。因为一个空间不能被释放两次所以程序崩溃。
在这里插入图片描述

深拷贝
深拷贝是不仅仅拷贝值,而且还要给s2.a另外开辟空间
在这里插入图片描述

在这里插入图片描述这样就不会释放两次同样的空间。
可能有的同学对memcpy这个函数不熟悉,我简答的提一下
在这里插入图片描述

参数意义
destNew buffer(新的区域)
srcBuffer to copy from(拷贝来自的区域,不定时作定语,又是一个小细节~)
countNumber of characters to copy (拷贝的字节数)

总结拷贝深浅拷贝

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

总结拷贝构造函数

因为C++规定自定义类型传值传参时都会调用自己的拷贝构造函数又因为传指针,传参数多写个&不好看,且拷贝需要额外的开销所以我们在c++中函数的参数多用引用的方式。

赋值、运算符重载

运算符重载

C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数
我们能直接比较对象d1和d2吗?不能
在这里插入图片描述
但是我们又了赋值运算符重载后就可以
在这里插入图片描述

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

编号事项
1不能通过连接其他符号来创建新的操作符:比如operator@
2重载操作符必须有一个类类型参数
3用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
4作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
5. * :: sizeof ? : 注意以上5个运算符不能重载。

对于第3,4点,就是把操作符重载函数写进类里面了。
在这里插入图片描述

赋值运算符重载

运算符重载是一种特殊的默认成员函数

赋值运算符重载和拷贝构造函数的区别

赋值运算符重载是把对象赋值给已经存在的对象
拷贝构造函数是根据一个对象构造另一个对象

因为赋值运算函数也是特殊的默认成员函数

日期类的赋值运算符重载

Date& operator=(const Date& d) // 传引用加快效率
 {
 if(this != &d) // 防止 d1 = d1 的无用功
       {
            _year = d._year;
            _month = d._month;
            _day = d._day;
       }
        
        return *this; // d1=(d2=d3) 根据结合性从右往左
        // 后面这个括号完成返回d2
        
 }

编译器默认生成的赋值运算函数的功能

对于内置类型拷贝,自定义类型调用对应的拷贝构造函数

总结

在这里插入图片描述

const成员

将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

const成员函数的应用场景

当我们用const修饰对象时,我们写的的print方法就调不动了
这是为什么呢?

	void print() 
	{
		cout << this->_year << '/' << this->_month << '/' << this->_day << endl;
	}

在这里插入图片描述
因为这里出现了权限的放大,指针和引用可以权限变小但不能放大这里的print是隐藏传了this指针的。const d1 ,d1不能被修改,但是print函数里面可以修改d1对象里面的值 所以出现了权限的放大。我们需要把print的this 改为 const Date * 但是this在形参列表是隐藏的,不能直接写。所以c++规定这种情况在函数那里加const

	void print() const
	{
		cout << this->_year << '/' << this->_month << '/' << this->_day << endl;
	}

可以任意加const吗?

既然权限只可以缩小,Date * 可以 到const Date * 那么我们可以在成员函数里任意加 const吗?答案不能因为const修饰成员函数表明在该成员函数中不能对类的任何成员进行修改。
对于要修改的成员的函数不能加const 如
在这里插入图片描述

总结成员函数什么时候能加const

该成员函数不修改成员的可以加const,换言之 只读可以加,可读可写的不能加

谢谢观看

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值