c++ 类和对象(中)类的6个默认成员函数

问题:如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?
并不是的,任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。

class Date
{};

在这里插入图片描述
注意:虽然是空类,但是系统会为其分配1个字节来占位。

构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,完成对象的初始化,并且在对象的生命周期内只调用一次。
特征:
1. 函数名与类名相同
2. 不具有任何类型,无返回值。
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数也可以重载。

class Date
{

public :
// 1.无参构造函数
	Date ()
	{}
// 2.带参构造函数
	Date (int year, int month , int day )
	{
		_year = year ;
		_month = month ;
		_day = day ;
	}
private :
	int _year ;
	int _month ;
	int _day ;
};
void TestDate()
{
	Date d1; // 调用无参构造函数
	Date d2 (2020, 3, 31); // 调用带参的构造函数
	
	// 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
	// 比如以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
	Date d3();//错误做法
}

5. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。

class Date
{
public:
	/*
	// 如果用户显式定义了构造函数,编译器将不再生成默认的无参构造函数
	Date (int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	*/
private:
	int _year;
	int _month;
	int _day;
};
void Test()
{
	Date d1;
	// 没有显式定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数,但是都是随机值

}

6. 无参的构造函数全缺省的构造函数都称为默认构造函数并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

// 默认构造函数
class Date
{
public:
	Date()//无参的构造函数
	{
		_year = 1900 ;
		_month = 1 ;
		_day = 1;
	}
	Date (int year = 1900, int month = 1, int day = 1) //全缺省的构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
private :
	int _year ;
	int _month ;
	int _day ;
};
// 以下测试函数能通过编译吗?
void Test()
{
	Date d1;//错误,这样不能确定到底是调用上面两个中的哪一个构造函数
}

在这里插入图片描述
所以说,无参的构造函数和全缺省的构造函数不能同时存在,而且一般建议默认构造函数写成全缺省函数格式。

7.C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。所以说,编译器生成的默认构造函数并不是没有作用的!

class Time//时间类
{
public:
	Time()
	{
		_hour = 0;
		_minute = 0;
		_second = 0;
		cout << "Time()" << endl;//打印这句证明调用过
	}
private:
	int _hour;
	int _minute;
	int _second;
};

class Date//日期类
{
private:
	// 基本类型(内置类型)
	int _year;
	int _month;
	int _day;
	// 自定义类型
	Time _t;  //时间类对象
};

int main()
{
	Date d; //调用日期类的默认构造函数,且对自定义类型的时间类再去调用他自己的默认构造函数
	return 0;
}

析构函数

析构函数:析构函数是特殊的成员函数。与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
比如归还向系统申请的空间之类的,不然会造成内存泄漏等事故。

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

class SeqList  //以链表结构来举例
{
public :
	SeqList (int capacity = 10)//构造函数来完成数据的初始化工作
	{
		_pData = (int*)malloc(capacity * sizeof(int));//在堆上开辟的内存
		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;
};

5. 和编译器自动生成的默认构造函数一样,编译器自动生成的析构函数编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。

class String  //名字
{
public:
	String(const char* str = "anna")//构造函数来完成名字初始化为anna
	{
		_str = (char*)malloc(strlen(str) + 1); //加上'\0'的字节
		strcpy(_str, str);
	}
	~String()
	{
		cout << "~String()" << endl;
		free(_str); //释放资源
		_str=nullptr;
	}
private:
	char* _str;
};

class Person  //人 
{
	private:
	String _name;//自定义类型成员   程序结束后会自动调用它的析构函数
	int _age;//内置类型成员
};
int main()
{
	Person p;//定义对象
	return 0;
}

拷贝构造函数

有时需要用到多个完全相同的对象,像以前的每个对象都要进行相同的初始化,很不方便,而c++中提供了对象的复制机制即(拷贝构造函数和赋值运算符重载),来解决这样的问题。
构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
特征:
1.拷贝构造函数也是特殊的成员函数
2. 拷贝构造函数是构造函数的一个重载形式。
3.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用

class Date
{
public:
	Date(int year = 2020, int month = 3, int day = 31)//全缺省的构造函数
	{
		_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;
};
int main()
{
	Date d1;//构造函数
	Date d2(d1);//拷贝构造函数
	return 0;
}

在这里插入图片描述
4. 若未显示定义,系统生成默认的拷贝构造函数。而 默认的拷贝构造函数对象按内存存储的字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。所以一般不涉及动态分配的数据,可以用默认的拷贝构造函数。但是一旦涉及动态分配的数据,必须要自己定义拷贝构造函数(使用深拷贝)。

// 这里如果自己不定义拷贝构造函数,系统调用默认的则程序会崩溃掉
class String
{
public:
	String(const char* str = "jack")
	{
		_str = (char*)malloc(strlen(str) + 1);  //动态开辟申请空间
		strcpy(_str, str);
	}
~String()
	{
		cout << "~String()" << endl;
		free(_str);
		_str=nullptr;
	}
private:
	char* _str;
};
int main()
{
	String s1("hello");
	String s2(s1);//拷贝构造	
}

解释:但是由于成员数据涉及动态开辟,浅拷贝只是把值简单的复制一次再赋值,这样在上面的场景中,拷贝构造完之后必然有两个指针指向同一块内存,问题是,当其中一个对象的被析构后,资源被释放掉了,但是还有一个指针还指向这那块已经被归还给os的内存,无缘无故就变成了个野指针了。故规定成员数据涉及动态开辟的,要拷贝构造,不能使用默认的浅拷贝!

赋值运算符重载

先谈运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数格式:返回值类型 operator操作符(参数列表)

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

赋值运算符重载
和拷贝构造函数一样,也是实现对象的复制机制。只不过是对赋值运算符的重载来完成的。
特征:
1.参数类型为引用,也是避免无限调用构造函数
2. 返回*this,所以返回值为引用
4. 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个默认的,完成对象按字节序的值(浅)拷贝。

class Date
{
public :
	Date(int year = 1900, int month = 1, int day = 1)//全缺省的构造函数
	{
		_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;
		}
	}
private:
	int _year ;
	int _month ;
	int _day ;
};
int main()
{
	Date d1(2020331);
	Date d2;//先初始化
	d2 = d1;// 再 d1调用operator=完成拷贝
	
	//注意一点
	Date d3(2020,3,31);
	Date d4=d3;//这种写法,调用的是拷贝构造函数,而不是赋值运算符重载
	//解释:这是用d3来初始化d4,而初始化的操作是拷贝构造函数来完成的
	return 0;
}

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

先谈const修饰类的成员函数

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

class Date
{
public :
	/*void Display ()
	{
		cout<<"Display ()" <<endl;

	}*/
	void Display () const
	{
		cout<<"Display () const" <<endl;
	}
private :
	int _year ; // 年
	int _month ; // 月
	int _day ; // 日
};
int main()
{
	Date d1 ;
	d1.Display ();
	/*const Date d2;
	d2.Display ();*/
	return 0;
}

利用上述代码来验证下面的问题:

  1. const对象可以调用非const成员函数吗?
    不可以,本来const对象里的成员不允许被修改的,要是调用非const函数时里面被修改了,两者冲突,不被允许。
  2. 非const对象可以调用const成员函数吗?
    可以,虽然非const对象里的成员数据允许被修改,调用const成员函数使其不能被修改,权限虽然被缩小了,但不冲突。
  3. const成员函数内可以调用其它的非const成员函数吗?
    同上
  4. 非const成员函数内可以调用其它的const成员函数吗?
    同上

总结一句话:把他们想象成权限问题,非const表示可读可写,const表示只读,权限只能缩小,不能被放大。(可以包容,但不能纵容,哈哈!)。

取地址及const取地址操作符重载
这两个也是默认的成员函数,系统一般会自动生成默认的。

class Date
{
public :
	Date* operator&() 
	{
		return this ;
	}
	const Date* operator&()const     //确切一点说后面的const是修饰成员函数的*this而不是this,保护调用对象不能被修改
	{
		return this ;
	}
private :
	int _year ; 
	int _month ; 
	int _day ; 
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容!

实现一个时间类(对本节知识的运用)

#include<iostream>
using namespace std;
class Date
{
public:
	int  GetYearMonthday(int year, int month)
	{
		static int monthday[13] = { 0,31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		if (year % 400 == 0 || ((year % 4 == 0) && (year % 100 == 0))&& month==2)
		{
			return 29;
		}
		return monthday[month];
	}

	Date(int year = 1900, int month = 1, int day = 1)  //全缺省默认构造函数
	{
		if (year > 0 && (month > 0 && month<13) && (day>0 && day < GetYearMonthday(year, month)))
		{
			_year = year;
			_month = month;
			_day = day;
		}
		else
			cout << "输入时间不合法!!请重新输入" << endl;
	}
	Date(const Date& d)   //拷贝构造函数
	{
		_year = d._year;
		_month = d._month;
		this->_day = d._day;
	}
	Date& operator=(const Date& d)//赋值运算符重载
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
		return *this;
	}
	/*Date operator+(int days)   
	{
		Date ret = *this;   
		ret._day += days;
		while (ret._day > GetYearMonthday(ret._year, ret._month))
		{
			ret._day -= GetYearMonthday(ret._year, ret._month);
			ret._month++;
			if (ret._month == 13)
			{
				ret._year++;
				ret._month = 1;
			}
		}
		return ret;
	}*/
	Date operator+(int days)   //  复用+=   单纯的算结果,自己的值不变  操作一个临时变量
	{
		Date ret = *this;  //因为是初始化 这个其实调用的是拷贝构造函数,而不是赋值运算符重载    后者的使用情况是   先初始化Date ret;  再赋值 ret = *this;
		ret += days;
		return ret;
	}

	Date& operator+=(int days)    //自己的值也改变了    
	{
		this->_day += days;
		while (this->_day > GetYearMonthday(this->_year, this->_month))
		{
			this->_day -= GetYearMonthday(this->_year, this->_month);
			this->_month++;
			if (this->_month == 13)
			{
				this->_year++;
				this->_month = 1;
			}
		}
		return *this;   
	}
	Date operator-(int days)     //自己值不变
	{
		Date ret = *this;
		ret -= days;
		return ret;
	}
	Date & operator-=(int days)
	{
		if (days < 0)
		{
			*this += (-days);   //减的是负数,相当于加法 复用+=
			return *this;
		}
		_day -= days;
		while (_day <= 0)
		{
			_month--;
			if (_month == 0)
			{
				_year--;
				_month = 12;
			}
			_day += GetYearMonthday(_year, _month);
		}
		return *this;
		
	}
	
	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;     //返回时,由于是临时变量 不能传引用,因为函数结束后,临时变量销毁,引用的本体没了,引用也不能用,  所以返回值 又是一次拷贝构造
	}

	inline bool operator>(const Date& d)const
	{
		return (_year > d._year) ||
			(_year == d._year && _month > d._month) ||
			(_year == d._year&& _month == d._month && _year > d._year);
	}
	inline bool operator==(const Date& d)const
	{
		return _year == d._year
			&& _month == d._month
			&& _day == d._day;
	}
	//为了提高代码复用性 ,下面所有关系运算符重载都可以 复用上面这两个   即 > 和==     并且都可以写成内联函数
	inline bool operator>=(const Date& d)const
	{
		return (*this > d) || (*this == d);
	}
	inline bool operator<(const Date& d)const
	{
		return !(*this >= d);
	}
	inline bool operator<=(const Date& d)const
	{
		return !(*this>d);
	}

	inline bool operator!=(const Date& d)const
	{
		return !(*this == d);
	}
	void print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};


发布了55 篇原创文章 · 获赞 152 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/qq_44785014/article/details/105219116