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個運算符是不能重載的。