1. 程式人生 > >c++:類與物件,封裝,訪問限定符,預設成員函式

c++:類與物件,封裝,訪問限定符,預設成員函式

到底什麼是類?什麼是物件?

         類是一個抽象的概念,它不存在於現實中的時間/空間裡,類只是為所有的物件定義了抽象的屬性與行為。
         類是一個靜態的概念,類本身不攜帶任何資料。當沒有為類建立任何物件時,類本身不存在於記憶體空間中。 物件是一個動態的概念,每一個物件都存在著有別於其它物件的屬於自己的獨特的屬性和行為,物件的屬性可以隨著它自己的行為而發生改變。

        就好比:你是人,人是一個概括,是一個類,是虛擬不存在的;而你是真實存在的,有血有肉,你就是“人”這個類中具體的物件。

封裝:

      所謂封裝就是將某些東西包裝盒隱藏起來,讓外界無法直接使用,只能通過某些特定的方式才能訪問。封裝的目的是增強安全性和簡化程式設計,使用者不必瞭解具體的實現細節,而只是通過外部介面以及特定的訪問許可權來使用類的成員。

        我們可以通過封裝使一部分成員充當類與外部的介面,而將其它的成員隱藏起來,這樣就限制了外部對成員的訪問,也使不同類之間的相互影響度降低。

訪問限定符:

類由兩部分構成,類宣告和類體:

class 類名

{

類體的內容

}                         

class是關鍵字,用來定義類,“class類名”是類的宣告部分,兩個大括號以及中間的內容是類體,類體中包括了資料部分和對這些資料進行操作的函式,而這就體現了把資料和操作封裝在一起。封裝在類物件中的成員都對外界隱蔽,不能呼叫。所以需要用到“訪問限定符” :

public:公有成員,既能被本類內的成員函式所引用,也可以被類的作用域內的其他函式引用,也就是任何位置都可以訪問

private:私有成員,不能被外部訪問,本類中訪問

protected:宣告的成員稱為受保護的成員,子類,只能在本類中允許訪問

類中實現成員方法預設時inline

在面向物件的程式設計中,在宣告類時,一般都是把所有的資料指定為私有的,使它們與外界隔離。把需要讓外界呼叫的成員函式設定為公用的。在類外雖然不能直接訪問私有成員,但是可以通過呼叫公用成員函式來引用甚至修改私有資料成員。

例如,這樣一個類,我們把資料和功能封裝起來,並展示一下訪問限定符的作用:

 class Animal
 {
   public: //這就是公共成員,外部的介面
     void SetAnimalName(string strname);
     void ShowAnimalName();
   private: //這是私有成員,外部是無法訪問到的
     string m_strName;
 };

物件的生成與銷燬:

    物件的生成:(1)物件開闢記憶體空間

                          (2)呼叫建構函式對記憶體空間進行初始化(賦資源)

    物件的銷燬:(1)釋放物件所佔的其他資源(呼叫解構函式)

                          (2)釋放物件所佔的記憶體空間

什麼是this指標:指向物件記憶體的地址
       在每個成員函式中都包含一個特殊的指標,這個指標的名字是固定的,就是this指標,它是指向本類物件的指標,它的值是當前被呼叫的成員函式所在的物件的起始地址。例如,當呼叫成員函式a.volume時,系統就把物件a的起始地址賦給this指標,於是在成員函式引用資料成員時,就按照this指標所指的地址找到物件a的資料成員。普通的成員方法是thiscall的呼叫約定,普通的成員方法依靠物件呼叫。

什麼是過載函式:

         c++中在同一範圍中宣告幾個功能類似的同名函式,但是這些同名函式的形參(引數的個數,型別或者順序)必須不停,也就是說同一個運算子完成不同的運算子。常用來實現功能類似而所處理不同的資料不同的問題,過載函式的返回值必須相同。

淺拷貝和深拷貝的區別:

        淺拷貝:沒有建立記憶體,只賦值地址。

        深拷貝:建立了記憶體,把值全部拷貝一份。

        區別在於: 複製時,指標是否有重新開闢記憶體空間。

c++中為了方便物件的使用,定義了六個預設函式(若程式不定義或不呼叫,編譯器自動定義或呼叫的函式)

    一、建構函式

           建構函式是一種特殊的成員函式,與其他成員函式不同,它不需要使用者來呼叫它,而是在建立物件時自動執行。建構函式是在宣告類的時候由類的設計者定義的,程式使用者只需在定義物件的同時指定資料成員的初值即可。建構函式的名字必須與類名相同,以便編譯系統能識別為建構函式來處理。它不具有任何型別,不返回任何值。是用來初始化物件的記憶體空間。

          特點:可以過載 、函式名就是類名、有this指標、按照定義順序構造

                    不能手動呼叫  :因為是呼叫建構函式來對物件的開闢的記憶體空間進行初始化,而物件的生成又靠建構函式生成,如果此時是手動呼叫,沒有物件生成的記憶體空間,如何初始化?舉個例子,人不能選擇自己的出生。

class Student
{
public:
	Student(char* name, bool sex, int age)//完全構造
	{
		std::cout << this <<" :Student::Student(char*,bool,int)" << std::endl;
		mname = new char[strlen(name) + 1]();
		strcpy(mname, name);
		msex = sex;
		mage = age;
	}
	Student(char* name, bool sex)//不完全構造
	{
		std::cout << this << " :Student::Student(char*, bool)" << std::endl;
		mname = new char[strlen(name) + 1]();
		strcpy(mname, name);
		msex = sex;
	}
	Student()//系統預設的建構函式(空函式)
	{
		std::cout << this << " :Student::Student()" << std::endl;
	}
}

二、解構函式:來銷燬物件開闢的記憶體空間

               作用:1.防止記憶體洩漏、2.釋放物件所佔的其他資源。

               特點:不可過載,只有一個 ,先構造再析構(在棧上,符合棧的原理)

                        1  .可以手動呼叫 : 本來預設是當清棧(是在棧上開闢)時,系統會呼叫這個函式來釋放物件的記憶體空間,但是手動來呼叫去釋放也是可以的。舉個例子,人可以活到壽命終了後去世,也可以自己手動killmyself。

                         2. 一個事例:  str1.~student();   這種情況下,會退化成普通的成員方法,不會影響到系統呼叫解構函式去銷燬資源,會導致記憶體塊被釋放兩次,系統崩潰。

	~Student()
	{
		std::cout << this << " :Student::~Student()" << std::endl;
		delete[] mname; 
		mname = NULL;
	}

    三、 拷貝建構函式 :拿一個已經存在的物件來生成一個相同型別的新物件。

         例如: student st2=st1; st1是一個已經存在的物件,st2是想要生成的一個新的物件。

         特點:函式名為類名,只有一個形參(未初始化的物件的引用),形參時類物件的引用,防止遞迴構造形參物件。

         呼叫拷貝建構函式的情況:

                    (1) 一個物件作為函式引數,以值傳遞的方式傳入函式體;

                    (2) 一個物件作為函式返回值,以值傳遞的方式從函式返回;

                    (3)一個物件用於給另外一個物件進行初始化(常稱為賦值初始化)

         如果涉及到指標的話,這個函式系統預設為淺拷貝,但是實際上應該用深拷貝才能成功,結合下圖理解:

 

	Student(const Student& rhs)  //拷貝建構函式
	{
		std::cout << this << " :Student::Student(const Student&)" << std::endl;
		mname = new char[strlen(rhs.mname) + 1]();
		strcpy(mname, rhs.mname);
		msex = rhs.msex;
		mage = rhs.mage;
	}

 

五、賦值運符的過載函式(operator=) 拿一個已存在的物件賦值給相同型別的已存在物件(預設  淺拷貝) 

         首先為什麼要對賦值運算子“=”進行過載。某些情況下,當我們編寫一個類的時候,,並不需要為該類過載“=”運算子,因為編譯系統為每個類提供了預設的賦值運算子“=”,使用這個預設的賦值運算子操作類物件時,該運算子會把這個類的所有資料成員都進行一次賦值操作。

   例如: good1=good2;

          通過使用系統預設的賦值運算子“=”,可以讓物件good2中的所有資料成員的值與物件good1相同。這種情況下,編譯系統提供的預設賦值運算子可以正常使用。但是當good1和good2進行析構的時候,由於重複釋放了一塊記憶體,導致程式崩潰報錯。在這種情況下,就需要我們過載賦值運算子“=”了。

所以這樣:

         good1.operator=(good2);//進行賦值

         當有連等的情況下,比如:

          good1=good2=good3;  則必須得有返回值。

     四個步驟: 1.判斷自賦值

                         2.釋放舊資源(因為預設是淺拷貝,如果不釋放舊資源的話,就會造成內寸洩漏,原理如下圖)

                         3.開闢新資源

                         4.賦值
     形參const作用:
                        1.防止實參被修改
                        2.接收隱式生成的臨時量

 

class Test
{
public:
	Test(int a, int b)
	{
		std::cout << this << "Test:: Test(int,int)" << std::endl;
		ma = a;
		mb = b;
	}
	Test(int a)
	{
		std::cout << this << "Test:: Test(int)" << std::endl;
		ma = a;
	}
	Test()
	{
		std::cout << this << "Test:: Test()" << std::endl;
	}
	Test(const Test& rhs)
	{
		std::cout << this << "Test:: Test(const Test&)" << std::endl;
		ma = rhs.ma;
		mb = rhs.mb;
	}
	Test& operator=(const Test& rhs)
	{
		std::cout << this << "Test::operator = (const Test&)" << std::endl;
		if (this != &rhs)
		{
			ma = rhs.ma;
			mb = rhs.mb;
		}
		return *this;
	}
	~Test()
	{
		std::cout << this << "Test::~Test()" << std::endl;
	}

	int getValue()
	{
		return ma;
	}
private:
	int ma;
	int mb;
};

其他知識點:
         臨時物件
                   1.隱式生成臨時物件  編譯器推演需要的物件型別

                         例如 : Test test7 = 10;   首先利用有整數形參的建構函式,生成一個臨時物件,來生成test7這個新物件。但是10並沒有定義型別,需要編譯器自我推演。

                   2.顯式生成臨時物件  程式指明要生成的物件型別

                          例如:  Test test8 = Test(10);   這個地方原理同上,但是10已經定義了型別為 Test,所以是顯式生成。

                   生存週期為:遇到表示式結束。

        優化
                  如果臨時物件生成的目的是為了生成新物件,則以生成臨時物件的方式來生成新物件,這樣就減少了一次生成臨時物件的過程,更加優化。

                  例如:Test test7 = 10;

                       此處要生成新物件test7,原本應該等號左邊和右邊都要生成一個臨時物件,然後臨時物件進行賦值,如果進行優化,則左邊不產生臨時物件,右邊產生臨時物件,減少一個建構函式構造臨時物件的過程。


         引用:(&和* 都不生成新的物件)

                    如果有引用會提升臨時物件的生存週期 ,使臨時物件變得和引用物件一樣的生存週期

                    例如:Test&rest10=Test(10,20);

                    原本應該是 呼叫三個函式,首先Test(10,20)尋到有定義為整形形參的建構函式構造兩個物件,然後呼叫賦值函式,最後再呼叫解構函式釋放開闢的記憶體。 但是用 &後,提貨test10的生存週期,就不用再遇到表示式;時就呼叫解構函式去釋放了,所以就只用呼叫兩個函式就可以了,沒有解構函式。

          臨時量
                   內建型別  ==》  常量
                   自定義型別  ==》  變數   記憶體
                   隱式生成    ==》  常量  
        
         類型別的返回值 
                      都是由臨時物件帶出來。

其他兩個函式不常用到,瞭解就好。