c++類與物件之預設成員函式
阿新 • • 發佈:2018-11-16
c++類與物件(二)
1.類的6個預設成員函式
一:建構函式
- 建構函式是一個特殊的成員函式,名字與類名相同,建立類型別物件時由編譯器自動呼叫,保證每個資料成員都有一個合適的初始值,並且在物件的生命週期內只調用一次。
- 建構函式是特殊的成員函式,其特徵如下:
- 函式名與類名相同。
- 無返回值。
- 物件構造(物件例項化)時編譯器自動呼叫對應的建構函式。
- 建構函式可以過載。
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(); }
- 建構函式可以在類中定義,也可以在類外定義。
- 如果類中沒有顯式定義建構函式,則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; }
- 無參的建構函式和全預設的建構函式都稱為預設建構函式,並且預設建構函式只能有一個。
// 預設建構函式 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物件應該呼叫哪一個建構函式。所以會報錯。
解構函式
- 與建構函式功能相反,在物件被銷燬時,由編譯器自動呼叫,完成類 的一些資源清理和汕尾工作。
- 解構函式是特殊的成員函式,其特徵如下:
- 解構函式名是在類名前加上字元 ~。
- 無引數無返回值。
- 一個類有且只有一個解構函式。若未顯式定義,系統會自動生成預設的解構函式。
- 物件生命週期結束時,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修飾),在用已存在的類型別物件建立新物件時由編譯器自動呼叫。
- 拷貝建構函式也是特殊的成員函式,其特徵如下:
- 拷貝建構函式是建構函式的一個過載形式。
- 拷貝建構函式的引數只有一個且必須使用引用傳參,使用傳值方式會引發無窮遞迴呼叫。如下圖
- 若未顯示定義,系統會預設生成預設的拷貝建構函式。 預設的拷貝建構函式會按照成員的宣告順序依次 拷貝類成員進行初始化。
**請注意:**物件的賦值都是對一個已經存在的物件賦值,因此必須先定義被賦值的物件,才能進行賦值。而物件的複製則是從無到有建立一個新物件,並使它與一個已有的物件完全相同(包括物件的結構和成員的值)。 - 普通建構函式和拷貝建構函式的區別:
- 形式:
類名(形參表列); //普通建構函式的宣告,如Box(int h, int w, int len);
類名(類名& 物件名); //拷貝建構函式的宣告,如Box(Box &b);
- 在建立物件時,實參型別不同。系統會根據實參的型別決定呼叫普通建構函式或拷貝建構函式。
Box box1(12,15,16); //實參為整數,呼叫普通建構函式
Box box2(box1); //實參是物件名,呼叫拷貝建構函式
- 在什麼情況下呼叫
- 普通建構函式在程式中建立物件時被呼叫。
- 拷貝建構函式在用一個已有物件複製一個新物件時被呼叫。
- 函式的返回值是類的物件。在函式呼叫完畢將返回值帶回函式呼叫處時,此時需要將函式中的物件複製一個臨時物件並傳給該函式的呼叫處。
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;
}
- 賦值運算子主要有四點:
- 引數型別
- 返回值
- 檢測是否自己給自己賦值
- 返回*this
注意:一個類如果沒有顯式定義賦值運算子過載,編譯器也會生成一個,完成值的拷貝工作。
哪些運算子不能過載?
- C++中不能過載的運算子:“?:”、“.”、“::”、“sizeof”和”.*”
- 原因如下:
- 在具體講解各個運算子不能過載之前,先來說明下【過載】:過載的本意是讓操作符可以有新的語義,而不是更改語法——否則會引起混亂。
【注】過載的部分規則:運算子函式的引數至少有一個必須是類的物件或者類的物件的引用。
- “?:”運算子,假如能夠過載,那麼問題來了,看下面的語句:
exp1?exp2:exp3
該運算子的本意是執行exp2和exp3中的一個,可是過載後,你能保證只執行了一個嗎?還是說兩個都能執行?亦或兩條都不能執行? “?:”運算子的跳轉性質就不復存在了,這就是“?:”運算子不能夠被過載的最主要原因。
- “.”運算子,假如能夠過載,那麼,問題來了,看下面的例子:
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++語言了。
-
“::”運算子,M::a,該運算子只是在編譯的時候域解析,而沒有運算的參與進來,由前面【注】重規則可知,如果過載之後,::運算子有了新的語義,那是不是會引起混淆呢?
-
“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)並不一定是該類佔用的位元組大小!
- ”.*”引用指向類成員的指標
以上的5個運算子是不能過載的。