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

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

今天開(kāi)始學(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ù)來(lái)清理內(nèi)存。那么拷貝構(gòu)造函數(shù)與拷貝賦值函數(shù)是什么呢?它們的作用是?

先來(lái)看一段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};
};

再來(lái)看下面的代碼:

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ù)有了基本的印象呢,下面我們來(lái)深入學(xué)習(xí)。

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

inline
String::String(const char* cstr=0)//用來(lái)創(chuàng)建無(wú)參實(shí)例或以字符串("aaaa"或char[5])為參數(shù)的實(shí)例
{
    if(cstr)//有參
    {
        m_data=new char[strlen(cstr)+1];//為m_data開(kāi)辟空間,大小為參數(shù)長(zhǎng)度+1(存放'\0')
        strcpy(m_data,cstr);//賦值
    }
    else//無(wú)參
    {
        m_data=new char[1];//開(kāi)辟大小為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ù)制過(guò)去,是一種淺拷貝,當(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ù)(無(wú)內(nèi)存問(wèn)題)

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

inline
String& String::operator =(const String& str)
{
    if(this==&str)//檢測(cè)自我復(fù)制(self assignment)
        return *this;
    delete[] m_data;//原理內(nèi)存(原內(nèi)存長(zhǎng)度不可更改)
    m_data=new char[strlen(str.m_data)+1];//重新開(kāi)辟長(zhǎng)度適當(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。

最后再來(lái)重載一下'<<'方便輸出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);//分配??臻g
    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)用來(lái)殺掉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用來(lái)記錄分配內(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(自寫(xiě),處理class中動(dòng)態(tài)分配內(nèi)存)

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

[]的本質(zhì)是命令編譯器讀取pointer(12bit)或double(23bit)上方的用來(lái)記錄數(shù)組長(zhǎng)度的內(nèi)存來(lái)獲取數(shù)組長(zhǎng)度,并喚起dtor該長(zhǎng)度的次數(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用來(lái)識(shí)別具體是哪一組數(shù)據(jù)。
  2. static修飾的實(shí)例類作用域?yàn)槿?,在程序結(jié)束時(shí)銷毀。
  3. static會(huì)使數(shù)據(jù)與函數(shù)與類分離,在內(nèi)存中單獨(dú)存儲(chǔ),且只有一份。因?yàn)闆](méi)有this指針,所以static函數(shù)只能處理static數(shù)據(jù)。
  4. 類中聲明靜態(tài)實(shí)例不會(huì)分配內(nèi)存(聲明)。在類外初始化時(shí)分配(定義)。
  5. 靜態(tài)函數(shù)的調(diào)用方法:(1)通過(guò)實(shí)例調(diào)用。例:Account a;a.set();(2)通過(guò)class name來(lái)調(diào)用。例:Account::set;(與臨時(shí)變量語(yǔ)法類似)。

補(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í)例的過(guò)程放到對(duì)外窗口中即可,只有外界通過(guò)窗口訪問(wèn)時(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è)問(wèn)題:

(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è)問(wèn)題,為什么cout可以接受可種各樣的數(shù)據(jù)并彈出呢?

cout屬于_IO_ostream_withassign類,繼承于ostream,我們來(lái)看一下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來(lái)代替,之后在使用時(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)庫(kù)為例,標(biāo)準(zhǔn)庫(kù)中所有的命名都被包含在了命名空間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)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容