C++ ------类和对象详解六大默认成员函数

news2025/1/21 21:58:59

文章目录

  • 类的6个默认成员函数
    • 概念
  • 构造函数
    • 概念
    • 特点
  • 析构函数
    • 概念
    • 特征
  • 拷贝构造函数
    • 概念
    • 特点
  • 赋值运算符重载
    • 运算符重载的概念
    • 特性
    • 赋值运算符重载
    • 格式
    • 特性
  • 取地址及const取地址操作符重载

类的6个默认成员函数

如果我们定义一个类,然后这个类中什么也没有。那么这里的类就什么也没有吗?其实不然,任何类在里面什么都不写时,编译器都会生成6个默认成员函数。

概念

用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
六个默认成员函数
在这里插入图片描述

构造函数

我们来看一个Date类

#include <iostream>
using namespace std;

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(2023, 7, 23);
	d1.Print();
	Date d2;
	d2.Init(2023, 7, 24);
	d2.Print();
	return 0;
}

观察上述代码,我们需要显式的调用Init函数,这个函数可以给私有的成员变量赋值,但是如果每次都使用这个方法设置信息,就显得有点麻烦了,这里我们引出了构造函数的概念:

概念

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

特点

函数名和类名相同,没有返回值,可以发生重载,对象实例化时编译器自动调用构造函数。除此之外我们还需要了解一下的特点,我们看一个代码:

class Date
{
public:
	
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	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 d;
	d.Print();
	return 0;
}

在这里插入图片描述

我们看到上述代码中我们显式的定义了一个构造函数,编译器会调用我们自己定义的构造函数,也就是说当我们自己定义了一个构造函数,编译器就不会生成无参的默认构造函数。
接下来我们了解一下默认构造函数:
我们再来看一段代码

class Date
{
public:
	//构造函数 任务初始化对象
	//无参构造函数
	//编译器会自动调用构造函数。
	//需要注意,我们在显式的定义构造函数时,编译器不会生成默认的构造函数。
	//编译器生成的默认构造函数不会对内置类型进行初始化,生成的构造函数是
	//无参的构造函数。
	//默认构造函数包括一下三类:我们自己写的无参构造函数,还有我们不写时编译器默认生成的无参构造函数。
	//全缺省构造函数,这三个构造函数只能在类中出现一个,否则就会出现调用的二义性。
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	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 d;
	d.Print();
	return 0;
}

在这里插入图片描述

我们可以看到,上述代码发生了编译错误,多个默认构造函数和对重载函数的调用不明确。这是因为C++规定默认构造函数分为编译器自动生成的构造函数,自己定义的构造函数和全缺省构造函数,这三个构造函数只能在类中出现一个,所以我们在定义构造函数时,不能将两个默认构造函数同时写在同一个类中。
我们再来思考一个问题,编译器自动生成的构造函数,会对内置类型进行初始化吗?答案是不会,我们通过以下代码来验证这一点

class Date
{
public:
	//需要注意,我们在显式的定义构造函数时,编译器不会生成默认的构造函数。
	//编译器生成的默认构造函数不会对内置类型进行初始化,生成的构造函数是
	//无参的构造函数。
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	//实例化对象
	Date d;
	d.Print();
	return 0;
}

在这里插入图片描述

我们可以看到运行结果中没有对内置类型初始化,那么默认生成的构造函数会做些什么呢,C++把类型分为内置类型和自定义类型,内置类型就是语言提供的数据类型,如int,char等,自定义类型就是我们使用class/struct关键字自己定义的类型,编译器默认生成的构造函数会对自定义类型成员_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;
}

在这里插入图片描述
以上代码是调用了Time的构造函数的。那么对于内置类型成员不初始化的缺陷,C++11又定义:内置类型成员变量在类中声明时可以给默认值。

class Time
{
public:
	//自己定义的默认构造函数
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
public:
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	//基本类型
	//缺省值
	int _year = 1;
	int _month = 1;
	int _day = 1;
	//自定义类型
	Time _t;
};
int main()
{
	//实例化对象
	Date d;
	d.Print();
	return 0;
}

在这里插入图片描述

析构函数

我们来看下面的代码:

typedef int DateType;
class Stack
{
public:
	//默认构造函数
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;
		_a = (DateType*)malloc(sizeof(DateType) * capacity);
		if (NULL == _a)
		{
			perror("malloc failed!");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}

	void Push(DateType data)
	{
		//扩容
		_a[_top] = data;
		_top++;
	}
	//其他方法……

	void Destory()
	{
		free(_a);
		_a = NULL;
		_top = _capacity = 0;
	}
private:
	size_t _capacity;
	size_t _top;
	DateType* _a;
};

int main()
{
	Stack st;
	st.Push(1);
	st.Push(2);
	st.Push(3);
	st.Destory();
	return 0;
}

上面的代码我们会发现,在最后我们写了一个函数叫做Destory,这里的这个函数的作用是销毁资源,需要我们显式调用,有点麻烦,那么我们怎么样可以让系统自动调用呢?我们就引出了析构函数的概念。

概念

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

typedef int DateType;
class Stack
{
public:
	//默认构造函数
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;
		_a = (DateType*)malloc(sizeof(DateType) * capacity);
		if (NULL == _a)
		{
			perror("malloc failed!");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}
	void Push(DateType data)
	{
		//扩容
		_a[_top] = data;
		_top++;
	}
	//其他方法……

	//析构函数
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(_a);
		_a = NULL;
		_top = _capacity = 0;
	}
	/*void Destory()
	{
		free(_a);
		_a = NULL;
		_top = _capacity = 0;
	}*/
private:
	size_t _capacity;
	size_t _top;
	DateType* _a;
};

int main()
{
	Stack st;
	st.Push(1);
	st.Push(2);
	st.Push(3);
	//st.Destory();
	return 0;
}

在这里插入图片描述

我们可以看到上面的代码调用了显式定义的析构函数,析构函数的特征是什么和构造函数的特征相同吗,我们来简单讨论一下:

特征

从上述代码可知析构函数具有如下特点:

  • 函数名和类名相同且在函数名的前面加上字符~。
  • 没有返回值类型。
  • 一个类只能有一个析构函数。若为显示定义,系统会自动生成默认的析构函数,注意:析构函数不能重载。
  • 对象声明周期结束时,C++编译系统自动调用析构函数。编译器默认生成的析构函数不会free堆上的空间。
typedef int DateType;
class Stack
{
public:
	//默认构造函数
	Stack(size_t capacity = 3)
	{
		cout << "Stack(size_t capacity = 3)" << endl;
		_a = (DateType*)malloc(sizeof(DateType) * capacity);
		if (NULL == _a)
		{
			perror("malloc failed!");
			return;
		}
		_capacity = capacity;
		_top = 0;
	}

	void Push(DateType data)
	{
		//扩容
		_a[_top] = data;
		_top++;
	}
	//其他方法……

	//析构函数
	//~Stack()
	//{
	//	cout << "~Stack()" << endl;
	//	free(_a);
	//	_a = NULL;
	//	_top = _capacity = 0;
	//}
	/*void Destory()
	{
		free(_a);
		_a = NULL;
		_top = _capacity = 0;
	}*/
private:
	size_t _capacity;
	size_t _top;
	DateType* _a;
};

int main()
{
	Stack st;
	//st.Destory();
	return 0;
}

在这里插入图片描述

  • 编译器默认生成的析构函数,对自定义类型成员调用它们的析构函数
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类生成的默认析构函数。
注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数。

  • 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如Date类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类。

拷贝构造函数

我们来看下面的代码引出拷贝构造函数

class Date
{
public:
	//全缺省默认构造
	Date(int year = 2023, int month = 7, int day = 30)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	//基本类型
	int _year = 1970;
	int _month = 1;
	int _day = 1;
};

int main()
{
	Date d;
	//我们如何用已存在的对象来构造一个和它相同的对象呢?显然,
	//我们使用拷贝构造可以完成这个任务。
	//调用拷贝构造
	Date d1(d);
	return 0;
}

上述代码我们知道想要利用已存在的对象,实例化一个和它相同的对象我们需要调用拷贝构造函数。

概念

拷贝构造函数:只有单个形参,该形参是对类类型对象的应用一般常用const修饰,目的是为了不能对原有的对象进行修改,在用已存在的类类型对象创建新对象时由编译器自动调用。

特点

  • 拷贝构造函数时构造函数的一个重载形式。
  • 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用拷贝构造。我们来看下面的代码;
class Date
{
public:
	//全缺省默认构造
	Date(int year = 2023, int month = 7, int day = 30)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(Date d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	//基本类型
	int _year = 1970;
	int _month = 1;
	int _day = 1;
};

int main()
{
	Date d;
	//我们如何用已存在的对象来构造一个和它相同的对象呢?显然,
	//我们使用拷贝构造可以完成这个任务。
	//调用拷贝构造
	Date d1(d);
	return 0;
}

在这里插入图片描述

上面的拷贝构造函数参数为传值调用,这会引发编译器报错,这是因为在传值传参时会进行拷贝构造,而调用拷贝构造还是需要传参,这就会导致函数一直传参,传值传参就会拷贝构造。那么就造成了无穷递归调用

在这里插入图片描述

  • 若为显式定义,编译器会自动生成默认的拷贝构造函数,默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝或者值拷贝,但是我们在拷贝堆上的数据时需要把堆上的数据也拷贝过来,而不单单是拷贝一个堆上空间的一个指针。这不是真正的拷贝,也算是浅拷贝,上面的这种拷贝叫做深拷贝,这里的深拷贝会在以后详细讲解。我们这里只关心浅拷贝。
class Date
{
public:
	//全缺省默认构造
	Date(int year = 2023, int month = 7, int day = 30)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	//基本类型
	int _year = 1970;
	int _month = 1;
	int _day = 1;
};

int main()
{
	Date d;
	//我们如何用已存在的对象来构造一个和它相同的对象呢?显然,
	//我们使用拷贝构造可以完成这个任务。
	//调用拷贝构造,我们没有显式定义拷贝构造函数,编译器会默认生成拷贝构造。
	Date d1(d);
	return 0;
}

这里还是一样,对于自定义类型,编译器默认生成的拷贝构造函数会去调用自定义类型的拷贝构造函数和普通的构造函数相同。

  • 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们对于没有涉及到资源申请的类,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,我们需要深拷贝。
    我们来看下面的代码:
typedef int DataType;
class Stack
{
public:
	//全缺省默认构造函数
	Stack(size_t capacity = 10)
	{
		_a = (DataType*)malloc(capacity * sizeof(int));
		if (NULL == _a)
		{
			perror("malloc failed!");
			return;
		}

		_capacity = capacity;
		_top = 0;
	}

	void Push(const DataType x)
	{
		//扩容
		_a[_top] = x;
		_top++;
	}
	//析构函数
	~Stack()
	{
		if (_a)
		{
			free(_a);
			_a = NULL;
			_capacity = _top = 0;
		}
	}
private:
	DataType* _a;
	size_t _top;
	size_t _capacity;
};

int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);

	Stack s2(s1);
	return 0;
}

在这里插入图片描述

该段程序运行崩溃,因为上述程序实例化了一个对象s1,入栈4个数据。然后再使用该对象来拷贝构造一个新的对象,当程序结束时,s2会调用析构函数对资源进行销毁,而s1也会调用析构函数对资源进行销毁,那么这就出现了free两次的情况,解决方案只能是进行深度拷贝,这里的深度拷贝会在以后的文章中讲解
在这里插入图片描述

  • 拷贝构造的应用场景
    使用已存在对象创建新对象,上面的代码就是这种场景
    函数参数类型为类类型
    函数返回值类型为类类型对象
    看一下下面的代码
class Date
{
public:
	//不是默认构造函数
	Date(int year, int minute, int day)
	{
		cout << "Date(int year, int minute, int day)" << this << endl;
	}
	//拷贝构造
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	//析构函数
	~Date()
	{
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

Date Test(Date d)
{
	Date temp(d);
	return temp;
}

int main()
{
	//调用构造函数
	Date d1(2023, 7, 30);
	Test(d1);
	return 0;
}

在这里插入图片描述

上面的代码在构造d1对象时使用了构造函数,在给类类型形参传参的时候调用了一次拷贝构造,在用d构造temp对象时使用了一次拷贝构造,在返回temp对象时调用了一次拷贝构造,程序运行结束后销毁Test中的temp对象使用了一次析构函数,销毁Test函数的参数d使用了析构函数,销毁Test函数返回时创建的临时对象使用了一次析构函数,销毁main函数中的d1使用了一次析构函数。为了提高效率我们在做类类型传参和类类型作为返回值的时候,尽量使用引用。

赋值运算符重载

在这一节我们需要先了解运算符重载的概念和实现

运算符重载的概念

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

class Date
{
public:
	//不是默认构造函数
	Date(int year, int minute, int day)
	{
		cout << "Date(int year, int minute, int day)" << this << endl;
	}
	//拷贝构造
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
	}
	//析构函数
	~Date()
	{
		cout << "~Date()" << endl;
	}
public:
	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;
}

int main()
{
	Date d1(2023,7,30);
	Date d2(2023,7,30);
	//可以隐式调用也可以显式调用
	cout << (d1 == d2) << endl;
	//显式调用
	cout << operator==(d1, d2) << endl;
}

在这里插入图片描述

为了解决上述问题,我们需要把运算符重载函数,写到类里面去,但是函数参数会少一个,这是由于成为成员函数就会增加一个this指针,而运算符重载函数的参数有几个是由操作符的操作数决定的。也可以用友元函数进行解决,这个我们后面会说。

class Date
{
public:
	//不是默认构造函数
	Date(int year, int minute, int day)
	{
		cout << "Date(int year, int minute, int day)" << this << endl;
	}
	//拷贝构造
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
	}
	//析构函数
	~Date()
	{
		cout << "~Date()" << endl;
	}
	//运算符重载
	bool operator==(const Date& d1)
	{
		return (*this)._year == d1._year && (*this)._month == d1._month && (*this)._day == d1._day;
	}
public:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2023,7,30);
	Date d2(2023,7,30);
	//可以隐式调用也可以显式调用
	cout << (d1 == d2) << endl;
	//显式调用
	cout <<d1.operator==(d2) << endl;
}

在这里插入图片描述

特性

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

赋值运算符重载

class Date
{
public:
	//不是默认构造函数
	Date(int year, int minute, int day)
	{
		cout << "Date(int year, int minute, int day)" << this << endl;
	}
	//拷贝构造
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
	}
	//析构函数
	~Date()
	{
		cout << "~Date()" << endl;
	}
	//运算符重载
	bool operator==(const Date& d1)
	{
		return _year == d1._year && 
			_month == d1._month && 
			_day == d1._day;
	}
	//赋值运算符重载
	//赋值运算符不能在类外定义,因为我们没有在类中显式定义赋值运算符重载函数
	//编译器会自动生成,这就和类外定义的赋值运算符重载函数冲突了。另外定义成全局函数
	//就没有this指针这一说法了,就必须写两个参数
	Date& operator=(const Date& d1)
	{
		//我们需要确定不是两个相同的对象赋值
		if (this != &d1)
		{
			cout << "Date& operator=(const Date& d1)" << endl;
			_year = d1._year;
			_month = d1._month;
			_day = d1._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	//Date d1(2023, 7, 30);
	//Date d2(2023, 7, 30);
	可以隐式调用也可以显式调用
	//cout << (d1 == d2) << endl;
	显式调用
	//cout << d1.operator==(d2) << endl;

	//两个对象赋值操作就要用到赋值运算符重载
	Date d3(1,1,1);
	Date d4(1,1,1);
	d3 = d4;

	//注意写成这样为调用拷贝构造
	Date d5 = d3;
}

在这里插入图片描述

格式

  • 赋值运算符重载格式
  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&: 返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • 检测是否自己给自己赋值
  • 返回* this:要符合连续赋值的含义。 a = b = c

特性

赋值运算符只能重载成类的成员函数不能重载成全局函数,因为赋值运算符重载函数是一个默认成员函数,所以编译器会默认生成赋值重载函数,跟构造函数和拷贝函数的行为一样。Date和MyQueue不需要我们自己实现赋值重载,stack需要我们自己实现,因为默认生成的是浅拷贝。

class Date
{
	friend Date& operator=(const Date& d1, const Date& d2);
public:
	//不是默认构造函数
	Date(int year, int minute, int day)
	{
		cout << "Date(int year, int minute, int day)" << this << endl;
	}
	//拷贝构造
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
	}
	//析构函数
	~Date()
	{
		cout << "~Date()" << endl;
	}
	//运算符重载
	bool operator==(const Date& d1)
	{
		return _year == d1._year &&
			_month == d1._month &&
			_day == d1._day;
	}
	//赋值运算符重载
	//赋值运算符不能在类外定义,因为我们没有在类中显式定义赋值运算符重载函数
	//编译器会自动生成,这就和类外定义的赋值运算符重载函数冲突了。另外定义成全局函数
	//就没有this指针这一说法了,就必须写两个参数

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

Date& operator=(const Date& d1,const Date& d2)
{
	//我们需要确定不是两个相同的对象赋值
	if (&d2 != &d1)
	{
		cout << "Date& operator=(const Date& d1)" << endl;
		d1._year = d2._year;
		d1._month = d2._month;
		d1._day = d2._day;
	}
	return d1;
}
int main()
{
	//Date d1(2023, 7, 30);
	//Date d2(2023, 7, 30);
	可以隐式调用也可以显式调用
	//cout << (d1 == d2) << endl;
	显式调用
	//cout << d1.operator==(d2) << endl;

	//两个对象赋值操作就要用到赋值运算符重载
	Date d3(1, 1, 1);
	Date d4(1, 1, 1);
	d3 = d4;

	//注意写成这样为调用拷贝构造
	Date d5 = d3;
}

在这里插入图片描述

C++ primer中有一句话说:我们可以重载赋值运算符。不论形参的类型是什么,赋值运算符都必须定义为成员函数。

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

class Time
{
public:
	//构造函数
	Time(int hour = 0, int minute = 0, int second = 0)
	{
		cout << "Date(int hour = 0, int minute = 0, int second = 0)" << endl;
	}

	Time& operator=(const Time& t)
	{
		//判断是不是同一个对象
		if (this != &t)
		{
			cout << "Time& operator=(const Time& t)" << endl;
			_hour = t._hour;
			_minute = t._minute;
			_second = t._second;
		}
		return *this;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date
{
public:
	//不是默认构造函数
	Date(int year, int month, int day)
	{
		cout << "Date(int year, int month, int day)" << this << endl;
	}
	//拷贝构造
	Date(const Date& d)
	{
		cout << "Date(const Date& d)" << endl;
	}
	//析构函数
	~Date()
	{
		cout << "~Date()" << endl;
	}
	
private:
	//内置类型
	int _year;
	int _month;
	int _day;
	//自定义类型
	Time _t;
};


int main()
{
	Date d3(1, 1, 1);
	Date d4(1, 1, 1);
	d3 = d4;

	//注意写成这样为调用拷贝构造
	Date d5 = d3;
}

在这里插入图片描述

如果类中未涉及到资源管理,赋值运算符的重载是否实现都可以,一旦涉及到资源管理则必须要显式实现。

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

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

class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&()const //这里的const代表了不能修改对象
	{
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	cout << &d1 << endl;
	Date* p1 = &d1;
	cout << p1 << endl;
}

在这里插入图片描述

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

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

相关文章

java之juc

juc是java.util.current的简写&#xff0c;意思是并发编程。 锁是什么&#xff1f;如何判断锁的是谁&#xff1f; 生产者和消费者问题 synchronized版本 package com.demo.juc.pc;/*** 线程之间的通信问题&#xff0c;生产者和消费者问题&#xff01;* 线程交替执行** a b …

聊聊这几年的科技风口

作者&#xff1a;朱金灿 来源&#xff1a;clever101的专栏 为什么大多数人学不会人工智能编程&#xff1f;>>> 数数这几年的科技风口&#xff1a;AR&#xff08;包括什么MR、VR&#xff09;、区块链(包括后来的什么web3)、元宇宙到现在的AI&#xff0c;下面逐一谈谈…

AI创作ChatGPT源码系统搭建教程+附源码/支持GPT4.0/支持ai绘画/mj以图生图/Mind思维导图

本系统使用Nestjs和Vue3框架技术&#xff0c;持续集成AI能力到本系统&#xff01; ● 支持微信环境静默登录&#xff08;可开启或关闭&#xff09;、浏览器微信主动扫码登录、邮箱注册登录 ● 无认证公众号后台可关闭微信登录功能 ● 支持GPT3模型、GPT4模型、GPT联网功能 ● M…

TMS WEB Core Crack,TMS软件Delphi组件RADical Web

TMS WEB Core Crack,TMS软件Delphi组件RADical Web 使用我们的现代web应用程序框架&#xff0c;可以节省宝贵的时间并创造丰富的用户体验。我们所有的工具都由经验丰富的开发人员组成的专门团队提供支持。您可以信赖卓越的服务、活跃的社区和我们不断的创新。TMS Software是您的…

Restful的详细介绍~

RESTFUL简介&#xff1a; Restful是我们看待服务器的一种方式&#xff0c;我们都知道Java一切皆对象&#xff0c;因此在Java中&#xff0c;我们可以将所有的内容都看成对象&#xff0c;而在这里&#xff0c;RESTFUL是我们看待服务器的一种方式&#xff0c;我们可将服务器中的所…

【计算机视觉|人脸建模】3D人脸重建基础知识(入门)

本系列博文为深度学习/计算机视觉论文笔记&#xff0c;转载请注明出处 一、三维重建基础 三维重建&#xff08;3D Reconstruction&#xff09;是指根据单视图或者多视图的图像重建三维信息的过程。 1. 常见三维重建技术 人工几何模型仪器采集基于图像的建模描述基于几何建模…

MP的开发流程-1

MP的开发流程 1、添加依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.ap…

Java Design and development -- QQ chat

About ARTS - Complete one ARTS per week: ● Algorithm: Do at least one LeetCode algorithm per week Review: Read and comment on at least one technical article in English ● Tips: Learn at least one technical trick ● Share: Share a technical article with op…

#P0999. [NOIP2008普及组] 排座椅

题目描述 上课的时候总会有一些同学和前后左右的人交头接耳&#xff0c;这是令小学班主任十分头疼的一件事情。不过&#xff0c;班主任小雪发现了一些有趣的现象&#xff0c;当同学们的座次确定下来之后&#xff0c;只有有限的 DD 对同学上课时会交头接耳。 同学们在教室中坐…

不同情境下沟通有哪些可用的工具箱?

在不同情境下&#xff0c;沟通的工具箱可以包括以下几个方面&#xff1a; 面对面交流&#xff1a;面对面交流是最直接和有效的沟通方式。可以通过面对面的会议、面谈或小组讨论等方式进行沟通。面对面交流可以更好地传递情感和非语言信息&#xff0c;有助于建立信任和理解。 …

基于峰谷分时电价引导下的电动汽车充电负荷优化(matlab代码)

目录 1 主要内容 峰谷电价优化 电动汽车充电负荷变化 2 部分代码 3 程序结果 1 主要内容 该程序基本复现《基于峰谷分时电价引导下的电动汽车充电负荷优化》&#xff0c;代码主要做的是基于NSGA-II的电动汽车充电负荷优化&#xff0c;首先&#xff0c;在研究电动汽车用户充…

Spring声明式事务@Transactional的一些问题的测试及求证

1.Spring的声明式事务Transactional问题 前提&#xff1a;有两个方法&#xff0c;a方法对a表做修改操作&#xff0c;b方法对b表做修改操作 a方法调用b方法&#xff0c;然后a方法报错&#xff0c;伪代码如下 public void a() {//数据库修改操作CompensateLogDO compensateLogDO…

英文论文(sci)解读复现【NO.14】基于关注机制的葡萄叶片病害检测

此前出了目标检测算法改进专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读发表高水平学术期刊中的 SCI论文&a…

【宝藏系列】STM32之C语言基础知识

【宝藏系列】STM32之C语言基础知识 文章目录 【宝藏系列】STM32之C语言基础知识1️⃣位操作2️⃣define宏定义3️⃣ifdef条件编译4️⃣extern变量声明5️⃣typedef类型别名 C语言是单片机开发中的必备基础知识&#xff0c;本文列举了部分 STM32 学习中比较常见的一些C语言基础知…

OAuth机制_web站点接入微软azure账号进行三方登录

文章目录 ⭐前言⭐微软三方登录流程&#x1f496; web站点获取微软账号流程&#x1f496; node封装微软登录接口&#x1f496; webapp 自定义code换token&#x1f496; 调用 Microsoft Graph API&#x1f496; 前端唤醒authlink进行登录回调逻辑 ⭐结束 ⭐前言 大家好&#xf…

React(1)——快速入门

一、React背景简介 官网和资料 英文官网: https://reactjs.org/中文官网: 快速入门 – React (docschina.org) 3、如果JS基础忘了及时查看文档&#xff1a;JavaScript - 学习 Web 开发 |多核 (mozilla.org) JavaScript - 标签 - 汤姆大叔 - 博客园 (cnblogs.com) 4、React…

day49-Todo List(待办事项列表)

50 天学习 50 个项目 - HTMLCSS and JavaScript day49-Todo List&#xff08;待办事项列表&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8" /><meta name"viewport" co…

缓存数据同步技术Canal

说明&#xff1a;缓存数据同步&#xff0c;以Redis为例&#xff0c;如何保证从Redis中取出来的数据与MySQL中的一致&#xff1f;在微服务架构下&#xff0c;通常可以用以下两种技术来实现&#xff1a; MQ&#xff1a;在修改数据的同时&#xff0c;发送一个消息修改缓存&#x…

SpringBoot月度员工绩效考核管理系统【附任务书|ppt|万字文档(LW)和搭建文档】

主要功能 员工登录&#xff1a; ①首页、个人中心&#xff1a;修改密码、个人信息管理等 ②公告信息管理、绩效指标管理、绩效考核管理 管理员登录&#xff1a; ①首页、个人中心&#xff1a;修改密码、个人信息管理等 ②公告信息管理、部门管理、岗位管理、员工管理、绩效指标…