指針深刻理解

指針深刻理解

看完鵬哥講的c語言進階視頻后,又找來C語言深度剖析這本書仔細看了一遍,來進一步鞏固和理解指針這個重點。

1:數組

img

如上圖所示,當我們定義一個數組 a 時,編譯器根據指定的元素個數和元素的類型分配確定大小(元素類型大小*元素個數)的一塊內存,并把這塊內存的名字命名為 a。名字 a 一旦與這塊內存匹配就不能被改變。a[0],a[1]等為 a 的元素,但并非元素的名字。

這里需要注意的是:

1 sizeof(數組名),計算整個數組的大小,sizeof內部單獨放一個數組名,數組名表示整個數組。

  1. &數組名,取出的是數組的地址。&數組名,數組名表示整個數組。除此1,2兩種情況之外,所有的數組名都表示數組首元素的地址。
A)char *p = “abcdef”;
B)char a[] =123456;

例子A中若是想訪問字符串中的e的話,有兩種方法,

(1)*(p+4)這是這種方法,因為p中存放著這塊內存首地址,加上偏移量4,即可訪問到e。

(2)p[4]:編譯器總是把以下標的形式的操作解析為以指針的形式的操作。p[4]這個操作會被解析成:先取出 p 里存儲的地址值,然后加上中括號中 4 個元素的偏
移量,計算出新的地址,然后從新的地址中取出值。也就是說以下標的形式訪問在本質上與以指針的形式訪問沒有區別,只是寫法上不同罷了。

B的話和A同理。

下面繼續來看一段代碼來深刻理解&a和a的區別是啥:

int main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
return 0;
}

首先上面已經說過了除了sizeof(數組名)和&數組名,其余的數組名都是表示數組首元素的地址。

對指針進行加 1 操作,得到的是下一個元素的地址,而不是原有地址值直接加 1。所以,一個類型為 T 的指針的移動,以 sizeof(T) 為移動單位。 因此,對上題來說,a 是一個一維數組,數組中有 5 個元素; ptr 是一個 int 型的指針。

&a + 1: 取數組 a 的首地址,該地址的值加上 sizeof(a) 的值,即 &a + 5sizeof(int),也就是下一個數組的首地址,當前指針已經越過了數組的界限。

ptr這個指針變量存放的是下一個數組的首地址,ptr也就是指向a[5],ptr-1也就是指向a[4],

*(a+1): a,&a 的值是一樣的,但意思不一樣,a 是數組首元素的首地址,也就是 a[0]的首地址,&a 是數組的首地址,a+1 是數組下一元素的首地址,即 a[1]的首地址,&a+1 是下一個數組的首地址。所以輸出 2

在這里插入圖片描述

2:c指針數組與數組指針區別和理解

指針數組和數組指針從基本的漢字表面上去理解,會發現指針數組的主語是數組,數組指針的主語是指針,所以很明顯的第一個區別就是指針數組是數組,用來存放指針。

在這里插入圖片描述

下面接著看一段代碼:

#include <stdio.h>int main()
{char a[5] = { 'A','B','C','D' };char(*p3)[5] = &a;char(*p4)[5] = a;return 0;
}

在這里插入圖片描述

因為char(*p3)[5] = &a ;char(*p4)[5] = a;定義的都是數組指針,指向的數組大小為char[5]類型,雖然&a和a的值是一樣的,但是其表示的意義是不一樣的,這里一樣的原因理解是,但由于&a 和 a 的值一樣,而變量作為右值時編譯器只是取變量的值,所以運行并沒有什么問題。

對上述代碼進行更改,將數組指針指向的數組大小發生更改。

int main()
{
char a[5]={'A','B','C','D'};
char (*p3)[3] = &a;
char (*p4)[3] = a;
return 0;
}

運行結果也好理解:p3+1 和 p4+1 的值就相當于跳過sizeof(char)*(數組指針所指向數組的大小,要是數組大小為3,這里就是3)

在這里插入圖片描述

1:指針數組詳解

其中指針數組的一個應用是可以用一維數組模擬二維數組。

#include <stdio.h>int main()
{int arr1[] = { 1,2,3,4,5,6,7,8,9 };int arr2[] = { 7,8,9,6,5,4,1,2,3 };int arr3[] = { 7,8,9,5,2,1,0,4,6 };int* arr[] = { arr1,arr2,arr3 };//arr中每個元素都為一個指針,指針指向對應的數組;每個元素為對應數組的首地址int i;for (i = 0; i < 3; i++){int j;for (j = 0; j < 9; j++){printf("%d", *(arr[i] + j));//實現對應元素的遍歷;//printf("%d", arr[i][j]);//實現對應元素的遍歷}printf("\n");}return 0;
}

兩種不同的方式來訪問并打印數組元素:

  1. 使用指針運算和解引用操作符 *

    printf("%d", *(arr[i] + j));
    

    這里,arr[i] 獲取指針數組 arr 的第 i 個元素,即指向 arr1arr2arr3 的指針之一。arr[i] + j 計算出指向當前數組第 j 個元素的指針,而 *(arr[i] + j) 解引用該指針,獲取該元素的值。

  2. 使用數組下標訪問:

    printf("%d", arr[i][j]);
    

    這種方式更直觀。arr[i] 獲取指向某個數組的指針,而 arr[i][j] 直接訪問該數組的第 j 個元素。這是因為 arr[i] 被視為指向第 i 個數組的首元素的指針,而 arr[i][j] 就是訪問該指針所指向數組的第 j 個元素。

第二種寫法的原因如下:在C語言中,arr[i][j] 這種雙重下標訪問方式背后的原理與指針算術緊密相關。當你有一個指向指針的指針(或者數組的數組,或者指針數組,如本例所示),這種雙重下標訪問實際上是兩步操作的簡寫:

  1. arr[i] 訪問第 i 個元素,這里的元素是指向 int 的指針(在你的例子中,它指向 arr1arr2arr3)。
  2. [j] 對該指針進行解引用,訪問它所指向數組的第 j 個元素。
1.1理解指針的指針和數組的數組

在C語言中,當你聲明一個如 int* arr[] 的數組時,你創建了一個數組,其每個元素都能存儲一個指向 int 類型的指針。所以,arr 是一個數組,但每個 arr 的元素(比如 arr[0]arr[1]arr[2] 等)是一個指針,指向一個 int 數組。

1.2雙重下標訪問的工作原理

當你使用 arr[i][j] 進行訪問時,arr[i] 首先得到第 i 個數組的首地址(一個指針)。然后,[j] 操作符被應用到這個地址上,它實際上是通過指針算術來完成的:從這個地址開始,移動 jint 的大小的距離,來訪問特定的元素。這是因為在C語言中,數組名或指針被用作數組時,pointer[offset] 等價于 *(pointer + offset)

1.3為什么這樣做是有效的

這種訪問方式之所以有效,是因為C語言設計時就考慮到了這種使用場景。數組和指針在很多情況下是可以互換的。當你聲明一個像 int *arr[] 這樣的指針數組時,C語言允許你通過像訪問二維數組那樣的語法來訪問它,即使它實際上是一個數組的數組或指針的數組。這種設計極大地簡化了對于復雜數據結構的操作,同時保持了代碼的可讀性和易于理解。

簡而言之,arr[i][j] 能夠直接訪問指針所指向數組的第 j 個元素,是因為C語言的設計允許通過指針算術和解引用操作符的組合來簡化這種操作,使得指針數組的操作既直觀又高效。

3:地址的強制轉換

struct Test
{
int Num;
char *pcName;
short sDate;
char cha[2];
short sBa[4];
}*p;

假設 p 的值為 0x100000。 如下表表達式的值分別為多少?

p + 0x1 = 0x___ ?
(unsigned long)p + 0x1 = 0x___?
(unsigned int*)p + 0x1 = 0x___?

在C語言中,當你對一個指針進行算術操作時,如 p + 1,這個操作會根據指針指向的數據類型的大小來移動指針。這意味著,如果 p 是一個指向 struct Test 的指針,那么 p + 1 不是簡單地將 p 的值增加 1,而是增加 sizeof(struct Test) 的大小,這樣 p + 1 就指向了內存中緊接著 p 指向的 struct Test 實例之后的下一個 struct Test 實例的起始位置。

給定的結構體 Test 包含以下成員:

  • 一個 int 類型的 Num,通常是4個字節。
  • 一個 char * 類型的 pcName,指針大小通常是4個字節(在32位架構上)或8個字節(在64位架構上)。
  • 一個 short 類型的 sDate,通常是2個字節。
  • 一個 char 類型的數組 cha[2],總共2個字節。
  • 一個 short 類型的數組 sBa[4],總共8個字節。

根據這些信息,我們可以計算 struct Test 的大小。然而,實際的結構體大小還需要考慮到內存對齊(padding)。內存對齊是編譯器自動進行的,以確保結構體中的每個成員都按照其自然對齊要求放置在內存中,這可能會導致結構體的實際大小比簡單加起來的成員大小要大。,假定 struct Test 的總大小是20字節。這個大小已經考慮了可能的內存對齊。因此,當你做 p + 1 操作時,指針會按照結構體的實際大小(20字節)進行移動。

如果 p 的初始值是 0x100000,那么 p + 1 的計算將是:

0x100000 + sizeof(struct Test) * 1

如果 sizeof(struct Test) 是20字節,那么:

p + 1 = 0x100000 + 20 = 0x100014
(unsigned long)p + 0x1 = 0x100001
(unsigned int*)p + 0x1 = 0x100004

unsigned long將p強制類型轉為無符號長整型,數值不發生改變,但是所屬類型已經發生了改變。

unsigned int*一個指向無符號整型的指針,所以為4個字節。

4:二維數組

實際上內存不是表狀的,而是線性的。見過尺子吧?尺子和我們的內存非常相似。一般尺子上最小刻度為毫米,而內存的最小單位為 1 個 byte。平時我們說 32 毫米,是指以零開始偏移 32 毫米;平時我們說內存地址為 0x0000FF00 也是指從內存零地址開始偏移0x0000FF00 個 byte。既然內存是線性的,那二維數組在內存里面肯定也是線性存儲的。實際上其內存布局如下圖:

char a[3][4];

在這里插入圖片描述

以數組下標的方式來訪問其中的某個元素:a[i][j]。編譯器總是將二維數組看成是一個一維數組,而一維數組的每一個元素又都是一個數組。a[3]這個一維數組的三個元素分別為:a[0],a[1],a[2]。a[i][j]的首地址為&a[i]+j*sizof(char) 寫為指針的形式為:a+i*sizeof(char)*4+j*sizeof(char) ,可以換算成以指針的形式表示:*(*(a+i)+j)。解釋 *(a+i) 解引用得到第 i 行的首地址這一點,可能會有些混淆。對于二維數組 char a[3][4];a 本身可以被視為指向其第一個元素(即第一行 a[0])的指針。這里的每個元素(每一行)本身是一個 char[4] 類型的數組。因此,a 的類型是 char (*)[4],即指向一個含有 4 個字符的數組的指針。當我們說 *(a+i) 時,這里的操作實際上是在進行兩個步驟:

  1. 指針算術運算a+i 計算的是第 i 行的首地址。由于 a 是指向第一行的指針,a+i 實際上是利用指針算術來計算第 i 行的起始地址。這里的 i 乘以的是每行的大小(在本例中是 4 個 char),這是自動完成的,不需要顯式乘以 sizeof(char) 或行的大小。這一步并沒有解引用,只是計算地址。
  2. 解引用*(a+i) 的操作是對計算得到的地址進行解引用。但是,這里的“解引用”可能會造成一些理解上的混淆。在這個上下文中,我們不是在獲取一個 char 值,而是獲取指向第 i 行首元素的指針。這是因為 a+i 已經是一個指向 char[4] 的指針,解引用這個指針實際上給出的是 char[4] 類型的數組的第一個元素的地址,這與直接使用 a[i] 是等價的。
  3. 因此,當我們說“解引用得到第 i 行的首地址”時,我們實際上是在描述獲取到這一行數組的起始點的過程。在 C 語言中,數組名(在這個例子中是 a[i])被用作表達式時,會被轉換(除了 sizeof& 操作符的情況)為指向其第一個元素的指針。所以,*(a+i)a[i] 在這里是等價的,都表示第 i 行的首地址。

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

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

相關文章

突破編程_C++_STL教程( list 的實戰應用)

1 std::list 的排序 1.1 基礎類型以及 std::string 類型的排序 std::list的排序可以通過調用其成員函數sort()來實現。sort()函數使用默認的比較操作符&#xff08;<&#xff09;對std::list中的元素進行排序。這意味著&#xff0c;如果元素類型定義了<操作符&#xff…

身份證識別系統(安卓)

設計內容與要求&#xff1a; 通過手機攝像頭捕獲身份證信息&#xff0c;將身份證上的姓名、性別、出生年月、身份證號碼保存在數據庫中。1&#xff09;所開發Apps軟件至少需由3-5個以上功能性界面組成。要求&#xff1a;界面美觀整潔、方便應用&#xff1b;可以使用Android原生…

ChatGPT聊圖像超分

筆者就YOLO系列方法詢問了ChatGPT的看法&#xff0c;可參考&#xff1a; ChatGPT是如何看待YOLO系列算法的貢獻呢&#xff1f; 續接前文&#xff0c;今天繼續拿圖像超分領域的經典方法來詢問ChatGPT的看法&#xff0c;這里主要挑選了以下幾個方案SRCNN、ESPSRN、EDSR、RCAN、…

JS 對象數組排序方法測試

輸出 一.Array.prototype.sort() 1.默認排序 sort() sort() 方法就地對數組的元素進行排序&#xff0c;并返回對相同數組的引用。默認排序是將元素轉換為字符串&#xff0c;然后按照它們的 UTF-16 碼元值升序排序。 由于它取決于具體實現&#xff0c;因此無法保證排序的時…

數據可視化基礎與應用-02-基于powerbi實現醫院數據集的指標體系的儀表盤制作

總結 本系列是數據可視化基礎與應用的第02篇&#xff0c;主要介紹基于powerbi實現醫院數據集的指標體系的儀表盤制作。 數據集描述 醫生數據集doctor 醫生編號是唯一的&#xff0c;名稱會存在重復 醫療項目數據projects 病例編號是唯一的&#xff0c;注意這個日期編號不是真…

面試時如何回答接口測試怎么進行

一、什么是接口測試 接口測試顧名思義就是對測試系統組件間接口的一種測試&#xff0c;接口測試主要用于檢測外部系統與系統之間以及內部各個子系統之間的交互點。測試的重點是要檢查數據的交換&#xff0c;傳遞和控制管理過程&#xff0c;以及系統間的相互邏輯依賴關系等。 …

【C++ 07】string 類的常用接口介紹

文章目錄 &#x1f308; Ⅰ string 類對象的常見構造函數&#x1f308; Ⅱ string 類對象的容量相關操作&#x1f308; Ⅲ string 類對象的訪問及遍歷1. 下標訪問及遍歷2. 正向迭代器訪問3. 反向迭代器訪問 &#x1f308; Ⅳ string 類對象的修改操作1. 插入字符或字符串2. 字符…

數據分析業務面試題

目錄 Q1:請簡述數據分析的工作流程? Q2:你經常用到的數據分析方法有哪些,舉例說明? Q3:公司最近一周的銷售額下降了,你如何分析下降原因? Q4:店鋪銷售額降低如何分析? Q5:若用戶留存率下降如何分析? Q6:店鋪商品銷售情況分布后 Q7:如何描述店鋪經營狀況?…

Vue前端的工作需求

加油&#xff0c;新時代打工人&#xff01; 需求&#xff1a; 實現帶樹形結構的表格&#xff0c;父數據顯示新增下級&#xff0c;和父子都顯示編輯。 技術&#xff1a; Vue3 Element Plus <template><div><el-table:data"tableData"style"width…

了解游戲中的數據同步

目錄 數據同步 通過比較來看狀態同步和幀同步 狀態同步 幀同步 幀同步實現需要的條件 兩者相比較 數據同步 在聯機游戲中&#xff0c;我的操作和數據要同步給同一局游戲中其他所有玩家&#xff0c;其他玩家的操作和數據也會同步給我。這叫做數據同步&#xff0c;目前數據…

國產數據庫概述

這是ren_dong的第33篇原創 1、什么是數據庫&#xff1f; 1.1、基本概念 定義&#xff1a;數據庫是 按照一定的數據結構組織、存儲和管理數據的倉庫。可視為電子化的文件柜&#xff0c;用戶可以對文件中的數據進行新增、查詢、更新、刪除等操作。 作用&#xff1a;業務數據 存儲…

kettle下載及安裝

JDK下載 安裝kettle之前需要安裝JDK JDK下載鏈接&#xff1a;JDK下載 配置環境變量&#xff1a; 新建系統變量&#xff1a;變量值為JDK安裝路徑 Path新增&#xff1a; kettle下載 鏈接地址&#xff1a;PDI&#xff08;kettle&#xff09; 點擊下載 同意 Click here to a…

【XIAO ESP32S3 sense 通過 ESPHome 與 Home Assistant 連接】

XIAO ESP32S3 sense 通過 ESPHome 與 Home Assistant 連接 1. 什么是 ESPHome 和 Home Assistant&#xff1f;2. 軟件準備3. 開始4. 將 Grove 模塊與 ESPHome 和 Home Assistant 連接5. Grove 連接和數據傳輸6. Grove -智能空氣質量傳感器 &#xff08;SGP41&#xff09;7. OV2…

Filter(過濾器)

文章目錄 過濾器的編寫&#xff1a;過濾器 APIFilterFilterConfigFilterChain 生命周期過濾器核心方法的細節多個過濾器執行順序<br /> 過濾器——Filter&#xff0c;它是JavaWeb三大組件之一。另外兩個是Servlet和Listener。 它是在2000年發布的Servlet2.3規范中加入的一…

Go語言基礎基礎

簡介 Go語言&#xff08;也稱為Golang&#xff09;是一種靜態類型、編譯型語言&#xff0c;由Google的Robert Griesemer、Rob Pike和Ken Thompson于2007年設計&#xff0c;首次公開發布于2009年。Go的設計初衷是解決當時谷歌內部面臨的軟件開發問題&#xff0c;特別是在處理大…

百度文庫旋轉驗證碼識別

最近研究了一下圖像識別&#xff0c;一直找到很好的應用場景&#xff0c;今天我就發現可以用百度的旋轉驗證碼來做一個實驗。沒想到效果還挺好&#xff0c;下面就是實際的識別效果。 1、效果演示 2、如何識別 2.1準備數據集 首先需要使用爬蟲&#xff0c;對驗證碼圖片進行采…

區塊鏈媒體發布推廣10個熱門案例解析-華媒舍

區塊鏈技術的發展已經引起了媒體的廣泛關注&#xff0c;越來越多的區塊鏈媒體紛紛發布推廣相關的熱門案例。本文將介紹10個成功的區塊鏈媒體推廣案例&#xff0c;并分享它們的成功秘訣&#xff0c;幫助讀者更好地了解區塊鏈媒體推廣的方法與技巧。 隨著區塊鏈技術的成熟和應用場…

第二證券:富時羅素擴容 A股引入國際增量資金

日前&#xff0c;英國富時羅素指數公司&#xff08;FTSE Russell&#xff0c;簡稱“富時羅素”&#xff09;公布的全球股票指數&#xff08;FTSE Global Equity Index Series&#xff09;半年度指數檢查陳述顯現&#xff0c;將新調入A股76只、調出1只。此前&#xff0c;富時羅素…

Leetcode 3049. Earliest Second to Mark Indices II

Leetcode 3049. Earliest Second to Mark Indices II 1. 解題思路2. 代碼實現3. 算法優化 題目鏈接&#xff1a;3049. Earliest Second to Mark Indices II 1. 解題思路 這道題我看貌似難度報表&#xff0c;比賽的時候貌似只有36個人搞定了這道題目&#xff0c;然后最快的人…

【LeetCode】升級打怪之路 Day 12:單調隊列

今日題目&#xff1a; 239. 滑動窗口最大值 | LeetCode 今天學習了單調隊列這種特殊的數據結構&#xff0c;思路很新穎&#xff0c;值得學習。 Problem&#xff1a;單調隊列 【必會】 與單調棧類似&#xff0c;單調隊列也是一種特殊的數據結構&#xff0c;它相比與普通的 que…