網站首頁 編程語言 正文
文章目錄
- 前言
- 簡單類的內存模型
- 理解靜態成員屬性和方法的特點
- 只有對象才會有內存模型!!
- 成員函數如何獲取對象數據??
- 基類與派生類的內存分配
- 派生類繼承基類
- 類的內存占用情況分析
前言
下面的工具查看對象的內存模型:
- 利用VS查看內存模型
簡單類的內存模型
首先,明白一個類的組成及其內存模型分布。如下圖所示,類的成員中除了普通成員變量
之外,其他的均不存儲在對象的存儲空間中,只存儲在靜態區。
也就是說,一個類可以實例化n個對象,除了普通成員變量分別存儲在n個對象的內存空間中,其他的成員在內存中只有一份。即,一個類的所有對象共享這份數據(靜態成員函數
、普通成員函數
、虛函數
、靜態成員變量
)。
理解靜態成員屬性和方法的特點
很重要的一點技巧:
- static的靜態成員屬性等能通過直接用類加上靜態成員屬性的方式調用,因為它屬于類,不屬于對象。
- 而成員方法只能聲名一個對象(實例化)去利用對象調用(與this指針有關)
只有對象才會有內存模型!!
一定注意:只有對象才會有內存模型,類是沒有對象模型的。類是實例化對象的一種方法,是告訴編譯器如何去在內存中分配內存、初始化,從而構造出一個對象。
類沒有獨立的內存空間,類的成員函數和其他類的成員函數都存放在一起,這個地方叫做代碼區。這些成員函數在 name mangling過程重新命名,然后通過類名來索引函數。
所以,類不存在于內存中,只存在于我們的代碼和編譯器中,是告訴編譯器如何去構造對象的一份說明書。
成員函數如何獲取對象數據??
疑問:對象的成員變量有很多份,但成員函數只有一份,那么成員函數在執行的時候,怎么知道是誰調用的呢?
舉例:
class Test{
public:
static void test1();
int test2() {return m_a + m_b;}
private:
int m_a;
int m_b;
static int s_c;
};
int Test::test2(){
return m_a + m_b;
}
int main(){
Test t1;
Test t2;
int ret1 = t1.test2();
int ret2 = t2.test2();
}
你看,成員函數只有一份,但是對象的成員變量有很多份。由于不是每個對象都保存一份函數,那么成員函數在執行的時候,他怎么知道該取哪個對象的成員呢?
答案:this每個成員函數都有一個隱藏參數 this,存在于每個成員函數的第一個參數,this是對象的內存地址。
int Test::test2(Test* this){
return this->m_a + this->m_b;
}
總結,對象調用成員函數的流程是:
1. 對象調用成員函數
2.通過對象獲取所屬的類名;
3. 根據類名找到對應的成員函數;
4. 將對象的內存空間的地址通過this指針傳給成員函數的this指針;
5. 成員函數就可以通過這個指針找到對應對象的成員。
基類與派生類的內存分配
派生類繼承基類
內存分配時,是在于基類對象不同的內存地址處,按基類的成員變量類型,開辟一個同樣的類型空間,但注意開辟后派生對象的空間,不是復制基類的成員的值,而是僅僅開辟那種成員類型的空間,未初始化時,里面存在的數是不確定的。
針對未初始化的問題可以使用下面兩種方法:
- 在繼承的基類中定義默認無參構造函數去初始化,
- 在子類中使用初始化列表對基類內存空間進行相應的參數初始化。
然后派生類自己定義的成員變量是排在繼承的A類成員下面。
- 如果派生類定義的變量名與基類相同,則此變量覆蓋掉繼承的基類同名變量,
注意,覆蓋不是刪除,也 就是派生類中繼承自基類的成員變量依然存在,而且值也不發生變化。如果想用此繼承自基類的成員變量,則要加
::
,
- 在成員函數中訪問時,直接用
base::i
,即可,- 用派生類的對象a訪問時,如果此繼承自基類的成員變量是對象可訪問的(Public類型),則用
a.base::i
訪問。
#include "head.h"
using namespace std;
class Base{
public:
int i;
int j;
Base(){
i = 1;
j = 1;
}
Base(int a,int b):i(a),j(b){}
};
class Sub:public Base{
public:
int m;
int n;
int i;
int j;
Sub():m(1),n(1),i(1),j(1){}
Sub(int a,int s,int d,int f):m(a),n(s),i(d),j(f){}
Sub(int a,int s,int d,int f,int g,int h):m(a),n(s),i(d),j(f),Base(g,h){}
};
int main(){
Base b(1,2);
Sub s(1,2,3,4,5,6);
cout << s.m << " "<<s.n <<" "<<s.i << " " << s.j <<\
" " << s.Base::i << " "<< s.Base::j << endl;
}
輸出結果為:
1 2 3 4 5 6
從派生類對象繼承的兩個基類變量的值和及基類對象兩個成員變量的值得比較看,足以驗證上述結論:
子類繼承的基類的成員,只是在另一個內存空間內開辟一個這種類型的成員變量,它的值并不是基類的值,編譯器只是負責把這一部分空間類型設置為與基類的類型相同
類的內存占用情況分析
類所占內存的大小是由成員變量(靜態變量除外)決定的,成員函數(這是籠統的說,后面會細說)是不計算在內的。
成員函數還是以一般的函數一樣的存在。a.fun()是通過fun(a.this)來調用的。所謂成員函數只是在名義上是類里的。其實成員函數的大小不在類的對象里面,同一個類的多個對象共享函數代碼。而我們訪問類的成員函數是通過類里面的一個指針實現,而這個指針指向的是一個table,table里面記錄的各個成員函數的地址(當然不同的編譯可能略有不同的實現)。所以我們訪問成員函數是間接獲得地址的。所以這樣也就增加了一定的時間開銷,這也就是為什么我們提倡把一些簡短的,調用頻率高的函數聲明為inline形式(內聯函數)。
(一)
class CBase
{
};
sizeof(CBase)=1;
為什么空的什么都沒有是1呢?
c++要求每個實例在內存中都有獨一無二的地址。//注意這句話!!!!!!!!!!
空類也會被實例化,所以編譯器會給空類隱含的添加一個字節,這樣空類實例化之后就有了獨一無二的地址了。所以空類的sizeof為1
。
(二)
class CBase
{
int a;
char p;
};
sizeof(CBase)=8;
記得對齊的問題。int 占4字節//注意這點和struct的對齊原則很像!!!!!
char占一字節,補齊3字節
(三)
class CBase
{
public:
CBase(void);
virtual ~CBase(void);
private:
int a;
char *p;
};
再運行:sizeof(CBase)=12
C++ 類中有虛函數的時候有一個指向虛函數的指針(vptr),在32位系統分配指針大小為4字節。無論多少個虛函數,只有這一個指針,4字節
。//注意一般的函數是沒有這個指針的,而且也不占類的內存。
(四)
class CChild : public CBase
{
public:
CChild(void);
~CChild(void);
virtual void test();
private:
int b;
};
輸出:sizeof(CChild)=16;
可見子類的大小是本身成員變量的大小加上父類的大小。//其中有一部分是虛擬函數表的原因,一定要知道父類子類共享一個虛函數指針。
(五)
#include
class a {};
class b{};
class c:public a{
virtual void fun()=0;
};
class d:public b,public c{};
int main()
{
cout<<"sizeof(a)"<<sizeof(a)<<endl;
cout<<"sizeof(b)"<<sizeof(b)<<endl;
cout<<"sizeof(c)"<<sizeof(c)<<endl;
cout<<"sizeof(d)"<<sizeof(d)<<endl;
return 0;}
程序執行的輸出結果為:
sizeof(a) =1
sizeof(b)=1
sizeof(c)=4
sizeof(d)=8
前三種情況比較常見,注意第四種情況。類d的大小更讓初學者疑惑吧,類d是由類b,c派生邇來的,它的大小應該為二者之和5,為什么卻是8 呢?這是因為為了提高實例在內存中的存取效率.類的大小往往被調整到系統的整數倍.并采取就近的法則,里哪個最近的倍數,就是該類的大小,所以類d的大小為8個字節.
總結:
空的類是會占用內存空間的,而且大小是1,原因是C++要求每個實例在內存中都有獨一無二的地址。 (一)類內部的成員變量:
普通的變量:是要占用內存的,但是要注意對齊原則(這點和struct類型很相似)。
static修飾的靜態變量:不占用內容,原因是編譯器將其放在全局變量區。 (二)類內部的成員函數: 普通函數:不占用內存。
虛函數:要占用4個字節,用來指定虛函數的虛擬函數表的入口地址。所以一個類的虛函數所占用的地址是不變的,和虛函數的個數是沒有關系的。
原文鏈接:https://blog.csdn.net/weixin_46535567/article/details/124693731
相關推薦
- 2022-03-31 詳解C語言中的Static關鍵字_C 語言
- 2022-07-11 Verilog中$display和$write任務以及格式化輸出
- 2023-07-09 Go語言new與make區別
- 2022-11-20 Python利用pangu模塊實現文本格式化小工具_python
- 2023-12-17 Spring自帶定時器實現定時任務,方法一
- 2022-09-21 Android開發兩個activity之間傳值示例詳解_Android
- 2022-06-26 React?Native?加載H5頁面的實現方法_React
- 2023-06-18 C#如何動態創建lambda表達式_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支