咱們在 C 語言中,每一個變量都有其初始值。那麼問題來了,對象中成員變量的初始值是多少呢?從設計的角度來看,對象只是變量,所以:在棧上建立對象時,成員變量初始爲隨機值;在堆上建立對象時,成員變量初始爲隨機值;在靜態存儲區建立對象時,成員變量初識爲 0 值。數組
下來咱們以代碼爲例進行驗證,代碼以下安全
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } }; Test gt; int main() { printf("gt.i = %d\n", gt.getI()); printf("gt.j = %d\n", gt.getJ()); Test at; printf("at.i = %d\n", at.getI()); printf("at.j = %d\n", at.getJ()); Test* pt = new Test; printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); return 0; }
gt 對象是在靜態存儲區建立的,因此 gt.i 和 gt.j 應該都爲 0,;at 對象是在棧上建立的,因此 at.i 和 at.j 應該都爲隨機值;pt 對象是在堆上建立的,因此 pt->i 和 pt->j 應該也爲隨機值。咱們來編譯下,看看是否如咱們所分析的那樣呢?網絡
咱們看到前面兩個如咱們所分析的那樣,最後一個不同。咱們再來看看BCC編譯器呢ide
咱們看到BCC編譯器是如咱們所分析的那樣。因此咱們不能依賴於某種編譯器的特性。函數
在生活中的對象都是在初始化後上市的,初識狀態(出廠設置)是對象廣泛存在的一個狀態。那麼程序中如何對一個對象進行初始化呢?通常而言,對象都須要一個肯定的初識狀態。解決方案即是在類中提供一個 public 的 initialize 函數,對象建立後當即調用 initialize 函數進行初始化。下來咱們以代碼爲例進行分析,在上面代碼基礎上加上 initialize 函數
學習
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } void initialize() { i = 1; j = 2; } }; Test gt; int main() { gt.initialize(); printf("gt.i = %d\n", gt.getI()); printf("gt.j = %d\n", gt.getJ()); Test at; at.initialize(); printf("at.i = %d\n", at.getI()); printf("at.j = %d\n", at.getJ()); Test* pt = new Test; pt->initialize(); printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); return 0; }
咱們編譯,看看結果是否初始化好呢優化
咱們看到已經所有初始化爲按照咱們所想要的狀態了。可是這個就存在一個問題了,initialize 只是一個普通的函數,必須顯示調用才行。若是爲調用 initialize 函數的話,結果是不肯定的。若是咱們忘記在 at 對象中調用 initialize 函數,編譯結果以下spa
那麼這時問題來了,咱們該如何解決這個問題呢?在 C++ 中介意定義與類名相同的特殊成員函數,這種特殊的成員函數叫作構造函數。注意:構造函數沒有返回類型的聲明;構造函數在對象定義時自動被調用。那麼這時咱們就能夠將上面的程序改成這樣設計
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } Test() { printf("Test() Begin\n"); i = 1; j = 2; printf("Test() End\n"); } }; Test gt; int main() { printf("gt.i = %d\n", gt.getI()); printf("gt.j = %d\n", gt.getJ()); Test at; printf("at.i = %d\n", at.getI()); printf("at.j = %d\n", at.getJ()); Test* pt = new Test; printf("pt->i = %d\n", pt->getI()); printf("pt->j = %d\n", pt->getJ()); return 0; }
咱們編譯後結果以下對象
咱們這樣是否是就方便不少呢?那確定了。咱們能夠明顯看到定義了三個對象後,調用了三次構造函數。那麼咱們既然知道了有構造函數這一類的函數,它是否能像通常函數那樣進行帶參數呢?構造函數能夠根據須要定義參數;一個類中能夠存在多個重載的構造函數;構造函數的重載遵循 C++ 重載的規則。咱們以前說過定義和聲明不一樣,在對象這塊也一樣適用。對象定義和對象聲明時不一樣的:對象定義 -- 申請對象的空間並調用構造函數;對象聲明 -- 告訴編譯器存在這樣一個對象。下來咱們以代碼爲例進行分析
#include <stdio.h> class Test { public: Test() { printf("Test()\n"); } Test(int v) { printf("Test(int v), v = %d\n", v); } }; int main() { Test t1; // 調用 Test() Test t2(1); // 調用 Test(int v) Test t3 = 2; // 調用 Test(int v) int i(10); printf("i = %d\n", i); return 0; }
咱們看到第 18 行的 t1 對象的構造函數確定調用了 Test(),第 19 和 20 行則是調用了 Test(int v);在 C 語言中還有 int i(10) 這種寫法,咱們看看編譯是否會經過?
咱們看到編譯經過,而且如咱們所分析的那樣。那麼構造函數的調用是否有什麼規則呢?在通常狀況下,構造函數在對象定義時被自動調用,一些特殊狀況下,須要手工調用構造函數。咱們如何利用構造函數來建立一個數組呢?
#include <stdio.h> class Test { private: int m_value; public: Test() { printf("Test()\n"); m_value = 0; } Test(int v) { printf("Test(int v), v = %d\n", v); m_value = v; } int getValue() { return m_value; } }; int main() { Test ta[3] = {Test(), Test(1), Test(2)}; for(int i=0; i<3; i++) { printf("ta[%d].getValue() = %d\n", i, ta[i].getValue()); } Test t = Test(10); printf("t.getValue() = %d\n", t.getValue()); return 0; }
咱們首先來分析下,數組第一個成員調用的構造函數應該是 Test(),後面兩個成員調用的是 Test(int v) 函數,並打印出相應的值。最後定義的對象 t,它會打印出構造函數和獲得的值都爲 10,咱們來看看編譯結果
下來咱們來開發一個數組類解決原生數組的安全性問題:提供函數獲取數組長度;提供函數獲取數組元素;提供函數設置數組元素。咱們來看看它是怎麼實現的
IntArray.h 源碼
#ifndef _INTARRAY_H_ #define _INTARRAY_H_ class IntArray { private: int m_length; int* m_pointer; public: IntArray(int len); int length(); bool get(int index, int& value); bool set(int index, int value); void free(); }; #endif
IntArray.cpp 源碼
#include "IntArray.h" IntArray::IntArray(int len) { m_pointer = new int[len]; for(int i=0; i<len; i++) { m_pointer[i] = 0; } m_length = len; } int IntArray::length() { return m_length; } bool IntArray::get(int index, int& value) { bool ret = (0 <= index) && (index <= length()); if( ret ) { value = m_pointer[index]; } return ret; } bool IntArray::set(int index, int value) { bool ret = (0 <= index) && (index <= length()); if( ret ) { m_pointer[index] = value; } return ret; } void IntArray::free() { delete[] m_pointer; }
test.cpp 源碼
#include <stdio.h> #include "IntArray.h" int main() { IntArray a(5); for(int i=0; i<a.length(); i++) { a.set(i, i+1); } for(int i=0; i<a.length(); i++) { int value = 0; if( a.get(i, value) ) { printf("a[%d] = %d\n", i, value); } } a.free(); return 0; }
咱們編譯後獲得以下結果
下來咱們來看看特殊的構造函數:無參構造函數和拷貝構造函數。無參構造函數顧名思義就是沒有參數的構造函數,而拷貝構造函數則是參數爲 const class_name& 的構造函數。那麼這兩類構造函數有什麼區別呢?無參構造函函數是當類中沒有定義構造函數時,編譯器默認提供一個無參構造函數,而且其函數體爲空;拷貝構造函數是當類中沒有定義拷貝構造函數時,編譯器默認提供一個拷貝構造函數,簡單的進行成員變量的值複製。下來咱們以代碼爲例進行分析
#include <stdio.h> class Test { private: int i; int j; public: int getI() { return i; } int getJ() { return j; } /* Test() { printf("Test()\n"); } Test(const Test& t) { printf("Test(const Test& t)\n"); i = t.i; j = t.j; } */ }; int main() { Test t1; Test t2 = t1; printf("t1.i = %d, t1.j = %d\n", t1.getI(), t1.getJ()); printf("t2.i = %d, t2.j = %d\n", t2.getI(), t2.getJ()); return 0; }
咱們先將本身提供的無參構造函數和拷貝構造函數註釋掉,編譯下,看編譯器是否提供默認的構造函數,是否能夠經過
咱們看到編譯是經過的,也就是說,編譯器經過了默認的構造函數。咱們再來本身提供呢,看看是否會發生衝突
咱們看到打印出了本身定義的語句,證實它是調用了咱們本身寫的構造函數。那麼這個拷貝構造函數的意義在哪呢?一是兼容 C 語言的初始化方式,二是初始化行爲可以符合預期的邏輯。那麼這塊就牽扯到是淺拷貝仍是深拷貝。淺拷貝是拷貝後對象的物理狀態相同,深拷貝是拷貝後對象的邏輯狀態相同。注意:編譯器提供的拷貝構造函數只進行淺拷貝!
下來咱們以實例代碼看看對象的初始化是怎樣進行的
#include <stdio.h> class Test { private: int i; int j; int* p; public: int getI() { return i; } int getJ() { return j; } int* getP() { return p; } Test(int v) { i = 1; j = 2; p = new int; *p = v; } Test(const Test& t) { i = t.i; j = t.j; p = new int; *p = *t.p; } void free() { delete p; } }; int main() { Test t1(3); Test t2(t1); printf("t1.i = %d, t1.j = %d, *t1.p = %d\n", t1.getI(), t1.getJ(), *t1.getP()); printf("t2.i = %d, t2.j = %d, *t2.p = %d\n", t2.getI(), t2.getJ(), *t2.getP()); t1.free(); t2.free(); return 0; }
咱們看看 t1 應該進行的是淺拷貝,t2 應該進行的是深拷貝。咱們看看編譯結果
咱們若是隻有淺拷貝,沒有深拷貝的話,看看結果會是怎樣的,將第 34 - 41 行的代碼註釋掉,將第 54 和 55 行的打印 *p 的值改成打印 p 的地址。
咱們看到它運行的時候報段錯誤了,t1.p 和 t2.p 指向了同一個地址。咱們看看它是怎樣進行的
咱們看到將同一個地址釋放兩次確定是會出問題的,這時咱們就須要進行深拷貝了。那麼咱們就要考慮到底何時須要進行深拷貝?當對象中有成員指代了系統中的資源時,如:成員指向了動態內存空間,成員打開了外存中的文件,成員使用了系統中的網絡端口...
咱們在實現拷貝構造函數這塊有個通常性的原則,自定義拷貝構造函數時,必需要實現深拷貝。那麼咱們再來優化下以前的數組類
IntArray.h 源碼
#ifndef _INTARRAY_H_ #define _INTARRAY_H_ class IntArray { private: int m_length; int* m_pointer; public: IntArray(int len); IntArray(const IntArray& obj); int length(); bool get(int index, int& value); bool set(int index, int value); void free(); }; #endif
IntArray.cpp 源碼
#include "IntArray.h" IntArray::IntArray(int len) { m_pointer = new int[len]; for(int i=0; i<len; i++) { m_pointer[i] = 0; } m_length = len; } IntArray::IntArray(const IntArray& obj) { m_length = obj.m_length; m_pointer = new int[obj.m_length]; for(int i=0; i<obj.m_length; i++) { m_pointer[i] = obj.m_pointer[i]; } } int IntArray::length() { return m_length; } bool IntArray::get(int index, int& value) { bool ret = (0 <= index) && (index <= length()); if( ret ) { value = m_pointer[index]; } return ret; } bool IntArray::set(int index, int value) { bool ret = (0 <= index) && (index <= length()); if( ret ) { m_pointer[index] = value; } return ret; } void IntArray::free() { delete[] m_pointer; }
test.cpp 源碼
#include <stdio.h> #include "IntArray.h" int main() { IntArray a(5); for(int i=0; i<5; i++) { a.set(i, i+1); } for(int i=0; i<a.length(); i++) { int value = 0; if( a.get(i, value) ) { printf("a[%d] = %d\n", i, value); } } printf("\n"); IntArray b = a; for(int i=0; i<b.length(); i++) { int value = 0; if( b.get(i, value) ) { printf("b[%d] = %d\n", i, value); } } a.free(); b.free(); return 0; }
咱們看看編譯結果是否如咱們代碼所寫的那樣,建立數組並初始化。用數組 a 初始化數組 b。
經過對對象的構造的學習,總結以下:一、每一個對象在使用以前都應該初始化;二、類的構造函數用於對象的初始化,構造函數與類同名而且沒有返回值;三、構造函數在對象定義時被自動調用,構造函數能夠根據須要定義參數;四、構造函數之間能夠存在重載關係,而且構造函數遵循 C++ 中重載函數的規則;五、對象定義時會觸發構造函數的調用,在一些狀況下能夠手動調用構造函數;六、C++ 編譯器會默認提供構造函數;七、無參構造函數用於定義對象的默認初識狀態,拷貝構造函數在建立對象時拷貝對象的狀態;八、對象的拷貝有淺拷貝和深拷貝兩種方式:淺拷貝使得對象的物理狀態相同,而深拷貝則使得對象的邏輯狀態相同。
歡迎你們一塊兒來學習 C++ 語言,能夠加我QQ:243343083。