1. 程式人生 > >c++類與物件之預設成員函式

c++類與物件之預設成員函式

c++類與物件(二)

1.類的6個預設成員函式

在這裡插入圖片描述

一:建構函式

  • 建構函式是一個特殊的成員函式,名字與類名相同,建立類型別物件時由編譯器自動呼叫,保證每個資料成員都有一個合適的初始值,並且在物件的生命週期內只調用一次。
  • 建構函式是特殊的成員函式,其特徵如下:
  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 (2015, 1, 1); // 呼叫帶參的建構函式
	// 注意:如果通過無參建構函式建立物件時,物件後面不用跟括號,否則就成了函式宣告
	// 以下程式碼的函式:聲明瞭d3函式,該函式無參,返回一個日期型別的物件
	Date d3();
}
  1. 建構函式可以在類中定義,也可以在類外定義。
  2. 如果類中沒有顯式定義建構函式,則C++編譯器會自動生成一個無參的預設建構函式,一旦使用者顯式定義編譯器將不再生成。
class Date
{
public:
	void SetDate(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
// 如果使用者顯式定義了建構函式,編譯器將不再生成
	Date (int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

void Test()
{
// 加入沒有定義建構函式,物件也可以建立成功,因此此處呼叫的是編譯器生成的預設建構函式
	Date d;
}
  1. 無參的建構函式和全預設的建構函式都稱為預設建構函式,並且預設建構函式只能有一個。
// 預設建構函式
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;
}

以上測試函式能通過編譯嗎?
不能,當建立物件d1時,程式不知道d1物件應該呼叫哪一個建構函式。所以會報錯。

解構函式

  • 與建構函式功能相反,在物件被銷燬時,由編譯器自動呼叫,完成類 的一些資源清理和汕尾工作。
  • 解構函式是特殊的成員函式,其特徵如下:
  1. 解構函式名是在類名前加上字元 ~。
  2. 無引數無返回值。
  3. 一個類有且只有一個解構函式。若未顯式定義,系統會自動生成預設的解構函式。
  4. 物件生命週期結束時,C++編譯系統系統自動呼叫解構函式。
typedef int DataType;
class SeqList
{
public :
	SeqList (int capacity = 10)
	{
		_pData = (DataType*)malloc(capacity * sizeof(DataType));
		assert(_pData);
		_size = 0;
		_capacity = capacity;
	}
	~ SeqList()
	{
		if (_pData)
		{
			free(_pData );//釋放堆上的空間
			_pData = NULL; //將指標置為空
			_capacity = 0;
			_size = 0;
		}
	}
private :
	int* _pData ;
	size_t _size;
	size_t _capacity;
};
  • 注意:解構函式體內不是刪除物件,而是做一些物件刪除前的相關清理工作。

拷貝建構函式

  • 只有單個形參,該形參是對本類型別物件的引用(一般常用const修飾),在用已存在的類型別物件建立新物件時由編譯器自動呼叫。
  • 拷貝建構函式也是特殊的成員函式,其特徵如下:
  • 拷貝建構函式是建構函式的一個過載形式。
  • 拷貝建構函式的引數只有一個且必須使用引用傳參,使用傳值方式會引發無窮遞迴呼叫。如下圖
    在這裡插入圖片描述
  • 若未顯示定義,系統會預設生成預設的拷貝建構函式。 預設的拷貝建構函式會按照成員的宣告順序依次 拷貝類成員進行初始化。
    **請注意:**物件的賦值都是對一個已經存在的物件賦值,因此必須先定義被賦值的物件,才能進行賦值。而物件的複製則是從無到有建立一個新物件,並使它與一個已有的物件完全相同(包括物件的結構和成員的值)。
  • 普通建構函式和拷貝建構函式的區別:
  1. 形式:
類名(形參表列);  //普通建構函式的宣告,如Box(int h, int w, int len);
類名(類名& 物件名);  //拷貝建構函式的宣告,如Box(Box &b);
  1. 在建立物件時,實參型別不同。系統會根據實參的型別決定呼叫普通建構函式或拷貝建構函式。
Box box1(12,15,16);  //實參為整數,呼叫普通建構函式
Box box2(box1);   //實參是物件名,呼叫拷貝建構函式
  1. 在什麼情況下呼叫
  • 普通建構函式在程式中建立物件時被呼叫。
  • 拷貝建構函式在用一個已有物件複製一個新物件時被呼叫。
  • 函式的返回值是類的物件。在函式呼叫完畢將返回值帶回函式呼叫處時,此時需要將函式中的物件複製一個臨時物件並傳給該函式的呼叫處。
Box f()
{
	Box box1(12,15,18);
	return box1;
}

int main()
{
	Box box2;
	box2=f();
	return 0;
}

賦值操作符過載

  • 運算子過載
  • 運算子過載是具有特殊函式名的函式,也具有其返回值型別,函式名字以及引數列表,其返回值型別與引數列表與普通的函式類似,函式名字為:關鍵字operator後面接需要過載的運算子符號。返回值型別
    operator 需要過載的操作符(引數列表)
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;
	}
	Date operator+(int days)
	{
		Date temp(*this);
		temp._day += days;
		return temp;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Test ()
{
	Date d(2018, 9, 26);
	d = d + 10;
}

注意

  • 不能通過連線其他符號來建立新的操作符:比如[email protected]
  • 過載操作符必須有一個類型別或者列舉型別的運算元
  • 用於內建型別的操作符,其含義不能改變,例如:內建的整型+,不 能改變其含義
  • 作為類成員的過載函式,其形參看起來比運算元數目少1成員函式的
  • 操作符有一個預設的形參 this,限定為第一個形參。

賦值運算子的過載

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;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);
	Date d3(2018, 10, 27);
	d2 = d3;
}

  • 賦值運算子主要有四點:
  1. 引數型別
  2. 返回值
  3. 檢測是否自己給自己賦值
  4. 返回*this

注意:一個類如果沒有顯式定義賦值運算子過載,編譯器也會生成一個,完成值的拷貝工作。

哪些運算子不能過載?

  • C++中不能過載的運算子:“?:”、“.”、“::”、“sizeof”和”.*”
  • 原因如下:
  • 在具體講解各個運算子不能過載之前,先來說明下【過載】:過載的本意是讓操作符可以有新的語義,而不是更改語法——否則會引起混亂。
    【注】過載的部分規則:運算子函式的引數至少有一個必須是類的物件或者類的物件的引用。
  1. ?:”運算子,假如能夠過載,那麼問題來了,看下面的語句:
 exp1?exp2:exp3

該運算子的本意是執行exp2和exp3中的一個,可是過載後,你能保證只執行了一個嗎?還是說兩個都能執行?亦或兩條都不能執行? “?:”運算子的跳轉性質就不復存在了,這就是“?:”運算子不能夠被過載的最主要原因。

  1. “.”運算子,假如能夠過載,那麼,問題來了,看下面的例子:
class Y 
{
public:
        void fun();
        // ...
};
class X 
{ // 假設可以過載"."運算子
    public:
        Y* p;
        Y& operator.() 
        { 
            return *p;
         }
        void fun();
        // ...
};
void g(X& x){
        x.fun(); //請告訴我,這裡的函式fun()到底是X的,還是Y的?
}  

“.”運算子的本意是引用物件成員的,然而被過載後就不能保證本意,從而帶來運算子意義的混淆,如果每個人都這麼過載,那更不容易學習C++語言了。

  1. “::”運算子,M::a,該運算子只是在編譯的時候域解析,而沒有運算的參與進來,由前面【注】重規則可知,如果過載之後,::運算子有了新的語義,那是不是會引起混淆呢?

  2. “sizeof”運算子,該運算子不能被過載的主要原因是內部許多指標都依賴它,舉例說明過載的後果:

​​A b[10];//A是類
A* p = &a[3];
A* q = &a[3];
p++;//執行後,p指向a[4],記住是指向a[4]!根據C++規定,該操作等同於p+sizeof(A),此時p應該比q大A類所佔位元組的大小,事實上,p並不一定會比q大這麼多,因為你把sizeof()運算子過載了啊!這時的sizeof(A)並不一定是該類佔用的位元組大小!
  1. ”.*”引用指向類成員的指標
    以上的5個運算子是不能過載的。

還有兩個預設成員函式我沒有實現,過兩天我會補上。