天天看點

c++類與對象之預設成員函數

c++類與對象(二)

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

c++類與對象之預設成員函數

一:構造函數

  • 構造函數是一個特殊的成員函數,名字與類名相同,建立類類型對象時由編譯器自動調用,保證每個資料成員都有一個合适的初始值,并且在對象的生命周期内隻調用一次。
  • 構造函數是特殊的成員函數,其特征如下:
  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修飾),在用已存在的類類型對象建立新對象時由編譯器自動調用。
  • 拷貝構造函數也是特殊的成員函數,其特征如下:
  • 拷貝構造函數是構造函數的一個重載形式。
  • 拷貝構造函數的參數隻有一個且必須使用引用傳參,使用傳值方式會引發無窮遞歸調用。如下圖
    c++類與對象之預設成員函數
  • 若未顯示定義,系統會預設生成預設的拷貝構造函數。 預設的拷貝構造函數會按照成員的聲明順序依次 拷貝類成員進行初始化。

    **請注意:**對象的指派都是對一個已經存在的對象指派,是以必須先定義被指派的對象,才能進行指派。而對象的複制則是從無到有建立一個新對象,并使它與一個已有的對象完全相同(包括對象的結構和成員的值)。

  • 普通構造函數和拷貝構造函數的差別:
  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個運算符是不能重載的。

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

繼續閱讀