您好,登錄后才能下訂單哦!
(1)從程序設(shè)計的角度來看,對象只是變量,定義對象就是定義變量,所以:
在棧上創(chuàng)建對象時,成員變量初始值為隨機值
在堆上創(chuàng)建對象時,成員變量初始值為隨機值
在靜態(tài)數(shù)據(jù)區(qū)上創(chuàng)建對象時,成員變量初始值為0
(2)全局變量和static修飾的局部變量存儲在靜態(tài)數(shù)據(jù)區(qū),沒有顯式初始化其值為0(bss/ZI段)
/**
從程序設(shè)計的角度來看,對象只是變量,定義對象就是定義變量,所以:
在棧上創(chuàng)建對象時,成員變量初始值為隨機值
在堆上創(chuàng)建對象時,成員變量初始值為隨機值
在靜態(tài)數(shù)據(jù)區(qū)上創(chuàng)建對象時,成員變量初始值為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 t1;
printf("t1.i = %d\n", t1.getI());
printf("t1.j = %d\n", t1.getJ());
Test* pt = new Test;
printf("pt->i = %d\n", pt->getI());
printf("pt->j = %d\n", pt->getJ());
delete pt;
return 0;
}
(1)生活中的對象都是初始化之后上市的,如手機,電腦等,我們希望程序中的對象也可以初始化為固定值
(1)在類中提供一個public的initialize函數(shù)
(2)函數(shù)中手工對類的成員進行顯式初始化
(3)對象創(chuàng)建后需要立即調(diào)用initialize函數(shù)進行初始化
(4)initialize函數(shù)只是一個普通函數(shù),如果未及時調(diào)用,運行結(jié)果是不確定的
/**
對象創(chuàng)建后需要立即調(diào)用initialize函數(shù)進行初始化
initialize函數(shù)只是一個普通函數(shù),如果未及時調(diào)用,運行結(jié)果是不確定的
**/
#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 t1;
//t1.initialize();
printf("t1.i = %d\n", t1.getI());
printf("t1.j = %d\n", t1.getJ());
t1.initialize();
Test* pt = new Test;
pt->initialize();
printf("pt->i = %d\n", pt->getI());
printf("pt->j = %d\n", pt->getJ());
delete pt;
return 0;
}
(1)C++中可以定義與類同名的構(gòu)造函數(shù)
(2)構(gòu)造函數(shù):與類同名、沒有任何的返回值類型、在對象定義時會被自動調(diào)用
#include <stdio.h>
class Test
{
private:
int i;
int j;
public:
int getI() { return i; }
int getJ() { return j; }
Test() //構(gòu)造函數(shù):與類同名、沒有任何的返回值類型、在對象定義時會被自動調(diào)用
{
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 t1;
printf("t1.i = %d\n", t1.getI());
printf("t1.j = %d\n", t1.getJ());
Test* pt = new Test;
printf("pt->i = %d\n", pt->getI());
printf("pt->j = %d\n", pt->getJ());
delete pt;
return 0;
}
(1)構(gòu)造函數(shù)可以根據(jù)需要定義參數(shù)
(2)一個類中可以存在多個重載的構(gòu)造函數(shù)構(gòu)造函數(shù)的重載遵循C++重載的規(guī)則
/**
(1)構(gòu)造函數(shù)可以根據(jù)需要定義參數(shù)
(2)一個類中可以存在多個重載的構(gòu)造函數(shù)構(gòu)造函數(shù)的重載遵循C++重載的規(guī)則
**/
#include <stdio.h>
class Test
{
public:
Test()
{
printf("Test()\n");
}
Test(int v)
{
printf("Test(int v), v = %d\n", v);
}
};
int main()
{
Test t; // 調(diào)用 Test()
Test t1(1); // 調(diào)用 Test(int v)
//注意此處的Test t1(1),因為1是int類型數(shù)據(jù),所以是要告訴編譯器這個對象初始化時調(diào)用的構(gòu)造函數(shù)
//參數(shù)為int而且只有一個參數(shù)
Test t2 = 2; // 調(diào)用 Test(int v)
//C++中支持這樣初始化
int i(100);
printf("i = %d\n", i);
return 0;
}
(3)構(gòu)造函數(shù)在對象定義時會被自動調(diào)用,此外我們可以手工調(diào)用構(gòu)造函數(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 ta[3] = {Test(), Test(1), Test(2)}; //我們可以手工調(diào)用構(gòu)造函數(shù)
for(int i=0; i<3; i++)
{
printf("ta[%d].getValue() = %d\n", i , ta[i].getValue());
}
/* int i(100); //C++中是可以這樣初始化的,等價于int i = 100;
printf("i = %d.\n", i);
Test t = Test(100);
printf("t.getValue() = %d\n", t.getValue());
*/
return 0;
}
注意:
對象的聲明和定義不同,對象定義:申請對象的空間并調(diào)用構(gòu)造函數(shù)
對象聲明:告訴編譯器有這樣一個對象
(1)就是沒有參數(shù)的構(gòu)造函數(shù)
(2)當(dāng)類中沒有定義構(gòu)造函數(shù)時(拷貝構(gòu)造函數(shù)也是構(gòu)造函數(shù)),編譯器會默認提供一個無參構(gòu)造函數(shù),其函數(shù)體為空
(1)參數(shù)為const class_name&的構(gòu)造函數(shù)
(2)當(dāng)類中沒有定義拷貝構(gòu)造函數(shù)時,編譯器會默認提供一個拷貝構(gòu)造函數(shù),簡單的進行成員變量的復(fù)制(淺拷貝)
(3)拷貝構(gòu)造函數(shù)的意義:兼容C語言的初始化方式(使用變量為其他變量賦值),使用已創(chuàng)建的對象為其他對象賦值
#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;
}
int getPP()
{
return *p;
}
/*Test(const Test& t) //編譯器提供的默認拷貝構(gòu)造函數(shù)
{
i = t.i;
j = t.j;
}
Test() //編譯器提供的默認無參構(gòu)造函數(shù)
{ //函數(shù)體為空
}*/
Test(int v)
{
i = 1;
j = 2;
p = new int;
*p = v;
}
void free()
{
delete p;
}
};
int main()
{
Test t1(1);
Test t2 = t1; //默認進行淺拷貝
printf("t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP());
printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t1.getP());
//test
printf("t1.*p = %d.\n", t1.getPP());
printf("t2.*p = %d.\n", t2.getPP());
//結(jié)果表明t1(1),把1作為參數(shù),傳給了構(gòu)造函數(shù)
t1.free();
//t2.free(); // double free or corruption (fasttop): 0x09441008
return 0;
}
(1)淺拷貝:拷貝后的物理狀態(tài)相同
(2)深拷貝:拷貝后的邏輯狀態(tài)相同
(3)編譯器默認提供的拷貝構(gòu)造函數(shù)只進行淺拷貝
什么時候需要深拷貝?
對象中有成員使用了系統(tǒng)資源(成員指向了動態(tài)內(nèi)存空間、成員打開了外存中的文件、成員使用了系統(tǒng)中的網(wǎng)絡(luò)端口)
注意:
(1)調(diào)用淺拷貝構(gòu)造函數(shù)進行初始化,初始化的兩個變量不但參數(shù)相同,而且共用同一塊內(nèi)存,在兩次釋放內(nèi)存時就會出錯
(2)工程中自定義拷貝構(gòu)造函數(shù)時,必然要實現(xià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(const Test& t) //自定義構(gòu)造函數(shù),深拷貝
{
i = t.i;
j = t.j;
p = new int; //必須有這一步,重新分配資源
*p = *t.p; //注意這里是*t.p
}
Test(int v)
{
i = 1;
j = 2;
p = new int;
*p = v;
}
void free()
{
delete p;
}
};
int main()
{
Test t1(3);
Test t2(t1); //深拷貝,兩個對象有不同的內(nèi)存空間
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;
}
(1)在類中可以定義const成員,const成員會被分配空間,存儲位置取決于其對象定義在哪里
(2)類中的const是只讀變量
(3)在類中不能直接對const成員進行初始化,只能在初始化列表中指定初始值
//在類中不能直接對const成員進行初始化,只能在初始化列表中指定初始值
#include <stdio.h>
class Test
{
private:
const int ci; //只讀變量
public:
Test() : ci(1)
{
// ci = 10;
}
int getCI()
{
return ci;
}
};
int main()
{
Test t;
printf("t.ci = %d\n", t.getCI());
return 0;
}
(1)C++中提供了初始化列表對成員進行初始化
(2)語法規(guī)則
ClassName::ClassName() //構(gòu)造函數(shù)
: m1(v1),m2(v2, v3),m3(v1) //初始化列表
{
//構(gòu)造函數(shù)函數(shù)體
}
(3)注意事項:
成員的初始化順序與成員的聲明順序相同,與初始化列表中的順序無關(guān)
初始化列表先于構(gòu)造函數(shù)的函數(shù)體執(zhí)行
/*
C++中提供了初始化列表對成員進行初始化
成員的初始化順序與成員的聲明順序相同,與初始化列表中的順序無關(guān)
初始化列表先于構(gòu)造函數(shù)的函數(shù)體執(zhí)行
*/
#include <stdio.h>
class Value
{
private:
int mi;
public:
Value(int i)
{
printf("i = %d\n", i);
mi = i;
}
int getI()
{
return mi;
}
};
class Test
{
private:
Value m2;
Value m3;
Value m1;
public:
Test() : m1(1), m2(2), m3(3) //成員的初始化順序與成員的聲明順序相同,與初始化列表中的順序無關(guān)
{
printf("Test::Test()\n");
}
};
int main()
{
Test t; //初始化列表先于構(gòu)造函數(shù)的函數(shù)體執(zhí)行
return 0;
}
(1)局部變量的構(gòu)造順序依賴于程序的執(zhí)行流,所以開發(fā)中要避免使用goto語句)(破壞程序的執(zhí)行流)
(2)堆對象的構(gòu)造順序依賴于new的使用順序
(3)全局對象的構(gòu)造順序是不確定的,不同的編譯器使用不同的規(guī)則確定構(gòu)造順序,所以要盡量避免全局對象
//局部變量的構(gòu)造順序依賴于程序的執(zhí)行流,所以開發(fā)中要避免使用goto語句(破壞程序的執(zhí)行流)
#include <stdio.h>
class Test
{
private:
int mi;
public:
Test(int i)
{
mi = i;
printf("Test(int i): %d\n", mi);
}
Test(const Test& obj)
{
mi = obj.mi;
printf("Test(const Test& obj): %d\n", mi);
}
int getMi()
{
return mi;
}
};
int main()
{
int i = 0;
Test a1 = i; // Test(int i): 0
while( i < 3 )
{
Test a2 = ++i; // Test(int i): 1, 2, 3
}
goto End;
Test a(100); // crosses initialization of ‘Test a’編譯報錯,但是在vc10中編譯時ok的
End:
printf("a.mi = %d\n", a.getMi()); //此處的訪問必然導(dǎo)致bug
return 0;
}
(1)生活中對象都是初始化后才上市的,對象被銷毀前會做一些清理工作
方案1:
提供一個public的free函數(shù),
(1)當(dāng)對象不再需要時立即調(diào)用free函數(shù)進行清理
(2)free只是一個普通的函數(shù),必須顯示的調(diào)用
(3)對象銷毀之前沒有做清理,很可能造成資源泄漏
方案2:
析構(gòu)函數(shù)
(1)C++中可以定義一個特殊的清理函數(shù),析構(gòu)函數(shù),功能和構(gòu)造函數(shù)相反
(2)析構(gòu)函數(shù)在對象銷毀時被自動調(diào)用
(3)析構(gòu)函數(shù)沒有返回值也沒有參數(shù)(表明析構(gòu)函數(shù)在一個類中是唯一的,不可能重載)
(4)語法:
~ClassName()
(5)一般當(dāng)類中自定義了構(gòu)造函數(shù),并且函數(shù)中使用了系統(tǒng)資源,則需要定義析構(gòu)函數(shù),釋放系統(tǒng)資源,防止內(nèi)存泄漏
#include <stdio.h>
class Test
{
int mi;
public:
Test(int i)
{
mi = i;
printf("Test(): %d\n", mi);
}
~Test()
{
printf("~Test(): %d\n", mi);
}
};
int main()
{
Test t(1);
Test* pt = new Test(2);
delete pt;
return 0;
}
(1)直接調(diào)用構(gòu)造函數(shù)將產(chǎn)生一個臨時對象,臨時對象的生命周期只有一條語句的時間,臨時對象的作用域只在一條語句中
(2)臨時對象是C++中值得警惕的灰色地帶,是性能的瓶頸,也是bug的來源之一
/**直接調(diào)用構(gòu)造函數(shù)將產(chǎn)生一個臨時對象,臨時對象的生命周期只有一條語句的時間,臨時對象的作用域只在一條語句中**/
#include <stdio.h>
class Test
{
int mi;
public:
Test(int i)
{
mi = i;
}
Test()
{
Test(0); //直接調(diào)用構(gòu)造函數(shù)將產(chǎn)生一個臨時對象,臨時對象的作用域只有一行代碼。
// 所以此處就相當(dāng)于空
}
void print()
{
printf("mi = %d\n", mi);
}
};
int main()
{
Test t;
t.print();
return 0;
}
(3)實際工程開發(fā)中需要人為的避開臨時對象
(4)現(xiàn)代C++編譯器會盡力避開臨時對象
思考:如何解決構(gòu)造函數(shù)的代碼復(fù)用問題?
方案是提供一個private的init函數(shù),然后在構(gòu)造函數(shù)中去調(diào)用它.
/**
思考如何解決構(gòu)造函數(shù)的代碼復(fù)用問題?
方案是提供一個private的init函數(shù),然后在構(gòu)造函數(shù)中去調(diào)用它
**/
#include <stdio.h>
class Test {
int mi;
void init(int i) //提供一個private的init函數(shù),然后在構(gòu)造函數(shù)中去調(diào)用它
{
mi = i;
}
public:
Test(int i) {
init(i);
}
Test() {
init(0);
}
void print() {
printf("mi = %d\n", mi);
}
};
int main()
{
Test t;
t.print();
return 0;
}
回顧構(gòu)造函數(shù)的特點:
與類同名,沒有返回值,在對象創(chuàng)建時被動調(diào)用,用于對象的初始化
1、如何判斷構(gòu)造函數(shù)的執(zhí)行結(jié)果?
一般來說無法判斷。但是我們可以人為的類中定義一個用于表明構(gòu)造函數(shù)執(zhí)行結(jié)果的變量,并在構(gòu)造函數(shù)結(jié)束的地方給該變量賦值,最后通過讀取該變量的值來得知構(gòu)造函數(shù)的執(zhí)行結(jié)果
2、在構(gòu)造函數(shù)中執(zhí)行return語句會發(fā)生什么?
首先在構(gòu)造函數(shù)中指向他return是合法的,執(zhí)行return語句后構(gòu)造函數(shù)立即結(jié)束
3、構(gòu)造函數(shù)執(zhí)行結(jié)束是否意味著對象構(gòu)造成功?
構(gòu)造函數(shù)只提供自動初始化成員變量的機會,不能保證初始化邏輯一定成功。構(gòu)造函數(shù)決定的是對象的初始化狀態(tài),而不是對象的誕生。
也就是說構(gòu)造函數(shù)初始化操作的失敗不影響對象的誕生
初始化操作不能按照預(yù)期完成而得到的對象
是C++中的合法對象,也是bug的來源
#include <stdio.h>
class Test
{
int mi;
int mj;
bool mStatus;
public:
Test(int i, int j) : mStatus(false)
{
mi = i;
//return;
mj = j;
mStatus = true;
}
int getI()
{
return mi;
}
int getJ()
{
return mj;
}
int status()
{
return mStatus;
}
};
int main()
{
Test t1(1, 2);
if( t1.status() )
{
printf("t1.mi = %d\n", t1.getI());
printf("t1.mj = %d\n", t1.getJ());
}
return 0;
}
工程開發(fā)中的構(gòu)造過程可分為:
第一階段構(gòu)造:(真正的構(gòu)造函數(shù))
資源無關(guān)的初始化操作,不可能出現(xiàn)異常的操作
第二階段構(gòu)造:(返回值表示初始化狀態(tài)的普通函數(shù))
需要時用系統(tǒng)資源的操做,可能出現(xiàn)異常情況(內(nèi)存申請,訪問文件)
如圖所示:(27-2)
#include <stdio.h>
class TwoPhaseCons
{
private:
TwoPhaseCons() // 第一階段構(gòu)造函數(shù)
{
}
bool construct() // 第二階段構(gòu)造函數(shù)(普通函數(shù),返回值表示系統(tǒng)資源初始化狀態(tài))
{
return true;
}
public:
TwoPhaseCons* NewInstance() // 對象創(chuàng)建函數(shù)
{
TwoPhaseCons* ret = new TwoPhaseCons();
// 若第二階段構(gòu)造失敗,返回 NULL
if( !(ret && ret->construct()) )
{
delete ret;
ret = NULL;
}
return ret;
}
};
//TwoPhaseCons* TwoPhaseCons::NewInstance()
int main()
{
TwoPhaseCons* obj = TwoPhaseCons::NewInstance();
printf("obj = %p\n", obj);
delete obj;
return 0;
}
總結(jié):
(1)二階構(gòu)造認為的將初始化分為兩部分,能夠確保創(chuàng)建的對象都是完整的
(2)二階構(gòu)造的構(gòu)造函數(shù)都是私有的,并提供了一個用于創(chuàng)建對象的靜態(tài)函數(shù)指針???(通過類名直接訪問,然后創(chuàng)建對象),所以最終的對象分配在堆區(qū)
(3)實際工程中需要初始化的數(shù)據(jù)都是比較多的,所以對象創(chuàng)建在堆區(qū)是合理的
使用二階構(gòu)造完善之前的數(shù)組類
析構(gòu)函數(shù)的調(diào)用順序
析構(gòu)函數(shù)與對應(yīng)的構(gòu)造函數(shù)的調(diào)用順序相反,所以我們之只要知道構(gòu)造函數(shù)的調(diào)用順序就可以知道析構(gòu)的順序
(1)單個函數(shù)創(chuàng)建時構(gòu)造函數(shù)的調(diào)用順序(先父母,后他人,再自己)
1、調(diào)用父類的構(gòu)造過程
2、調(diào)用成員變量的構(gòu)造函數(shù)
3、調(diào)用類自身的構(gòu)造函數(shù)
(2)對于棧對象和全局對象,類似于入棧和出棧的順序,最先構(gòu)造的對象最后被析構(gòu)
(3)堆對象的析構(gòu)發(fā)生在使用delete的時候,與delete的使用順序相關(guān)
/**
析構(gòu)函數(shù)與對應(yīng)的構(gòu)造函數(shù)的調(diào)用順序相反,所以我們之只要知道構(gòu)造函數(shù)的調(diào)用順序就可以知道析構(gòu)的順序
(1)單個函數(shù)創(chuàng)建時構(gòu)造函數(shù)的調(diào)用順序
1、調(diào)用父類的構(gòu)造過程
2、調(diào)用成員變量的構(gòu)造函數(shù)
3、調(diào)用類自身的構(gòu)造函數(shù)
**/
#include <stdio.h>
class Member
{
const char* ms;
public:
Member(const char* s)
{
printf("Member(const char* s): %s\n", s);
ms = s;
}
~Member()
{
printf("~Member(): %s\n", ms);
}
};
class Test
{
Member mA; //調(diào)用成員變量的構(gòu)造函數(shù)
Member mB;
public:
Test() : mB("mB"), mA("mA") //初始化列表對成員進行初始化,與對象的的構(gòu)造順序無關(guān)
{
printf("Test()\n");
}
~Test()
{
printf("~Test()\n");
}
};
Member gA("gA");
int main()
{
Test t; //調(diào)用類自身的構(gòu)造函數(shù)
//對象的構(gòu)造函數(shù)掉順序:
//gA, mA, mB, Test()
//對象的析構(gòu)順序與構(gòu)造順序相反
return 0;
}
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。