(GeekBand)C++面向?qū)ο蟾呒?jí)編程(上)第二周筆記(1)

第七節(jié) 三大函數(shù):拷貝函數(shù)氏堤,拷貝賦值评肆,析構(gòu)

今天開始學(xué)習(xí)另一個(gè)經(jīng)典類,String(僅從三大函數(shù)的角度)毁嗦。

  • 構(gòu)造函數(shù)
  • 拷貝構(gòu)造函數(shù)
  • 拷貝賦值函數(shù)
  • 析構(gòu)函數(shù)

大家都知道,我們?cè)趧?chuàng)建實(shí)例的時(shí)候回铛,編譯器會(huì)自動(dòng)調(diào)用class中的構(gòu)造函數(shù)對(duì)實(shí)例進(jìn)行初始化狗准,而在銷毀實(shí)例或生命周期結(jié)束時(shí)會(huì)自動(dòng)調(diào)用其析構(gòu)函數(shù)來清理內(nèi)存。那么拷貝構(gòu)造函數(shù)與拷貝賦值函數(shù)是什么呢茵肃?它們的作用是腔长?

先來看一段String類接口:

class String
{
private:
    char *m_data;
public:
    String(const char* cstr=0);//構(gòu)造函數(shù)
    String(const String& str);//拷貝構(gòu)造函數(shù)
    String& operator=(const String&str);//拷貝賦值函數(shù)
    ~String();//析構(gòu)函數(shù)
    char* get_c_str() const{return m_data};
};

再來看下面的代碼:

int main()
{
    String s1();//調(diào)用構(gòu)造函數(shù)
    //String S1;//調(diào)用構(gòu)造函數(shù)
    String s2("hello");//調(diào)用構(gòu)造函數(shù)
    String s3(s1);//調(diào)用拷貝構(gòu)造函數(shù)
    String s4=s1;//調(diào)用拷貝構(gòu)造函數(shù)
    s4=s2;//拷貝賦值函數(shù)
    return 0;
}

看了上面的代碼,是不是對(duì)四個(gè)函數(shù)有了基本的印象呢验残,下面我們來深入學(xué)習(xí)捞附。

構(gòu)造函數(shù)/析構(gòu)函數(shù)

inline
String::String(const char* cstr=0)//用來創(chuàng)建無參實(shí)例或以字符串("aaaa"或char[5])為參數(shù)的實(shí)例
{
    if(cstr)//有參
    {
        m_data=new char[strlen(cstr)+1];//為m_data開辟空間,大小為參數(shù)長度+1(存放'\0')
        strcpy(m_data,cstr);//賦值
    }
    else//無參
    {
        m_data=new char[1];//開辟大小為1的空間您没,存放'\0'
        *m_data='\0';
    }
}//構(gòu)造函數(shù)
inline 
String::~String()
{
    delete[] m_data;//釋放內(nèi)存鸟召,注意用[]
}//析構(gòu)函數(shù)

int main()
{
    String* p=new String("hello");//調(diào)用構(gòu)造函數(shù)
    delete p;//調(diào)用析構(gòu)函數(shù)
    return 0;
}//測(cè)試函數(shù)

比較中規(guī)中矩的用法。

拷貝構(gòu)造函數(shù)/拷貝賦值函數(shù)/析構(gòu)函數(shù)

inline
String::String(const String& str)
{
    m_data=str.m_data;
}//淺拷貝構(gòu)造函數(shù)(編譯器自帶的那一套)
String s1="hello world";
String s2=s1;

我們知道氨鹏,在執(zhí)行String s1="hello world";String s2=s1;這樣的操作時(shí)欧募,編譯器會(huì)調(diào)用默認(rèn)拷貝構(gòu)造函數(shù),然而這種默認(rèn)的拷貝構(gòu)造函數(shù)只是單純的將s1中的數(shù)據(jù)一個(gè)bit一個(gè)bit的復(fù)制過去仆抵,是一種淺拷貝跟继,當(dāng)我們的class中有point成員時(shí),這種淺拷貝會(huì)使s1,s2中的兩個(gè)point指向同一塊內(nèi)存镣丑,這是很危險(xiǎn)的舔糖。這時(shí),我們需要為編譯器提供一個(gè)新的莺匠、深拷貝的拷貝構(gòu)造函數(shù)剩盒。

inline
String::String(const String& str)
{
    m_data=new char[strlen(str.m_data)+1];
    strcpy(m_data,str.m_data);
}//深拷貝構(gòu)造函數(shù)(無內(nèi)存問題)

以上為拷貝構(gòu)造函數(shù)只能解決在新建實(shí)例時(shí)對(duì)class中point成員指向問題,在實(shí)例已經(jīng)創(chuàng)建完成后的賦值動(dòng)作慨蛙,同樣會(huì)存在指針指向相同內(nèi)存的問題辽聊,我們需要通過重載操作符'='來解決這個(gè)問題,即拷貝賦值函數(shù)期贫。

inline
String& String::operator =(const String& str)
{
    if(this==&str)//檢測(cè)自我復(fù)制(self assignment)
        return *this;
    delete[] m_data;//原理內(nèi)存(原內(nèi)存長度不可更改)
    m_data=new char[strlen(str.m_data)+1];//重新開辟長度適當(dāng)?shù)目臻g
    strcpy(m_data,str.m_data);//賦值
    return *this;//返回this
}//拷貝賦值函數(shù)
String s1("hello world");
String s2;
s2=s1;//調(diào)用拷貝賦值函數(shù)

以上就是String中的big three跟匆。

最后再來重載一下'<<'方便輸出String實(shí)例

#include<iostream>
ostream& operator<<(ostream& os,const String& str)
{
    os<<str.get_c_str();
    return os;
}
{
    String s1("hello ");
    cout<<s1;
}

第八節(jié) 堆,棧與內(nèi)存管理

  • 棧(Steak)

是存在于某作用域(scope)的一塊內(nèi)存空間(memory space)通砍。

  • 堆(Heap)

也成system heap,是由操作系統(tǒng)提供的一塊global內(nèi)存空間玛臂,程序可以動(dòng)態(tài)分配(dynamic allocated)從其中獲得若干區(qū)塊(blocks)烤蜕。

class Complex{...};
...
{
    Complex c1(1,2);//分配棧空間
    Complex* p=new Complex(3);//動(dòng)態(tài)分配堆空間
}

static object與global object兩種都在main之前存在迹冤,(聯(lián)想構(gòu)造函數(shù))在程序結(jié)束時(shí)銷毀(析構(gòu)函數(shù))讽营。

new:先分配memory,再調(diào)用ctor

Complex* pc=new Complex(1,2);
//編譯器轉(zhuǎn)化為
void* mem=operator new(sizeof(Complex));//分配內(nèi)存(大小決定于class數(shù)據(jù)類型),內(nèi)部會(huì)調(diào)用malloc(n)
pc=static_cast<Complex*>(mem);//轉(zhuǎn)型
pc->Complex::Complex(1,2);//構(gòu)造函數(shù)

delete:先調(diào)用dt,在釋放memory

String* ps=new String(Hello);
delete ps;
//編譯器轉(zhuǎn)化為
String::~String(ps);//調(diào)用析構(gòu)函數(shù)泡徙,殺掉class中動(dòng)態(tài)分配的內(nèi)存
operator delete(ps);//釋放內(nèi)存橱鹏,內(nèi)部調(diào)用free(ps),free(ps)用來殺掉class本身(即數(shù)據(jù)成員);

小結(jié):構(gòu)造函數(shù)與析構(gòu)函數(shù)的作用是初始化和清理class中動(dòng)態(tài)分配的內(nèi)存堪藐,真正執(zhí)行創(chuàng)建和銷毀object的操作是malloc(),與free()

動(dòng)態(tài)分配所得的內(nèi)存塊,in VC

我們創(chuàng)建一個(gè)Complex類(雙double成員)莉兰,一般會(huì)認(rèn)為被動(dòng)態(tài)分配8個(gè)bit。

但實(shí)際上礁竞,在VC中的情況是這樣的(VC中每個(gè)內(nèi)存塊一定是16的倍數(shù)):

|Complex

|00000041(Cookies)
|Debugger Header(debug,32+4bit)
|Complex Object(data,8bit)
|00000000(pad,16bit)
|00000041(Cookies)
8+(32+4)+(4*2)

->52

->64(debug模式下)

| 00000011(Cookies)

| Complex(data,8bit)
| 00000011(Cookies)
8+(4*2)

->16

以上Debug標(biāo)注為在調(diào)試模式下所分配的內(nèi)存糖荒。而Cookies用來記錄分配內(nèi)存的大小,上例中分別為00000040與00000010,利用最后一位記錄是否已經(jīng)分配,分配后則為00000041與00000011模捂。

以上為Complex object,String object與其結(jié)構(gòu)相似在這就不多做贅述捶朵。

下面分析一下動(dòng)態(tài)分配所得的array:

| Complex[3]

| 51h(Cookies)
| Debugger Header(debug,32+4bit)
| 3(4bit)
| double(24bit)
| 00000000(pad,8bit)
| 51h(Cookies)
(8 *3)+(32+4)+(4 *2)+4

->72

->80

非debug模式下為:

(8 *3)+(4 *2)+4

->36

->48

| String[3]

| 41h(Cookies)
| Debugger(32+4bit)
| 3
| pointer(12bit)
| 00000000(pad,4bit)
| 41h(Cookies)

(4 *3)+(32+4)+(4 *2)+4

->60

->64

array new一定要搭配array delete

String* p=new String[3];
delete[] p;//喚起3次dtor
//只有加"[]"編譯器才會(huì)明白要清理的對(duì)象是一個(gè)數(shù)組,才會(huì)讀取3,喚起三次dtor(自寫狂男,處理class中動(dòng)態(tài)分配內(nèi)存)

如果例子中是Complex類综看,則不論是不是數(shù)組使用delete p都沒有問題,因?yàn)椴淮嬖陬愔袆?dòng)態(tài)分配內(nèi)存并淋,所以不需要dtor,只要調(diào)用free()清理double即可珍昨,但是為了規(guī)范县耽,方便記憶,單個(gè)object使用delete,數(shù)組使用delete[]镣典。

[]的本質(zhì)是命令編譯器讀取pointer(12bit)或double(23bit)上方的用來記錄數(shù)組長度的內(nèi)存來獲取數(shù)組長度兔毙,并喚起dtor該長度的次數(shù)。

第九節(jié) 擴(kuò)展補(bǔ)充:類模板兄春,函數(shù)模板澎剥,及其他

補(bǔ)充:static

  1. 通常100個(gè)實(shí)例就有100組數(shù)據(jù),而函數(shù)保存在代碼區(qū)赶舆,只有一份哑姚。一份函數(shù)處理多組數(shù)據(jù),靠this指針芜茵。調(diào)用方法:c1.real()->complex::real(&c1);將地址傳入this用來識(shí)別具體是哪一組數(shù)據(jù)叙量。
  2. static修飾的實(shí)例類作用域?yàn)槿郑诔绦蚪Y(jié)束時(shí)銷毀九串。
  3. static會(huì)使數(shù)據(jù)與函數(shù)與類分離绞佩,在內(nèi)存中單獨(dú)存儲(chǔ)寺鸥,且只有一份。因?yàn)闆]有this指針品山,所以static函數(shù)只能處理static數(shù)據(jù)胆建。
  4. 類中聲明靜態(tài)實(shí)例不會(huì)分配內(nèi)存(聲明)。在類外初始化時(shí)分配(定義)肘交。
  5. 靜態(tài)函數(shù)的調(diào)用方法:(1)通過實(shí)例調(diào)用笆载。例:Account a;a.set();(2)通過class name來調(diào)用。例:Account::set;(與臨時(shí)變量語法類似)酸些。

補(bǔ)充:把ctors放在private中

單例模式(Singleton)

class A
{
public:
    static A& getInstance(return a;);//對(duì)外窗口
    setup(){...}
private:
    A();//構(gòu)造函數(shù)
    A(const A& rhs);//拷貝構(gòu)造函數(shù)
    static A a;//實(shí)例
};

然而上述方法還不夠完美宰译,實(shí)例a在object創(chuàng)建時(shí)就已經(jīng)存在,有些浪費(fèi)空間魄懂,最好是在使用時(shí)才創(chuàng)建沿侈。只需要將創(chuàng)建實(shí)例的過程放到對(duì)外窗口中即可,只有外界通過窗口訪問時(shí)再創(chuàng)建市栗。

class A
{
public:
    static A& getInstance();
    setup(){...}
private:
    A();
    A(const A& rhs);
    ...
};
A& A::getInstance()
{
    static A a;
    return a;
}

解析:實(shí)現(xiàn)單例要解決兩個(gè)問題:

(1)控制權(quán)限缀拭,不允許到處創(chuàng)建實(shí)例。解決方法:將構(gòu)造函數(shù)放在private中填帽,則只允許在類內(nèi)創(chuàng)建實(shí)例蛛淋。

(2)控制數(shù)量,只允許創(chuàng)建一個(gè)實(shí)例篡腌。解決方法:使用static修飾實(shí)例褐荷。

補(bǔ)充:cout

在這里先拋出一個(gè)問題,為什么cout可以接受可種各樣的數(shù)據(jù)并彈出呢嘹悼?

cout屬于_IO_ostream_withassign類叛甫,繼承于ostream,我們來看一下ostream的定義杨伙。

class ostream:virtual public ios
{
public:
    ostream& operator<<(char c);
    ostream& operator<<(unsigned char c){return(*this)<<(char)c;}
    ostream& operator<<(signed char c){return (*this)<<(char)c;}
    ostream& operator<<(const char *s);
    ostream& operator<<(const unsigned char *s){return (*this)<<(const char*)s;}
    ostream& operator<<(const signed char *s){return (*this)<<(const char*)s;}
    ostream& operator<<(const void *p);
    ostream& operator<<(int n);
    ostream& operator<<(unsigned int n);
    ostream& operator<<(long n);
    ostream& operator<<(unsigned long n);
    ...
}

不難發(fā)現(xiàn)ostream中對(duì)操作符"<<"進(jìn)行了大量重載其监,正因如此cout才可以接收各種各樣的數(shù)據(jù)類型。

補(bǔ)充:類模板(class template)

適用情況:相同操作限匣,不同數(shù)據(jù)

template<typename T>
class A
{
private:
    T data;
public:
    A();
    T get(return data);
};

int main()
{
    A<int> a;//綁定
    cout<<a.get()<<endl;
    return 0;
}

在定義類時(shí)不指定數(shù)據(jù)類型抖苦,而是使用一個(gè)符號(hào)T來代替,之后在使用時(shí)綁定類型米死。

補(bǔ)充:函數(shù)模板(function template)

顧名思義使用在非類成員函數(shù)的模板锌历。

template<class T>
T min(T& a,T& b)
{
    return a>b?b:a;//T的類型會(huì)影響具體調(diào)用那一種'>'操作符重載
}

int main()
{
    int a,b;
    cin>>a>>b;
    cout<<min(a,b);
    return 0;
}

在調(diào)用使用模板的函數(shù)時(shí)編譯器會(huì)進(jìn)行“引數(shù)推倒(argument deduction)”,即利用傳進(jìn)的實(shí)參在決定T類型峦筒,并推出返回類型或重載類型辩涝。

補(bǔ)充:命名空間(namespace)

以標(biāo)準(zhǔn)庫為例,標(biāo)準(zhǔn)庫中所有的命名都被包含在了命名空間std中勘天,使用的方法有幾種:

  1. using namespace std;cin,cout;
  2. using std::cout;std::cin,cout;
  3. (NONE);std::cin,stu::cout;

更多細(xì)節(jié)深入

一下是不包含在課程中的內(nèi)容怔揩,順便記錄一下捉邢。

  1. operator type()const;//轉(zhuǎn)換函數(shù)
  2. explicit complex(...):initialization list{}
  3. pointer-like object
  4. function-like object
  5. Namespace
  6. template specialization
  7. Standard Library
  8. variadic template(c11)
  9. move ctor(c11)
  10. Rvalue reference(c11)
  11. auto(c11)
  12. lambda(c11)
  13. range-base for loop(c11)
  14. unordered containers(c11)
    ...

今天就到這吧。商膊。伏伐。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市晕拆,隨后出現(xiàn)的幾起案子藐翎,更是在濱河造成了極大的恐慌,老刑警劉巖实幕,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吝镣,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡昆庇,警方通過查閱死者的電腦和手機(jī)末贾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來整吆,“玉大人拱撵,你說我怎么就攤上這事”眚” “怎么了拴测?”我有些...
    開封第一講書人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長府蛇。 經(jīng)常有香客問我集索,道長,這世上最難降的妖魔是什么汇跨? 我笑而不...
    開封第一講書人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任务荆,我火速辦了婚禮,結(jié)果婚禮上扰法,老公的妹妹穿的比我還像新娘蛹含。我一直安慰自己毅厚,他們只是感情好塞颁,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著吸耿,像睡著了一般祠锣。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咽安,一...
    開封第一講書人閱讀 49,111評(píng)論 1 285
  • 那天伴网,我揣著相機(jī)與錄音,去河邊找鬼妆棒。 笑死澡腾,一個(gè)胖子當(dāng)著我的面吹牛沸伏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播动分,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼毅糟,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了澜公?” 一聲冷哼從身側(cè)響起姆另,我...
    開封第一講書人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坟乾,沒想到半個(gè)月后迹辐,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡甚侣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年明吩,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片渺绒。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡贺喝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宗兼,到底是詐尸還是另有隱情躏鱼,我是刑警寧澤,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布殷绍,位于F島的核電站染苛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏主到。R本人自食惡果不足惜茶行,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望登钥。 院中可真熱鬧畔师,春花似錦、人聲如沸牧牢。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塔鳍。三九已至伯铣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間轮纫,已是汗流浹背腔寡。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掌唾,地道東北人放前。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓忿磅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親凭语。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贝乎,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容