C++类和对象(中)--6个默认构造函数

目录

一.六个默认构造函数

 二.拷贝构造函数

 2.1 拷贝构造函数的概念

2.2 构造函数的特性

 三.析构函数

3.1 析构函数的概念

3.2 析构函数的特性

 四.拷贝构造函数

4.1 拷贝构造函数的概念

4.2 拷贝构造函数的特性

 五.赋值运算符重载

5.1 运算符重载的语法

5.2 赋值运算符重载

六.日期类的实现

七. const成员

7.1 const修饰类的成员函数

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


一.六个默认构造函数

        如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是的,任何一个类在我们不写的情 况下,都会自动生成下面6 个默认成员函数。
        构造函数、析构函数、拷贝构造和赋值重载是其中的重点,两个取地址重载很少会自己实现,默认提供的已经够用。

 二.拷贝构造函数

 2.1 拷贝构造函数的概念

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

2.2 构造函数的特性

构造函数 是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象
构造函数的特性如下:
        1.函数名与类名相同。
        2.无返回值。
        3.对象实例化时,编译器自动调用
        4.构造函数可以重载
        5.如果没有显式的构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户定义了构造函数,编译器将不在自动生成构造函数。
        6.无参的构造函数和全缺省的构造函数都称为默认的构造函数,并且默认构造函数至多只能有一个。
        7.编译器自己生成的默认构造函数,对内置类型成员并没有什么有用的处理,依旧是随机值,但是对自定义类型成员会调用它的构造函数来初始化。

 三.析构函数

3.1 析构函数的概念

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

3.2 析构函数的特性

析构函数的特性如下:
        1.析构函数的函数名是类名前加字符~。
        2. 无参数,无返回值。
        3.一个类只有一个析构函数,如果用户没有显式定义析构函数,系统将生成默认的析构函数。
        4.对象声明周期结束时,C++编译器自动调用析构函数。
        5.编译器生成的析构函数无法对动态开辟的空间进行释放,因此,当一个类中有动态开辟的空间时,必须用户写出显式的析构函数。
        6.编译器默认生成的析构函数,对自定义类型成员调用它的析构函数。

 四.拷贝构造函数

4.1 拷贝构造函数的概念

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

4.2 拷贝构造函数的特性

拷贝构造函数的特性如下:

        1. 拷贝构造函数 是构造函数的一个重载形式
        2.无返回值,函数名和类名相同
        3. 拷贝构造函数的 参数只有一个 必须使用引用传参 ,使用 传值方式会引发无穷递归调用
        4. 若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
        5. 那么 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,我们还需要自己实现吗?当然像 日期类这样的类是没必要的。那么下面的类呢?
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);
    return 0;
}

        这里如果使用编译器默认生成的拷贝构造函数,s2和s1的_str将指向同一块内存,使用这俩对象时,会造成修改一个另一个也被修改的问题,最严重的问题是,调用它们的析构函数时,第二次调用实际为对已经释放过的空间再次进行释放,程序会崩溃。

拷贝构造函数的用法
1.Date d2(d1);  
2.Date d2 = d1; 

 五.赋值运算符重载

5.1 运算符重载的语法

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

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

5.2 赋值运算符重载

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 ;
};

注意:

1. 赋值运算符函数名为 operator=  ,参数为 const 类名& ,返回值为 类名& , 返回 *this。

2. 要检测是否是自己给自己赋值。

3. 如果没有显式的写赋值运算符重载,编译器会默认生成一个,默认生成的只完成字节序的值拷贝,即浅拷贝。

六.日期类的实现

#include<iostream>
using namespace std;
class Date
{
public:
	int GetMonthDay(int year, int month)
	{
		int MonthDays[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
		if (month == 2 && (year % 4 == 0 && year % 100 != 0 || year % 400 == 0))
			return 29;
		return MonthDays[month];
	}
	Date(int year = 0, int month = 1, int day = 1)
	{
		if (year >= 0
			&& month >= 1 && month <= 12
			&& day >= 1 && day < GetMonthDay(year, month))
		{
			_year = year;
			_month = month;
			_day = day;
		}
		else
			cout << "非法日期" << endl;
	}
	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;
	}
	//d1 == d2;
	//d1.operator==(d2);  ->  d1.operator==(&d1,d2);
	inline bool operator==(const Date& d)   //->bool operator==(Date* this, Date& d);
	{
		return _year == d._year
			&&_month == d._month
			&&_day == d._day;
	}
	inline bool operator>(const Date& d)
	{
		return _year > d._year
			|| (_year == d._year&&_month > d._month)
			|| (_year == d._year&&_month == d._month&&_day > d._day);
	}
	inline bool operator<(const Date& d)
	{
		return _year < d._year
			|| (_year == d._year&&_month < d._month)
			|| (_year == d._year&&_month == d._month&&_day < d._day);
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
	bool operator>=(const Date& d)
	{
		//return !(*this<d);
		return *this>d || *this == d;
	}
	bool operator<=(const Date& d)
	{
		//return !(*this>d);
		return *this < d || *this == d;
	}
	bool operator!=(const Date& d)
	{
		return !(*this == d);
	}
	//d1+10
	Date operator+(int day)
	{
		Date ret(*this);
		ret += day;
		return ret;
		//Date d(*this);   //拷贝一个*this,之后修改d ,*this不变
		//d._day += day;
		//while (d._day > GetMonthDay(d._year, d._month))
		//{
		//	d._day -= GetMonthDay(d._year, d._month);
		//	++d._month;
		//	if (d._month > 12)
		//	{
		//		d._month -= 12;
		//		++d._year;
		//	}
		//}
		/*return d;*/
	}
	//d1 += 10;
	Date& operator+=(int day)
	{
		if (day<0)
		{
			return *this -= -day;
		}
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			++_month;
			if (_month > 12)
			{
				_month -= 12;
				++_year;
			}
		}
		return *this;
	}
	// d1-10;
	Date operator-(int day)
	{
		Date ret(*this);
		ret -= day;
		return ret;
		/*Date d = *this;
		d._day -= day;
		while (d._day <= 0)
		{
			d._month--;
			if (d._month == 0)
			{
				d._month = 12;
				d._year--;
			}
			d._day += GetMonthDay(d._year, d._month);
		}
		return d;*/
	}
	//d1-=10;
	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;
	}
	//d1-d2;
	int operator-(const Date& d)
	{
		Date max = *this;
		Date min = d;
		int flag = 1;
		if (*this < d)
		{
			max = d;
			min = *this;
			flag = -1;
		}
		int n = 0;
		while (min != max)
		{
			++min;
			++n;
		}
		return n*flag;
	}
	//++d1;  -> d1.operator++(&d1);
	Date& operator++()
	{
		*this += 1;
		return *this;
	}
	//d1++;  -> d1.operator++(&d1,0);
	Date& operator++(int)  //为了构成函数重载
	{
		Date tmp(*this);
		*this += 1;
		return tmp;
	}
	//--d1;
	Date& operator--()
	{
		*this -= 1;
		return *this;
	}
	//d1--;
	Date& operator--(int)
	{
		Date tmp(*this);
		*this -= 1;
		return tmp;
	}

private:
	int _year;
	int _month;
	int _day;
};
void test1()
{
	Date d1(2021, 9, 15);
	Date d2(d1 - -1000);
	Date d3(d1);
	Date d4 = d3-10;
	int n = d4 - d3;
	cout << n << endl;
	d1.Print();
	d2.Print();
	d3.Print();
	d4.Print();
}

int main()
{
	test1();

	return 0;
}

七. const成员

7.1 const修饰类的成员函数

const 修饰的类成员函数称之为 const 成员函数 const 修饰类成员函数,实际修饰该成员函数 隐含的this指针 ,表明在该成员函数中 不能对类的任何成员进行修改。
注意:
① 两个同名的成员函数,一个非const,一个为const构成重载。
② 调用规则:
        当只有const版本时,非const对象和const对象调用的都是const版本。
        当只有非const版本时,非const对象可以调用,const对象无法调用。        
        当const版本和非const版本都存在时,const对象调用const版本,非const对象调用非const版本。

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

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{ 
public :
    Date* operator&()
    {
        return this ;
    }
 
    const Date* operator&()const
    {
        return this ;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};
        这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比 如想让别人获取到指定的内容!
  • 3
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值