1. 程式人生 > >C++:類與物件二

C++:類與物件二

類中的預設成員函式

類中預設成員函式一共有六個:(1)建構函式(2)拷貝建構函式(3)解構函式(4)賦值操作符過載(5)取地址操作符過載(6)const修飾的取地址操作符修改。

一.建構函式

建構函式就是隨著物件被建立而自動呼叫的公有成員函式,有且僅在物件被定義時,自動呼叫一次,主要用於對對物件的初始化,

它的特徵:

1. 函式名與類名相同。

2. 無返回值。

3. 物件構造(物件例項化)時系統自動呼叫對應的建構函式。

4. 建構函式可以過載。

5. 建構函式可以在類中定義,也可以在類外定義。

6. 如果類定義中沒有給出建構函式,則C++編譯器自動產生一個預設的建構函式,但只要我們定義了一個建構函式,系統就不會自動生成預設的建構函式。

7. 無參的建構函式和全預設值的建構函式都認為是預設建構函式,並且預設的建構函式只能有一個。

class Date
{
public:
	Date()//無參的建構函式
	{

	}
	Date(int year, int month, int day)//有參的建構函式
	{
		_year = year;
		_month = month;
		_day = day;
	}
         /*Date(int year = 1999, int month = 12, int day = 31)//出錯  無參的建構函式和帶全預設引數的建構函式只能有一個
	{
		_year = year;
		_month = month;
		_day = day;
	}*/
	void print()
	{
		cout << _year << " " << _month << " " << _day << " " << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;//呼叫無參的建構函式
	Date d2(2018, 10, 31);//呼叫有參的建構函式
	d2.print();//  2018 10 31

	system("pause");
	return 0;
}

二.拷貝建構函式

建立物件時,用同類型的物件來初始化,這種建構函式就是拷貝建構函式。

它的特性:

1.拷貝建構函式是建構函式的過載;

2.拷貝建構函式的引數只有一個,而且只能傳引用。(傳值的話會引發無窮的遞迴呼叫)

3.若沒有顯示定義,系統會預設生成一個拷貝建構函式。

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
	void print()
	{
		cout << _year << " " << _month << " " << _day << " " << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2018, 20 ,31);
	Date d2 = d1;
	Date d3(d1);
	system("pause");
	return 0;
}

理解拷貝建構函式傳值引發無窮遞迴:

如果是傳值,在第一次呼叫就要獲取到d的值,就引發了物件的拷貝,再傳值,又引發了物件的拷貝,依次就會在引發物件的拷貝和傳值當中無限遞迴

三.解構函式

當一個物件的生命週期結束的時候,C++編譯系統會自動呼叫一個成員函式,這個成員函式就是解構函式。

它的特性:

1.解構函式在類名前加~

2.無引數無返回值

3.一個類只能有一個解構函式

4.在物件的生命週期結束時,C++編譯系統會自動呼叫

class Date//日期類
{
public:
	~Date()//解構函式
	{

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

解構函式主要完成的任務:不是刪除物件,而是在物件刪除前做一些清理事項。

class Slist
{
public:
        Slist(int size)
        {
	        int* _arr = (int*)malloc(size*sizeof(int));
	}
        ~Slist()
	{
		if (_arr)
		{
			free(_arr);//釋放空間
			_arr = NULL;//指標制空
		}
	}
private:
	int* _arr;
};

四.運算子過載

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date & d)
	{
		_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(2018, 20 ,31);
	Date d2 = d1;
	Date d3(d1);
	if (d3 == d2)//錯誤error C2678: 二進位制“==”: 沒有找到接受“Date”型別的左運算元的運算子(或沒有可接受的轉換)	

	{
		printf("相同的日期\n");
	}
	system("pause");
	return 0;
}

我們判斷相同的日期的時候,用“==”符號程式碼可讀性非常好。但是這種符號C++編譯器是不會識別的,因此我們引入了運算子過載。

運算子過載的特性:

1. operator + 合法的運算子 構成函式名    (過載“>”:operator>)

2. 過載運算子以後,不能改變運算子的優先順序/結合性/運算元個數

class Date
{
public:
	Date(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	Date(const Date & d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
	bool operator==(const Date& d)//運算子的過載
	{
		return _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(2018, 20 ,31);
	Date d2 = d1;
	Date d3(d1);
	if (d3 == d2)
	{
		printf("相同的日期\n");
	}
	system("pause");
	return 0;
}

在C++中,大多數運算子都能被運算子過載實現,

5個C++不能過載的運算子:(1).*(2)sizeof(3)::(4)?.(5).

.             (成員訪問運算子)

.*            (成員指標訪問運算子)

::             (域運算子)

sizeof    (長度運算子)

?:            (條件運算子)

五.賦值運算子的過載

1.賦值運算子的過載是對一個已經存在的物件進行拷貝賦值。

2.當程式沒有顯式地提供一個以本類或本類的引用為引數的賦值運算子過載函式時,編譯器會自動生成這樣一個賦值運算子過載函式
 

class Date
{
public:
	Date()
	{

	}
	Date(int year, int month, int day)//建構函式
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << " " << _month << " " << _day << " " << endl;
	}
	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(2018, 20 ,31);
	Date d2, d3, d4;
	d2 = d1;
	d1.print();
	d2.print();
	system("pause");
	return 0;
}

程式碼實現如上,那麼問題來了?

為什麼賦值運算子的過載返回值是一個類型別的引用? void可以嗎?

我們操作一下的程式碼:

class Date
{
public:
	Date()
	{

	}
	Date(int year, int month, int day)//建構函式
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void print()
	{
		cout << _year << " " << _month << " " << _day << " " << endl;
	}
	void 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(2018, 20 ,31);
	Date d2, d3, d4;
	d2 = d1;
	d4 = d3 = d2;//連續賦值出錯
	d1.print();
	d2.print();
	d3.print();
	d4.print();
	system("pause");
	return 0;
}

 

 d2=d1沒有報錯,而d4 = d3 = d2連續賦值出錯。所以返回值是類型別的引用是為了連續賦值。

六.類的取地址操作符過載 及 const修飾的取地址操作符過載

這兩個成員函式一般不需要重新定義,編譯器會預設生成

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