类的默认成员函数(C++)

之前我们看到,一个空类他其实也是有大小的,但是我们所定义的空类就真的什么也没有,只是一个空类吗?

类的6个默认成员函数

总的来说,任何一个类在我们什么都不写的情况下,都会自动生成6个默认的成员函数。

  • 构造函数:完成初始化工作
  • 析构函数:完成清理函数
  • 拷贝构造:使用同一个类的对象初始化创建对象
  • 赋值重载:主要是把一个对象赋值给另一个对象
  • 取地址重载:有两个函数,主要是对普通对象和const对象取地址

但是这些默认生成的成员函数也不一定是靠谱的。
在这里插入图片描述

构造函数

class Data
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

这样的类,我们每次在创建一个新的对象的时候都需要完成初始化的操作,而在C++中对这一个过程做了优化,允许在创建类的时候对类中的成员变量进行初始化,看上去很神奇,其实他的本质还是调用了函数,即构造函数。
构造函数是一个特殊的成员函数,名字与类名相同,在创建类的对象时编译器会自动调用,保证每一个数据成员都有一个合适的初值,在对象的生命周期内只调用一次。
特性:

  1. 函数名与类名相同
  2. 没有返回值
  3. 对象实例化时编译器自动处理对应的构造函数
  4. 构造函数也支持重载
class Data
{
public:
	//无参构造函数
	Data()
	{}
	//带参构造函数
	Data(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

void fun1()
{
	Data a;//调用无参构造函数
	Data b(2020, 3, 18);//调用带参构造函数

	Data c();//错误的调用
}

构造函数本质还是一个函数,可以支持函数重载的,如果想要通过无参构造函数来创建对象的时候,对象名的后面不用跟上括号(第三种写法),否则就不是函数调用,而是函数声明了。

  1. 如果类中没有显示我们定义的构造函数,那么C++编译器会自动生成一个无参的默认构造函数。而如果我们定义了一个构造函数,那么编译器将不再生成,在使用的时候直接调用用户自定义的构造函数。
  2. 默认构造函数可以是无参的构造函数,也可以是全缺省的构造函数,并且默认构造函数只能有一个。
class Data
{
public:
	//无参构造函数
	Data()
	{
		_year = 2020;
		_month = 3;
		_day = 19;
	}
	//全缺省构造函数
	Data(int year = 2020, int month = 3, int day = 19)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

我们知道,全缺省的函数也可以不用传参,要是我们在定义全缺省构造函数的同时,又调用一个无参构造函数,这样编译器可以通过吗?答案是显而易见的,编译器会报"指定了多个默认构造函数",以及"对重载函数的调用不明确"的错误。
同样的都是无参数可以通过的,但是放在一个类中编译器不知道应该执行哪一个函数,就会报错,所以说两种默认构造函数在同一个类中只能存在一种。

在这里插入图片描述
7. 如果我们在一个类中没有定义构造函数,编译器自动生成的默认构造函数会完成什么作用?
在这里插入图片描述
事实证明他没有完成我们想要的初始化操作,成员变量的结果依旧是一个随机值,那么这时候自动生成的默认构造函数开起来好像没有什么用。。。
而C++的类型有自定义类型和内置类型(基本类型)。内置类型就是在语法上已经存在的类型,就像int,char,double等等我们可以直接使用的类型;另一种就是我们自己定义的类型,就像struct,union,class等等。
在这里插入图片描述
我们发现编译器生成的默认构造函数可以对自定义类型的成员 _time进行访问,并调用time类的默认构造函数。

析构函数

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

  1. 析构函数的函数名是在类名的前面加~字符
  2. 析构函数没有参数也没有返回值
  3. 一个类有且只有一个析构函数。同构造函数一样,如果未定义,编译器会自动生成一个默认的析构函数。
  4. 在对象的生命周期结束的时候,C++编译系统会自动调用析构函数。
class arr
{
public:
	arr(int capacity = 10)
	{
		_capacity = capacity;
		_size = 0;
		_a = (int*)malloc(sizeof(int)* _capacity);
	}

	~arr()
	{
		if (_a)
		{
			free(_a); // 释放堆上的空间
			_a = NULL; // 将指针置为空
			_capacity = 0;
			_size = 0;
		}
	}

private:
	int* _a;
	int _size;
	int _capacity;
};
  1. 同构造函数一样,编译器默认生成的析构函数也会调用自定义类型的成员的析构函数。

在这里插入图片描述

拷贝构造

构造函数只有单个形参,这个参数是对他自己类型的对象的一个引用(一般使用const修饰),从而创造出另一个一模一样的对象。在用已存在的类的类型对象创建新对象时由编译器自动调用。
特性:

  1. 拷贝构造函数是构造函数的一个重载形式,函数名同类名,不同的是他只有类本身类型的一个参数
class Data
{
public:
	Data(const Data& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
  1. 拷贝构造函数的参数只有一个并且必须使用引用传参,使用传值的方式会引发无穷递归调用。

在这里插入图片描述

  1. 若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储,并按照字节序完成拷贝,这种拷贝叫做浅拷贝或者值拷贝。
  2. 既然编译器可以默认生成的拷贝构造函数就可以完成字节序的拷贝,我们还需要自己实现吗?如果成员变量中没有指针类型的变量,我们就可以不用自定义拷贝构造函数,但是如果有指针类型的成员变量,在默认的浅拷贝时会出错,这时就需要深拷贝。
    在这里插入图片描述

我们可以看到,在没有return 0之前,a和b类的内容都是一模一样的,两个指针变量指向的是同一块内存地址。然后当return 0之后,由于a和b类在栈中的存储的顺序,先处理的是b类,直接调用析构函数,这时发现a类也被析构了,但是问题是这时还没有调用a类的析构函数。
所以说,当b类析构函数执行结束后,再次调用a类的析构函数就会造成空指针的访问,肯定会出错的,这个时候默认的浅拷贝就出现了问题,就需要我们自定义的深拷贝了(后面再说)。

运算符重载

在C++中引入了运算符的重载,这是一个具有特殊函数名的函数,同时也具有返回值,函数名以及参数列表,其返回值类型和参数列表都和普通函数差不多。
意义

  • 为了增强程序的可读性,让自定义类型可以像内置类型一样使用运算符。

使用方式

  • 返回值类型 operator操作符(参数列表)

注意

  • 不能通过连接其他符号来创建新的操作符,就像 operator@,这是错误的。
  • 重载操作符必须有一个类的类型或者枚举类型的操作数
  • 用于内置类型的操作符,他的含义不能改变,就像 内置的整形+,不能在函数内部使用-等其他操作,也就是不能把函数的本意改变。
  • 作为类成员的重载函数时,他的形参可以比操作数的数目少1个,是因为成员函数的操作符有一个默认的形参this指针,可以当做一个形参。
  • .*(成员指针访问运算符),::(域运算符),sizeof(长度运算符),?:(条件运算符),. (成员访问运算符)这五个运算符不可以被重载。
class Data
{
public:
	//类中
	bool operator==(Data& d2)
	{
		return _day == d2._day &&\
			_month == d2._month &&\
			_year == d2._year;
	}
//private:
	int _year;
	int _month;
	int _day;
};
//类外
bool operator==(const Data& d1, const Data& d2)
{
	return d1._day == d2._day &&\
		d1._month == d2._month &&\
		d1._year == d2._year;
}

int main()
{
	Data d1;
	Data d2;
	d1 == d2;
    operator==(d1, d2);
	return 0;
}

如果我们把运算符重载的函数写在类的外面时,只能访问公有的成员变量,私有的不可以访问,只能通过函数来访问。
而调用运算符重载函数,有两种方式,一种就是像我们调用普通函数一样去使用,第二种就是像我们使用变量那种形式进行简洁的使用。
在这里插入图片描述
通过查看,发现两种使用方式在底层调用子程序的时候,都是调用的同一个子程序。所以说,使用运算符重载还是比较简单,方便的。
特点:

  1. 参数类型
  2. 有返回值
  3. 还需要检测是否自己给自己赋值
  4. 返回*this
  5. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝(浅拷贝)。
int main()
{
	Data d1;
	Data d2 = d1;//这里调用的是拷贝构造,不是运算符重载
	return 0;
}

拷贝构造是一个已经存在的类给另一个不存在的类拷贝一个数据,运算符重载是给两个都存在的类之间的互相赋值。

const 修饰类的成员变量

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
在这里插入图片描述
在使用const 的时候,需要注意,const变量的权限只能缩小,不能放大。以日期类为例

赋值变量类型待赋值变量类型是否可行
const Date*(只读)const Date*(只读)可行
Date*(可读可写)Date*(可读可写)可行
const Date*(只读)Date* (可读可写)不可行
Date*(可读可写)const Date*(只读)可行

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

class Date
{
public :
    Date* operator&()
    {
        return this ;
    }
    const Date* operator&()const
    {
    return this ;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};

这两个默认成员函数可以返回这个类this指针的地址,一般用默认生成的就好了。如果想让用户获取到指定的内容,可以稍加修改自定义弄一个。

默认函数实现日期类

class Date
{
public:
	// 获取某年某月的天数
	int GetMonthDay(int year, int month)
	{
		static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		int day = days[month];//拿到当前月除了2.29的最大日期

		//如果出现2月29日
		if (month == 2
			&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
		{
			day += 1;
		}

		return day;
	}

	// 全缺省的构造函数
	Date(int year = 1900, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	// 拷贝构造函数
	// d2(d1)
	Date(const Date& d)
	{
		_day = d._day;
		_month = d._month;
		_year = d._year;
	}

	// 赋值运算符重载
	// d2 = d3 -> d2.operator=(&d2, d3)
	Date& operator=(const Date& d)
	{
		_day = d._day;
		_month = d._month;
		_year = d._year;
		return *this;
	}
	// 析构函数
	~Date()
	{
		_year = 0;
		_month = 0;
		_day = 0;
	}
	// 日期+=天数
	Date& operator+=(int day)
	{
		//如果日期是负数
		if (day < 0)
		{
			*this -= -day;
			return *this;
		}

		_day += day;
		int maxDay = GetMonthDay(_year, _month);//获取当前月的最大天数
		while (maxDay < _day)
		{
			_day -= maxDay;//减掉当前月的天数
			_month++;//月+1
			//如果月超了,年+1,月变成1月
			if (_month == 13)
			{
				_month = 1;
				_year++;
			}
			maxDay = GetMonthDay(_year, _month);
		}
		return *this;
	}

	// 日期-=天数
	Date& operator-=(int day)
	{
		//如果要减的日期是负数
		if (day < 0)
		{
			*this += -day;
			return *this;
		}
		while (_day <= day)
		{
			_month--;//当前月份-1
			//如果没得减,返回上一年
			if (_month == 0)
			{
				_month = 12;
				_year--;
			}
			int maxDay = GetMonthDay(_year, _month);
			_day += maxDay;
		}

		_day -= day;
		return *this;
	}
	// 日期+天数
	Date operator+(int day)
	{
		Date tmp(*this);
		//tmp.operator+=(day);
		tmp += day;
		return tmp;
	}
	// 日期-天数
	Date operator-(int day)
	{
		Date tmp(*this);
		//tmp.operator-=(day);
		tmp -= day;
		return tmp;
	}
	
	// 前置++ , ++i
	Date& operator++()
	{
		*this += 1;
		return *this;
	}
	// 后置++ ,为了区分前置++,加一个参数 i++
	Date operator++(int)//Date operator++(int i) //也可以这样
	{
		Date tmp(*this);//拷贝++之前的值
		*this += 1;
		return tmp;
	}
	// 后置-- i--
	Date operator--(int)
	{
		Date tmp(*this);//拷贝++之前的值
		*this -= 1;
		return tmp;
	}
	// 前置--  --i
	Date& operator--()
	{
		*this -= 1;
		return *this;
	}

	// >运算符重载
	bool operator>(const Date& d)
	{
		根据年直接比
		//if (_year > d._year)
		//	return true;
		年相等比月
		//if (_year == d._year && _month > d._month)
		//	return true;
		年月相等比天
		//if (_year == d._year && _month == d._month && _day > d._day)
		//	return true;

		if ((_year > d._year) || \
			(_year == d._year && _month > d._month) || \
			(_year == d._year && _month == d._month && _day > d._day))
			return true;
		return false;

	}
	// ==运算符重载
	bool operator==(const Date& d)
	{
		return _day == d._day &&\
			_month == d._month &&\
			_year == d._year;
	}
	// >=运算符重载
	inline bool operator >= (const Date& d)
	{
		//即不小于 小于为假
		return !(*this < d);
	}

	// <运算符重载
	bool operator < (const Date& d)
	{
		//如果等于,和大于,则为假
		if (*this == d || *this>d)
			return false;
		return true;
	}
	// <=运算符重载
	bool operator <= (const Date& d)
	{
		//即不大于,大于为假
		return  !(*this > d);
	}
	// !=运算符重载
	bool operator != (const Date& d)
	{
		//即等于为假
		return !(*this == d);
	}
	bool LeapYear(int year)
	{
		if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
			return true;

		return false;
	}
	// 日期-日期 返回天数
	int operator-(const Date& d)
	{
		Date small;
		Date big;
		if (*this > d)
		{
			big = *this;
			small = d;
		}
		else
		{
			big = d;
			small = *this;
		}
		//累加法
		int numDay = 0;
		while (small!=big)
		{
			small++;
			numDay++;
		}
		return numDay;
	}

	void Printf()
	{
		cout << _year << "-" << _month << "-" << _day;
	}
private:
	int _year;
	int _month;
	int _day;
};

在这里插入图片描述
代码链接github

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值