天天看點

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

三大函數——拷貝構造、拷貝指派、析構函數

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

拷貝構造——接受的是自己這種東西

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

ctor和dtor構造函數和析構函數

字元串有兩種:

一種是前面有一個常數,用于記錄字元串的長度,此字元串的末尾沒有結束符号。

另一種是字元串的末尾有結束符号,字元串的開頭沒有用于記錄字元串長度的常數。

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他
C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

new就是配置設定記憶體,配置設定了一個字元的記憶體。

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

配置設定了一個字元的記憶體,然後把結束符傳進來,這樣就形成了一個空字元串

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

strlen是一個函數,擷取字元串的長度(strlen是計算機C語言函數,計算字元串s的(unsigned int型)長度,不包括'\0'在内)你的class裡面有指針,你多半是要做動态配置設定。是以你要在他生命結束前,調用析構函數,把記憶體釋放掉)

拷貝指派函數

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

如圖中的紅框①②③,是要把右手裡面的東西拷貝指派給左邊的步驟:

a)清空左邊的東西

b)申請和右邊一樣大的記憶體空間

c)拷貝

如果沒有上面那句檢測自我指派(

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

),會出現如下情況:

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

檢測是否為自我指派,不僅僅是為了效率,還是為了安全性。

output 函數

為了列印類中的東西,我們要重載操作符 "<<",由于成員函數有預設this指針,如果将重載"<<"設定成成員函數,那麼變量的位置要發生改變,這不符合人們的使用規範,是以,重載"<<"要設定成全局函數。

ostream& operator<<(ostream& os, const String& str)
{
   os << str.get_c_str();
   return os;
}
           

任何東西,隻要你能直接丢給cout,你就直接丢給他輸出好了。先看一下整體代碼:

#ifndef __MYSTRING__
#define __MYSTRING__

class String
{
public:                                 
   String(const char* cstr=0);                     
   String(const String& str);                    
   String& operator=(const String& str);         
   ~String();                                    
   char* get_c_str() const { return m_data; }
private:
   char* m_data;
};

#include <cstring>

inline
String::String(const char* cstr)
{
   if (cstr) {
      m_data = new char[strlen(cstr)+1];
      strcpy(m_data, cstr);
   }
   else {   
      m_data = new char[1];
      *m_data = '\0';
   }
}

inline
String::~String()
{
   delete[] m_data;
}

inline
String& String::operator=(const String& str)
{
   if (this == &str)
      return *this;

   delete[] m_data;
   m_data = new char[ strlen(str.m_data) + 1 ];
   strcpy(m_data, str.m_data);
   return *this;
}

inline
String::String(const String& str)
{
   m_data = new char[ strlen(str.m_data) + 1 ];
   strcpy(m_data, str.m_data);
}

#include <iostream>
using namespace std;

ostream& operator<<(ostream& os, const String& str)
{
   os << str.get_c_str();
   return os;
}

#endif
           

注意到 get_c_str函數的傳回值是char *,剛剛好可以直接給cout進行輸出,是以我們寫了get_c_str函數來進行輸出。

堆棧與記憶體管理

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

stack object 的生命周期

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

static local object

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

global object 的生命周期

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

heap object 的生命期

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

new——先配置設定記憶體,後調用構造函數

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

new的動作分解:

a)調用 operator new 函數來配置設定記憶體(operator new 底層調用的是malloc)。對應的,上圖配置設定出

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

b)第二個動作把我們建立的變量做一個類型轉換

c)通過指針調用構造函數Complex(注意:構造函數在類裡面,是以是成員函數,會有this指針。誰調用成員函數,this指針就指向誰。是以,上圖中的第三步完整的寫法應該是如圖所示的形式:

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

這裡的this指針指向了pc)

delete:先調用析構函數,再釋放記憶體

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

動态配置設定記憶體塊 in VC

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

根據上圖第一個矩形(debug模式下的情況):

1、new  一個複數會獲得的記憶體是 8 byte(上圖中第一個矩形草綠的部分)。

2、在調試模式下,你會得到灰色的部分,上面每一格是4byte(即

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

),一共 4*8=32個位元組。

3、還會得到草綠色矩形下面的那一個 4byte(即

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

4、上下兩個磚紅色的矩形區域是cookie

記憶體一共需要  8+(32+4)+(4*2)=52,而VC給你配置設定的記憶體塊一定是16的倍數(現在不提為什麼),是以填補了三個深綠色的填補物pad

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

看起來配置設定很浪費,但是這是必要的浪費,因為回收的時候,作業系統需要依據這裡的資訊來進行回收。

根據上圖中第二個矩形(release 模式):

配置設定記憶體8byte,加上上下cookie剛剛好16byte,是以無需調整,無需添加填補物pad。

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

上下cookie的作用

記錄整塊給你的記憶體大小,便于系統回收,讓系統知道回收多大記憶體。

讓我們看一下cookie上記錄的數字——000000041,對于第一個矩形,記憶體一共配置設定了64byte,64的16進制表示是40,而cookie上的數字是41,為什麼呢?40借助最後一個bit,最後一位,标志我這塊記憶體是給出去了還是收回來了。這裡是給出去了,對于程式來說是獲得了,是以最後一位标志位1,是以是41。

同理,對于第二個矩形,系統給出的記憶體是16byte,16的16進制是10,這裡cookie上寫的是11,因為這是程式獲得的記憶體。

為什麼可以借最後一個bit來标志這一塊記憶體是給出了還是收回了?

因為配置設定的記憶體都是16的倍數,16的倍數最後四個bit都是0,我們就可以借一位來表示記憶體的狀态。

為什麼array new 要搭配 array delete

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

分析上圖中第一個矩形(debug模型下的array new配置設定的記憶體分析)

1、複數申請的數組長度是3,是以申請了三塊記憶體(

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

2、在調試模式下要加上那個header,上面是32下面是4(即

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

3、加上向下cookIe(

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

4、在VC中(别的編譯器不明),會用一個4byte的記憶體記錄數組的長度,即圖中的

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

是以記憶體配置設定為 8*3+32+4+4*2+4=72 ,湊16的倍數,是以配置設定記憶體為80。

array new要搭配array delete

否則會造成記憶體洩漏。讓我們看看是哪一種記憶體洩漏

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

記憶體洩漏的是動态配置設定的記憶體。

寫不寫 [],清空的記憶體大小是相等的,因為cookie上有記錄。但是不寫 [] ,編譯器不知道要對每一個對象進行分别的析構。析構的時候,先分别析構各個記憶體對象,然後再析構母體的那個地方(

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

)。發生記憶體洩漏的是這裡(

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

複習string的實作過程

設計一個class,我總是要去思考我需要什麼樣的資料。由于不知道字元串的長度,是以大部分人設計字元串這種類中的資料都是在裡面放一根指針,将來要配置設定多大的字元串内容,就動态地去配置設定字元串的大小,用new的方式去動态配置設定一塊記憶體(在32位的平台裡面,一根指針占記憶體是4位元組,是以不管你裡面字元有多長,字元串本身就4個位元組的記憶體)

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他
C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

Class裡面帶指針,是以我要關注三個重要的函數:

拷貝構造:他是一個構造函數,是以沒有傳回值。他要有一個拷貝藍本,藍本就是他自己(傳入reference是可以的,又因為我們不會改變藍本,是以前面可以加一個const)

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

拷貝指派:指派是要把來源段的拷貝到目的端,是以涞源段的内容和拷貝構造是相同的(是以他傳入的參數和拷貝構造的參數是相同的)

因為傳入的值我們不打算去改變他,是以前面加一個const。

拷貝指派的傳回值(要不要return by reference,要看函數執行所傳回的結果是不是放在裡local object中,隻要不是local object,就可以傳reference)

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

析構函數:

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

輔助函數:我們希望把最後的結果丢給cout來輸出到螢幕上(加了const是因為不會改變資料)

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他
C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他
C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

拷貝指派函數:

涞源段拷貝到目的端,目的端是已經存在的東西,是以

第一個動作應該是把目的端的記憶體清空

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

第二個動作是重新配置設定一塊夠大的空間:

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

第三個動作是把來源端拷貝到目的端:

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

接下來要思考指派之後的傳回值(如果不寫傳回值的話,連串的指派行為就會受限)

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他
C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

&str得到的是一根指針

String&是引用

擴充補充:類模闆、函數模闆以及其他

進一步補充:static

誰調用我,誰就是那個this pointer,是以c的位址就是this pointer

成員函數有一個隐藏的參數this pointer,但是我們不能寫進去,這個是編譯器幫我們寫進去

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他
C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

靜态資料:加入了static的資料,就跟對象脫離了,他不屬于對象,他在記憶體的某一個區域單獨有一份,我們不必知道他在那裡,反正後面的代碼能夠找得到就好了

靜态函數:他的身份跟一般的成員函數字記憶體中是相同的,我們所指的相同指的是他也在記憶體中隻有一份。函數在記憶體中當然隻有一份,不可能因為你建立了好幾個對象,就有好幾個函數

靜态函數跟一般函數的差别就在于,靜态函數沒有this pointer。是以靜态函數如果去處理資料,他隻能去處理靜态的資料。

例子:

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

進一步補充:把ctors(構造函數)放在private區

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他
C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

當我們希望寫的class隻産生一個對象的時候,可以這麼用。

把構造函數寫在private裡面,這樣外界就無法再建立對象。

這麼寫有個缺陷,就是如果外界不需要這個資料,這個資料依然存在,這樣會造成記憶體的浪費。更好的寫法如下:

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他
C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

進一步補充:cout

為什麼cout可以接受任何類型的資料,因為裡面重載了很多

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

進一步補充:類模闆

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

模闆會造成代碼的膨脹,但是這并不是缺點,因為你确實是需要這種類型的函數,即使不用模闆,你也要寫出來

進一步補充:function template,函數模闆

類模闆在用的時候要明确指出類型(

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

),函數模闆則不需要,因為編譯器會做實參的推導(argument deduction)

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

進一步補充:namespace

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

namespace等同于你把你的東西都封鎖在這個命名空間裡了,這樣就不會打架。

Using directive(使用指令):等同于你把封鎖打開,調用的時候就用寫全名(e.g std::cin)了,可以直接寫cin

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

Using declaration:一行一行的打開,不是全開,因為裡面東西可能會很多

C++面向對象進階程式設計(上) 第二周 侯捷三大函數——拷貝構造、拷貝指派、析構函數堆棧與記憶體管理擴充補充:類模闆、函數模闆以及其他

或者是都不打開,就每一步都規規矩矩的寫全名

繼續閱讀