1. 程式人生 > 其它 >C++物件記憶體模型延伸理解

C++物件記憶體模型延伸理解

技術標籤:C++c++多型

我們是面向什麼程式設計

在程式設計時,時常會說我們是面向過程程式設計,還是面向物件程式設計。其它這是把底層記憶體處理過程,通過編譯器來抽象實現。事管是面向什麼程式設計,其它底下都是面向記憶體程式設計。

面處物件程式設計時的記憶體模型

在程式設計過程中,如c++等高階語言都是面向物件程式設計的方式實現。都要涉及到物件的記憶體模型問題。如果我例項化一個物件,那這個物件在記憶體中是如何存放的,如果這個物件還存在繼承關係,那繼承的基類與派生類在記憶體中又是怎麼樣存放,存在什麼樣的關係呢。

本篇只說幾個比較簡單的問
1) 繼承中基類的物件與派生類物件在記憶體中存放。
2)繼承關係中虛擬函式在記憶體如何存放,基類與派生類虛擬函式是什麼關係。

3)多型的實現原理

程式碼:

#include <iostream>
#include <string>
#include <thread>
#include <string.h>
#include <vector>
#include <iterator>
#include <algorithm>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fstream>
using namespace std; class Base{ public: Base(){}; Base(int age, const char* name); void showBaseName(){cout << "This is Base name" << endl;} virtual void virtualShowBaseName(){ cout << "virtual show Base name:" << this->mName <<
endl; } virtual void virtualShowBaseAge(){ cout << "virtual show Base age:" << this->mAge << endl; } virtual void virtualShowBaseInfo(){ cout << "virtual Base info age:" << this->mAge << endl; } ~Base(){ cout << "Base destry class" << endl; } protected: int showBaseAge(){ return this->mAge; } private: int mAge; const char* mName; }; class inherBase:public Base{ public: inherBase(): Base(110, "zhangsi"){}; inherBase(const char* myName): Base(112, myName){ mName = myName; mAge = 120; } void showinherBasebName(){cout << "inherBaseb name:" << this->mName << endl;} void showBaseName(){cout << "this is myBase name:" << endl;} virtual void virtualShowBaseName(){ cout << "virtual show myBase name:" << this->mName << endl; } virtual void virtualShowMyBaseInfo(){ cout << "virtual inher Base info age:" << this->mAge << endl; } ~inherBase(){ cout << "myBase destry class" << endl; } private: int mAge; const char* mName; }; Base::Base(int age, const char* name){ this->mAge = age; this->mName = name; } int main(int argc, char** argv) { Base* bs = new Base(11, "tudou"); inherBase* ibs = new inherBase("zhangsan"); Base* ms = static_cast<Base*>(ibs); //物件中的型別強制轉換,相當於指標加上作用域,再作記憶體訪問。 puts("------------ reload and virtual demo -----------------"); ms->showBaseName(); ibs->showBaseName(); ms->virtualShowBaseName(); ms->virtualShowBaseInfo(); ibs->virtualShowBaseName(); delete ibs; return 0; }
This is Base name
this is myBase name:
virtual show myBase name:zhangsan
virtual Base info age:112
virtual show myBase name:zhangsan

以簡單的單繼承,沒有虛擬函式時,基類被派生類繼承後,所有的資料及方法都是被派生類繼承了過來,相當於在派生類的記憶體一塊區域存放了一個以基類名為名稱空間的物件;如果派生類中對基類的函式有重寫過,那麼並不會影響基類的函式,因為基類函式名的空間作用域是基類,而派生類重寫的函式作用域是派生類中。彼此其它是在不同的作用域中,所以派生類函式的重寫不會覆蓋基類函式。這就可以理解為什麼從派生類強制轉換成基類時,此時呼叫的函式是基類中的函式,而不是派生類中重寫的函式。

的以有如下的程式碼執行及結果

    inherBase* ibs = new inherBase("zhangsan");
    Base* ms = static_cast<Base*>(ibs); //物件中的型別強制轉換,相當於指標加上作用域,再作記憶體訪問。
    ms->showBaseName();
    ibs->showBaseName();
This is Base name
this is myBase name:

通過記憶體杳看工具也是可看到更清析的記憶體模型排列情況。

單繼承有虛擬函式情況下,因為虛擬函式的存在,所以需要在物件記憶體中建一個表,稱為虛擬函式表。這個虛擬函式表是通過指標來訪問ptrtable,表存放的內容就是虛擬函式指標。這個虛擬函式指標是基類與派生類共享的,其它也是相當於在基類、派生類中使用指標,指向一片記憶體區域,這個記憶體區域存放的是虛擬函式指標(這個共享指標存放在物件記憶體的首地址處)。是基類,派生類使用虛指標來實現記憶體共享的來實現的。所以不管如何的進行指標型別的強制轉換,基類,派生類都可以訪問到虛擬函式表。
注意:因為這個虛擬函式表記憶體區域是共享的,那麼如果是同名的虛擬函式,那麼會虛擬函式指標只能存在一個,那麼基類的虛擬函式就會被派生類覆蓋掉,所以多型時的虛擬函式只能呼叫到基類虛擬函式。如果虛擬函式不重寫,而是增加,那麼基類、派生類的虛擬函式相互不影響。

派生類指標可以訪問基類虛擬函式指標,也可以訪問派生虛擬函式。但基類型別指標是無法訪問到派生類虛擬函式。

    Base* bs = new Base(11, "tudou");
    inherBase* ibs = new inherBase("zhangsan");
    Base* ms = static_cast<Base*>(ibs); //物件中的型別強制轉換,相當於指標加上作用域,再作記憶體訪問。
    
    ms->virtualShowBaseName();
    ibs->virtualShowBaseName(); //基類虛擬函式被覆蓋,只執行派生類虛擬函式。

    ms->virtualShowBaseInfo();
    //ms->virtualShowMyBaseInfo(); //ERROR!!! 這個編譯錯誤,因為基類的作用域無法訪問到派生類的虛擬函式。
    
	ibs->virtualShowBaseInfo();
	ibs->virtualShowMyBaseInfo(); //ibs是派生類指標,可以訪問到基類的虛擬函式,也可以訪問到派生類虛擬函式。

在這裡插入圖片描述

關於多型的原理,是通過物件指標型別的強制轉換來實現的,從上面的分析可以看出,強制轉換後可派生類虛擬函式指標可訪問基類的虛擬函式指標,也可以訪問到派生類的虛擬函式指標的。通過強制轉換成基類指標來訪問到派生類中的成員方法(虛擬函式)叫多型。說白了就是基類與派生類共享記憶體,通過指標強制轉換來實現對共享記憶體的訪問。但因為基類與派生類又有不同的記憶體作用域,所以基類與派生類能訪問到記憶體作用域不同。從而實現了狀態的轉換,操作了不同作用域的記憶體區域。