天天看點

C++之類成員間指針處理

在一個類中,如果類沒有指針成員,一切友善,因為預設合成的析構函數會自動處理所有的記憶體。但是如果一個類帶了指針成員,那麼需要我們自己來寫一個析構函數來管理記憶體。

在<<c++ primer>> 中寫到,如果一個類需要我們自己寫析構函數,那麼這個類,也會需要我們自己寫拷貝構造函數和拷貝指派函數。

下面我們先定義一個類頭檔案帶指針:

HasPtr.h

#ifndef CLASSPOINTER_HASPTR_H
#define CLASSPOINTER_HASPTR_H


class HasPtr {
public:
    HasPtr(int i, int *p);   //構造函數

    ~HasPtr();  //析構函數

    int get_ptr_value();

    void set_ptr_value(int *p);

    int get_val();

    void set_val(int v);

private:
    int val;
    int *ptr;
};


#endif //CLASSPOINTER_HASPTR_H
           

HasPtr.cpp類的實作:

#include "HasPtr.h"
#include <iostream>

using namespace std;

HasPtr::HasPtr(int i, int *p) {
    val = i;
    ptr = p;
}

int HasPtr::get_ptr_value() {
    return *ptr;
}

void HasPtr::set_ptr_value(int *p) {
    ptr = p;
}

int HasPtr::get_val() {
    return val;
}

void HasPtr::set_val(int v) {
    val = v;
}

HasPtr::~HasPtr() {
    cout << "Destructor of HasPtr!" << endl;
}
           

main.cpp

#include <iostream>
#include "HasPtr.h"

using namespace std;

int main() {
    int temp = 100;
    HasPtr ptr(2, &temp);
    cout << ptr.get_ptr_value() << endl;
    cout << ptr.get_val() << endl;
    system("PAUSE");
    return 0;
}
           

結果:

100

2

請按任意鍵繼續. . .

按下任意鍵:

100

2

請按任意鍵繼續. . .

Destructor of HasPtr!

Process finished with exit code 0

執行主函數,我們發現在按下任意鍵時,析構函數自動調用了。

在main方法中,stack(棧)上定義好了一個類的執行個體化對象,當退出main函數之後,自動調用類的析構函數。

如果我們将對象的聲明改為動态的,也即是放在heap(堆)上,會有什麼特點,是以main函數我們将改為:

main.cpp

#include <iostream>
#include "HasPtr.h"

using namespace std;

int main() {
    int temp = 100;
    HasPtr *ptr = new HasPtr(2, &temp);
    cout << ptr->get_ptr_value() << endl;
    cout << ptr->get_val() << endl;
    system("PAUSE");
    return 0;
}
           

結果:

100

2

請按任意鍵繼續. . .

按下任意鍵:

100

2

請按任意鍵繼續. . .

Process finished with exit code 0

細心的同學已經發現,這時候的析構函數并沒有執行。要怎麼做它才會執行呢?我們可以嘗試删除指針,在return前增加delete (ptr):

main.cpp

#include <iostream>
#include "HasPtr.h"

using namespace std;

int main() {
    int temp = 100;
    HasPtr *ptr = new HasPtr(2, &temp);
    cout << ptr->get_ptr_value() << endl;
    cout << ptr->get_val() << endl;
    system("PAUSE");
    delete(ptr);
    return 0;
}
           

結果:

100

2

請按任意鍵繼續. . .

按下任意鍵:

100

2

請按任意鍵繼續. . .

Destructor of HasPtr!

Process finished with exit code 0

這時我們發現析構函數執行了!

結論:
  1. 當一個對象在stack(棧)上時,析構函數自動調用。
  2. 方一個函數在heap(堆)上時,需要用delete語句,析構函數才會被執行。

下面我們示範在棧上删除指針會有什麼發生(僅需修改部分代碼即可):

HasPtr.cpp

#include "HasPtr.h"
#include <iostream>

using namespace std;

HasPtr::HasPtr(int i, int *p) {
    val = i;
    ptr = p;
}

int HasPtr::get_ptr_value() {
    return *ptr;
}

void HasPtr::set_ptr_value(int *p) {
    ptr = p;
}

int HasPtr::get_val() {
    return val;
}

void HasPtr::set_val(int v) {
    val = v;
}

HasPtr::~HasPtr() {
    cout << "Destructor of HasPtr!" << endl;
    delete(ptr); // 析構函數中删除指針
}
           

main.cpp

#include <iostream>
#include "HasPtr.h"

using namespace std;

int main() {
    int temp = 100;
    HasPtr ptr(2, &temp);
    cout << ptr.get_ptr_value() << endl;
    cout << ptr.get_val() << endl;
    system("PAUSE");
    return 0;
}
           

結果:

100

2

請按任意鍵繼續. . .

Destructor of HasPtr!

Process finished with exit code 0

正常列印,那也沒有問題啊。由于本人測試實在CLion裡面執行的,編譯器具有保護機制,故而沒有出現錯誤。當嘗試使用指令行執行後,正常列印,但是抛出了類似這樣的錯誤。

C++之類成員間指針處理

由此看來delete不能删除stack上的指針。

下面我們使用動态參數來測試(僅需改變main.cpp):

main.cpp

#include <iostream>
#include "HasPtr.h"

using namespace std;

int main() {
    int *temp = new int(100);
    HasPtr ptr(2, temp);
    cout << ptr.get_ptr_value() << endl;
    cout << ptr.get_val() << endl;
    system("PAUSE");
    return 0;
}
           

結果:

100

2

請按任意鍵繼續. . .

Destructor of HasPtr!

Process finished with exit code 0

無論在哪裡執行,都不會抛出錯誤,因為指針的删除是在堆上操作的。

結論:
  1. delete語句不能夠直接删除stack上的指針值。
  2. delete語句隻能删除heap上的指針值,也就是new的對象。

預設拷貝函數和預設指派操作:

這裡我們調用預設的構造函數和預設的指派操作,看看會出現什麼,為了友善檢視,我在析構函數中列印了目前對象的位址,以及在main方法中列印了對象位址,這樣就可以看到哪個對象調用了析構函數:

HasPtr.cpp

#include "HasPtr.h"
#include <iostream>

using namespace std;

HasPtr::HasPtr(int i, int *p) {
    val = i;
    ptr = p;
}

int HasPtr::get_ptr_value() {
    return *ptr;
}

void HasPtr::set_ptr_value(int *p) {
    ptr = p;
}

int HasPtr::get_val() {
    return val;
}

void HasPtr::set_val(int v) {
    val = v;
}

HasPtr::~HasPtr() {
    cout << "Destructor of HasPtr!" << this << endl;
    delete(ptr);
}
           

main.cpp

#include <iostream>
#include "HasPtr.h"

using namespace std;

int main() {
    int *temp = new int(100);
    HasPtr ptr(2, temp);
    cout << "ptr------------------>" << &ptr << endl;
    cout << ptr.get_ptr_value() << endl;
    cout << ptr.get_val() << endl;

    HasPtr ptr2(ptr);
    cout << "ptr2----------------->" << &ptr << endl;
    cout << ptr2.get_ptr_value() << endl;
    cout << ptr2.get_val() << endl;

    HasPtr ptr3 = ptr;
    cout << "ptr3----------------->" << &ptr << endl;
    cout << ptr3.get_ptr_value() << endl;
    cout << ptr3.get_val() << endl;

    system("PAUSE");
    return 0;
}
           

結果:

ptr------------------>0x28ff24

100

2

ptr2----------------->0x28ff24

100

2

ptr3----------------->0x28ff24

100

2

請按任意鍵繼續. . .

Destructor of HasPtr!0x28ff14

Destructor of HasPtr!0x28ff1c

Destructor of HasPtr!0x28ff24

Process finished with exit code 0

拷貝預設構造函數和指派操作,将會直接複制指針值,不是指針所指向的值。是指針的變量,也是指針的位址。

調用delete指針值時,删除的值在不同調用時候不同,也就是說明指針值指向的記憶體位址在調用的時候是重新配置設定的。

繼續閱讀