C++中的對(duì)象初始化操作代碼
當(dāng)對(duì)象在創(chuàng)建時(shí)獲得了一個(gè)特定的值,我們說(shuō)這個(gè)對(duì)象被初始化。初始化不是賦值,初始化的含義是創(chuàng)建變量賦予其一個(gè)初始值,而賦值的含義是把當(dāng)前值擦除,而以一個(gè)新值來(lái)替代。對(duì)象初始化可以分為默認(rèn)初始化、直接初始化、拷貝初始化以及值初始化。
// new edit on 2020.7.23
#pragma once
#include <iostream>
using namespace std;
class ClassTest {
public:
//定義默認(rèn)構(gòu)造函數(shù)
ClassTest()
{
c[0] = '\0';
cout << "1) ClassTest()" << endl;
}
// 直接初始化
ClassTest(const char* pc)
{
strcpy_s(c, pc);
cout << "2) ClassTest (const char *pc)" << endl;
}
//復(fù)制/拷貝構(gòu)造函數(shù)
ClassTest(const ClassTest& ct)
{
strcpy_s(c, ct.c);
cout << "3) ClassTest(const ClassTest& ct)" << endl;
}
//重載賦值操作符
ClassTest& operator=(const ClassTest& ct)
{
strcpy_s(c, ct.c);
cout << "4) ClassTest& operator=(const ClassTest &ct)" << endl;
return *this;
}
private:
char c[256];
};
ClassTest func(ClassTest temp) { return temp; }
int demo_test() {
cout << "ct1: ";
ClassTest ct1("ab"); // 直接初始化
cout << "ct2: ";
ClassTest ct2 = "ab"; // 直接初始化
/*
輸出說(shuō)明:關(guān)于編譯優(yōu)化:
ClassTest ct2 = "ab";
它本來(lái)是要這樣來(lái)構(gòu)造對(duì)象的:
首先,調(diào)用構(gòu)造函數(shù)ClassTest(const char *pc)函數(shù)創(chuàng)建一個(gè)臨時(shí)對(duì)象。
然后,調(diào)用復(fù)制構(gòu)造函數(shù),把這個(gè)臨時(shí)對(duì)象作為參數(shù),構(gòu)造對(duì)象ct2。然而,編譯也發(fā)現(xiàn),復(fù)制構(gòu)造函數(shù)是
公有的,即你明確地告訴了編譯器,你允許對(duì)象之間的復(fù)制,而且此時(shí)它發(fā)現(xiàn)可以通過(guò)直接調(diào)用重載的
構(gòu)造函數(shù)ClassTest(const char *pc)來(lái)直接初始化對(duì)象,而達(dá)到相同的效果,所以就把這條語(yǔ)句優(yōu)化為
ClassTest ct2("ab")。
*/
cout << "ct3: ";
ClassTest ct3 = ct1; // 復(fù)制初始化
cout << "ct4: ";
ClassTest ct4(ct1); // 復(fù)制初始化
cout << "ct5: ";
ClassTest ct5 = ClassTest(); // 默認(rèn)構(gòu)造函數(shù)
cout << "\nct6: "; // 依次調(diào)用 1)、2)、4),即默認(rèn)、直接、重載
ClassTest ct6;
ct6 = "caoyan is a good boy!";
cout << "\nct7: ";
ClassTest ct7; // 依次調(diào)用 1)、3)、3)、4)
ct7 = func(ct6);
return 0;
}
old code:
// (1)默認(rèn)初始化
int i1;//默認(rèn)初始化,在函數(shù)體之外(初始化為0)
int f(void)
{
int i2;//不被初始化,如果使用此對(duì)象則報(bào)錯(cuò)
}
string empty;//empty非顯示的初始化為一個(gè)空串,調(diào)用的是默認(rèn)構(gòu)造函數(shù)
// (2)拷貝初始化
string str1(10,'9');//直接初始化
string str2(str1);//直接初始化
string str3 = str1;//拷貝初始化
// (3)值初始化
vector<int> v1(10);//10個(gè)元素,每個(gè)元素的初始化為0
vector<string> v2(10);//10個(gè)元素,每個(gè)元素都為空
int *pi = new int;//pi指向一個(gè)動(dòng)態(tài)分配的,未初始化的無(wú)名對(duì)象
string *ps = new string;//初始化為空string
int *pi = new int;//pi指向一個(gè)未初始化的int
int *pi = new int(1024);//pi指向的對(duì)象的值為1024
string *ps = new string(10,'9');//*ps為"9999999999"
string *ps1 = new string;//默認(rèn)初始化為空string
string *ps2 = new string();//值初始化為空string
int *pi1 = new int;//默認(rèn)初始化
int *pi2 = new int();//值初始化為0
1、C++ Copy初始化
在《inside the c++ object model》一書中談到copy constructor的構(gòu)造操作,有三種情況下,會(huì)以一個(gè)object的內(nèi)容作為另一個(gè)object的初值:
- 第一種情況:XXaa=a;第二種情況:XXaa(a);
- 第三種情況:externfun(XXaa);fun(a)函數(shù)調(diào)用
- 第四種情況:XXfun(){...};XXa=fun();函數(shù)返回值的時(shí)候
下面我們就上述的四種情況來(lái)一一驗(yàn)證
#include <iostream>
using namespace std;
class ClassTest {
public:
ClassTest() //定義默認(rèn)構(gòu)造函數(shù)
{
c[0] = '\0';
cout << "ClassTest()" << endl;
}
ClassTest(const char *pc) // 直接初始化
{
strcpy_s(c, pc);
cout << "ClassTest (const char *pc)" << endl;
}
ClassTest(const ClassTest &ct) //復(fù)制構(gòu)造函數(shù)
{
strcpy_s(c, ct.c);
cout << "ClassTest(const ClassTest& ct)" << endl;
}
ClassTest &operator=(const ClassTest &ct) //重載賦值操作符
{
strcpy_s(c, ct.c);
cout << "ClassTest& operator=(const ClassTest &ct)" << endl;
return *this;
}
private:
char c[256];
};
ClassTest func(ClassTest temp) { return temp; }
int main() {
cout << "ct1: ";
ClassTest ct1("ab"); //直接初始化
cout << "ct2: ";
ClassTest ct2 = "ab"; //復(fù)制初始化
/*輸出說(shuō)明:
ClassTest ct2 = "ab";
它本來(lái)是要這樣來(lái)構(gòu)造對(duì)象的:
首先,調(diào)用構(gòu)造函數(shù)ClassTest(const char *pc)函數(shù)創(chuàng)建一個(gè)臨時(shí)對(duì)象。
然后,調(diào)用復(fù)制構(gòu)造函數(shù),把這個(gè)臨時(shí)對(duì)象作為參數(shù),構(gòu)造對(duì)象ct2。然而,編譯也發(fā)現(xiàn),復(fù)制構(gòu)造函數(shù)是
公有的,即你明確地告訴了編譯器,你允許對(duì)象之間的復(fù)制,而且此時(shí)它發(fā)現(xiàn)可以通過(guò)直接調(diào)用重載的
構(gòu)造函數(shù)ClassTest(const char *pc)來(lái)直接初始化對(duì)象,而達(dá)到相同的效果,所以就把這條語(yǔ)句優(yōu)化為
ClassTest ct2("ab")。
*/
cout << "ct3: ";
ClassTest ct3 = ct1; //復(fù)制初始化
cout << "ct4: ";
ClassTest ct4(ct1); //直接初始化
cout << "ct5: ";
ClassTest ct5 = ClassTest(); //復(fù)制初始化
cout << "ct6: ";
ClassTest ct6; //復(fù)制初始化
ct6 = "caoyan is a good boy!";
cout << "ct7: ";
ClassTest ct7;
ct7 = func(ct6);
return 0;
}
測(cè)試結(jié)果:

我們可以看到,比較復(fù)雜的是ct6和ct7,其中ct6還是比較好理解的,ct7這種情況比較難懂,為什么會(huì)有兩個(gè)拷貝構(gòu)造函數(shù)的調(diào)用????
第一次拷貝構(gòu)造函數(shù)的調(diào)用:第一次很簡(jiǎn)單,是因?yàn)楹瘮?shù)參數(shù)的傳遞,將ct6作為參數(shù)傳遞給temp,用ct6的值初始化temp會(huì)調(diào)用拷貝構(gòu)造函數(shù);
第二次拷貝構(gòu)造函數(shù)的調(diào)用:因?yàn)橐祷匾粋€(gè)ClassTest對(duì)象,我們的編譯器怎么做????首先它將temp對(duì)象拷貝到func函數(shù)的上一級(jí)棧幀中,它的上一級(jí)棧幀是main函數(shù)的棧幀,那么當(dāng)函數(shù)返回時(shí),參數(shù)出棧,temp對(duì)象的內(nèi)存空間就會(huì)被收回,但是它的值已經(jīng)被拷貝到main棧幀的一個(gè)預(yù)留空間中,所以從temp到預(yù)留空間的拷貝也是調(diào)用拷貝構(gòu)造函數(shù),最后一步就是給ct7賦值,毫無(wú)疑問(wèn)調(diào)用賦值構(gòu)造函數(shù);對(duì)棧幀不同的同學(xué)可以看看《程序員的自我修養(yǎng)》一書,里面講得很詳細(xì)!
2、初始化列表、構(gòu)造函數(shù)與=賦值之間的區(qū)別
總所周知,C++對(duì)象在創(chuàng)建之時(shí),會(huì)由構(gòu)造函數(shù)進(jìn)行一系列的初始化工作。以沒(méi)有繼承關(guān)系的單個(gè)類來(lái)看,除了構(gòu)造函數(shù)本身的產(chǎn)生與指定,還涉及到初始化步驟,以及成員初始化方式等一些細(xì)節(jié),本篇筆記主要對(duì)這些細(xì)節(jié)進(jìn)行介紹,弄清C++對(duì)象在初始化過(guò)程中一些基本運(yùn)行規(guī)則。
構(gòu)造函數(shù)指定
通常,我們?cè)谠O(shè)計(jì)一個(gè)類的時(shí)候,會(huì)為這個(gè)類編寫對(duì)應(yīng)的default constructor、copy constructor、copy assignment operator,還有一個(gè)deconstructor。即便我們僅僅編寫一個(gè)空類,編譯器在編譯時(shí)仍舊會(huì)為其默認(rèn)聲明一個(gè)default constructor、copy constructor、copy assignment operator與deconstructor,如果在代碼里面存在著它們的使用場(chǎng)景,那么這個(gè)時(shí)候編譯器才會(huì)創(chuàng)建它們。
class MyCppClass {}
一旦我們?yōu)橐粋€(gè)類編寫了default constructor,那么編譯器也就不會(huì)為其默認(rèn)生成default constructor,對(duì)于其他幾個(gè)函數(shù)也一樣。對(duì)于編譯器默認(rèn)生成的constructor來(lái)說(shuō),它會(huì)以一定規(guī)則對(duì)每一個(gè)數(shù)據(jù)成員進(jìn)行初始化??紤]到成員初始化的重要性,在編寫自己的constructor時(shí)就需要嚴(yán)謹(jǐn)認(rèn)真了,特別是在類的派生與繼承情況下這點(diǎn)顯得尤為重要。對(duì)于copy constructor和assignment operator的運(yùn)用場(chǎng)景,這里不得不多說(shuō)一點(diǎn),見(jiàn)如下代碼:
#include <iostream>
using std::cout;
using std::endl;
class MyCppClass
{
public:
MyCppClass()
{
std::cout <<"In Default Constructor!" <<std::endl;
}
MyCppClass(const MyCppClass& rhs)
{
std::cout <<"In Copy Constructor!" <<std::endl;
}
MyCppClass& operator= (const MyCppClass& rhs)
{
std::cout <<"In Copy Assignment Operator!" <<std::endl;
return *this;
}
};
int main()
{
MyCppClass testClass1; // default constructor
MyCppClass testClass2(testClass1); // copy constructor
testClass1 = testClass2; // copy assignment operator
MyCppClass testClass3 = testClass1; // copy constructor
return 0;
}
執(zhí)行結(jié)果:

這里需要注意的是,一般情況下我們總是以為在‘='運(yùn)算符出現(xiàn)的地方都是調(diào)用copy assignment operator,上
面這種情況卻是個(gè)例外。也就是,當(dāng)一個(gè)新對(duì)象被定義的時(shí)候,即便這個(gè)時(shí)候是使用了'='運(yùn)算符,它真實(shí)調(diào)用的是初始化函數(shù)copy constructor,而不是調(diào)用copy assignment operator去進(jìn)行賦值操作。
Why初始化列表
一個(gè)對(duì)象在初始化時(shí)包括了兩個(gè)步驟:
首先,分配內(nèi)存以保存這個(gè)對(duì)象;
其次,執(zhí)行構(gòu)造函數(shù)。
在執(zhí)行構(gòu)造函數(shù)的時(shí)候,如果存在有初始化列表,則先執(zhí)行初始化列表,之后再執(zhí)行構(gòu)造函數(shù)的函數(shù)體。那么,為什么會(huì)引入初始化列表呢?
C++與C相比,在程序組織上由“以函數(shù)為基本組成單位的面向過(guò)程”變遷到“基于以類為中心的面向?qū)ο蟆?,與此同時(shí)類也作為一種復(fù)合數(shù)據(jù)類型,而初始化列表無(wú)非就是進(jìn)行一些數(shù)據(jù)的初始化工作??紤]到這里,也可以較為自然的推測(cè)初始化列表與類這種數(shù)據(jù)類型的初始化有著關(guān)聯(lián)。
在引入初始化列表之后,一個(gè)類對(duì)應(yīng)數(shù)據(jù)成員的初始化就存在有兩種方式。下面是類的數(shù)據(jù)成員類型分別為內(nèi)置類型、自定義類型時(shí)的一個(gè)對(duì)比。?
// 數(shù)據(jù)成員類型為內(nèi)置類型
class MyCppClass
{
public:
// 賦值操作進(jìn)行成員初始化
MyCppClass
{
counter = 0;
}
// 初始化列表進(jìn)行成員初始化
MyCppClass : counter(0)
{
}
private:
int counter;
}
當(dāng)類的數(shù)據(jù)成員類型為內(nèi)置類型時(shí),上面兩種初始化方式的效果一樣。當(dāng)數(shù)據(jù)成員的類型同樣也為一個(gè)類時(shí),初始化的過(guò)程就會(huì)有不一樣的地方了,比如:
// 數(shù)據(jù)成員類型為自定義類型:一個(gè)類
class MyCppClass
{
public:
// 賦值操作進(jìn)行成員初始化
MyCppClass(string name)
{
counter = 0;
theName = name;
}
// 初始化列表進(jìn)行成員初始化
MyCppClass : counter(0), theName(name)
{
}
private:
int counter;
string theName;
}
在構(gòu)造函數(shù)體內(nèi)的theName = name這條語(yǔ)句,theName先會(huì)調(diào)用string的default constructor進(jìn)行初始化,之后再調(diào)用copy assignment opertor進(jìn)行拷貝賦值。而對(duì)于初始化列表來(lái)說(shuō),直接通過(guò)copy constructor進(jìn)行初始化。
明顯起見(jiàn),可以通過(guò)如下的代碼進(jìn)行測(cè)試。
#include <iostream>
#include <string>
class SubClass
{
public:
SubClass()
{
std::cout <<" In SubClass Default Constructor!" <<std::endl;
}
SubClass(const SubClass& rhs)
{
std::cout <<" In SubClass Copy Constructor!" <<std::endl;
}
SubClass& operator= (const SubClass& rhs)
{
std::cout <<" In SubClass Copy Assignment Operator!" <<std::endl;
return *this;
}
};
class BaseClass
{
public:
BaseClass(const SubClass &rhs)
{
counter = 0;
theBrother = rhs;
std::cout <<" In BaseClass Default Constructor!" <<std::endl;
}
BaseClass(const SubClass &rhs, int cnt):theBrother(rhs),counter(cnt)
{
std::cout <<" In BaseClass Default Constructor!" <<std::endl;
}
BaseClass(const BaseClass& rhs)
{
std::cout <<" In BaseClass Copy Constructor!" <<std::endl;
}
BaseClass& operator= (const BaseClass& rhs)
{
std::cout <<" In BaseClass Copy Assignment Operator!" <<std::endl;
return *this;
}
private:
int counter;
SubClass theBrother;
};
int main()
{
SubClass subClass;
std::cout <<"\nNo Member Initialization List: " <<std::endl;
BaseClass BaseClass1(SubClass);
std::cout <<"\nMember Initialization List: " <<std::endl;
BaseClass BaseClass2(SubClass, 1);
return 0;
}
執(zhí)行結(jié)果:

也就是,在涉及到自定義類型初始化的時(shí)候,使用初始化列表來(lái)完成初始化在效率上會(huì)有著更佳的表現(xiàn)。這也是初始化列表的一大閃光點(diǎn)。即便對(duì)于內(nèi)置類型,在一些情況下也是需要使用初始化列表來(lái)完成初始化工作的,比如const、references成員變量。這里有篇筆記,對(duì)初始化列表有著非常詳盡的描述。
幾個(gè)初始化名詞
在閱讀《Accelerated C++》中文版時(shí),總是碰到“缺省初始化”、“隱式初始化”以及“數(shù)值初始化”,最初在理解這幾個(gè)名詞的時(shí)候幾費(fèi)周折,總覺(jué)得為什么一個(gè)初始化操作造出了如此多的名詞,為此沒(méi)少花時(shí)間來(lái)弄清楚它們之間的關(guān)系。
為了更好的理解它們,先對(duì)C++當(dāng)中的數(shù)據(jù)類型進(jìn)行簡(jiǎn)單劃分。在C++里面,數(shù)據(jù)類型大致可以分為兩種:第一種是內(nèi)置類型,比如float, int, double等;第二種是自定義類型,也就是我們常用的class, struct定義的類。在對(duì)這些類型的數(shù)據(jù)進(jìn)行初始化時(shí),差別就體現(xiàn)出來(lái)了:對(duì)于內(nèi)置類型,在使用之前必須進(jìn)行顯示的初始化,而對(duì)于自定義類型,初始化責(zé)任則落在了構(gòu)造函數(shù)身上。
int x = 0; // 顯示初始化x SubClass subClass; // 依賴SubClass的default constructor進(jìn)行初始化
上面的名詞“缺省初始化”描述的就是當(dāng)內(nèi)置類型或者自定義類型的數(shù)據(jù)沒(méi)有進(jìn)行顯示初始化時(shí)的一種初始化狀態(tài)。而“隱式初始化”描述的是在該狀態(tài)下面進(jìn)行的具體操作方式,比如對(duì)于內(nèi)置類型來(lái)說(shuō),缺省初始化狀態(tài)下進(jìn)行的隱式初始化實(shí)際上是未定義的,而自定義類型的隱式初始化則依賴于其constructor。
前面提到過(guò)C++不保證內(nèi)置類型的初始化,但是當(dāng)內(nèi)置類型在作為一個(gè)類的成員時(shí),在某些特定的條件下該內(nèi)置類型的成員會(huì)被編譯器主動(dòng)進(jìn)行初始化,對(duì)于這個(gè)過(guò)程也就是所謂的數(shù)值初始化。在《Accelerated C++》當(dāng)中列出了如下的幾種情況:
- 對(duì)象被用來(lái)初始化一個(gè)容器元素
- 為映射表添加一個(gè)新元素,對(duì)象是這個(gè)添加動(dòng)作的副作用
- 定義一個(gè)特定長(zhǎng)度的容器,對(duì)象為容器的元素
測(cè)試如下:
#include <iostream>
#include <vector>
#include <map>
#include <string>
using std::cout;
using std::endl;
using std::vector;
using std::map;
using std::string;
class NumbericInitTestClass
{
public:
void PrintCounter()
{
cout <<"counter = " <<counter <<endl;
}
private:
int counter;
};
int main()
{
NumbericInitTestClass tnc;
tnc.PrintCounter();
map<string, int> mapTest;
cout <<mapTest["me"] <<endl;
vector<NumbericInitTestClass> vecNumbericTestClass(1);
vecNumbericTestClass[0].PrintCounter();
return 0;
}
對(duì)于沒(méi)有進(jìn)行初始化的內(nèi)置類型,是一個(gè)未定義的值2009095316,而對(duì)于2, 3種情況來(lái)說(shuō),均被初始化為0,對(duì)于第1種情況我還沒(méi)有想到合適的場(chǎng)景。

回過(guò)頭想想,為了書中的一些相似的名詞,去想辦法把它們湊在一起總是顯得有些牽強(qiáng)附會(huì):)一些規(guī)則這里附上幾條有關(guān)初始化的基本規(guī)則,它們多來(lái)源于《Effective C++》:
1. 為內(nèi)置型對(duì)象進(jìn)行手工初始化,因?yàn)镃++不保證初始化它們。
2. 構(gòu)造函數(shù)最好使用成員初值列(member initialization list),而不要在構(gòu)造函數(shù)體內(nèi)使用賦值操作。初值列列出的成員變量,其排列次序應(yīng)該和它們?cè)赾lass中聲明的次序相同。
3. C++不喜歡析構(gòu)函數(shù)吐出異常。
4. 在構(gòu)造函數(shù)與析構(gòu)函數(shù)期間不要調(diào)用virtual函數(shù),因?yàn)檫@類調(diào)用從不下降至derived class。
5. copying函數(shù)應(yīng)該確保復(fù)制“對(duì)象內(nèi)所有成員變量”及“所有base class成分”。
參考文章
到此這篇關(guān)于C++中的對(duì)象初始化的文章就介紹到這了,更多相關(guān)C++對(duì)象初始化內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單航班管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單航班管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12
C++中關(guān)鍵字Struct和Class的區(qū)別
這篇文章主要介紹了C++中關(guān)鍵字Struct和Class的區(qū)別,本文用大量實(shí)例講解了Struct和Class的區(qū)別,需要的朋友可以參考下2014-09-09
詳談c++11 final與override說(shuō)明符
下面小編就為大家?guī)?lái)一篇詳談c++11 final與override說(shuō)明符。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-01-01
C語(yǔ)言創(chuàng)建線程thread_create()的方法
這篇文章主要介紹了C語(yǔ)言創(chuàng)建線程thread_create()的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02
詳解C++中實(shí)現(xiàn)繼承string類的MyString類的步驟
這篇文章主要介紹了C++中實(shí)現(xiàn)繼承string類的MyString類的步驟,其中的要點(diǎn)是要實(shí)現(xiàn)運(yùn)算符的重載,需要的朋友可以參考下2016-04-04

