【C++指針】搭建起程序與內存深度交互的橋梁(下)

?

?

??? 🔥🔥?個人主頁?點擊🔥🔥


每文一詩? 💪🏼

????????往者不可諫,來者猶可追——《論語·微子篇》

? ? ? ??譯文:過去的事情已經無法挽回,未來的歲月還可以迎頭趕上。???


目錄

C++內存模型

new與delete動態分配內存

動態分配單個變量(例如; int* ptr = new int(10))

動態分配數組(例如 int* arr = new int[5] )

分配內存失敗的情況

一維數組與指針

使用數組名/和數組名加下標訪問數組中元素及其地址

使用指針訪問數組中元素及其地址

二維數組與指針

行指針

函數指針

指針用作函數參數

用函數指針來傳遞函數


?

C++內存模型

?

????????棧區:由編譯器自動管理,用于存儲局部變量、函數參數和返回地址。每當調用一個函數時,會在棧上分配一塊新的棧幀,函數執行完畢后,棧幀自動釋放。

????????堆區:也被叫做自由存儲區,由程序員手動管理。通過new操作符在堆上分配內存,使用delete操作符釋放內存。

????????數據存儲區:用于存放全局變量和靜態變量。在程序啟動時分配內存,程序結束時釋放內存。

????????代碼區:用于存放程序的可執行代碼。代碼區是只讀的,防止程序在運行過程中被意外修改。

棧區和堆區區別

  1. 管理方式不同:棧區是系統自動管理的,在離開作用域時,會自動釋放
  2. 空間大小不同:棧區大小操作系統預先設定,一般只有8M。如果在棧上分配的內存超過了棧的最大容量,會導致棧溢出(Stack Overflow)錯誤;堆區空間僅受限與物理內存空間,所以相對較大。
  3. 效率不同:棧區內存的分配和釋放速度非常快,因為它只需要移動棧指針;而堆區內存分配和釋放需要操作系統復雜的操作。

new與delete動態分配內存

new:動態分配內存

delete:? 釋放分配的內存

在堆區動態分配內存的步驟:

  1. 聲明一個指針
  2. 用new運算符向系統的堆區申請一塊內存,并用指針指向這塊內存
  3. 通過解引用的方式,來取出這塊內存中的值
  4. 當這塊內存不用時,需用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);

這個是將需要傳遞的函數的名稱傳遞過去,函數的名稱就是函數的地址

?輸出

若本文對你有幫助,你的支持是我創作莫大的動力!

??? 🔥🔥?個人主頁?點擊🔥🔥

?

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/74593.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/74593.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/74593.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

JavaScript創建對象的多種方式

在JavaScript中&#xff0c;創建對象有多種方式&#xff0c;每種方式都有其優缺點。本文將介紹四種常見的對象創建模式&#xff1a;工廠模式、構造函數模式、原型模式和組合模式&#xff0c;并分析它們的特點以及如何優化。 1. 工廠模式 工廠模式是一種簡單的對象創建方式&am…

muduo庫的思路梳理

前言 對于muduo庫源碼的剖析我發現還是有些混亂的&#xff0c;所以這里再次梳理一下muduo網絡庫爭取可以簡單明了 首先對于muduo庫來說&#xff0c;不能想的得太過于復雜&#xff0c;它無非就是一個線程池加上epoll組成的網絡庫 這里我們從用的角度出發理解muoduo網絡庫 #inc…

Keil5 安裝全攻略

Keil5 安裝全攻略 Keil5 是一款廣泛用于嵌入式開發的 IDE&#xff0c;支持多種微控制器架構&#xff08;如 ARM、C51&#xff09;。本文將詳細介紹 Keil5 的安裝步驟、常見問題及解決方法&#xff0c;幫助您快速上手。 1. 安裝前的準備工作 (1) 系統要求 操作系統&#xff1…

C語言do...while語句將數字反轉后輸出

一、題目引入 輸入一個數字,將各位數字反轉后輸出? 參考代碼: 二、分析代碼 接著圖片中的分析 第一 ->a 的值變為12 第二 ->進入while循環條件,a為12不等于0循環才停止(a的值為12,顯然不等于0) 所以繼續進行循環 第三 ->此時b的值為12取各位上的數字(即2) 打印…

優選算法系列(前綴和 _下) k

目錄 五&#xff1a;和為 k 的子數組&#xff08;medium&#xff09; 題目鏈接&#xff1a;560. 和為 K 的子數組 - 力扣&#xff08;LeetCode&#xff09; 解法&#xff1a; 代碼&#xff1a; 六&#xff1a;和可被 K 整除的子數組&#xff08;medium&#xff09; 題目鏈…

mac m3 pro 部署 stable diffusion webui

什么是Stable Diffusion WebUI &#xff1f; Stable Diffusion WebUI 是一個基于Stable Diffusion模型開發的圖形用戶界面&#xff08;GUI&#xff09;工具。通過這個工具&#xff0c;我們可以很方便的基于提示詞&#xff0c;描述一段文本來指導模型生成相應的圖像。相比較通過…

OpenCV圖像拼接(6)根據權重圖對源圖像進行歸一化處理函數normalizeUsingWeightMap()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 cv::detail::normalizeUsingWeightMap 是 OpenCV 中用于圖像拼接細節處理的一個函數。它根據權重圖對源圖像進行歸一化處理&#xff0c;通常用于…

23種設計模式-外觀(Facade)設計模式

外觀設計模式 &#x1f6a9;什么是外觀設計模式&#xff1f;&#x1f6a9;外觀設計模式的特點&#x1f6a9;外觀設計模式的結構&#x1f6a9;外觀設計模式的優缺點&#x1f6a9;外觀設計模式的Java實現&#x1f6a9;代碼總結&#x1f6a9;總結 &#x1f6a9;什么是外觀設計模式…

capl語言基礎語法(二)

1.strncpy&#xff1a;將字符串復制到另一個字符串中。 輸入&#xff1a; dest 是目標字符串。 src 是源字符串。 n 是要復制的最大字符數。 語法&#xff1a; char *strncpy(char *dest, const char *src, size_t n); 例子&#xff1a; strncpy(gStringRep,"",…

QLoRA和LoRA 微調

QLoRA 其實是一種結合了量化和 LoRA 微調技術的統一方法&#xff0c;而不是同時使用兩種不同的微調方式。換句話說&#xff0c;QLoRA 的意思就是&#xff1a;先把大模型的主權重用低精度&#xff08;例如 4-bit&#xff09;量化&#xff0c;從而大幅減少存儲需求&#xff1b;然…

Qt Concurrent 并發 Map 和 Map-Reduce

并發 Map 和 Map-Reduce QtConcurrent::map()會對容器中的每個項目應用一個函數&#xff0c;對項目進行就地修改。QtConcurrent::mapped() 類似于 map()&#xff0c;但它返回的是一個包含修改內容的新容器。QtConcurrent::mappedReduced() 類似于 mapped()&#xff0c;只不過修…

RT-Thread-線程管理

一、線程管理 RT_Thread線程管理主要是實現線程管理和調度&#xff0c;線程分為用戶線程和系統線程。RT_Thread的線程調度器是搶占式的&#xff0c;尋找就緒狀態最高優先級線程。 線程管理的API函數 創建線程函數 rt_thread_t rt_thread_create( const char *name, //線程名稱 …

【CC2530 教程 十二】CC2530 Z-Stack 硬件抽象層

目錄 一、硬件抽象層簡介&#xff1a; &#xff08;1&#xff09;HAL 硬件抽象層是什么&#xff1f; &#xff08;2&#xff09;通俗易懂的解釋&#xff1a; &#xff08;3&#xff09;具體例子&#xff1a; 二、硬件抽象層HAL&#xff1a; &#xff08;1&#xff09;HAL…

Linux如何判斷磁盤是否已分區?

在 Linux 系統中&#xff0c;判斷磁盤是否已分區可通過以下方法實現&#xff1a; 方法 1&#xff1a;使用 fdisk -l 命令 此命令會列出所有磁盤及其分區的詳細信息&#xff1a; sudo fdisk -l輸出解讀&#xff1a; 若磁盤&#xff08;如 /dev/sdb&#xff09;下有類似 /dev/…

《熔化焊接與熱切割作業》考試注意事項

考試前的準備 攜帶必要的證件和材料&#xff1a;考生需攜帶身份證、準考證等有效證件&#xff0c;以及考試所需的焊接工具、材料等。確保證件齊全&#xff0c;避免因證件問題影響考試。 提前檢查焊接設備和工具&#xff1a;在考試前&#xff0c;考生應仔細檢查焊接設備和工具是…

Matlab Hessian矩陣計算(LoG算子)

文章目錄 一、簡介二、實現代碼三、實現效果參考資料一、簡介 圖像的Hessian矩陣用于描述圖像灰度值的二階導數,可以用來分析圖像的局部曲率和變化。例如,在圖像邊緣檢測、特征點檢測等任務中,Hessian矩陣能幫助我們識別圖像的結構。 Hessian矩陣定義 對于二維圖像,Hessian…

selenium之處理彈框(alert、confirm、prompt)

彈框 WebDriver提供了一個API, 用于處理JavaScript提供的三種類型的原生彈窗消息. 這些彈窗由瀏覽器提供限定的樣式.&#xff1b;分別為以下三種 alerts警告框confirm確認框prompt提示框 話不多說&#xff0c;開始實踐下就知道怎么一回事了 alerts 警告框&#xff0c;顯示…

Visual Studio 2019 Qt QML 項目環境搭建常見問題處理方法

在 Visual Studio 2019 運行 Qt/QML 項目比直接使用QtCreator環境麻煩&#xff0c;主要是有qmake 的一些配置項不能在 Visual Studio中設置。下面整理一些常見問題的處理方法&#xff0c;供參考&#xff1a; 搭建VS Qt 環境&#xff0c;在Visual Studios 2019下面安裝 Qt Vis…

【Linux】POSIX信號量與基于環形隊列的生產消費者模型

目錄 一、POSIX信號量&#xff1a; 接口&#xff1a; 二、基于環形隊列的生產消費者模型 環形隊列&#xff1a; 單生產單消費實現代碼&#xff1a; RingQueue.hpp&#xff1a; main.cc&#xff1a; 多生產多消費實現代碼&#xff1a; RingQueue.hpp&#xff1a; main.…

RAG優化:python從零實現GraphRag 一場文檔與知識的“戀愛”之旅

嘿,親愛的算法工程師們,準備好迎接一場文檔與知識的“戀愛”之旅了嗎?今天我們要介紹的 Graph RAG,就像是一位“紅娘”,幫助文檔和知識在圖的世界里找到彼此,擦出智慧的火花! 文章目錄 為什么需要 Graph RAG?Graph RAG 的“戀愛秘籍”準備好了嗎?讓我們開始吧!環境設…