类与对象【中】

在这里插入图片描述

欢迎来到Cefler的博客😁
🕌博客主页:那个传说中的man的主页
🏠个人专栏:题目解析
🌎推荐文章:题目大解析2

在这里插入图片描述


👉🏻类的默认6个成员函数

实际上,我们在声明一个类的时候,编译器会默认生成6个成员函数。
默认成员函数:用户没有显式实现(即自己主动写),编译器会生成的成员函数称为默认成员函数

👉🏻构造函数

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

  • ⚡️名字和类名相同
  • ⚡️无返回值
  • ⚡️在实例化对象时自动调用
  • ⚡️允许函数重载
 class Date
 {
 public:
	 Date()//无参构造函数
	 {
		 _year = 1;
		 _month = 1;
		 _day = 1;
	 }
	 Date(int year,int month,int day)//有参构造函数
	 {
		 _year = year;
		 _month = month;
		 _day = day;
	 }
 private:
	 int _year;
	 int _month;
	 int _day;
 };
 int main()
 {
	 Date d1;
	 Date d2(2023, 8, 5);
	 return 0;
 }

这里注意,如果是设置无参构造函数,实例化对象,对象不能单单只给一个().
因为编译器不知道你是声明函数还是声明变量。

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

内置类型和自定义类型与默认构造函数的关系

首先我们先了解下内置类型和自定义类型

内置类型:诸如int、char、double等基本类型,指针全部都是内置类型
自定义类型:class、struct 、union……

默认构造函数:类的对象不需要传参就会自动调用的成员函数。

它们和默认构造函数的关系是什么呢?
默认构造函数对内置类型不进行处理;
默认构造函数会对自定义类型成员调用它的构造函数。
我们举个例子👇🏻👇🏻

 class A1
 {
 public:
	 A1()
	 {
		 a = 1;
		 cout << a << endl;
	 }
	 int a;
 };
 class A2
 {
 public:
	 int b;
	 A1 a1;
 };
 int main()
 {
	 A2 var;
	 cout << var.b << endl;
	 return 0;
 }

在这里插入图片描述
我们可以看到,对于内置类型b,默认构造函数对其没有处理,所以最后的结果是随机值。
但是a1是自定义类型,它在被实例化对象为var时,默认构造函数自动启动,去调用它本身的构造函数,所以它被初始化了。

但是到这里,我们就会发现,如果我们自己不显现一个构造函数,那么内置类型的变量岂不是全都是随机值?
所以在c++11中,为了补这个坑,可以允许内置类型成员变量在类中声明时可以给默认值

📌注意!
如果自定义类型的变量没有默认构造函数,则会报错。

缺省参数和构造函数的配合

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

 class Date
 {
 public:
	 Date()//无参构造函数
	 {
		 _year = 1;
		 _month = 1;
		 _day = 1;
	 }
	 Date(int year = 2023,int month = 8 ,int day = 5)//全缺省构造函数
	 {
		 _year = year;
		 _month = month;
		 _day = day;
	 }
 private:
	 int _year;
	 int _month;
	 int _day;
 };

上述中,有无参构造函数和全缺省构造函数。
在语法上是没错的,因为构造函数允许重载。
但是在实例化对象时会出错。
在这里插入图片描述
因为出现了两个默认构造函数,所以编译器不知道用哪个。
除非这里给了实参,可以让编译器知道用哪个默认构造函数才不会出问题。

🍽如果这里有一个不是全缺省构造函数,则另一个无参构造函数就是唯一的默认构造函数。

 class Date
 {
 public:
	 Date()//无参构造函数
	 {
		 _year = 1;
		 _month = 1;
		 _day = 1;
	 }
	 Date(int year ,int month = 8 ,int day = 5)//有参构造函数
	 {
		 _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();
	 return 0;
 }

在这里插入图片描述

当我们显示定义了一个构造函数,无论无参还是拷贝,此时编译器都不会生成默认构造函数了。

👉🏻析构函数

概念
C++中的析构函数是一种特殊的成员函数,用于在对象被销毁时执行清理操作。析构函数的名称与类名相同,但前面加上一个波浪号(~)。当对象的生命周期结束时(例如,对象超出范围、delete操作符被调用或程序退出),析构函数会自动调用。

析构函数通常是用于释放动态空间上的资源。
特性

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

注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数

  1. 关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器生成的默认析构函数对自定类型成员调用它的析构函数
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;
}

程序运行结束后输出:~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类生的默认析构函数

🖐所以,如果类中没有申请资源(申请动态空间)时,析构函数可以不写,直接使用编译器生成的默认析构函数

📌注意!
后定义的类型先调用析构函数。

👉🏻拷贝构造函数

在引出拷贝构造函数之前,我们先看一个类传值传参的案例。

 class _Stack
 {
 public:
	  _Stack()
	 {
		a = (DataType*)malloc(sizeof(DataType)*3);
		top = 0;
		capacity = 3;
	 }
	void Push(DataType x)
	{
		if (top == capacity)
		{
			int newcapacity = top == 0 ? 4 : capacity * 2;
			DataType* tmp = (DataType*)realloc(a, sizeof(DataType) * newcapacity);
			if (tmp == NULL)
			{
				perror("realloc fail");
				exit(-1);
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[top++] = x;
	}
	bool Empty()
	{
		return top == 0;
	}
	void Pop()
	{
		assert(!Empty());
		top--;
	}
	~_Stack()
	{
		cout << "执行了" << endl;
		free(a);
		a = nullptr;
		top = capacity = 0;
	}
 private:
	 DataType* a;
	 int top;
	 int capacity;
 };

 void func(_Stack s)
 {
 }
 int main()
 {
	 _Stack s1;
	 func(s1);
	 return 0;
 }

最后的运行结果是运行崩溃!
在这里插入图片描述

这是为什么呢?而后,在我们的调试下,会发现,这问题竟然出在最后的析构函数上。

首先我们知道,这里我们类的传参,是一个传值操作,按理来说应该不会出现什么问题。
但是这里栈传值最要命的地方,是在于它传的值当中,有一个是指针,即地址。
在调试中,我们可以发现,s1和s中,a的地址都是一样的。
但是这样会出现什么问题呢?我们需得知道,类的对象无论是实参还是形参,它都会在类的生命周期结束时自动调用析构函数销毁资源,而这里a所指向的空间就是我们申请的资源。
但是s1和s都存储了这个空间的地址,意味着这个空间会被释放两次
在这里插入图片描述
第一次析构还没出现问题,但此时a指针已经被置为空指针。
🫖等到第二次析构时,就会出现引用空指针的错误
所以,在类传值中,就很怕遇到这种传值操作中,存在指针数据的传值。

拷贝构造函数引入

在实例化对象时,我们有没有想过创建一个与已存在的对象一模一样的对象?
在这里插入图片描述
就是将已存在的对象当作实参进行传参,然后在新的对象内部的构造函数自动将数据初始化和该对象的数据一样。
于是c++就提供了拷贝构造函数来实现对拷贝对象这一需求。
而拷贝构造函数有以下特征👇🏻

  • 拷贝构造函数是构造函数重载的一种体现
  • 拷贝构造函数的参数只能有一个且必须是类类型的引用,这里类类型就是相同类型,那么为什么不能用传值而用引用呢?我们看如下这个例子
class Date
{
public:
 Date(int year = 1900, int month = 1, int day = 1)
 {
 _year = year;
 _month = month;
 _day = day;
 }
    Date(const Date d)   // 错误写法:编译报错,会引发无穷递归;加上const防止被修改
 {
 _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;
}

为什么说会引发递归?只能说,我们因为拷贝构造函数可以拷贝对象的数据,但也因为这个,会进入无限的递归。

这里第一层,d1传值传参给d2,没有问题。
但是到了类中的拷贝构造函数中时,我们要调用这个函数前,这个形参又是自定义类型,我们又必须再先调用这个形参的拷贝构造函数,如此下去,将会重复上述操作,无穷无尽,造成死递归。
那么为什么说引用就不会出现问题呢,因为引用不是传值,这里不能算作参数,所以不会递归。

默认拷贝构造函数

我们不写(不显现)的拷贝构造函数,就是默认拷贝构造函数,和先前的构造函数和析构函数的特性略有不同。

默认拷贝构造函数会对内置类型的变量值拷贝,会去调用自定义类型的拷贝构造函数。

🍸所以这里我们可以总结:

  • 只有内置类型的类中无需自己写拷贝构造函数,这种浅拷贝无需自己写
  • 若有自定义类型的类,那个自定义类型的类内部需要有拷贝构造函数
  • 说白了拷贝构造函数干的也是初始化的活,只是它只接受同类型的类的参数传参而已
  • 类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请
    时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

🍷拷贝构造函数典型调用场景

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象

拷贝构造函数解决Stack传值问题

上述Stack传值问题主要就是因为有两个指针指向了同一资源空间。
而拷贝构造函数可以做到的是,我接收你传过来Stack的值,里面的内置类型数据我不更改,但是资源空间我再额外申请一个新的。

  _Stack(const _Stack& s)//拷贝构造函数
{
		a = (DataType*)malloc(sizeof(DataType)*s.capacity);
		if(NULL == a)
		{
		perrot("malloc fail");
		return ;
		}
		memcpy(a,s.a,sizeof(DataType)*s.top);
		top = s.top;
		capacity = s.capacity;
 }

这样就可以解决析构同一空间两次的问题了。

👉🏻 赋值运算符重载

运算符重载

C++中的运算符重载是指通过定义自定义的操作符函数来改变操作符的行为。通过运算符重载,我们可以使用自定义的操作符来执行特定的操作。

函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)

运算符重载的语法如下:

返回类型 operator 操作符 (参数列表) {
    // 实现操作符的功能
}

📢注意

  • 不能通过连接其他符号来创建新的操作符:比如operator@

  • 重载操作符必须有一个类类型参数(至少有一个是

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

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

 class Date
 {
 public:
	 //Date()//无参构造函数
	 //{
		// _year = 1;
		// _month = 1;
		// _day = 1;
	 //}
	 Date(int year = 2023, int month = 8, int day = 5)//有参构造函数
	 {
		 _year = year;
		 _month = month;
		 _day = day;
	 }
	 void  Print()
	 {
		 cout << _year << "/" << _month << "/" << _day << endl;
	 }

	 int _year;
	 int _month;
	 int _day;
 };
 bool operator<(const Date& x,const Date& y)
 {
	 if (x._year < y._year)
		 return true;
	 else if (x._year == y._year && x._month < y._month)
		 return true;
	 else if (x._year == y._year && x._month == y._month && x._day < y._day)
		 return true;
	 else
		 return false;

 int _year;
	 int _month;
	 int _day;

}
 int main()
 {
	 Date d1;
	 Date d2(2022);
	 cout << (d1< d2) << endl;
	cout << operator<(d1, d2) << endl;//两种写法都可以

	 return 0;
 }

在这里插入图片描述

  • 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐
    藏的this
 class Date
 {
 public:

	 Date(int year = 2023, int month = 8, int day = 5)//有参构造函数
	 {
		 _year = year;
		 _month = month;
		 _day = day;
	 }
	 void  Print()
	 {
		 cout << _year << "/" << _month << "/" << _day << endl;
	 }

	 int _year;
	 int _month;
	 int _day;
	  bool operator<(const Date& y)//只保留一个就够了
 {
	 if (_year < y._year)
		 return true;
	 else if (_year == y._year && month < y._month)
		 return true;
	 else if (_year == y._year && _month == y._month && _day < y._day)
		 return true;
	 else
		 return false;
		 }

 int _year;
	 int _month;
	 int _day;
 };

运算符重载复用

我们如果已经用运算符重载写了比较日期小于和等于的情况,那么大于等于、不等于我们还需要再更改原代码吗,不需要了,这里可以运算符重载复用

 //接下来复用以上的运算符重载函数就行了
	 bool operator <=(const Date& y)
	 {
		 return operator<(y) || operator == (y);
		/* return *this < y || *this == y;*///这样也可以
	 }
	 bool operator >(const Date& y)
	 {
		 return  !operator<=(y);
	 }
	 bool operator>=(const Date& y)
	 {
		 return !operator<(y);
	 }

🌇实现日期加上天数

 class Date
 {
 public:
	 //Date()//无参构造函数
	 //{
		// _year = 1;
		// _month = 1;
		// _day = 1;
	 //}
	 Date(int year = 2023, int month = 8, int day = 5)//有参构造函数
	 {
		 _year = year;
		 _month = month;
		 _day = day;
	 }
	 void  Print()
	 {
		 cout << _year << "/" << _month << "/" << _day << endl;
	 }
	 bool operator<(const Date& y)//只保留一个就够了
	 {
		 if (_year < y._year)
			 return true;
		 else if (_year == y._year && _month < y._month)
			 return true;
		 else if (_year == y._year && _month == y._month && _day < y._day)
			 return true;
		 else
			 return false;
	 }
	 bool operator==(const Date& y)
	 {
		 return _year == y._year && _month == y._month && _day == y._day;
	 }
	 //接下来复用以上的运算符重载函数就行了
	 bool operator <=(const Date& y)
	 {
		 return operator<(y) || operator == (y);
		/* return *this < y || *this == y;*///这样也可以
	 }
	 bool operator >(const Date& y)
	 {
		 return  !operator<=(y);
	 }
	 bool operator>=(const Date& y)
	 {
		 return !operator<(y);
	 }
	 int GetMonthDays(int year,int month)
	 {
		 int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		 if (month == 2 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
			 return 29;
		 return monthArray[month];
	 }
	 Date operator+=(int day)
	 {
		 _day += day;
		 while (_day > GetMonthDays(_year, _month))
		 {
			 _day -= GetMonthDays(_year, _month);
			 _month++;
			 if (_month == 13)
			 {
				 _year++;
				 _month = 1;
			 }
		 }
		 return *this;//因为自己更改了,返回自己
	 }
 private:
	 int _year;
	 int _month;
	 int _day;
 };
 int main()
 {
	 Date d1;
	 Date d2 = d1 += 50;
	
	 return 0;
 }

Date类的实现

🫥Date.h

#pragma once
#include <iostream>

using namespace std;
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1);//构造函数
	int GetMonthDays(int year, int month);
	int IsLeap(int year);
	void Print();
	Date(const Date& d);//拷贝构造函数
	Date& operator=(const Date& d);
	
	bool operator<(const Date& d);
	bool operator==(const Date& d);
	bool operator!=(const Date& d);
	bool operator<=(const Date& d);

	bool operator>(const Date& d);
	bool operator>=(const Date& d);

	Date& operator+=(int day);
	Date operator+(int day);

	Date& operator-=(int day);
	Date operator-(int day);

	Date& operator++();//前置++
	Date operator++(int);//后置++,给个int占位,为了区分前置后置++
	//二者既是运算符重载,也是函数重载

	Date& operator--();//前置--
	Date operator--(int);//后置--

	int operator-(const Date& d);//日期-日期



private:
	int _year;
	int _month;
	int _day;
};

🫥Date.cpp

#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"
void Date::Print()
{
	cout << _year << "/" << _month << "/" << _day <<  endl;
}
int Date::GetMonthDays(int year, int month)
{
	static int monthArray[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//加个static是该函数如果被多次调用,就不用每次都创建该数组空间了
	if (month == 2 && (year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
		return 29;
	return monthArray[month];
}
int Date::IsLeap(int year)
{
	if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)
		return 366;
	else
		return 365;
}
Date::Date(int year , int month, int day )//构造函数
{
	_year = year;
	_month = month;
	_day = day;
	//检查日期是否合理
	if (year < 1 || month < 1 || month >12 || day >GetMonthDays(year, month))
	{
		cout << "日期不合理" << endl;
		exit(-1);
	}

}
Date::Date(const Date& d)//拷贝构造函数
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
Date& Date:: operator=(const Date& d)//运算符重载
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}

bool Date::	 operator<(const Date& y)//只保留一个就够了
{
	if (_year < y._year)
		return true;
	else if (_year == y._year && _month < y._month)
		return true;
	else if (_year == y._year && _month == y._month && _day < y._day)
		return true;
	else
		return false;
}

bool Date::operator==(const Date& y)
{
	return _year == y._year && _month == y._month && _day == y._day;
}
bool Date::operator!=(const Date& y)
{
	return !(*this == y);
}
//接下来复用以上的运算符重载函数就行了
bool Date::operator <=(const Date& y)
{
	return operator<(y) || operator == (y);
	/* return *this < y || *this == y;*///这样也可以
}

bool Date::operator >(const Date& y)
{
	return  !operator<=(y);
}
bool Date::operator>=(const Date& y)
{
	return !operator<(y);
}

Date& Date::operator+=(int day)
{
	*this = *this + day;
	return *this;//因为自己更改了,返回自己
}
Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp._day += day;
	while (tmp._day > GetMonthDays(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDays(tmp._year, tmp._month);
		tmp._month++;
		if (tmp._month == 13)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}
	return tmp;
}

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += (-day);//又可以复用
	}
	_day -= day;
	while (_day < 1)
	{
		_month--;
		if (_month < 1)
		{
			_year--;
			_month = 12;
		}
		_day += GetMonthDays(_year, _month);

	}
	return *this;
}

Date Date::operator-(int day)
{
	Date tmp(*this);
	tmp -= day;
	return tmp;
}

Date& Date::operator++()
{
	*this += 1;
	return *this;
}

Date Date::operator++(int)
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}

Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

Date Date::operator--(int)
{
	Date tmp(*this);
	*this -= 1;
	return tmp;
}

//int Date:: operator-(const Date& d)
//{
//	//int maxM = _month > d._month ? _month : d._month;
//	//int minM = _month < d._month ? _month : d._month;
//	int maxY = _year>d._year ? _year : d._year;
//	int minY = _year < d._year ? _year : d._year;
//	int i = 0 ,subDays = 0;
//	for (i = 1; i <= _month; i++)
//	{
//		subDays += GetMonthDays(_year, i);
//	 }
//	for (i = 1; i <= d._month; i++)
//	{
//		subDays -= GetMonthDays(d._year, i);
//	}
//	if (_year < d._year)
//		subDays = -subDays;
//	for (i = minY; i < maxY; i++)
//	{
//		subDays += IsLeap(i);
//	}
//	if (_year < d._year)
//		subDays += (d._day - _day);
//	else
//		subDays += (_day - d._day);
//	
//	return fabs(subDays);
//}

int Date:: operator-(const Date& d)
{
	Date max = *this;
	Date min = d ;
	int flag = 1;
	if (*this < d)
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;
	while (max != min)
	{
		++min;
		n++;
	}
	return n ;


}

函数解析

🗣

	Date& Date:: operator=(const Date& d)//运算符重载
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}

⭐️拷贝构造函数和赋值运算符重载函数的区别
拷贝构造函数:一个已经存在的对象去给一个即将要创建的对象初始化
赋值运算符重载函数:一个已经存在的对象去给另一个也已存在的对象赋值

这里参数不加引用也可以,不会陷入无限递归,因为这里进去后,d先调用它的拷贝构造
而d的拷贝构造函数我们已经优化过了(是引用),所以只要它的拷贝构造函数不陷入死递归
当d的拷贝构造调用完就会回到运算符重载函数中。

返回值为什么可以为引用类型?
首先因为这里*this出作用域不会销毁,可以使用(这是前提),其次这里专门用了引用是可以让返回值不用调用拷贝构造函数,提高了效率。有人会问为什么这里会调用拷贝构造函数?因为这个返回值类型是一个自定义类型,所以每次我们用自定义类型(做参,做返回值)的时候,它都会自动调用它的拷贝构造函数。但是这里调用拷贝构造函数属于是自己传值给自己(d(d))。

🗣
operator+=复用operator+

Date& Date::operator+=(int day)
{
	*this = *this + day;//拷贝构造调用2次,赋值一次
	return *this;
}
Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp._day += day;
	while (tmp._day > GetMonthDays(tmp._year, tmp._month))
	{
		tmp._day -= GetMonthDays(tmp._year, tmp._month);
		tmp._month++;
		if (tmp._month == 13)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}
	return tmp;
}

相比于operator+复用operator+=,这个版本的拷贝构造函数更多,所以不如之前的那个版本。

赋值运算符重载

  1. 赋值运算符重载格式
  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回*this :要复合连续赋值的含义
  1. 赋值运算符只能重载成类的成员函数不能重载成全局函数(即只能在类中重载)

原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现
一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,故赋值
运算符重载只能是类的成员函数

用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注
意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
重载完成赋值

📢注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理(如Stack)则必
须要实现。

👉🏻Const成员

🍵概念
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针
,表明在该成员函数中不能对类的任何成员进行修改。
🤔请思考下面的几个问题

  1. const对象可以调用非const成员函数吗?

答:不行

在这里插入图片描述
因为这里其实有个隐藏的this指针,而这个this指针的类型是Date*的,这里属于是权限的放大,所以不行。解决办法只有在Print()函数后面加上const成为const成员函数才可以
2. 非const对象可以调用const成员函数吗?

答:可以;权限的缩小是可以的

  1. const成员函数内可以调用其它的非const成员函数吗?

答:不可以;权限的放大不允许

  1. 非const成员函数内可以调用其它的const成员函数吗?

可以

取地址及const取地址操作符重载

学了运算符重载我们知道,这个主要是为了解决自定义类型不能使用运算符的问题(所以需要我们自己写运算符重载函数),但是这里有个例外,自定义类型可以使用取地址符号(但本质上还是&的运算符重载)

这两个默认成员函数一般不用重新定义 ,编译器默认会生成

class Date
{ 
public :
 Date* operator&()
 {
 return this ;


 }
 const Date* operator&()const
 {
 return this ;
 }
private :
 int _year ; // 年
 int _month ; // 月
 int _day ; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容

const int  func()
{
      int ret = 0;
      return ret;
}

这边const修饰的是返回值,但实际上这样修饰没有意义,因为返回值是是形参,而形参具有常性,本身就不能被修改。

const 修饰类成员函数重载

void Print()
	{

	}
	void Print() const
	{

	}

这两个函数能同时存在吗?
答案是可以的,这里其实构成重载,一个参数类型是非const,一个是const,当然符合函数重载的定义。
但是像Print函数的重载并没有多大意义,我们看看像这种重载一个比较具有的意义的例子👇🏻

#include <iostream>
using namespace std;
class ST
{
public:
	void PushBack(int x)
	{
		_a[_size++] = x;
	}
	size_t size() const
	{
		return _size;
	}
	const int& operator[](size_t i) const
	{
		return _a[i]; //这个返回值不可以修改
	}
	int& operator[](size_t i)
	{
		return _a[i];//这个返回值可以修改
	}
private:
	int* _a = (int*)malloc(sizeof(int) * 10);
	int _size = 0;
	int _capacity = 0;
};
void Add(ST& s)
{
	int i = 0;
	for (i = 0; i < s.size(); i++)
	{
		s[i]++;
	}
}
void Print(const ST& s)
{
	int i = 0;
	for (i = 0; i < s.size(); i++)
	{
		cout << s[i] << " ";
	}

}
int main()
{
	ST s1;
	s1.PushBack(1);
	s1.PushBack(2);
	s1.PushBack(3);
	s1.PushBack(4);
	Add(s1);
	Print(s1);
	return 0;
}

在这里插入图片描述

const int& operator[](size_t i) const
	{
		return _a[i];
	}
	int& operator[](size_t i)
	{
		return _a[i];
	}

这两个const重载的意义在于,函数调用时,如果是const对象调用,就会执行const operator;
非const对象调用,就会执行非 const operator;
一句话就是:执行最适合场景的那个函数

总结:一般只读函数(内部不修改成员),习惯用const修饰为const 成员函数


如上便是本期的所有内容了,如果喜欢并觉得有帮助的话,希望可以博个点赞+收藏+关注🌹🌹🌹❤️ 🧡 💛,学海无涯苦作舟,愿与君一起共勉成长

在这里插入图片描述
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值