构造、析构、拷贝构造、赋值运算符重载

发布于:2024-10-11 ⋅ 阅读:(9) ⋅ 点赞:(0)

类里面的成员函数什么都不写的时候,编译器会自动生成6个成员函数。
在这里插入图片描述
功能、特性、编译器默认的工作。
一.构造函数:
顶替原来SetDate函数。因为可能存在忘记调用初始化函数而直接进行使用。为了解决,C++引入了构造函数完成初始化。对象实例化的时候自动调用。(保证对象一定可以初始化)
构造函数可以重载。(可以有多个构造函数)
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主
要任务并不是开空间创建对象,而是初始化对象。
其特征如下: 函数名与类名相同。无返回值。对象实例化时编译器自动调用对应的构造函数。构造函数可以重载。
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
类的默认构造函数对于内置类型不处理,对自定义类型会处理(初始化)。

class Date
{ 
public:
	// 构造函数 自动调用 没有返回值 一定会初始化
	Date(int year, int month, int day) //有参的 和 缺省的 不构成重载 不能共存
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Data() // 无参的 和 缺省的 会产生歧义 不能共存
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	
	Date(int year = 1, int month = 1, int day = 1) //缺省的 最棒的
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year; // 对内置类型不初始化
	int _month;
	int _day;
};

class Bata
{
public:
	void Print()
	{};
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2024, 9, 29); // 调用有参数的构造函数 这个就可以直接自动调用构造函数初始化。
	Date d2; // 调用无参的构造函数
	Bata b1;// 默认生成的构造函数是随机值
	return 0;
}

二:析构函数:
析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
析构函数是特殊的成员函数。
其特征如下:析构函数名是在类名前加上字符 ~。无参数无返回值(不能重载)。 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。 对象生命周期结束时,C++编译系统系统自动调用析构函数。
出了main的作用域后会调用析构函数。
s2先析构,s1和s2在栈存放,s2后进先出。

class SeqList 
{ 
public:
	SeqList(int capacity = 10)
	{
		_pData = (DataType*)malloc(capacity * sizeof(DataType));
		assert(_pData);
		_size = 0;
		_capacity = capacity;
	}
	~SeqList()
	{
		free(_pData );   // 释放堆上的空间
		_pData = nullptr;   // 将指针置为空
		_capacity = 0;
		_size = 0;
	}
private:
	int* _pData ;
	int _size;
	int _capacity;
};
int main()
{
	SeqList s1;
	//地下调用不用自己再写 SeqList的init和destory 因为构造和析构就默认调用解决了 不用自己再调用了
	//...
	
	SeqList s2; // s1和s2是s2先析构
	return 0;
}

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

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

因为传值调用拷贝构造,传值的过程就是一个传参的拷贝构造,就一直在传值传参拷贝构造、传值传参拷贝构造。
对自定义类型传参的拷贝就是在调用拷贝构造。
对自定义类型传参如果是引用传参就只是别名不调用拷贝构造。
在这里插入图片描述
使用引用传值,仅仅是别名,因此只会调用一次。

class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// Date(Date d) // 传值方式会引发无穷递归调用
	// {}
	Date(const Date& d) // 防止对d的修改,因为需要把d给this赋值,加const不对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;
}

若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。
下面代码也可以完成拷贝。
但是对于日期类的值的整型类型浅拷贝可以完成,对于其他一些类型默认拷贝函数就不会完成拷贝会崩掉。

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;
};
int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

对于指针参数的拷贝构造,对s2而言,把指针的地址直接赋值给s2,那么在main结束后的析构过成就会造成对同一块空间的两次析构的过程,因此会崩掉。(s2先析构,s1后析构同一块空间就会坏。一次malloc只能free一次。)
其中一个对象插入删除数据会导致另一块空间也插入删除了数据。
深浅拷贝问题。

// 这里会发现下面的程序会崩溃掉?这里就需要深拷贝去解决。
class String
{
public:
	String(const char* str = "jack")
	{
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}
	~String()
	{
		cout << "~String()" << endl;
		free(_str);
	}
private:
	char* _str;
};
int main()
{
	String s1("hello");
	String s2(s1);
}

拷贝构造对于内置类型完成浅拷贝,对自定义类型会调用它的拷贝构造进行拷贝。

class Str
{
public:

private:
	int _str;
};
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;
	Str _ss;
};
int main()
{
	Date d1;
	Date d2(d1); // 对d1中的_ss调用_ss的拷贝构造进行拷贝。
	return 0;
}

四:赋值运算符重载:
运算符重载:
增强代码可读性,让自定义类型可以像内置类型一样使用运算符。
运算符重载和函数重载没有关联。函数重载是支持使用定义同名函数,运算符重载是为了让自定义类型可以像内置类型一样去使用运算符。

不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型或者枚举类型的操作数
用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的
操作符有一个默认的形参this,限定为第一个形参
.* 、 :: 、 sizeof 、?: 、 . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

自定义类型不支持运算符,C++可以用运算符重载来让类对象支持用某个运算符。

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;
};
void Test ()
{
	Date d1(2018, 9, 26);
	Date d2(2018, 9, 27);
	d1 == d2; // 其实是这个 d1.operator==(&d1, d2);
	cout<<(d1 == d2)<<endl;
}

class Array
{
public:
	Array()
	{
		for(int i = 0; i<10; ++i)
		{
			_a[i] = i;
		}
	}
	int& operator[](int pos) // 对象是在main定义的,这里可以用引用,出了类作用域值还在
	{
		return _a[pos];
	}
private:
	int _a[10];
};
int main()
{
	Array ar;
	cout << ar[3] << endl; // 打印3
	ar[3] = 30; // 可以完成修改
}

赋值运算符重载:也是拷贝行为,但不一样的是拷贝构造函数是创建一个对象时拿同类对象初始化的拷贝,这里的赋值拷贝是这两个对象已经存在了都被初始化过了,现在想把一个对象复制拷贝给另一个对象。
1.针对内置类型会完成浅拷贝,即对于Date的类不需要写重载系统默认即可,String就不行
2.针对自定义类型会调用它的赋值运算符重载进行拷贝
Date d5 = d1; 拷贝构造 不是赋值重载 因为d5这会儿也还没存在,正在创建。

class Date
{}
int main()
{
	Date d1(...);
	Date d2(d1); // 拷贝构造

	Date d3(...);
	Date d4;
	d4 = d3; // 赋值运算符重载(赋值拷贝) 两个已经存在的对象拷贝

	Date d5 = d1; // 拷贝构造
}
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void operator=(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(2018101);
	
	// 这里d1调用的编译器生成operator=完成拷贝,d2和d1的值也是一样的。写了就调用自己写的赋值拷贝。 
	d1 = d2;
	return 0;
}

连续赋值是右结合,传值返回会导致调用拷贝构造,传引用返回无影响。调用两次operator=。
为了防止自己给自己赋值加上if(this != &d)
在这里插入图片描述

class Date
{
	Date& operator=(const Date& d)
	{
		if(this != &d) // 不是自己给自己赋值才需要拷贝
		{
			_year = d._year ;
			_month = d._month;
			_day = d._day
		}
		return *this;
	}
};
int main()
{
	Date d1(2018101);
	Date d2;
	Date d3(2018101);
	
	d1 = d2 = d3; // d3给d2,d2给d1。这种方式上面的void operator=(const Date& d)无法满足 返回值需要为Date
	return 0;
}

构造和析构不写编译器内置类型不处理,自定义类型调用它的构造和析构
拷贝构造和赋值重载内置类型完成浅拷贝,自定义类型会调用他们的拷贝构造和赋值重载