您好,登錄后才能下訂單哦!
我們?cè)?C 語(yǔ)言中,每個(gè)變量都有其初始值。那么問(wèn)題來(lái)了,對(duì)象中成員變量的初始值是多少呢?從設(shè)計(jì)的角度來(lái)看,對(duì)象只是變量,因此:在棧上創(chuàng)建對(duì)象時(shí),成員變量初始為隨機(jī)值;在堆上創(chuàng)建對(duì)象時(shí),成員變量初始為隨機(jī)值;在靜態(tài)存儲(chǔ)區(qū)創(chuàng)建對(duì)象時(shí),成員變量初識(shí)為 0 值。
下來(lái)我們以代碼為例進(jìn)行驗(yàn)證,代碼如下
#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 對(duì)象是在靜態(tài)存儲(chǔ)區(qū)創(chuàng)建的,所以 gt.i 和 gt.j 應(yīng)該都為 0,;at 對(duì)象是在棧上創(chuàng)建的,所以 at.i 和 at.j 應(yīng)該都為隨機(jī)值;pt 對(duì)象是在堆上創(chuàng)建的,所以 pt->i 和 pt->j 應(yīng)該也為隨機(jī)值。我們來(lái)編譯下,看看是否如我們所分析的那樣呢?
我們看到前面兩個(gè)如我們所分析的那樣,最后一個(gè)不一樣。我們?cè)賮?lái)看看BCC編譯器呢
我們看到BCC編譯器是如我們所分析的那樣。所以我們不能依賴于某種編譯器的特性。
在生活中的對(duì)象都是在初始化后上市的,初識(shí)狀態(tài)(出廠設(shè)置)是對(duì)象普遍存在的一個(gè)狀態(tài)。那么程序中如何對(duì)一個(gè)對(duì)象進(jìn)行初始化呢?一般而言,對(duì)象都需要一個(gè)確定的初識(shí)狀態(tài)。解決方案便是在類中提供一個(gè) public 的 initialize 函數(shù),對(duì)象創(chuàng)建后立即調(diào)用 initialize 函數(shù)進(jìn)行初始化。下來(lái)我們以代碼為例進(jìn)行分析,在上面代碼基礎(chǔ)上加上 initialize 函數(shù)
#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; }
我們編譯,看看結(jié)果是否初始化好呢
我們看到已經(jīng)全部初始化為按照我們所想要的狀態(tài)了。但是這個(gè)就存在一個(gè)問(wèn)題了,initialize 只是一個(gè)普通的函數(shù),必須顯示調(diào)用才行。如果為調(diào)用 initialize 函數(shù)的話,結(jié)果是不確定的。如果我們忘記在 at 對(duì)象中調(diào)用 initialize 函數(shù),編譯結(jié)果如下
那么這時(shí)問(wèn)題來(lái)了,我們?cè)撊绾谓鉀Q這個(gè)問(wèn)題呢?在 C++ 中介意定義與類名相同的特殊成員函數(shù),這種特殊的成員函數(shù)叫做構(gòu)造函數(shù)。注意:構(gòu)造函數(shù)沒(méi)有返回類型的聲明;構(gòu)造函數(shù)在對(duì)象定義時(shí)自動(dòng)被調(diào)用。那么這時(shí)我們就可以將上面的程序改為這樣
#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; }
我們編譯后結(jié)果如下
我們這樣是不是就方便很多呢?那肯定了。我們可以明顯看到定義了三個(gè)對(duì)象后,調(diào)用了三次構(gòu)造函數(shù)。那么我們既然知道了有構(gòu)造函數(shù)這一類的函數(shù),它是否能像一般函數(shù)那樣進(jìn)行帶參數(shù)呢?構(gòu)造函數(shù)可以根據(jù)需要定義參數(shù);一個(gè)類中可以存在多個(gè)重載的構(gòu)造函數(shù);構(gòu)造函數(shù)的重載遵循 C++ 重載的規(guī)則。我們之前說(shuō)過(guò)定義和聲明不同,在對(duì)象這塊也同樣適用。對(duì)象定義和對(duì)象聲明時(shí)不同的:對(duì)象定義 -- 申請(qǐng)對(duì)象的空間并調(diào)用構(gòu)造函數(shù);對(duì)象聲明 -- 告訴編譯器存在這樣一個(gè)對(duì)象。下來(lái)我們以代碼為例進(jìn)行分析
#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; // 調(diào)用 Test() Test t2(1); // 調(diào)用 Test(int v) Test t3 = 2; // 調(diào)用 Test(int v) int i(10); printf("i = %d\n", i); return 0; }
我們看到第 18 行的 t1 對(duì)象的構(gòu)造函數(shù)肯定調(diào)用了 Test(),第 19 和 20 行則是調(diào)用了 Test(int v);在 C 語(yǔ)言中還有 int i(10) 這種寫法,我們看看編譯是否會(huì)通過(guò)?
我們看到編譯通過(guò),并且如我們所分析的那樣。那么構(gòu)造函數(shù)的調(diào)用是否有什么規(guī)則呢?在一般情況下,構(gòu)造函數(shù)在對(duì)象定義時(shí)被自動(dòng)調(diào)用,一些特殊情況下,需要手工調(diào)用構(gòu)造函數(shù)。我們?nèi)绾卫脴?gòu)造函數(shù)來(lái)創(chuàng)建一個(gè)數(shù)組呢?
#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; }
我們首先來(lái)分析下,數(shù)組第一個(gè)成員調(diào)用的構(gòu)造函數(shù)應(yīng)該是 Test(),后面兩個(gè)成員調(diào)用的是 Test(int v) 函數(shù),并打印出相應(yīng)的值。最后定義的對(duì)象 t,它會(huì)打印出構(gòu)造函數(shù)和得到的值都為 10,我們來(lái)看看編譯結(jié)果
下來(lái)我們來(lái)開(kāi)發(fā)一個(gè)數(shù)組類解決原生數(shù)組的安全性問(wèn)題:提供函數(shù)獲取數(shù)組長(zhǎng)度;提供函數(shù)獲取數(shù)組元素;提供函數(shù)設(shè)置數(shù)組元素。我們來(lái)看看它是怎么實(shí)現(xiàn)的
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; }
我們編譯后得到如下結(jié)果
下來(lái)我們來(lái)看看特殊的構(gòu)造函數(shù):無(wú)參構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)。無(wú)參構(gòu)造函數(shù)顧名思義就是沒(méi)有參數(shù)的構(gòu)造函數(shù),而拷貝構(gòu)造函數(shù)則是參數(shù)為 const class_name& 的構(gòu)造函數(shù)。那么這兩類構(gòu)造函數(shù)有什么區(qū)別呢?無(wú)參構(gòu)造函函數(shù)是當(dāng)類中沒(méi)有定義構(gòu)造函數(shù)時(shí),編譯器默認(rèn)提供一個(gè)無(wú)參構(gòu)造函數(shù),并且其函數(shù)體為空;拷貝構(gòu)造函數(shù)是當(dāng)類中沒(méi)有定義拷貝構(gòu)造函數(shù)時(shí),編譯器默認(rèn)提供一個(gè)拷貝構(gòu)造函數(shù),簡(jiǎn)單的進(jìn)行成員變量的值復(fù)制。下來(lái)我們以代碼為例進(jìn)行分析
#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; }
我們先將自己提供的無(wú)參構(gòu)造函數(shù)和拷貝構(gòu)造函數(shù)注釋掉,編譯下,看編譯器是否提供默認(rèn)的構(gòu)造函數(shù),是否可以通過(guò)
我們看到編譯是通過(guò)的,也就是說(shuō),編譯器通過(guò)了默認(rèn)的構(gòu)造函數(shù)。我們?cè)賮?lái)自己提供呢,看看是否會(huì)發(fā)生沖突
我們看到打印出了自己定義的語(yǔ)句,證明它是調(diào)用了我們自己寫的構(gòu)造函數(shù)。那么這個(gè)拷貝構(gòu)造函數(shù)的意義在哪呢?一是兼容 C 語(yǔ)言的初始化方式,二是初始化行為能夠符合預(yù)期的邏輯。那么這塊就牽扯到是淺拷貝還是深拷貝。淺拷貝是拷貝后對(duì)象的物理狀態(tài)相同,深拷貝是拷貝后對(duì)象的邏輯狀態(tài)相同。注意:編譯器提供的拷貝構(gòu)造函數(shù)只進(jìn)行淺拷貝!
下來(lái)我們以實(shí)例代碼看看對(duì)象的初始化是怎樣進(jìn)行的
#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 應(yīng)該進(jìn)行的是淺拷貝,t2 應(yīng)該進(jìn)行的是深拷貝。我們看看編譯結(jié)果
我們?nèi)绻挥袦\拷貝,沒(méi)有深拷貝的話,看看結(jié)果會(huì)是怎樣的,將第 34 - 41 行的代碼注釋掉,將第 54 和 55 行的打印 *p 的值改為打印 p 的地址。
我們看到它運(yùn)行的時(shí)候報(bào)段錯(cuò)誤了,t1.p 和 t2.p 指向了同一個(gè)地址。我們看看它是怎樣進(jìn)行的
我們看到將同一個(gè)地址釋放兩次肯定是會(huì)出問(wèn)題的,這時(shí)我們就需要進(jìn)行深拷貝了。那么我們就要考慮到底什么時(shí)候需要進(jìn)行深拷貝?當(dāng)對(duì)象中有成員指代了系統(tǒng)中的資源時(shí),如:成員指向了動(dòng)態(tài)內(nèi)存空間,成員打開(kāi)了外存中的文件,成員使用了系統(tǒng)中的網(wǎng)絡(luò)端口...
我們?cè)趯?shí)現(xiàn)拷貝構(gòu)造函數(shù)這塊有個(gè)一般性的原則,自定義拷貝構(gòu)造函數(shù)時(shí),必須要實(shí)現(xiàn)深拷貝。那么我們?cè)賮?lái)優(yōu)化下之前的數(shù)組類
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; }
我們看看編譯結(jié)果是否如我們代碼所寫的那樣,創(chuàng)建數(shù)組并初始化。用數(shù)組 a 初始化數(shù)組 b。
通過(guò)對(duì)對(duì)象的構(gòu)造的學(xué)習(xí),總結(jié)如下:1、每個(gè)對(duì)象在使用之前都應(yīng)該初始化;2、類的構(gòu)造函數(shù)用于對(duì)象的初始化,構(gòu)造函數(shù)與類同名并且沒(méi)有返回值;3、構(gòu)造函數(shù)在對(duì)象定義時(shí)被自動(dòng)調(diào)用,構(gòu)造函數(shù)可以根據(jù)需要定義參數(shù);4、構(gòu)造函數(shù)之間可以存在重載關(guān)系,并且構(gòu)造函數(shù)遵循 C++ 中重載函數(shù)的規(guī)則;5、對(duì)象定義時(shí)會(huì)觸發(fā)構(gòu)造函數(shù)的調(diào)用,在一些情況下可以手動(dòng)調(diào)用構(gòu)造函數(shù);6、C++ 編譯器會(huì)默認(rèn)提供構(gòu)造函數(shù);7、無(wú)參構(gòu)造函數(shù)用于定義對(duì)象的默認(rèn)初識(shí)狀態(tài),拷貝構(gòu)造函數(shù)在創(chuàng)建對(duì)象時(shí)拷貝對(duì)象的狀態(tài);8、對(duì)象的拷貝有淺拷貝和深拷貝兩種方式:淺拷貝使得對(duì)象的物理狀態(tài)相同,而深拷貝則使得對(duì)象的邏輯狀態(tài)相同。
歡迎大家一起來(lái)學(xué)習(xí) C++ 語(yǔ)言,可以加我QQ:243343083。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。