类和对象中 构造函数+析构函数+拷贝构造函数+操作符重载

1. 类的6个默认成员函数

如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数.
在这里插入图片描述

相关题目

1、类的析构函数调用完全按照构造函数调用的相反顺序进行调用
2、全局对象先于局部对象进行构造
3、静态对象先于普通对象进行构造
4、对象析构要在生存作用域结束的时候才进行析构,
5、因为赋值符号=需要对象进行驱动,所以只能重载为成员函数

2. 构造函数

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

构造函数特征

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

class Date*/
{
    
    
public:

	Date (int year, int month, int day)//带参的构造函数
	{
    
    
		cout << "Data(int year,int month,int day)"<< endl;
		_year = year;
		_month = month;
		_day = day;
	}
	Date()//无参的构造函数
	{
    
    
		cout << "Data()" << endl;
		_year = 2021;
		_month = 3;
		_day = 1;
	}
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	//想要的是,对象构造出来就初始化
	//我们没写,编译器默认生成了构造函数,但是d1里面的值还是随机值。
	//说明默认的构造在这里不行,需要我们自己写
	Date d1(2021,2,2); //调用带参的构造函数
	d1.Print();
	Date d2;//也可以不传参数初始化
	d2.Print();//给多个构造函数,有多种初始化方式

	return 0;
}
//将带参和无参结合,得到一个全缺省的构造函数
class Date
{
    
    
public:
	Date(int year = 2021, int month = 3, int day = 1)//带参的构造函数
	{
    
    
		cout << "Data(int year,int month,int day)" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	//Date()//无参的构造函数
	//{
    
    
	//	cout << "Data()" << endl;
	//	_year = 2021;
	//	_month = 3;
	//	_day = 1;
	//}
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1(2021, 2, 2); 
	d1.Print();
	Date d2;
	d2.Print();

	return 0;
}

C++把类型分成内置类型(基本类型)和自定义类型,内置类型如int/char…,自定义类型就是使用class/struct/union自己定义的类型。
如下程序,编译器生成的构造函数会对自定类型成员_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;
}
//不传参就可以调用的函数就是默认成员函数
//无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函//数,都可以认为是默认成员函数。

3. 析构函数

析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。

析构函数特征
  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。
  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
typedef int DataType;
class SeqList
{
    
    
public:
	SeqList(int capacity = 10)
	{
    
    
		_pData = (DataType*)malloc(capacity * sizeof(DataType));
		assert(_pData);
		_size = 0;
		_capacity = capacity;
	}
	~SeqList()//析构函数
	{
    
    
		if (_pData)
		{
    
    
			free(_pData); // 释放堆上的空间
			_pData = nullptr; // 将指针置为空
			_capacity = 0;
			_size = 0;
		}
	}
private:
	int* _pData;
	size_t _size;
	size_t _capacity;
};
int main()
{
    
    
	SeqList d1;
	return 0;
}

4. 拷贝构造函数

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

拷贝构造函数特征
  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//拷贝构造函数
	//调用拷贝构造要先传参,传参又形成了一个新的拷贝构造,
	//函数参数若为Date(Date d),则传参就是将d1的值传给的,相当于执行Date d = d1;
	//相当于又进行一次拷贝构造。如此一来,就会形成一个递归操作而且没有结束条件,造成堆栈溢出。
	//所以必须使用引用传参,形参是实参的别名,直接进入
	Date(const Date& d)//Date& d = d1
	{
    
    
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	void Print()
	{
    
    
		cout << _year << "-" << _month << "-"<<_day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1(2021,2,3);
	//d2的值需要跟d1一样
	Date d2(d1);//等价于Date d2 = d1;都是拷贝构造
	d1.Print();
	d2.Print();
	return 0;
}
  1. 若未显示定义,系统生成默认的拷贝构造函数。
    默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝。
  2. 那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像
    日期类这样的类是没必要的。
class Stack
{
    
    
public:
	Stack(int capacity = 4)
	{
    
    
		_a = (int*)malloc(sizeof(int)*capacity);
		_capacity = capacity;
		_size = 0;
	}
	~Stack()
	{
    
    
		free(_a);//释放栈上的空间
		_a = nullptr;//将指针置为空
		_capacity = 0;
	}
private:
	int* _a;
	int _size;
	int _capacity;
};
int main()
{
    
    
	Stack st1;
	Stack st2(st1);
	//函数存在栈里,后定义的先调用析构函数,先调用st2,调用之后在析构函数中_a被free掉,变成野指针,再调用st1,无法进行二次释放。
	//像stack这样管理着内存资源的类,浅拷贝是不行的,得搞成深拷贝。
	//Stack st2 = st1;
	return 0;
}

5. 赋值操作符重载

运算符重载

函数重载:函数名相同,参数不同
运算符重载:自定义类型可以像内置类型一样去用运算符

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

扫描二维码关注公众号,回复: 13137773 查看本文章
 //全局的operator==和operator>
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	//d1==d2 -> d1.operator==(d2) -> d1.operator==(&d1,d2);
	bool operator == (Date d2)
	{
    
    
		return _year == d2._year
			&& _month == d2._month
			&& _day == d2._day;
	}
	//d1>d2 -> d1.operator>(d2) -> d1.operator>(&d1,d2);
	bool operator>(const Date& d)
	{
    
    
		if (_year > d._year)
		{
    
    
			return true;
		}
		else if (_year == d._year)
		{
    
    
			if (_month > d._month) 
			{
    
    
				return true;
			}
			else if (_month == d._month)
			{
    
    
				if (_day > d._day)
				{
    
    
					return true;
				}
			}
		}
		return false;
	}
private:
	int _year;
	int _month;
	int _day;
};
// 这里会发现运算符重载成全局的就需要成员变量是共有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
int main()
{
    
    
	Date d1(2021, 2, 4);
	Date d2(d1);
	//operator == (d1,d2);
	//d1 == d2;
	//d1.operator == (&d1,d2);
	d1 == d2;

	return 0;
}
赋值运算符重载

赋值运算符注意四点:

  1. 参数类型
  2. 返回值
  3. 检测是否自己给自己赋值
  4. 返回*this
  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。
class Date
{
    
    
public:
	Date(int year = 1900, int month = 1, int day = 1)
	{
    
    
		_year = year;
		_month = month;
		_day = day;
	}
	d2 = d3
	Date& operator=(const Date& d)
	{
    
    
		if (this != &d)
		{
    
    
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
//operator=跟拷贝构造函数一样,我们不实现,编译器会生成一份默认的,对于Date
//默认的和重载的都可以用。像stack这样的类,需要自己实现深拷贝。
	//原因是析构的时候回释放两次
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
    
    
	Date d1(2021, 2, 4);
	Date d3(2021, 3, 1);
	d1 = d3;
	return 0;
}

注意:

Date d2 = d1;//定义的同时赋值都是拷贝构造
d2 = d1;//定义后才赋值就是赋值运算符重载

6.const成员

6.1 const修饰类的成员函数

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

class Date
{
    
    
public :
	void Display ()
	{
    
    
		cout<<"Display ()" <<endl;
		cout<<"year:" <<_year<< endl;
		cout<<"month:" <<_month<< endl;
		cout<<"day:" <<_day<< endl<<endl ;
	}
	void Display () const
	{
    
    
		cout<<"Display () 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 ;
	d1.Display ();
	const Date d2;
	d2.Display ();
}

需要思考几个问题:

  1. const对象可以调用非const成员函数吗?
    不行!const修饰的对象,只有读权限没有写权限。
  2. 非const对象可以调用const成员函数吗?
    可以!权限可以缩小不可以放大!
  3. const成员函数内可以调用其它的非const成员函数吗?
    不可以!
  4. 非const成员函数内可以调用其它的const成员函数吗?
    可以!

猜你喜欢

转载自blog.csdn.net/du1232/article/details/114670158