c++:類的六個默認成員函數

什麼是類?類是擁有相同屬性和行爲的集合

類中有六個默認的成員函數分別是:

  1. 構造函數
  2. 析構函數
  3. 拷貝構造函數
  4. 賦值操作符重載函數
  5. 取地址操作符重載函數
  6. 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. 新對象被創建,由編譯器自動調用,且在對象的生命期內僅調用一次。(不可以手動調動
  3. 如果沒有顯式定義時,編譯器會提供一個默認的構造函數。
  4. 不依賴對象調用(此時對象還沒有生成)
  5. 無返回值

類中包含以下成員時必須要在初始化列表中初始化:
(1)引用數據成員:因爲引用必須在定義時初始化,且不可重新賦值。
(2)const數據成員:因爲它必須初始化,不能賦值。
(3)類類型成員(該類沒有缺省的構造函數,有構造函數):因爲使用初始化列表可以不必調用默認構造函數來初始化,而是直接調用拷貝構造函數初始化。

二、析構函數

析構函數(destructor) 與構造函數相反,當對象脫離其作用域時(例如對象所在的函數已調用完畢),系統自動執行析構函數。

作用:釋放對象所佔的資源   

~student()
{
    delete [] mname;
    mname = NULL;
}

我們在創建對象時,給對象申請了空間,申請的空間必須手動去釋放,所以我們在析構函數中去釋放空間。
析構函數在對象生命週期結束前由系統自動調用。 

總結:

  1. 不能重載
  2. 可以手動調用,此時析構函數的調用退化普通函數的調用   
  3. 依賴對象調用

三、拷貝構造函數

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;
};

2.使用場景

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. 系統默認合成的爲淺拷貝,在大多數情況下,我們應該自己寫出拷貝構造函數
  2. 它的參數必須使用同類型對象的引用傳遞。因爲對象以值傳遞的方式進入函數體就會調用拷貝構造函數,這樣就會形成無限遞歸。最終導致棧溢出.

四、賦值操作符(=)重載函數

1.作用:用已存在的對象給相同類型的已存在對象賦值 

String& operator=(const String& rhs)

注意:此處的&引用可去除,不過或多生成一個臨時對象,不建議。

普通類型之間的賦值通過簡單的=完成

int a = 10;
int b = 20;
a = b;

對於類類型的對象我們需要對‘=’重載,以完成類類型對象之間的賦值。

默認的賦值運算符的重載函數爲淺拷貝。

2.深拷貝的實現過程(參考上面的實現過程)

  1. 自賦值判斷(防止越界訪問)
  2. 釋放舊的資源
  3. 生成新的資源
  4. 賦值

5、取址(&)運算符重載

String* operator&()
{
    return this;
}

取址操作符重載函數返回值爲該類型的指針,無參數。

6. const修飾的取址運算符重載

const String* operator&() const
{
    return this;
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章