GeekBand C++ 第二周

7.三大函數(shù):拷貝構(gòu)造,拷貝賦值,析構(gòu)

String s3(s1);//拷貝構(gòu)造函數(shù)(s3剛剛出現(xiàn))
String s4 = s1;//這種情況也是拷貝構(gòu)造(雖然用的'=',但是S4剛剛出現(xiàn))
s3 = s2;//拷貝賦值(s3已經(jīng)出現(xiàn))

無指針的類,不需要寫拷貝構(gòu)造和拷貝賦值。類內(nèi)帶指針,一定要寫拷貝構(gòu)造和拷貝賦值,不能用編譯器自動生成的。

  • 構(gòu)造函數(shù),參數(shù)類型是自身類型,則為拷貝構(gòu)造函數(shù)。
  • 拷貝賦值,重載=操作符,參數(shù)類型是自身類型。
  • 和構(gòu)造函數(shù)名稱相同,前面加~,是析構(gòu)函數(shù),當(dāng)類的對象死亡的時候,析構(gòu)函數(shù)會被調(diào)用。

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

inline
String::String(const char* cstr = 0)
{
  if(cstr){
    m_data = new char[strlen(cstr) + 1];
    strcpt(m_data, cstr);
  }else{
    m_data = new char[1];
    *m_data = '\0';
  }
}
  • c語言 字符串,以'\0'結(jié)尾,字符串長度,以'\0'標(biāo)記來計算。另一種在字符串的前面有長度標(biāo)示,后面沒有結(jié)束符。
  • 字符串長度為0,也要用一個字符,來保存'\0',為了析構(gòu)函數(shù)統(tǒng)一析構(gòu),一個字符用 new char[1] 來創(chuàng)建。
  • 當(dāng)字符串長度不為0,則用strlen()計算出長度+1,用來保存最后的'\0'。

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

inline
String::~String(){
    delete[] m_data;//array delete配合 array new
}
  • 析構(gòu)函數(shù)的作用,清理,cleanup。
  • 離開作用域時要釋放內(nèi)存。

big three
class with pointer members 必須有 copy ctor 和 copy operator=。如果沒有使用,則極易造成內(nèi)存泄露,且兩個類中的指針指向同一塊內(nèi)存,改變A,則B也被改變。

copy ctor
inline
構(gòu)造函數(shù),接受參數(shù)類型為本身,則為拷貝構(gòu)造函數(shù)。
深拷貝:首先創(chuàng)造足夠的空間,然后把內(nèi)容拷貝到新的對象中。
淺拷貝:則會造成兩個‘人’在‘看’同一個東西。

copy assignment operator 拷貝賦值函數(shù)
右邊的對象拷貝的左邊,左右兩邊原本都有內(nèi)容

  • 1.首先要清空左邊。
  • 2.然后在左邊分配和右邊一樣的空間。
  • 3.再把右邊的內(nèi)容拷貝到左邊。
  • 4.特別要注意,要檢測自我賦值,如果不檢測,則自身在拷貝之前就被干掉了,造成內(nèi)存錯誤。如果檢測到自我賦值,則直接返回。不單單是為了效率,更是為了安全。

8.堆,棧與內(nèi)存管理

8.1.Stack和Heap

Stack

是存在于某作用于(scope)的一塊內(nèi)存空間(memory space)。調(diào)用函數(shù)時,函數(shù)本身即會形成一個stack用來放置它所接收的參數(shù),以及返回地址,以及l(fā)ocal object。

Heap

是由操作系統(tǒng)提供的一塊global內(nèi)存空間,程序可以動態(tài)分配從其中獲得若干區(qū)塊,new出來的,必須手動delete掉。

stack objects的生命周期

stack object,即為local object,又稱為 auto object,生命在作用域結(jié)束之后就結(jié)束了。對象的析構(gòu)函數(shù)會被調(diào)用。

static local objects的生命周期

static object,其生命在作用域結(jié)束后仍然存在,直到整個程序結(jié)束。

global objects的生命周期

任何寫在大括號之外的對象,其生命在main函數(shù)之前就存在,在程序結(jié)束之后才結(jié)束,作用域是“整個程序”。

heap objects的生命周期

new得到的對象,在使用完畢之后要delete掉。如果沒有delete,則會造成內(nèi)存泄露。

8.2 new delete

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

Complex *pc = new Complex(1, 2);

編譯器把new分解為三個動作:

  • 1.分配內(nèi)存 void* mem = operator new(sizeof(Complex)); // 內(nèi)部調(diào)用malloc(n);
  • 2.轉(zhuǎn)型pc = static_cast<Complex*>(mem);
  • 3.構(gòu)造函數(shù)pc->Complex::Complex(1,2); // 其實際參數(shù)列表為 Complex::Complex(pc, 1, 2);

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

String *ps = new String("Hello");
...
delete ps;

編譯器把delete分解為兩個動作:

  • 1.析構(gòu)函數(shù) String::~String(ps); // 析構(gòu)函數(shù)會delete掉String類內(nèi)部動態(tài)分配的空間
  • 2.釋放內(nèi)存 operator delete(ps); // 其內(nèi)部調(diào)用free(ps);

共計兩次delete。

8.3 動態(tài)分配所得的內(nèi)存塊(memory block)

1.動態(tài)分配所得的對象

1.Complex *pc = new Complex(1, 2);

在debug模式下,class的前面有32字節(jié),后面有4字節(jié),前后cooky各4字節(jié),cooky為0x41,共計:

8+(32+4)+(4*2)=52 -> 64

在release模式下,class本身8個字節(jié),前后cooky各4個字節(jié),cooky為0x11,共計:

8+(4*2)=16 -> 16

上下cooky的作用,記錄整塊給你的大小。采用16進(jìn)制,如果是0,則代表系統(tǒng)回收,如果是1,則代表系統(tǒng)給出。在vs的編譯器下,給的內(nèi)存的大小為16的倍數(shù),所以cooky在16進(jìn)制時最后一位一直為0,所以可以用來標(biāo)記內(nèi)存的方向。

2.String *ps = new String("Hello");

在debug模式下,cooky為0x31,共計:

4+(32+4)+(4*2)=48 -> 48

在release模式下,cooky為0x11,共計:

4+(4*2)=12 -> 16

2.動態(tài)分配所得的 array

array new 要搭配 array delete,不然會出錯。

1.Complex *p = new Complex[3];

在debug模式下,cooky為0x51,共計:

(8*3)+(32+4)+(4*2)+4=72 -> 80

在release模式下,cooky為0x31,共計:

(8*3)+(4*2)+4=36 -> 48

2.String *p = new String[3];

在debug模式下,cooky為0x41,共計:

(4*3)+(32+4)+(4*2)+4=60 -> 64

在release模式下,cooky為0x31,共計:

(4*3)+(4*2)+4=24 -> 32

3.array new 一定要搭配 array delete

String *p = new String[3];
...
delete[] p; // 調(diào)用3次dtor
memory 解釋
21h cooky記錄內(nèi)存大小
3 數(shù)組的大小
String[0] 調(diào)用dtor
String[1] 調(diào)用dtor
String[2] 調(diào)用dtor
000000000(pad) 填充內(nèi)存
21h cooky記錄內(nèi)存大小
String *p = new String[3];
...
delete p; // 調(diào)用1次dtor
memory 解釋
21h cooky記錄內(nèi)存大小
3 數(shù)組的大小
String[0] 調(diào)用dtor
String[1] 未調(diào)用dtor
String[2] 未調(diào)用dtor
000000000(pad) 填充內(nèi)存
21h cooky記錄內(nèi)存大小

對比發(fā)現(xiàn),整塊的內(nèi)存并沒有發(fā)生內(nèi)存泄露,因為整塊內(nèi)存的大小記錄在cooky當(dāng)中。如果沒有寫array delete而寫的是delete,編譯器不知道下面有幾個對象,因此只有第一個也就是String[0]調(diào)用了dtor,其余的對象并沒有調(diào)用dtor。當(dāng)調(diào)用玩dtor之后,再釋放掉整塊的內(nèi)存。由此可以發(fā)現(xiàn),如果此時的例子是Complex類的話,那么由于類內(nèi)部沒有指針,所以即使用array new,但沒用array delete,也不會產(chǎn)生內(nèi)存泄露。

但是在寫代碼時,我們應(yīng)養(yǎng)成好的編碼習(xí)慣,array new 一定要搭配 array delete。

9.復(fù)習(xí)String類的實現(xiàn)

  • 1.防衛(wèi)式聲明
    #ifndef _MYSTRING_
    #define _MYSTRING_
    class String{
    ...
    };
    #endif
    
  • 2.如何去定義內(nèi)部變量
    • 放數(shù)組,但是數(shù)組的大小無法確定。
    • 放指針,當(dāng)需要時,動態(tài)分配(new)字符串的大小,在32位的系統(tǒng)中,一個指針是4byte,放在private中。
      char *m_data;
      
  • 3.ctor,放在public;
    String(const char* cstr = 0);
    
    • 只是接受字符串,不會改變,要加上const。
  • 4.class with point member:
    • copy ctor:
      String(cosnt String& str);
      
    • copy assignment operator:
      String& operator=(const String& str);
      
      • 對于copy ctor 和copy assignment函數(shù),不會改變被拷貝的對象,所以要加上const。
      • 返回拷貝的對象,因為返回結(jié)果不是放在local object中,目標(biāo)本來存在,因此使用return by reference。
    • dtor:
      ~String();
      
  • 5.輔助函數(shù)
    • 為了能夠cout字符串,因此需要一個函數(shù)能夠取出String類中的字符串。
      char* get_c_str() cosnt { return m_data; }
      
    • 因為函數(shù)簡單,直接使用inline的方式。因為不會改變對象的成員變量,因此需要加上const。
  • 6.ctor,copy ctor,copy assignment 都不需要加const
  • 7.ctor和dtor
    • ctor
      inline //建議編譯器
      String::String(cosnt char* cstr = 0){
          if(cstr){
              //以下兩個函數(shù)為C函數(shù),需要相應(yīng)頭文件
              m_data = new char[strlen(cstr) + 1];
              strcpy(m_data, cstr);
          }else{
              m_data = new char[1];
              *m_data = '\0';
          }
      }
      
    • dtor
      inline //建議編譯器
      String::~String(){
          delete[] m_data;
          //由于ctor使用了array new,因此這里也要使用array delete   
      }
      
    • copy ctor
      inline
      String::String(cosnt String& str){
          m_data = new char[strlen(str.m_data) + 1];
          strcpy(m_data, str.m_data);
      }
      
      • inline只是建議,不能inline的話也沒關(guān)系。
    • copy assignment operator
      inline
      String& String::operator= (cosnt String& str){//此時&為reference
          //首先判斷是否自我賦值,不單單是效率問題,更是正確與否的問題。
          if(this == &str)//此時的&為取地址
              return *this;
              
          //在進(jìn)行拷貝賦值
          delete[] m_data;
          m_data = new char[strlen(str.m_data) + 1];
          strcpy(m_data, str.m_data);
          return *this;
          //傳出去值不在乎用何種方式接受
      }
      
      • 關(guān)于返回值,當(dāng)不需要連續(xù)賦值時,則不需要返回值,當(dāng)需要連續(xù)賦值時,則需要返回值。
      String s1, s2, s3("Hello");
      s1 = s2 = s3;
      

10.擴(kuò)展補(bǔ)充:類模板,函數(shù)模板及其他

1.static

Class complex{
public:
    double real() const{
    return this->re;
    }
private:
    double re;
    double im;
};
  • C++的習(xí)慣寫法
complex c1, c2, c3;
cout << c1.real();
cout << c2.real();
  • 從C的角度考慮完成同上功能的寫法
complex c1, c2 ,c3;
cout << complex::real(&c1);
cout << complex::real(&c2);

同一個函數(shù)real(),之所以能處理不同對象的數(shù)據(jù),靠的就是this point。

static data members 在內(nèi)存的單獨位置,有且只有一份。

static member functions
同樣在內(nèi)存的單獨位置,函數(shù)本身也僅僅只有一份。但是跟一般的成員函數(shù)有個區(qū)別,它沒有this point。它只能去處理靜態(tài)的數(shù)據(jù)。

靜態(tài)的變量,在類的內(nèi)部只是生命,需要在類外部定義。類型 類名稱::變量名(初始化操作);

調(diào)用靜態(tài)函數(shù)的方法有兩種:

  • 1)通過object調(diào)用。但是this指針不會被作為參數(shù)傳入函數(shù)中。
  • 2)通過class name調(diào)用。

單例模式,把ctors放在private區(qū)域。

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

外界想要使用a,只能用過:getInstance()獲得。

meyers Singleton:

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

A& A::getInstance(){
    static A a;
    return a;
}

當(dāng)外界不需要使用這個類時,a不會被創(chuàng)建,只有當(dāng)外界需要使用這個類,調(diào)用了getInstance()函數(shù),a才會被創(chuàng)建。

2.關(guān)于cout

查看標(biāo)準(zhǔn)庫ostream代碼,重載了很多的operator<<

ostream

3.class template,類模板

在類的前面加上:

template<typename T>
class complex{
...
};

用T吧具體的類代替,當(dāng)實際使用時,根據(jù)實際的需要,生成具體的類代碼。

{
    complex<double> c1(2.5, 1.5); //用double代替T生成一份類的代碼
    complex<double> c2(2, 6); //用int代替T生成一份類的代碼
    
}

4.function template,函數(shù)模板

template<class T>
inline
const T& min(const T& a, const T& b){
    retuen a < b ? a : b;
}

所有比較大小都是這么操作,因此可以使用函數(shù)模板。實際比較時如何去比較,則依賴于需要比較大小的類。類似于這種函數(shù),稱之為算法。

{
    complex c1(1, 2), c2(3, 4), c3;
    c3 = min(c1, c2);//當(dāng)調(diào)用min()函數(shù)時,編譯器會進(jìn)行實參推導(dǎo)(argument deduction),不必再使用的時候指定類型。
}

5.namespace

避免全局變量,函數(shù)以及類的同名,則需要namespace,如果每個人自己頂一個namespace,則不會造成沖突。

  • using directive
    using namespace std;
    {
      cin >> ...;
      cout << ...;
    }
    
  • using declaration
    using std::cout;
    {
      std::cin >> ...;
      cout << ...;
    }
    
  • not use
    {
      std::cin >> ...;
      std::cout << ...;
    }
    

6.更多的細(xì)節(jié),仍需努力

仍需努力的部分
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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