全面学习c++类与对象(中)(非常重要)(析构构造拷贝函数赋值运算符重载等等)

发布于:2025-05-23 ⋅ 阅读:(22) ⋅ 点赞:(0)

类的默认成员函数

一个类什么也没有,叫空类,但空类实际上也有默认成员函数
类中默认给出6个成员函数。
默认成员函数:用户如果没有显性实现,编译器会默认生成的函数叫默认成员函数。

class 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(2025, 5, 18);
	d1.print();

	Date d2;
	d2.Init(2020, 1, 1);
	d2.print();
	return 0;
}

2025-5-18
2020-1-1

我们初始化d1,d2,都要自己调用 Init 函数如果类多个实例化很麻烦,所以有了构造函数。

概念

🚩 构造函数构造函数是一种特殊成员函数,名字与类相同,没有返回类型,由编译器自动调用,可以保证类的对象都有合适初始化,并在对象整个生命周期内只调用一次

//Date是在类里面的,重复代码我就省去了
Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}
int main()
{
	Date d1(2025,5,18);
	//d1.Init(2025, 5, 18);
	d1.print();

	Date d2(2020,1,1);
	//d2.Init(2020, 1, 1);
	d2.print();
	return 0;
}

特性

构造函数是特殊的成员函数,虽然叫构造,但他的作用不是开空间创建对象,而是初始化对象
特性:
1,函数名与类名相同
2,没有返回类型,
3,对象实例化编译器自动调用相应的构造函数
4,构造函数可以重载

class Date {
public:
	Date() {
	};
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
void test()
{
	Date d1;//调用无参构造
	Date d2(2025, 5, 18);//有参构造

	Date d3();//错误
}

注意d1,d3区别,无参调用构造函数时不要加(),不然编译器会以为是函数声明,

🚩5,如果类中没有显性构造函数,编译器会自动生成一个无参构造函数,如果类中有了任何显性构造函数编译器就不会生成,

class Date {
public:
	/*Date() {
	};*/
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
void test()
{
	Date d1;//报错
	Date d2(2025, 5, 18);//有参构造
}

报错,屏蔽了无参构造函数,因为类中已有构造函数,所以编译器不会生成默认无参函数

🚩6,编译器虽然生成了默认无参构造函数,但是对象的值还是随机值,那么默认无参构造函数到底有什么用?
答:c++把类型分为内置类型(基本类型),自定义类型,内置类型就是语言提供的数据类型(int,char,double),自定义类型就是自己定义(struct,class),看如下代码编译器生成默认构造函数会调用里面自定义类型调用自身的构造函数

#include <iostream>
using namespace std;
class Time {
public:
	Time(int hour = 10, int minute = 10, int second = 10)
	{
		_hour = hour;
		_minute = minute;
		_second = second;
		cout << _hour << minute << second << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
class Date {
public:

private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d1;
	return 0;
}

101010

默认构造函数不会初始化内置类型,所以c++11又打了补丁,对于内置类型可以在声明时给默认值

class Date {
public:

private:
	int _year=2025;
	int _month=5;
	int _day=19;
	Time _t;
};

🚩7,注意:无参的构造函数和全缺省值的构造函数和编译器自带的构造函数,都可以叫作默认构造函数,并且默认构造函数只有一个

class Date
{
public:
	Date()
	{
		_year = 1900;
		_month = 1;
		_day = 1;
	}
	Date(int year = 1900, int month = 1, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};
// 以下测试函数能通过编译吗?
void Test()
{
	Date d1;
}

不能,因为默认构造函数一个无参,一个全缺省,d1不知道去找哪个初始化。
改:去掉构造函数中的缺省值,

Date(int year,int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}

析构函数

概念:

与构造函数相反,析构函数不是对象本身的销毁,局部变量销毁是编译器干的活,而对象在销毁的过程就会调用析构函数,析构函数完成对象中资源清理工作

特性:

1,析构函数:~加类名
2,无返回值,
🚩3,析构函数不能重载,
🚩4,对象生命周期结束,编译系统自动调用析构函数,

class stack
{
public:
	stack(size_t capacity = 4)
	{
		_a = (int*)malloc(sizeof(int) * 4);
		if (_a == nullptr)
		{
			perror("fail malloc");
			return;
		}
		capacity = 4;
		_size = 0;
	}
	~stack()
	{
		free(_a);
		_a = nullptr;
		_capacity = 0;
		_size = 0;
	}
private:
	int* _a;
	int _size;
	int _capacity;
};
#include <iostream>
using namespace std;
class Time
{
public:
	~Time()
	{
		cout << "test" << endl;
	}
};
class Date
{
public:
	Date(int year=2025, int month=5, int day=21)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
	Time _time;
};
int main()
{
	Date d1;
	return 0;
}

test

与析构函数一样,要销毁d1, 系统默认生成了析构函数清空内置类型,同时调用自定义类型Time的析构函数,

拷贝构造函数

现实中有双胞胎,那么可不可以创建一个与存在的对象一模一样的新对象呢,

概念:

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

特性:

1,拷贝构造函数是构造函数的重载
🚩2,参数只有一个且必须是类类型的引用,使用传值方式会直接报错,(无限递归调用)

#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year = 2025, int month = 5, int day = 21)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    //Date(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,若没有显性定义,编译器会自动生成拷贝函数 ,默认拷贝函数按内存存储字节序存储,也叫浅拷贝或值拷贝,并自动调用了自定义类型的拷贝类型

#include <iostream>
using namespace std;
class Time
{
public:
	Time(int hour = 12, int minute = 52)
	{
		_hour = hour;
		_minute = minute;
	}
		Time(const Time& t)
	{   
		_hour = t._hour;
		_minute=t._minute;
		cout << "time(const Time& t)";
	}
private:
	int _hour;
	int _minute;
};
class Date
{
public:
	Date(int year = 2025, int month = 5, int day = 21)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	/*Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}*/
private:
	int _year;
	int _month;
	int _day;
	Time _t;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

time(const Time& t)

🚩4,编译器自动生成的已经完成字节序的拷贝,所以我们自己动手完成有什么用呢?且看以下代码:

#include <iostream>
using namespace std;
typedef int Typedate;
class stack
{
public:
	stack(int capacity = 4)
	{
		_a = (Typedate*)malloc(sizeof(int) * capacity);
		_capacity = capacity;
		_size = 0;
	}
	void stackpush(int x)
	{
		//Checkstack()
		_a[_size] = x;
		_size++;
	}
	~stack()
	{
		if (_a)
		{
			free(_a);
			_a = nullptr;
			_capacity = 0;
			_size = 0;
		}
	}
private:
	Typedate* _a;
	size_t _capacity;
	size_t _size;
};

int main()
{
	stack s1;
	s1.stackpush(2);
	s1.stackpush(3);
	s1.stackpush(5);
	stack s2(s1);
	return 0;
}

崩溃了
没有显性定义拷贝函数,所以s1原封不动拷贝给s2,两指针指向同一块空间。
当销毁时,s2先释放,s1不知道 又会释放,一块空间多次释放,所以崩溃了

注意:类中没有资源申请时,拷贝构造函数写不写都可以,当有资源申请时,拷贝构造函数必须要写,否则就是浅拷贝,

赋值运算符重载

运算符重载

C++为了代码可读性,引入运算符重载,运算符重载是具有特殊函数名的函数,也有返回类型,形参,
🚩 函数原型为:返回类型+operator后面接要重载的运算符

注意:
1,operator不能连接其他操作符创建新的操作符,如 operator@
2,重载操作符必须有一个类类型参数
3,不能改变内置类型之间的操作符如 +
4,作为类成员函数重载时,形参看似少一,因为自带this指针
5,.* :: sizeof ?: . 这5个操作符不能重载,

#include <iostream>
using namespace std;
class Date
{
public:
	Date(int year=2020, int month=5, int day=22)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	bool operator==(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	Date d2;
	cout << (d1 == d2) << endl;
	return 0;
}

1

赋值运算符重载

注意:
1,形参 const Date&变量
2,函数名 类名+&,有返回值方便连续赋值,
3,检查是否自我赋值
4,返回 *this

class Date
{
public:
	Date(int year = 2020, int month = 5, int day = 22)
	{
		_year = year;
		_month = month;
		_day = 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;
};

🚩 注意:赋值操作符重载必须在显性定义在类里面,若无显性实现,编译器会自己生成默认函数,这时再定义在全局就和默认的冲突了,

#include <iostream>
using namespace std;
class Time
{
public:
	Time(int hour=4, int minute=21)
	{
		_hour = hour;
		_minute = minute;
	}
	Time& operator=(const Time& t)
	{
		_hour = t._hour;
		_minute = t._minute;
		return *this;
	}
private:
	int _hour;
	int _minute;
};
class Date
{
public:
	Date(int year = 2020, int month = 5, int day = 22)
	{
		_year = year;
		_month = month;
		_day = 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;
	Time t;
};
int main()
{
	Date d1;
	Date d2(2020,5,18);
	d1 = d2;

	return 0;
}

默认进行浅拷贝,又来哪个问题,编译器自动完成字节存储拷贝了,我们自己实现干嘛?

#include <iostream>
using namespace std;
typedef int Datetype;
class stack
{
public:
	stack()
	{
		_a = (Datetype*)malloc(sizeof(int) * 10);
		if (_a == nullptr)
		{
			perror("fail malloc");
			return;
		}
		_capacity = 0;
		_size = 0;
	}
	void push(int x)
	{
		//Check();
		_a[_size++] = x;
	}
	~stack()
	{
		free(_a);
		_a = nullptr;
		_capacity = 0;
		_size = 0;
	}
private:
	Datetype* _a;
	size_t _size;
	size_t _capacity;
};
int main()
{
	stack s1;
	s1.push(1);
	s1.push(2);
	s1.push(3);
	s1.push(4);
	stack s2;
	s2 = s1;
	return 0;
}

又崩溃了
在这里插入图片描述
🚩 还是申请资源时,必须要自己实现赋值运算符重载

#include <iostream>
using namespace std;
class Date {
public:
	Date(int year = 2020, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	Date& operator++()
	{
		_day++;
		return *this;
	}
	Date operator++(int)
	{
		Date temp(*this);
		_day++;
		return temp;
	}
	void print()
	{
		cout << _year << "," << _month << "," << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.print();
	++d1;
	d1.print();
	Date d2 = d1++;
	d2.print();
	d1.print();
	return 0;
}

2020,1,1
2020,1,2
2020,1,2
2020,1,3

const

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << "Print()" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
	void Print() const
	{
		cout << "Print()const" << endl;
		cout << "year:" << _year << endl;
		cout << "month:" << _month << endl;
		cout << "day:" << _day << endl << endl;
	}
private:
	int _year; // 年
	int _month; // 月
	int _day; // 日
};
void Test()
{
	Date d1(2022, 1, 13);
	d1.Print();
	const Date d2(2022, 1, 13);
	d2.Print();
}

第2个print被const修饰,只能被const修饰的d2调用,
d1则可以调用两个print,权限只可以缩小
非const对象可以调用const成员函数,反过来就不行
非const成员函数可以调用const成员函数,反过来不行