?
?
??? 🔥🔥?個人主頁?點擊🔥🔥
每文一詩? 💪🏼
????????往者不可諫,來者猶可追——《論語·微子篇》
? ? ? ??譯文:過去的事情已經無法挽回,未來的歲月還可以迎頭趕上。???
目錄
C++內存模型
new與delete動態分配內存
動態分配單個變量(例如; int* ptr = new int(10))
動態分配數組(例如 int* arr = new int[5] )
分配內存失敗的情況
一維數組與指針
使用數組名/和數組名加下標訪問數組中元素及其地址
使用指針訪問數組中元素及其地址
二維數組與指針
行指針
函數指針
指針用作函數參數
用函數指針來傳遞函數
?
C++內存模型
?
????????棧區:由編譯器自動管理,用于存儲局部變量、函數參數和返回地址。每當調用一個函數時,會在棧上分配一塊新的棧幀,函數執行完畢后,棧幀自動釋放。
????????堆區:也被叫做自由存儲區,由程序員手動管理。通過new
操作符在堆上分配內存,使用delete
操作符釋放內存。
????????數據存儲區:用于存放全局變量和靜態變量。在程序啟動時分配內存,程序結束時釋放內存。
????????代碼區:用于存放程序的可執行代碼。代碼區是只讀的,防止程序在運行過程中被意外修改。
棧區和堆區區別
- 管理方式不同:棧區是系統自動管理的,在離開作用域時,會自動釋放
- 空間大小不同:棧區大小操作系統預先設定,一般只有8M。如果在棧上分配的內存超過了棧的最大容量,會導致棧溢出(Stack Overflow)錯誤;堆區空間僅受限與物理內存空間,所以相對較大。
- 效率不同:棧區內存的分配和釋放速度非常快,因為它只需要移動棧指針;而堆區內存分配和釋放需要操作系統復雜的操作。
new與delete動態分配內存
new:動態分配內存
delete:? 釋放分配的內存
在堆區動態分配內存的步驟:
- 聲明一個指針
- 用new運算符向系統的堆區申請一塊內存,并用指針指向這塊內存
- 通過解引用的方式,來取出這塊內存中的值
- 當這塊內存不用時,需用delete來釋放它
動態分配內存的兩類:
動態分配單個變量(例如; int* ptr = new int(10))
int* p = new int(2);std::cout<<"*p的值:"<<*p<<std::endl;//打印內存中的值delete p;
解析:
- int* p = new int(2);
??????? 首先在堆區new申請了一塊內存,這塊內存是int型,接著初始化該內存的值為2,最后返回新分配內存的地址,用一個int類型的指針來指向他。
- delete p;
????????釋放這塊內存
動態分配數組(例如 int* arr = new int[5] )
int* arry = new int[8];for(int i=0;i<8;i++){*(arry+i) = i;std::cout<<"第"<<i<<"個值"<<*(arry+i)<<std::endl;}delete[] arry;
解析:
int* arry = new int[8];
??????? 首先new申請了一塊內存,這塊內存存儲的是一個含有8位int型數據的數組,最后返回新分配內存的地址,用一個int類型的指針來指向他。這里的arry代表數組的首地址。
- ?*(arry+i) = i;
??????? 通過循環和解引用的方式為數組中每個數賦值。
delete[] arry;
????????釋放這塊內存
分配內存失敗的情況
??????? 如果需要分配含有大量的數據的數組,那么棧空間上分配是遠遠不夠的,需要在堆區分配。但是如果內存分配失敗,則會導致程序崩潰,但是我們不希望這樣,我們可以在內存分配失敗的時候捕捉到這個錯誤。
使用std::nothrow
int main(int argc, char const *argv[])
{int* arry = new(std::nothrow)int[100000];if(arry == nullptr)std::cout<<"分配內存失敗"<<std::endl;else{std::cout<<"分配內存成功"<<std::endl;arry[99999] = 0;delete[] arry;}return 0;
}
?
一維數組與指針
使用數組名/和數組名加下標訪問數組中元素及其地址
int arry[3] = {2,4,6};
std::cout<<"數組"<<std::endl;
std::cout<<arry<<std::endl;
std::cout<<&arry[0]<<std::endl;
std::cout<<arry+1<<std::endl;
std::cout<<arry+2<<std::endl;
std::cout<<arry[0]<<std::endl;
std::cout<<arry[1]<<std::endl;
std::cout<<arry[2]<<std::endl;
解析:
- 數組的名稱/數組第一個元素的地址 是同一個地址
- 數組名+n:數組第n個元素的地址
- 數組名[n]:數組第n個元素的內容
使用指針訪問數組中元素及其地址
int* p = arry;
std::cout<<"指針"<<std::endl;
std::cout<<p<<std::endl;
std::cout<<p+1<<std::endl;
std::cout<<p+2<<std::endl;
std::cout<<*(p)<<std::endl;
std::cout<<*(p+1)<<std::endl;
std::cout<<*(p+2)<<std::endl;
解析:
????????如果將數組名稱賦給一個指針變量,實際上是將數組的首地址賦給了指針。
- 指針+n:數組第n個元素的地址
- *(指針+n):數組第n個元素的內容
對于C++而言
數組名[下標] 解釋為 *(數組名首地址+下標)
地址[下標] 解釋為 *(地址+下標)
輸出
兩者是一樣的
二維數組與指針
在講二維數組之前,有必要去介紹對一個一維數組名取地址
void func2()
{int a[3] = {6,7,8};std::cout<<"數組第一個元素的地址:"<<a<<std::endl;std::cout<<"數組第一個元素的地址+1:"<<a+1<<std::endl;std::cout<<"數組的地址:"<<&a<<std::endl;//即為地址的地址,是一個行指針std::cout<<"數組的地址+1:"<<&a+1<<std::endl;int (*p)[3] = &a;//正確// int *p2 = &a;//錯誤S
}
解析:
?????? 我們都知道數組名a是代表數組第一個元素的地址,但是&a是數組的地址,雖然a和&a的地址是相同的,但是二者有著不同的類型。
??????? a的類型是 int*
??????? &a的類型是 int(*p)[],即行指針。
為了證明a和&a有著不同的含義,我們同時對兩個地址加1測試
輸出
- ?可見數組第一個元素的地址+1后,實際上是加了4,對于16進制,c后是d,e,f,0然后進位a變為b,所以是ac變b0
- 而數組的地址+1后,發現并沒有+4,而是+12,12是3*4得來的,因為數組有3個int型數據,每個數據占4個字節。
- 這也是行指針的作用,行指針+1后,實際上加上的是這一行數組組成的數組的總長度。
行指針
對于二維數組而言。
行指針格式: 數據類型 (*p)[列大小]
例如
int arry[2][3] = {{1,2,3},{4,5,6}};
int (*p)[3] = arry;
#include<iostream>int main(int argc, char const *argv[])
{int arry[2][3] = {{1,2,3},{4,5,6}};int (*p)[3] = arry;// 這種方式是一個行指針,也就是說該指針p指向的是二維數組中第一個包含三個int型數據的數組的地址// 對該地址進行解引用,就會得到該數組的首地址,再次解引用就會得到數組中具體的值。std::cout<<**p<<std::endl;//p為二維數組中每一行數組的地址,*p得到數組第一個元素的地址,**p得到數組元素的值std::cout<<*(*(p+1))<<std::endl;//p為二維數組中第0行數組的地址,再加1得到第1行數組的地址,解引用為第一行數組的第一個元素的地址,再解引用位第一個元素的值。std::cout<<*(*(p+1)+1)<<std::endl;//*(p+1)為第一行數組的第一個元素的地址,*(p+1)+1:再加1為第一行數組的第二個元素的地址,再解引用為第二個元素的值。std::cout<<*(p[1]+1)<<std::endl;//p[1]在c++中被解釋為*(p+1)// 個人理解:// 地址 + n:// 應看這個地址的類型,即這個指針的類型,如果這個指針是行指針,那么加1就是加上這一行數組的總共的字節數//例如p+1,p是行指針,存儲的是每一行數組的地址,加1其實是加上了4*3=12個字節//如果這個指針是普通指針,那么加1就是加上這個數組的單個元素的字節數//例如*(p+1)+1,*(p+1)是普通指針,存儲的是數組第一個元素的地址,加1其實是加上了4個字節return 0;
}
輸出
函數詳解:
int (*p)[3] = arry;
????????這種方式是一個行指針,也就是說該指針p指向的是二維數組中第一個包含三個int型數據的數組的地址
- std::cout<<**p<<std::endl;
????????p為二維數組中每一行數組的地址,*p得到數組第一個元素的地址,**p得到數組元素的值
- std::cout<<*(*(p+1))<<std::endl;
????????p為二維數組中第0行數組的地址,再加1得到第1行數組的地址,解引用為第一行數組的第一個元素的地址,再解引用位第一個元素的值。
- std::cout<<*(*(p+1)+1)<<std::endl;
????????*(p+1)為第一行數組的第一個元素的地址,*(p+1)+1:再加1為第一行數組的第二個元素的地址,再解引用為第二個元素的值。
- std::cout<<*(p[1]+1)<<std::endl;
????????p[1]在c++中被解釋為*(p+1)
個人理解:
對于 地址 + n:
- ?應看這個地址的類型,即這個指針的類型,如果這個指針是行指針,那么加1就是加上這一行數組的總共的字節數。例如p+1,p是行指針,存儲的是每一行數組的地址,加1其實是加上了4*3=12個字節
- 如果這個指針是普通指針,那么加1就是加上這個數組的單個元素的字節數。例如*(p+1)+1,*(p+1)是普通指針,存儲的是數組第一個元素的地址,加1其實是加上了4個字節
函數指針
指針用作函數參數
如果參數是一個 數組的話,必須傳遞數組的長度
下面用代碼解釋原因
#include<iostream>
void func(int* arr)
{std::cout<<"數組長度2="<<sizeof(arr)<<std::endl;for(int i =0;i<sizeof(arr)/sizeof(int);i++){std::cout<<*(arr+i)<<std::endl;}}
int main(int argc, char const *argv[])
{int arry[3] = {2,4,6};func(arry);std::cout<<"數組長度1="<<sizeof(arry)<<std::endl; return 0;
}
輸出
????????在函數func中,參數是一個指針變量,使用sizeof運算符的時候,會返回這個指針的大小,而指針的大小是一個常數8(在64位操作系統);而在main函數中,arry是一個數組名,在使用sizeof運算符的時候,會返回這個數組的大小。
??????? 所以在func函數中,sizeof(arr)/sizeof(int)的值是8/4等于2,所以數組只打印了索引為0和1的值。
正確的做法是參數中加上數組長度
#include<iostream>
void func(int* arr,int len)
{std::cout<<"數組長度2="<<sizeof(arr)<<std::endl;for(int i =0;i<len;i++){std::cout<<*(arr+i)<<std::endl;}}
int main(int argc, char const *argv[])
{int arry[3] = {2,4,6};func(arry,sizeof(arry)/sizeof(int));std::cout<<"數組長度1="<<sizeof(arry)<<std::endl; return 0;
}
輸出
用函數指針來傳遞函數
用途:可以用一個函數來調用別的函數.
做法:將該函數的參數設置為要調用函數的指針
聲明一個函數指針:
格式:返回值類型 (*函數指針名)(參數1,參數2)
通過函數指針調用函數
函數指針名(參數1,2)
在C++中,函數的名稱就是函數的地址
#include<iostream>int func2(int m)
{std::cout<<"函數2"<<std::endl;return m+1;
}
int func3(int m)
{std::cout<<"函數3"<<std::endl;return m-1;
}
void func(int(*pf)(int))
{std::cout<<"準備工作"<<std::endl;int n = pf(3);std::cout<<"返回值"<<n<<std::endl;std::cout<<"收尾工作"<<std::endl;
}
int main(int argc, char const *argv[])
{func(func2);func(func3);return 0;
}
函數解析:
這段代碼實現了函數傳遞函數,通過修改參數可以讓不同的函數被執行。??
???? 主要看的是void func(int(*pf)(int))
這個函數func的參數是一個函數指針
這個是將需要傳遞的函數的名稱傳遞過去,函數的名稱就是函數的地址
- 名稱:pf
- 返回值:int
- 參數:int 可不加變量名
func(func2);func(func3);
?輸出
若本文對你有幫助,你的支持是我創作莫大的動力!
??? 🔥🔥?個人主頁?點擊🔥🔥
?