C++類與對象(中)

1.類的6個默認成員函數

類六個默認函數包括構造、拷貝構造、析構、賦值運算符重載、取地址操作符重載、const修飾的取地址操作符重載,默認生成

2.構造函數

構造函數是一個特殊的成員函數,名字與類名相同,創建類類型對象時由編譯器自動調用,保證每個數據成員
都有 一個合適的初始值,並且在對象的生命週期內只調用一次

特性如下

1. 函數名與類名相同。

2. 無返回值。(也沒有void返回值)

3. 對象實例化時編譯器自動調用對應的構造函數。

4. 構造函數可以重載

  如果類中沒有顯式定義構造函數,則C++編譯器會自動生成一個無參的默認構造函數,一旦用戶顯式定
義編譯器將不再生成。

構造出的對象元素爲隨機值

重點:無參的構造函數和全缺省的構造函數都稱爲默認構造函數,並且默認構造函數只能有一個。注意:無參構造函數、全缺省構造函數、我們沒寫編譯器默認生成的構造函數,都可以認爲是默認成員

class A
{
private:
int a;
public:

A(int b=0)
{
......
}

A()
{
......
}

}

上面的代碼會編譯錯誤,因爲程序無法判斷,當設置一個無參數傳遞的對象調用哪一個構造函數。

小知識點:C++把類型分成內置類型(基本類型)和自定義類型。內置類型就是語法已經定義好的類型:如int/char...,自定義類型就是我們使用class/struct/union自己定義的類型,編譯器生成默認的構造函數會對自定類型成員t調用的它的默認成員如下

#include <iostream>
using namespace std;
class A
{
	int _a;
	public:
	A(int a=0)
	{
	a = _a;
	cout<<"使用a的構造函數"<<endl;	
	}	
};
class B
{
	int _b;
	A a;
	public:
};
int main()
{
	B b;
}

小知識點:作爲成員變量命名最好使用_加名字用來提高代碼的可讀性,也有加m_(member的首字母)的寫法

3.析構函數

與構造函數相對,析構函數:與構造函數功能相反,析構函數不是完成對象的銷燬,局部對象銷燬工作是由編譯器完成的。而對象在銷燬時會自動調用析構函數,完成類的一些資源清理工作,如內存的釋放,指針的歸零等。

析構函數是特殊的成員函數。其特徵如下:

1. 析構函數名是在類名前加上字符 ~。

2. 無參數無返回值。

3. 一個類有且只有一個析構函數。若未顯式定義,系統會自動生成默認的析構函數。

4. 對象生命週期結束時,C++編譯系統系統自動調用析構函數。系統會自動生成默認的析構函數。

4. 拷貝構造函數

屬於構造函數的一種,參數爲類的類型的引用,作用用來拷貝一個對象就像是ctrl c ctrl v的功能一樣,例子如下

#include <iostream>
using namespace std;
class A
{
	int _a;
	public:
	A(int a=0)
	{
	a = _a;
	cout<<"使用a的構造函數"<<endl;	
	}	
	A(A& a)
	{
	_a = a._a;
	}	
};

int main()
{
	B b;
}

注意的是系統默認的拷貝函數使用的是淺拷貝,逐個字節進行拷貝,對於一般類型的成員不會出錯,但是對於指針成員,拷貝出的新的指針成員,會指向相同的地址,產生一系列的麻煩。需要人爲寫拷貝函數,進行深拷貝。

知識點:拷貝函數的形參必須是引用傳遞,否則會引起無窮遞歸問題,

A(A a1)
	{
	_a = a1._a;
	}//值傳遞
//需要給 形參賦值,調用拷貝函數
	A(A a1)//上一個的形參
	{
	_a1 = a1._a;
	}//值傳遞給 形參的形參 賦值,調用拷貝函數
A(A a1)
	{
	_a1 = a1._a;
	}//值傳遞給 形參的形參 的形參賦值,調用拷貝函數
A(A a1)
	{
	_a1 = a1._a;
	}//值傳遞給 形參的形參 的形參 的形參賦值,調用拷貝函數
A(A a1)
	{
	_a1 = a1._a;
	}//值傳遞給 形參的形參 的形參 的形參 的形參賦值,調用拷貝函數

 

5.賦值運算符重載

根據邏輯,有時候需要比較兩個對象的大小或其他屬性,但是C++不支持自定義類型的對象的比較大小等操作需要重載運算符,

函數名字爲:關鍵字operator後面接需要重載的運算符符號。

函數原型:返回值類型 operator操作符(參數列表)

但是重載需要注意以下幾點

1.重載無法創造新的運算符如@等

2.重載操作數至少有一個類類型或者聯合類型

3.無法重載內置類型(int,char等)的操作符

4.共有5個操作符無法重載:.* 、:: 、sizeof 、?: 、. (注意第一個是點*而不是*)

例子如下爲對日期的重載:爲初始版本十分臃腫,精簡的方法爲函數的複用:具體看

這裏:https://blog.csdn.net/cat_want_fly/article/details/86506354

#include <iostream>
using namespace std;
class Date
{
public:
	
		int GetMonthDay(int year, int month)
{
int monthArray[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;
else
return monthArray[month];
}

	Date(int year = 2019, int month = 1, int day = 1)
{
	if (year > 0 
	&&month > 0 
	&& month < 13
	&&day > 0
	&&day <= GetMonthDay(year, month))
	{
	_year = year;
	_month = month;
	_day = day;
	}
	// 四個成員函數

else
	{
	cout << "日期非法" << endl;
	_year = 0;
	_month = 0;
	_day = 0 ;
	}
}

print()
{
	cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
Date(Date &date)
	{
		_year = date._year;
		_month= date._month;
		_day = date._day; 
	}

 


Date& operator+=(int day)
{   _day += day; 
	while(_day>GetMonthDay( _year, _month))
	{
		if(_month==12)
		{
			_month = 0;
			_year++;
		}
		if(_month!=0)
		{
		_day -= GetMonthDay(_year,  _month);
		_month++;
		}
		if(_month==0)
		{
			_day -= GetMonthDay(_year, 12);
		_month++;
		}
	}
return *this;
}
;

Date &operator-=(int day)
{
	_day -= day; 
	while(_day<1&&_year>0)
	{
	_month--;
	if(_month==0)
	{
		_month = 12;
		_year--; 
	}
	 _day+=GetMonthDay( _year, _month);
	 
	}
return *this;
};
// 前置
Date operator++()
{
	(*this)+=1;
}
;
Date operator--()
{
	(*this)-=1;
};

// 後置返回 改變數值之前的對象一個拷貝,
Date operator++(int)
{
	Date copy((*this));
	(*this)+=1;
	return copy;
}
;
Date operator--(int)
{
	Date copy((*this));
	(*this)-=1;
	return copy;
}
;

// d1-d2

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

~Date()
{
	//cout<<"調用析構函數"<<endl;
	_year = 0;
	_month = 0;
	_day = 0; 
} 

private:
	int _year;
	int _month;
	int _day;


};

重載完畢後要注意符號優先度如

cout<<d1==d2<<endl;//錯誤,d1先與<<運算,d2與<<運算,最後進行判斷相等操作
cout<< (d1==d2) <<endl;//正確

小知識點:

爲什麼前置++比後置++重載時少了一個int型的形參?

答:因爲前置和後置的運算符一樣,所以即使對其進行運算符重載,編譯器也無法區分兩者,所以這是對同名函數的重載,同名函數的重載需要不同的形參列表,所以int型的形參就是爲了區分兩個操作符函數,int 本身的值無意義。

6.取地址及const取地址操作符重載

這兩個默認函數一般不需要重載,編譯器會默認生成

如果重載,一般時爲了封裝,防止訪問到對象的地址,

      Date*	operator&()
	{
		return nullptr;
	}

如果設置一個Date類型的對象,並用&訪問它的地址會返回00000000.

7const成員

將const修飾的類成員函數稱之爲const成員函數,const修飾類成員函數,實際修飾該成員函數隱含的this
指針,表明在該成員函數中不能對類的任何成員進行修改。以上面的代碼爲例

相當於將隱含的this指針形參由Date *轉化爲了 const Date *

1.const對象可以調用非const成員函數嗎?

答:不可以,非const成員函數形參爲非const類型,傳參時編譯器試圖將const類型的指針轉化成非const類型,無法實現操作。
2. 非const對象可以調用const成員函數嗎?

答:可以,允許非const變量轉化爲const變量,this指針的類型可以轉化。
3. const成員函數內可以調用其它的非const成員函數嗎?

答:不可以,調用非const成員函數,就可能對const變量進行修改,這是不允許的,

c++規定 const成員函數內不能調用其它的非const成員函數
4. 非const成員函數內可以調用其它的const成員函數嗎?

答:可以,理由和第二題相同

(注意唯一的例外是析構函數,C++標準中說
A destructor can be invoked for a const, volatile or const volatile object.

析構函數可以爲const、volatile或const volatile對象調用析構函數。

因爲析構函數和資源的回收有關,如果無法訪問const函數就會造成內存泄漏)

const也可以用來重載函數

class A{
private:
int i;
public:
void f() 
{
cout<<"f()"<<endl;
}

void f() const
{
cout<<"f() const"<<endl;
}

}

const類型的對象調用const修飾的函數,非const類型的對象調用非const修飾函數,

(注意const寫在形參括號的右邊,如果寫在了函數名的左邊,編譯器會認爲函數返回類型爲const,如果是對void修飾,函數本身依然沒有返回值)

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章