C++類和動態內存分配

C++ Primer Plus讀書筆記

1.類靜態成員

  • 類中可以有一些靜態變量,不管這個類創建了多少個對象,這個變量始終都是保持不變的,因爲它是靜態變量,這就給一些在所有對象中都有相同值的類提供了方便。

  • 在進行類的變量定義時,將數組定義成指針的形式,可以不在定義時就將大小分配,而是在構造函數中使用new對其分配空間。

2.析構函數的調用

  • 析構函數的調用時機:
    • 當在成員函數中創建對象時,該函數執行完畢之後會調用析構函數!
    • 當對象是動態變量時,執行完定義該對象的程序塊時,將調用該對象的析構函數。
    • 當對象是靜態變量時,程序結束時將調用對象的析構函數。
    • 當對象是用new創建的時,僅當顯示使用delete刪除對象時,析構函數纔會被調用。

3.特殊成員函數

  • C++自動提供的成員函數:

    • 默認構造函數
      • 默認的構造函數裏面是沒有內容的
    • 默認析構函數
      • 類似與默認構造函數,是沒有內容的
    • 複製構造函數

      • 用於將一個對象複製到新創建的對象中,它用於初始化過程中,而不是常規的賦值過程中。它的函數原型爲:

        class_name(const class_name &)
        //ZhuMeng(const ZhuMeng &)
      • 新建一個對象並將其初始化爲現有對象時,複製構造函數將被調用

      • 默認的複製構造函數複製的是成員的值

    • 賦值運算符

      • 這種運算符的原型爲:
        class_name & class_name::operator=(const class_name &);
    • 地址運算符

4.這裏介紹一種錯誤

  • 頭文件
#ifndef ZHUMENG_H_
#define ZHUMENG_H

#include<iostream>

class ZhuMeng
{
private:
    int shi;
    int fen;
    int miao;
    static int numm;
public:
    ZhuMeng();

    ZhuMeng(int , int , int );
    explicit ZhuMeng(int );
    ~ZhuMeng();
    ZhuMeng operator+(ZhuMeng &) const;
    friend std::ostream & operator<<(std::ostream & o, const ZhuMeng & outp);
};

#endif
  • 實現文件
#include"ZhuMeng.h"

int ZhuMeng::numm = 0;

ZhuMeng::ZhuMeng()
{
    shi = 0;
    fen = 0;
    miao = 0;
    numm++;
}

ZhuMeng::ZhuMeng(int shi, int fen, int miao)
{ 
    this->shi = shi;
    this->fen = fen;
    this->miao = miao;
    numm++;
}

ZhuMeng::ZhuMeng(int shit)
{
    this->shi = shit;
    numm++;
}

ZhuMeng::~ZhuMeng()
{
    numm--;
}

ZhuMeng ZhuMeng::operator+(ZhuMeng & aaa) const 
{
    std::cout << this->shi << std::endl;
    ZhuMeng sum;
    sum.shi = this->shi + aaa.shi;
    sum.fen = this->fen + aaa.fen;
    sum.miao = this->miao + aaa.miao;
    return sum;
}

std::ostream & operator<<(std::ostream & o, const ZhuMeng & outp)
{
    o<<outp.shi<<std::endl<<outp.fen<<std::endl<<outp.miao<<std::endl;
    o<<"left num: "<<outp.numm;
    return o;
}
  • 執行文件
#include<iostream>
#include"ZhuMeng.h"
using namespace std;

ZhuMeng operator*(int all, ZhuMeng all_2)
{
    cout<<"success"<<endl;
}

void func(ZhuMeng a)
{
    cout<<a<<endl;
}

int main() 
{
    ZhuMeng default_zhumeng;
    ZhuMeng a1(13, 24, 10);
    ZhuMeng a2;//=a1;          **** #1
    func(default_zhumeng);
    func(a1);
    func(default_zhumeng);
    func(a1);
    func(a1);
    return 0;
}

輸出的結果爲:

0
0
0
left num: 2
13
24
10
left num: 1
0
0
0
left num: 0
13
24
10
left num: -1
13
24
10
left num: -2
  • 下面從以下幾個角度來分析

    1. 這裏的numm在類中作爲靜態變量,初始值定義在cpp文件中,靜態變量在內存中一直存在,作爲某個類所有對象之間共享的一個變量。

    2. numm的值爲什麼會變成負數呢?

      • 在重載<<運算符的時候,將一個ZhuMeng的對象作爲對象傳給這個函數

      • 函數的參數傳遞方式是按值傳遞,因此編譯器內部是將實參的值賦給形參,也就是用到了複製構造函數,這時候會調用實參的析構函數,以及賦值所用到的複製構造函數,並不是使用構造函數,而在C++中默認的賦值運算符是沒有讓numm++的,因此numm的值會-1。因此想要正確地執行所想的功能,這裏應該是定義一個複製構造函數

        ZhuMeng(const ZhuMeng & s)
        {
            numm++;
        
        }
    3. 1部分的代碼有什麼問題呢?

      • 這部分代碼,把等號加上,就是進行了賦值操作,而默認的賦值函數沒有記性numm++操作,同時這個賦值也是不安全的,如果有使用new delete而創建刪除的變量,則很有可能會出現錯誤,因爲指針和內存區域也進行了複製,而每次把對象當做實參進行傳遞之後會調用析構函數,會將前面使用new的變量刪除,同時後面如果使用兩次的話,會重複刪除同一塊區域導致意想不到的錯誤。

      • 因此解決方法是顯式定義賦值運算符(也就是重載 = 運算符)

        ZhuMeng & ZhuMeng::operator=(const ZhuMeng & a)
        {
            numm++;
            //這裏重新定義那些使用new分配空間的變量,並將a的對應值賦給他們即可
            return *this;
        }
    4. 還有一個問題:如果在類的函數中,某些數據是使用new delete函數進行創建的話,則需要在複製構造函數和賦值運算符中創建新的內存區域。如果不這樣做,則每一次調用析構函數的時候,刪除的都是同一個內容(因爲默認的複製和賦值函數都是這樣處理的),當一個變量被delete兩次的時候,就會發生未知的錯誤!!切記。

5.構造函數中使用new時應該注意的事項

  • 如果在構造函數中使用new來初始化指針成員,則需要在析構函數中使用delete

  • new和delete必須相互兼容,new對應delete,new[]對應delete[]

  • 如果有多個構造函數,則必須以同樣的方式使用new,要麼都帶[],要麼都不帶,因爲只有一個析構函數,所有的構造函數都必須與他兼容。
  • 可以在構造函數中將指針初始化爲0(C++11爲nullptr),因爲delete和delete[]都可以用於空指針。
  • 應當定義一個複製構造函數,通過深度複製將一個對象初始化爲另一個對象
  • 應當定義一個賦值運算符,通過深度複製的方式將一個對象複製給另一個對象
  • 下面是幾個錯誤的例子

    String String()
    {
    str = "123";//沒有使用new
    }


    String String(const char * s)
    {
    str = new char;//因爲是字符串,沒有使用[]是錯誤的
    }


    String String(const char * s)
    {
    len = strlen(s);
    str = new char[len+1];//這個是正確的,在長度後面留一個\0的位置
    }

5.關於返回對象

  • 1 返回指向const對象的引用

    • 使用const引用的常見原因是旨在提高效率,但對於何時可以採用這種方式存在一些限制。如果函數返回(通過調用對象的方法或將對象作爲參數)傳遞給它的對象,可以通過返回引用來提高效率。

    • 注意:

      • 返回對象將調用複製構造函數,而返回引用則不會
      • 引用指向的對象應該在調用函數執行時存在
  • 2 返回指向非const對象的引用

    • 重載賦值運算符

      • 這個是爲了提高效率
    • 重載<<運算符(與cout一起使用)

      • 這個是必須這樣做
  • 3 返回對象

    • 如果返回的對象是被調用函數中的局部變量,則不應該按引用的方式返回它,因爲在被調用函數執行完畢時,局部對象將執行其析構函數,因此,當控制權回到調用函數時,引用指向的對象將不再存在。
  • 4 返回const對象

    • 主要是爲了防止某些函數的左值被用於計算,比如a+b=c,將c賦給加法的結果,當然這樣是沒有意義的,如果返回const對象則可能會避免發生這種錯誤。
  • 總結:如果方法或函數要返回局部對象,則應該返回對象,而不是指向對象的引用。在這種情況下,將使用複製構造函數來生成返回的對象。如果方法或函數要返回一個沒有公有複製構造函數的類的對象,則必須返回一個指向這種對象的引用。如果返回引用和返回對象都可以的時候則最好使用返回引用的方法,效率會更高。

6.指向對象的指針

  • 使用new獲取指向對象的指針,使用delete則會調用對象的析構函數
ZhuMeng * pointer = new ZhuMeng();//括號裏針對不同構造函數有不同的寫法
  • 定位new運算符
char * buffer = new char[512];

ZhuMeng *pc3, *pc4;
pc3 = new (buffer) ZhuMeng();
pc4 = new (buffer + sizeof(ZhuMeng)) ZhuMeng();
  • 使用定位new運算符創建對象時,是使用一個新的對象來覆蓋用於第一個對象的內存單元。因此,如上面兩行代碼所示,必須在原有的地址上有一個偏移量,才能保證兩個部分不重疊。如果類動態地爲成員分配內存,則會出現錯誤。

  • 另外一點是使用delete,如果是用定位new運算符創建的對象,則在刪除的時候,如果使用了delete pc3,則刪掉的是buffer,因爲new/delete系統知道已經分配的是512個字節的內存,而對定位new運算符的操作則是一無所知。由於pc3和buffer指向的內容是一樣的,因此使用pc3刪掉的也是buffer。這樣的話,pc3和pc4的析構函數也不會執行,需要顯式地調用析構函數。

  • 使用delete [] buffer的時候是將整個buffer刪除(對應的new [])而不會爲每一pc調用析構函數(pc3和pc4的析構函數都不會被調用)。

pc4->~ZhuMeng();//釋放部分的內存
pc3->~ZhuMeng();

delete [] buffer;//將整個buffer都給釋放掉

7.類的成員初始化列表

  • 成員初始化列表是放在構造函數定義的名稱後面,用冒號指出
ZhuMeng::ZhuMeng(int qs):shi(qs)
{
    //......
}
  • 成員初始化列表就是上面代碼片中的shi(qs),將qs賦給shi。

  • 打個比方,如果shi是一個const int型的常量,則只能給它初始化而不能給他賦值,如果將shi = qs寫在構造函數中,則會報錯,因爲這是賦值,不是初始化,把這個類成員初始化列表寫在構造函數後邊,則會在執行到構造函數之前,也就是創建對象的時候進行初始化,而不是對其進行賦值,這樣就會有很大的好處。

  • 只有構造函數可以使用這種初始化列表的方法,並且對於const類的成員,必須使用這種語法,否則,則必須要在類內進行初始化。另外,被聲明爲引用的類成員也必須要使用這種語法。因爲引用與const類似,都是隻能在聲明的時候進行初始化。

  • 在類內進行初始化與使用初始化列表進行初始化是等價的,也就是下面這兩種描述是相同的。

class ZhuMeng{
    const int quan = 20;
}
ZhuMeng::ZhuMeng():quan(20)
{
    //...
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章