类与对象(二)

一、构造函数

构造函数是一个特殊的成员函数 ,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。 构造函数的名称虽然叫构造,但是需要注意的是,构造函数的主要任务并不是开空间创建对象,而是初始化对象。

1.1 构造函数的特性

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

1.1.1 构造函数类型:

  无参构造函数

class Date
{
public:

	//无参构造函数
	Date()
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}

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

int main()
{
	//调用无参构造函数
	Date d1;
	//如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明

	//如下函数:声明了d2函数,该函数无参,返回一个日期类型的对象
	Date d2();

	return 0;
}

带参构造函数

class Date
{
public:

	//带参构造函数
	Date(int year,int month ,int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

int main()
{
	//调用带参构造函数
	Date d1(2022,5,20);

	return 0;
}

1.1.2 默认构造函数

如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
特点:不传参就可以调用
class S
{
public:
/*
 // 如果用户显式定义了构造函数,编译器将不再生成
 Date (int year, int month, int day)
 {
 _year = year;
 _month = month;
 _day = day;
 }
 */
private:
	int _a;
};

class Date
{
public:


private:
	int _year;//内置类型
	int _month;//内置类型
	int _day;//内置类型
	S a;//自定义类型
};

int main()
{
	//没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数
	Date d1;

	return 0;
}

默认生成构造函数对于内置类型成员变量不做处理,对于自定义类型成员变量才会处理

内置类型/基本类型:int/char/double/指针...

自定义类型:class/struct去定义类型对象

默认构造函数类型 :

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。
1.无参的构造函数
class Date
{
public:
	//无参构造函数
	Date()
	{
		_year = 100;
		_month = 100;
		_day = 100;
	}

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

int main()
{
	
	Date d1;

	return 0;
}

2.全缺省构造函数

class Date
{
public:
	//全缺省构造函数
	Date(int year = 100, int month = 100, int day = 100)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	
	Date d1;

	return 0;
}
3. 我们没写,系统默认生成的构造函数
看下面这段代码:
class Stack
{
public:

	//默认构造函数有三种
	// 1.无参的构造函数
	// 2.全缺省构造函数
	// 3.我们没写,系统默认生成的构造函数
	
	//有参不是全缺省,下面函数不是默认构造函数
	Stack(int capacity)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		_top = 0;
		_capacity = capacity;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

class MyQueue
{
public:
	void StackPush()
	{}

	void StackPop()
	{}
private:
	Stack _s1;
	Stack _s2;
};

int main()
{

	MyQueue q1;

	return 0;
}

创建q1时,MyQueue里我们没写有显式定义构造函数,则系统自动生成默认构造函数,对自定义类型成员变量  _s1 、_s2 进行初始化。此时会调用Stack里的默认构造函数对_s1 、_s2进行初始化,但是Stack里面有显式定义构造函数没有默认构造函数,就会出现下面情景, 无法进行初始化。

调用默认构造函数过程(初始化时,会进行层层默认构造函数调用):

MyQueue(有默认构造函数) -> Stack(无默认构造函数)

无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。该三种类型的默认构造函数,都可以认为是默认成员函数
针对编译器自己生成默认成员函数,对内置类型/基本类型成员变量不初始化的问题,C++11打了补丁,可以直接给缺省值
class S
{
public:

private:
	//直接给的缺省值,编译器自己生成默认构造函数用
	int _a =100;
};

class Date
{
public:


private:
	直接给的缺省值,编译器自己生成默认构造函数用
	int _year = 100;
	int _month = 100;
	int _day = 100;

	S aa;
};

int main()
{
	Date d1;

	return 0;
}

总结:一般情况一个C++类,都要自己写构造函数。

一般只有少数情况可以让编译器默认生成,即:

1.类里面成员都是自定义类型成员,并且这些成员都提供了默认构造函数

2.如果还有内置类型成员,声明时给了缺省值


二、析构函数

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

2.1 析构函数的特性

class Stack
{
public:


	Stack(int capacity = 10)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		_top = 0;
		_capacity = capacity;
	}

	//析构函数
	~Stack()
	{
		free(_a);
		_a = NULL;
		_top = _capacity = 0;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	//栈里面定义对象,析构顺序和构造顺序是反的(后进先出)
	//构造顺序 s1,s2
	//析构顺序 s2,s1(后进先销毁)
	Stack s1;
	Stack s2;

	return 0;
}
  • 析构函数名是在类名前加上字符 ~。
  • 无参数无返回值。
  • 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数
  • 对象生命周期结束时,C++编译系统系统自动调用析构函数。

如果构造函数是Init ,那么析构函数就是Destroy

析构函数和构造函数一样,系统为显式定义,系统会自动生成默认的。


三、拷贝构造函数

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

3.1 拷贝构造函数的特性

class Date
{
public:
	Date(int year = 100, int month = 100, int day = 100)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造函数
	Date(const Date& a)
	{
		_year = a._year;
		_month = a._month;
		_day = a._day;
	}

	//普通构造函数
	Date(const Date* b)
	{
		_year = b->_year;
		_month = b->_month;
		_day = b->_day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;

	//调用拷贝构造函数
	Date d2(d1);

	//调用普通构造函数
	Date d3(d1);
	return 0;
}
  • 拷贝构造函数是构造函数的一个重载形式。
  • 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

3.1.1 浅拷贝

若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储 按字节序 完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
  • 内置类型成员会完成浅拷贝,值拷贝
  • 自定义类型成员,去调用这个成员的拷贝构造
class Date
{
public:
	Date(int year = 100, int month = 100, int day = 100)
	{
		_year = year;
		_month = month;
		_day = day;
	}

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

int main()
{
	Date d1;
	//此时会调用系统默认生成的拷贝构造函数,d1、d2值完全一样
	Date d2(d1);
	return 0;
}

但是不是所以的情况都能按字节序完成拷贝,即下面情况,按字节序程序会崩溃:

class Stack
{
public:

	Stack(int capacity = 10)
	{
		_a = (int*)malloc(sizeof(int) * capacity);
		_top = 0;
		_capacity = capacity;
	}

private:
	int* _a;
	int _top;
	int _capacity;
};

int main()
{
	Stack s1;
	Stack s2(s1);

	return 0;
}

这种情况浅拷贝时:

1.数据同时指向一块动态空间,修改数据会相互影响。

2.这块动态空间析构时会释放两次,程序会崩溃。

所以还是需要自己实现拷贝构造函数,需要用深拷贝来完成。

总结: 一般的类,系统生成的拷贝函数就够用了。只有像stack,Sqelist这样的需要自己直接管理资源的类,就需要自己实现深拷贝。

四、赋值运算符重载

4.1 运算符重载

函数结构:返回值类型 operator 操作符 (参数列表)

内置类型,可以直接用各种运算符,但是自定义类型,不能直接用各种运算符。为了自定义类型可以使用各种运算符,所以有了运算符重载的规则

class Date
{
public:
	Date(int year = 2022, int month = 5, int day = 20)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	//this指向的调用函数的对象是左操作数
	//bool operator==(Date* this , const Date& d)
	bool operator==(const Date& d)
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}

	//this指向的调用函数的对象是左操作数
	//bool operator<(Date* this , const Date& d)
	bool operator<(const Date& d)
	{
		if ((_year < d._year) ||
			(_year == d._year) && (_month < d._month) ||
			(_year == d._year) && (_month == d._month) && (_day < d._day))
		{
			return true;
		}
		else
		{
			return false;
		}
	}

	//this指向的调用函数的对象是左操作数
	//bool operator>(Date* this , const Date& d)
	bool operator>(const Date& d)
	{
		if ((_year > d._year) ||
			(_year == d._year) && (_month > d._month) ||
			(_year == d._year) && (_month == d._month) && (_day > d._day))
		{
			return true;
		}
		else
		{
			return false;
		}
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1(2022, 5, 20);
	Date d2(2022, 5, 21);
	Date d3(2022, 5, 22);

	//编译器会处理成对应重载运算符调用 if (d1.operator==(d2))
	if (d1 == d2)
	{
		cout << "==" << endl;
	}

	//编译器会处理成对应重载运算符调用 if (d3.operator>(d2))
	if (d3 > d2)
	{
		cout << ">" << endl;
	}

	//编译器会处理成对应重载运算符调用 if (d1.operator<(d2))
	if (d1 < d2)
	{
		cout << "<" << endl;
	}

	return 0;
}

需要注意的是:

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

4.2 赋值运算符重载

赋值运算符需要注意的点:

  • 参数类型
  • 返回值
  • 检测是否自己给自己赋值
  • 返回*this
  • 一个类 如果没有显式定义赋值运算符重载,编译器也会生成一个 ,完成对象按字节序的值拷贝。
class Date
{
public:
	Date(int year = 2022, int month = 5, int day = 20)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._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;
};

int main()
{
	Date d1(2022, 5, 20);
	Date d3(2022, 5, 21);

	//拷贝构造, 一个存在的对象去初始化另一个要创建的对象
	Date d2(d1);

	//赋值重载/复制拷贝, 两个已经存在对象之间赋值
	d3 = d2 = d1;//
	return 0;
}

五、日期类的实现

Date.h

#include <iostream>
#include <assert.h>
using namespace std;

class Date
{
public:

	//全缺省构造函数
	Date(int year = 2022, int month = 5, int day = 20)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}

	//判断是否为闰年
	bool IsLeapYear(int year)
	{
		return ((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0);
	}

	//获取某年某月的天数
	int GetMonthDay(int year, int month);

	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	//返回类型:
	//返回时存储空间还存在,则可以用引用返回或者传值返回( Date& / Date )
	//返回时存储空间使用权被回收,则只能用传值返回( Date )

	//日期 += 天数
	Date& operator+=(int day);

	//日期 + 天数
	Date operator+ (int day);

	//日期 - 天数
	Date operator-(int day);

	//日期 -= 天数
	Date& operator-=(int day);

	//日期 - 日期  返回天数
	int operator-(const Date& d);

	//前置和后置的区别:
	//后置有参数,来区分前后置
	//(int)意味着,只接收参数,但不使用

	//前置++
	Date& operator++()
	{
		*this += 1;
		return *this;
	}

	//后置++
	Date operator++(int)
	{
		Date ret(*this);
		*this += 1;
		return ret;
	}

	//前置--
	Date& operator--()
	{
		*this -= 1;
		return *this;
	}

	//后置--
	Date operator--(int)
	{
		Date ret(*this);
		*this -= 1;
		return ret;
	}

	// == 运算符重载
	bool operator==(const Date& d);

	// > 运算符重载
	bool operator>(const Date& d);

	// >= 运算符重载
	bool operator>=(const Date& d)
	{
		return *this > d || *this == d;
	}

	//复用

	// < 运算符重载
	bool operator<(const Date& d)
	{
		return !(*this >= d);
	}

	// <= 运算符重载
	bool operator<=(const Date& d)
	{
		return !(*this > d);
	}

	// != 运算符重载
	bool operator!=(const Date& d)
	{
		return !(*this == d);
	}

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

Date.cpp

#include "Date.h"

int Date::GetMonthDay(int year, int month)
{
	assert(year > 0 && month > 0 && month < 13);
	const static int Monthday[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
	if (month == 2 && IsLeapYear(year))
	{
		return 29;
	}
	else
	{
		return Monthday[month];
	}
}

Date& Date::operator+=(int day)
{
	if (day < 0)
		//负负得正,(-day)>0
		return *this -= -day;

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month >= 13)
		{
			++_year;
			_month = 1;
		}
	}

	return *this;
}

Date Date::operator+(int day)
{
	Date ret(*this);
	ret += day;

	return ret;
}

Date Date::operator-(int day)
{
	Date ret(*this);

	ret -= day;

	return ret;
}

Date& Date::operator-=(int day)
{
	if (day > 0)
		return *this += -day;

	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}

	return *this;
}

bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

bool Date::operator>(const Date& d)
{
	if (_year > d._year ||
		(_year == d._year) && (_month > d._month) ||
		(_year == d._year) && (_month == d._month) && (_day > d._day))
	{
		return true;
	}
	else
	{
		return false;
	}
}

int Date::operator-(const Date& d)
{
	//标记正负
	int flag = 1;
	Date Max = *this;
	Date Min = d;
	if (*this < d)
	{
		Max = d;
		Min = *this;
		flag = -1;
	}

	//计数
	int n = 0;
	while (Min != Max)
	{
		n++;
		Min++;
	}

	return flag * n;
}

六、const成员

6.1 const修饰的成员函数

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
class Date
{
public:
	//void Print(Date* const this) -> Date*
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Func(const Date&  d)
{
	d->Print();//d.Print(&d) -> const Date* 
}
int main()
{
	Date d1;
	Func(d1);
	d1.Print();//d1.Print(&d1) -> Date*
	return 0;
}

成员函数后加const:

class Date
{
public:
	//void Print(const Date* const this) -> const Date* 
	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Func(const Date& d)
{
	d.Print();//d.Print(&d) -> const Date* 
}
int main()
{
	Date d1;
	Func(d1);
	d1.Print();//d1.Print(&d1) -> Date*
	return 0;
}

原则:成员函数内只要不改成成员变量,成员函数建议都加const

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

class Date
{
public:
	Date* operator&()
	{
		//如果不想给别人懂地址,可以
		//return nullptr
		return this;
	}

	const Date* operator&()const
	{
		//如果不想给别人懂地址,可以
		//return nullptr
		return this;
	}
private:
	int _year;
	int _month;
	int _day;
};

这两个运算符一般不需要重载,不用重新定义,编译器默认会生成。 除非想让别人获取到指定的内容!


  • 42
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 33
    评论
评论 33
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Cristiano777.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值