什麼是類?類是擁有相同屬性和行爲的集合
類中有六個默認的成員函數分別是:
- 構造函數
- 析構函數
- 拷貝構造函數
- 賦值操作符重載函數
- 取地址操作符重載函數
- const修飾的取地址操作符重載函數
接下來對前四個函數進行具體的分析
我們先定義一個空類:
class A
{
};
在經過編譯器處理之後它就不在爲空,編譯器會自動加入一些默認的成員函數,即使在這些函數中什麼也不做。編譯器處理之後的類相當於:
class A
{
public:
A(); //構造函數
A(const A& a); //拷貝構造函數
~A(); //析構函數
A& operator =(const A& a); //賦值運算符重載
A* operator &(); //取址運算符重載
const A* operator &() const; //取址運算符重載
};
注意:這些函數在我們沒有顯式給出時編譯器會爲我們自動合成。
一、構造函數
1.什麼是構造函數?
構造函數是一個特殊的成員函數,名字與類名相同,創建類類型對象時,由編譯器自動調用,在對象的生命週期內只且只調用一次,以保證每個數據成員都有一個合適的初值。作用:初始化對象所佔的內存空間。
stdent()
{
}
class Time
{
public:
//構造函數
Time(int hour = 0, int minute = 0, int second = 0)
:_hour(hour),_minute(minute),_second(second)//初始化列表
{
doSomeThing...
}
private:
int _hour;
int _minute;
int _second;
};
2.初始化列表
用於對對象成員進行初始化,格式爲在函數名和函數體之間,以一個冒號開始,後面跟着以逗號隔開的數據成員列表,在每個成員後接一個圓括號,括號中爲初始化的內容。
3.數據成員初始化順序
即使初始化列表中的成員順序與定義順序不同,初始化順序實際也與數據成員定義的順序一致。即先給hour賦值,接着minute,最後second。
4.總結:
- 可以重載,實參決定了調用那個構造函數。(對不同對象賦不同資源)
- 新對象被創建,由編譯器自動調用,且在對象的生命期內僅調用一次。(不可以手動調動)
- 如果沒有顯式定義時,編譯器會提供一個默認的構造函數。
- 不依賴對象調用(此時對象還沒有生成)
- 無返回值
類中包含以下成員時必須要在初始化列表中初始化:
(1)引用數據成員:因爲引用必須在定義時初始化,且不可重新賦值。
(2)const數據成員:因爲它必須初始化,不能賦值。
(3)類類型成員(該類沒有缺省的構造函數,有構造函數):因爲使用初始化列表可以不必調用默認構造函數來初始化,而是直接調用拷貝構造函數初始化。
二、析構函數
析構函數(destructor) 與構造函數相反,當對象脫離其作用域時(例如對象所在的函數已調用完畢),系統自動執行析構函數。
作用:釋放對象所佔的資源
~student()
{
delete [] mname;
mname = NULL;
}
我們在創建對象時,給對象申請了空間,申請的空間必須手動去釋放,所以我們在析構函數中去釋放空間。
析構函數在對象生命週期結束前由系統自動調用。
總結:
- 不能重載
- 可以手動調用,此時析構函數的調用退化普通函數的調用
- 依賴對象調用
三、拷貝構造函數
1.作用:通過已經存在的對象來創建並初始化新對象。
函數名與類名相同,無返回值,有一個形參(常用const修飾),該參數是本類類型的引用。是構造函數的重載,
class Time
{
public:
//構造函數
Time(int hour = 0, int minute = 0, int second = 0)
:_hour(hour)
,_minute(minute)
,_second(second)
{
doSomeThing...
}
//拷貝構造函數
Time(const Time& t)
:_hour(t._hour)
,_minute(t._minute)
,_second(t._second)
{
doSomeThing...
}
private:
int _hour;
int _minute;
int _second;
};
1)使用已經存在的對象創建新的對象
Time t1(12,01,59);
Time t2(t1);12
2)傳值方式作爲函數的參數
void FunTest1(const Time t)
{}12
3)傳值方式作爲函數返回值
Time FunTest2()
{
Time t;
return t;
}
系統默認提供的爲淺拷貝,類成員變量有指針,實現深拷貝。
像上面這種做法,只是簡單的將s賦給 _pStr,即讓 _pStr也指向字符串s,這樣造成的後果是多個對象指向同一空間,析構(關於析構的概念在下面介紹)出錯,這種拷貝方式叫做淺拷貝。
class String
{
public:
String()
{
mptr = new char[1]();
}
String(char* ptr)
{
mptr = new char[strlen(ptr) + 1]();
strcpy_s(mptr, strlen(ptr) + 1, ptr);
}
String(const String& rhs)
{
mptr = new char[strlen(rhs.mptr) + 1]();
strcpy_s(mptr, strlen(rhs.mptr) + 1, rhs.mptr);
}
String& operator=(const String& rhs)
{
if (this != &rhs) //自賦值判斷
{
delete[] mptr;//釋放舊資源
mptr = new char[strlen(rhs.mptr) + 1]();//開闢新資源
strcpy_s(mptr, strlen(rhs.mptr) + 1, rhs.mptr);//賦值
}
return *this;
}
~String()
{
delete[] mptr;
mptr = NULL;
}
private:
char* mptr;
};
int main()
{
String str1("hello");
String str2(str1);
str1 = str2;
return 0;
}
如上實現了深拷貝.像這樣,給新創建的對象開闢一塊獨立的空間,再將舊對象的內容拷貝過來,這樣就不會發生如上的錯誤了,這種拷貝方式叫作深拷貝。圖解如下。
總結:
- 系統默認合成的爲淺拷貝,在大多數情況下,我們應該自己寫出拷貝構造函數,
- 它的參數必須使用同類型對象的引用傳遞。因爲對象以值傳遞的方式進入函數體就會調用拷貝構造函數,這樣就會形成無限遞歸。最終導致棧溢出.
四、賦值操作符(=)重載函數
1.作用:用已存在的對象給相同類型的已存在對象賦值
String& operator=(const String& rhs)
注意:此處的&引用可去除,不過或多生成一個臨時對象,不建議。
普通類型之間的賦值通過簡單的=完成
int a = 10;
int b = 20;
a = b;
對於類類型的對象我們需要對‘=’重載,以完成類類型對象之間的賦值。
默認的賦值運算符的重載函數爲淺拷貝。
2.深拷貝的實現過程(參考上面的實現過程)
- 自賦值判斷(防止越界訪問)
- 釋放舊的資源
- 生成新的資源
- 賦值
5、取址(&)運算符重載
String* operator&()
{
return this;
}
取址操作符重載函數返回值爲該類型的指針,無參數。
6. const修飾的取址運算符重載
const String* operator&() const
{
return this;
}