【c語言】——深入理解指針2

文章目錄

  • 一、指針數組
    • 指針數組模擬二維數組
  • 二、數組指針
    • 二維數組傳參的本質
  • 三、字符指針變量
  • 四、函數指針變量
    • 4.1. 函數指針的應用
    • 4.2 兩端有趣的代碼
    • 4.3. typedef關鍵字
      • 4.3.1 typedef 的使用
      • 4.3.2. typedef與#define對比
  • 五、函數指針數組
    • 函數指針數組的應用

一、指針數組

指針數組到底是指針還是數組呢?

整型數組 int arr[] : 存放整型的數組;
字符數組 char arr[] : 存放字符的數組;
可以類比得到:
指針數組 int* arr[] : 存放指針的數組.

因此,指針數組本質上是數組,數組中的每個元素都存放的是地址(指針),每個元素的類型為指針類型.

示例:下方代碼中
parr先和 [ ] 結合,因此它是一個數組,這個數組有3個元素,每個元素的類型為int*

int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 1,2,3,4,5 };
int arr3[5] = { 1,2,3,4,5 };
int* parr[3] = {arr1,arr2,arr3};

將數組名去掉就可以看出它的類型,即int* [3],也就是指針數組

指針數組模擬二維數組

int main()
{//創建三個整型數組int arr1[5] = { 1,2,3,4,5 };int arr2[5] = { 1,2,3,4,5 };int arr3[5] = { 1,2,3,4,5 };//由于數組名就是數組首元素地址,將每個數組名存放到指針數組parr中int* parr[3] = {arr1,arr2,arr3};int i = 0;for (i = 0;i < 3;i++)//遍歷三個數組名{int j = 0;for (j = 0;j < 5;j++)//遍歷每個數組{printf("%d ", parr[i][j]);}printf("\n");}return 0;
}

對 parr [ i ] [ j ] 進行解釋:

  1. parr [ i ] 訪問的是parr中的元素,找到的是第 i 個數組名,指向一維整形數組.
    比如 parr [ 0 ]找到的是 arr1, parr [ 1 ]找到的是 arr2, parr [ 2 ]找到的是 arr3, 因此 parr [ 0 ] 就是arr1數組名,本質就是第一個數組首元素地址
  2. parr [ i ] [ j ] 指向一維數組中的元素,找到的是第 i 個數組的第 j 個元素.
    比如parr [ 0 ] [ 0 ] == arr1 [ 0 ] == 1
  3. arr1, arr2, arr3本質上是指針,因此可以存到指針數組 parr 中.
  4. parr模擬的二維數組并非是真正的二維數組,因為二維數組在內存中是連續存放的.

在這里插入圖片描述

二、數組指針

數組指針到底是數組還是指針呢?

整型指針 int* p = &a : p 是存放整型變量地址、指向整型變量的指針
字符指針char* pc = 'c' : pc 是存放字符變量地址、指向字符變量的指針
浮點型指針 float* pf = &f: pf 是存放浮點型變量地址、指向浮點型變量的指針
數組指針:int (*p) [ 5 ] = &arr : p 是指向整型數組的指針.

因此,數組指針本質上是指針,是指向數組的指針,存放的是數組的地址
示例:

int arr[] = { 1,2,3 };
int (*p)[3] = &arr;
  1. p先與*結合,說明p是一個指針變量,然后與 [ ] 結合,說明它指向的是一個數組,數組中有三個元素,每個元素的類型為 int
  2. 數組指針的初始化:數組指針存的是數組的地址,因此將整個數組取地址賦值給數組指針就可以

指針數組與數組指針的區分:
指針數組存放指針的數組,“指針”是定語,修飾“數組”
數組指針指向數組的指針,“數組”是定語,修飾“指針”

二維數組傳參的本質

int arr[3][5] = { { 1,2,3,4,5 }, {2,3,4,5,6},{3,4,5,6,7 } }

在這里插入圖片描述
arr[3][5]可以看作是一個有三個元素的一維數組每個元素是一個一維數組,第一個元素是{1,2,3,4,5},第二個元素是{2,3,4,5,6},第三個元素是{3,4,5,6,7},因此,遵循數組名是數組首元素地址的原則,那么認為二維數組的數組名其實就是第一行數組的地址,數組的地址類型為數組指針,那么二維數組傳參的本質傳遞的是第一行數組的地址,需要用數組指針類型來接收

用數組指針來打印二維數組

void print(int (*p)[5])
{int i = 0;for (i = 0;i < 3;i++){for (int j = 0;j < 5;j++){printf("%d ", *(*(p + i) + j));//也可以寫成p[i][j]}printf("\n");}
}
int main()
{int arr[3][5] = { { 1,2,3,4,5 }, {2,3,4,5,6},{3,4,5,6,7 } };print(arr);return 0;
}

在這里插入圖片描述

p 是數組指針,p + i 得到的是第 i 行元素的地址,*( p + i ) 得到的是第 i 行元素的數組名,*( p + i ) + j 得到的是第 i 行第 j 個元素的地址,再進行解引用就得到該元素

有人會問,p + i 是地址,*( p + i ) 得到的應該是數據,*( p + i ) + j得到的還是數字,*(*( p + i ) + j)就是對數字進行解引用,沒有意義了.

實際上 p + i 是二維數組中第 i 行數組的地址,那么 p + 0 == p == arr [ 0 ], 以第一行數組為例,第一行數組是一維數組,arr [ 0 ] [ j ] 得到的是第一行數組第 j 個元素,那么arr [ 0 ]本質上就是第一行數組的數組名,也就是第一行數組首元素 1 的地址. 因此*(p + i)實際得到的是第 i 行的數組名本質還是地址,想得到 i 行 j 列 這個元素需要 *(*(p + i) + j).

二維數組在想象中是以行和列形式存在的,但是在內存中的存儲是連續的, 因此只需要知道首元素地址就可以連續訪問

在這里插入圖片描述
當然,二維數組傳參,形參部分也可以寫成二維數組(不能省略列),也可以寫成數組指針形式.

三、字符指針變量

char* a = "abcdef";

其中,a是一個字符指針,存放的是字符串第一個字符的地址,并不是整個字符串的地址,并且該字符串為常量字符串,不能被改變,因此嚴謹來說應該在前加上const

const char* a = "abcdef";

示例:

int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");
  1. str3 和 str4 是兩個相同的常量字符串, 在內存中只會開辟一塊內存空間,不同的指針指向相同的常量字符串的時候,指向的是同一塊內存空間,因此 str3 和 str4 是相同的
  2. 用相同的常量字符串去初始化兩個字符數組,內存會為兩個數組單獨開辟兩個內存空間,因此str1和str2是不同的,他們分別指向兩個不同的地址

在這里插入圖片描述

四、函數指針變量

函數指針:指向函數的指針,存放的是函數的地址.
首先了解什么是函數的地址:

void test()
{printf("666\n");
}
int main()
{test();printf("test = %p\n", test);printf("&test = %p\n", &test);return 0;
}

在這里插入圖片描述

可以看出,函數名 test 和 &test 的地址是一樣的,因此函數名就表示函數的地址,想存儲函數的地址,就需要函數指針變量

函數指針的初始化:

int Add(int x, int y)
{return x + y;
}
int (*pf1)(int, int) = Add;
int (*pf2)(int, int) = &Add;

Add是一個函數,pf1 (pf2) 先與*結合,表明其是一個指針,然后與后面的( )結合,表明其指向的是一個函數,函數內部的參數有兩個,分別都是 int 類型,函數的返回類型是 int 型.

去掉變量名pf1,就可以看出他的類型是 int ( * ) ( int , int )函數指針類型
在這里插入圖片描述

4.1. 函數指針的應用

用函數指針實現兩個數相加:
在這里插入圖片描述

可以看出,上述三種方法都可以實現Add函數的調用

  1. 直接用函數名調用函數.
  2. 利用函數指針 pf 取得Add函數的地址,然后對其解引用*pf得到函數名,再進行調用.
  3. 直接用函數指針pf進行調用.
    總結:函數名Add實際就是函數的地址,函數指針pf也是函數的地址,函數名直接調用函數就相當于用地址調用函數,那么直接用pf調用,不解引用也是可以的

比如:
在這里插入圖片描述

實際上可以看出:
005513CF為函數Add的地址
函數指針pf,*pf得到的都是005513CF,即函數Add的地址,因此利用函數指針調用函數的時候可以不用解引用,直接進行調用

4.2 兩端有趣的代碼

1.第一段代碼

(*(void (*)())0)();

在這里插入圖片描述

從內部向外看

  1. 黃色部分void ( * ) ( ),表明類型為函數指針類型
  2. 藍色括號 ( ) 0,表示將整型 0 強制類型轉換成函數指針類型,得到的是一個地址 00000000,意味著我們假設0地址處放著無參,返回類型為void的函數
  3. 綠色括號內部是對其進行解引用,得到的是該地址處的函數名
  4. 右側黑色括號表示調用這個函數
    因此調用的是0地址處的函數,函數的返回類型為void,函數參數為無參
  1. 第二段代碼
void (*signal(int, void(*)(int)))(int);

在這里插入圖片描述

signal,先與后面接的藍色括號結合,表明它指向一個函數,說明它是一個函數名,該函數內部有兩個參數,類型分別是 整型int 和 函數指針類型 void(*)(int),然后將函數名和函數參數去掉可以得到:
在這里插入圖片描述
函數還缺一個返回類型,因此該函數的返回類型為函數指針類型

將這段代碼改寫以下就可以更清楚看到:
在這里插入圖片描述

  1. signal 是函數名,指向的函數內部有兩個參數,參數類型分別是整型和函數指針類型,該函數的返回類型為函數指針類型
  2. 這種寫法只是便于分析,在編譯器中這種寫法是不允許的,只能寫成第一種形式

4.3. typedef關鍵字

4.3.1 typedef 的使用

如果類型寫起來太復雜,我們可以將其重新定義,進行簡化,比如
將unsigned int 重命名為uint

typedef unsigned int uint;

對于指針數組來說,正確的重命名為 parr_t 的方式是,parr_t 必須在 * 的右邊

typedef int(*parr_t)[5] 

同樣對于函數指針,將其重命名為pfun_t,必須放在*的右邊

typedef void(*pfun_t)(int,void(*)())//新的類型名必須放在*的右邊 
pfun_t signal(intpfun_t);//對代碼進行簡化

4.3.2. typedef與#define對比

typedef int* ptr_t;
ptr_t p1,p2;

int*類型重命名為 ptr_t ,p1, p2均為指針變量

#define PTR_t int*
PTR_t p1,p2;

將PTR_t 定義為int* 類型,那么p1為指針變量,p2為整型變量 ,因為替換之后為: int* p1, p2,是一個逗號表達式,*只會與p1結合

五、函數指針數組

函數指針數組:存放函數指針數組,本質是一個數組,用來存放函數指針變量.
函數指針數組的定義:

int (* parr [ 3 ] ) ( )

parr先與[ ]結合,表明它是一個數組,該數組有3個元素,每個元素的類型為函數指針類型 int ( * ) ( )

函數指針數組的應用

轉移表的實現:
實現一個具有加減乘除的計算器:

void menu()
{printf("1.add   2. sub \n");printf("3.mul   4. div \n");printf("0.exit \n");
}
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}int main()
{int a = 0;int b = 0;int input = 0;int ret = 0;do {menu();printf("請選擇:");scanf_s("%d", &input);switch (input){case 1:printf("請輸入兩個參數:");scanf_s("%d %d", &a, &b);ret = Add(a, b);printf("%d \n", ret);break;case 2:printf("請輸入兩個參數:");scanf_s("%d %d", &a, &b);ret = Sub(a, b);printf("%d \n", ret);break;case 3:printf("請輸入兩個參數:");scanf_s("%d %d", &a, &b);ret = Mul(a, b);printf("%d \n", ret);case 4:printf("請輸入兩個參數:");scanf_s("%d %d", &a, &b);ret = Div(a, b);printf("%d \n", ret);break;case 0:printf("退出\n");break;default:printf("請重新輸入:");break;}} while (input);return 0;
}

通過選擇1、2、3、4實現計算器的運算,但是這種方法有一個缺點,每一個case下面幾乎都是相同的功能,唯獨不同的就是進行函數的調用,因此我們應該想辦法將這一重復的功能封裝成一個函數去實現

利用函數指針數組實現計算器:

void menu()
{printf("1.add   2. sub \n");printf("3.mul   4. div \n");printf("0.exit \n");
}
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}int main()
{int a = 0;int b = 0;int input = 0;int ret = 0;int (*pf[5])(int, int) = { 0,Add,Sub,Mul,Div };do {menu();printf("請選擇:");scanf_s("%d", &input);if (input <= 4 && input >= 1){printf("請輸入操作數:");scanf_s("%d %d", &a, &b);ret = pf[input](a,b);printf("%d\n", ret);}else if(input==0){printf("退出\n");}else{printf("重新輸入");}} while (input);return 0;
}

在這里插入圖片描述
利用函數指針實現轉移表:

將重復的功能封裝到calc函數中實現,通過函數指針調用加減乘除函數
main是主調函數,calc函數是回調函數

void menu()
{printf("1.add   2. sub \n");printf("3.mul   4. div \n");printf("0.exit \n");
}
int Add(int x, int y)
{return x + y;
}
int Sub(int x, int y)
{return x - y;
}
int Mul(int x, int y)
{return x * y;
}
int Div(int x, int y)
{return x / y;
}
void calc(int (*pf)(int, int))
{int a, b;printf("請輸入兩個參數:");scanf_s("%d %d", &a, &b);int ret = pf(a, b);printf("%d \n", ret);
}
int main()
{int a = 0;int b = 0;int input = 0;int ret = 0;do {menu();printf("請選擇:");scanf_s("%d", &input);switch (input){case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);case 4:calc(Div);break;case 0:printf("退出\n");break;default:printf("請重新輸入:");break;}} while (input);return 0;
}

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

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

相關文章

python20-while和for in的美

課程&#xff1a;B站大學 記錄python學習&#xff0c;直到學會基本的爬蟲&#xff0c;使用python搭建接口自動化測試就算學會了&#xff0c;在進階webui自動化&#xff0c;app自動化 分支語句那些事兒 循環的類型循環的作用循環的構成要素while 循環while 循環實戰循環語句 for…

私人筆記:動手學大模型應用開發llm-universe項目環境創建

項目代碼&#xff1a;datawhalechina/llm-universe: 本項目是一個面向小白開發者的大模型應用開發教程&#xff0c;在線閱讀地址&#xff1a;https://datawhalechina.github.io/llm-universe/ 項目書&#xff1a;動手學大模型應用開發 一、初始化項目 uv init llm-universe-te…

剖析 Rust 與 C++:性能、安全及實踐對比

1 性能對比&#xff1a;底層控制與運行時開銷 1.1 C 的性能優勢 C 給予開發者極高的底層控制能力&#xff0c;允許直接操作內存、使用指針進行精細的資源管理。這使得 C 在對性能要求極高的場景下&#xff0c;如游戲引擎開發、實時系統等&#xff0c;能夠發揮出極致的性能。以…

詳細講解一下Java中的Enum

Java 中的 枚舉&#xff08;Enum&#xff09; 是一種特殊的類&#xff0c;用于表示一組固定且有限的常量&#xff08;如狀態、類型、選項等&#xff09;。它提供類型安全的常量定義&#xff0c;比傳統的常量&#xff08;如 public static final&#xff09;更強大和靈活。以下是…

首席人工智能官(Chief Artificial Intelligence Officer,CAIO)的詳細解析

以下是**首席人工智能官&#xff08;Chief Artificial Intelligence Officer&#xff0c;CAIO&#xff09;**的詳細解析&#xff1a; 1. 職責與核心職能 制定AI戰略 制定公司AI技術的長期戰略&#xff0c;明確AI在業務中的應用場景和優先級&#xff0c;推動AI與核心業務的深度…

LeetCode【劍指offer】系列(位運算篇)

劍指offer15.二進制中1的個數 題目鏈接 題目&#xff1a;編寫一個函數&#xff0c;輸入是一個無符號整數&#xff08;以二進制串的形式&#xff09;&#xff0c;返回其二進制表達式中數字位數為 ‘1’ 的個數&#xff08;也被稱為 漢明重量).&#xff09;。 思路一&#xff…

前端路由緩存實現

場景&#xff1a;以一體化為例&#xff1a;目前頁面涉及頁簽和大量菜單路由&#xff0c;用戶想要實現頁面緩存&#xff0c;即列表頁、詳情頁甚至是編輯彈框頁都要實現數據緩存。 方案&#xff1a;使用router-view的keep-alive實現 。 一、實現思路 1.需求梳理 需要緩存模塊&…

Buildroot編譯過程中下載源碼失敗

RK3588編譯編譯一下recovery&#xff0c;需要把buildroot源碼編譯一遍。遇到好幾個文件都下載失敗&#xff0c;如下所示 pm-utils 1.4.1這個包下載失敗&#xff0c;下載地址http://pm-utils.freedesktop.org/releases 解決辦法&#xff0c;換個網絡用windows瀏覽器下載后&…

Operator 開發入門系列(一):Hello World

背景 我們公司最近計劃將產品遷移到 Kubernetes 環境。 為了更好地管理和自動化我們的應用程序&#xff0c;我們決定使用 Kubernetes Operator。 本系列博客將記錄我們學習和開發 Operator 的過程&#xff0c;希望能幫助更多的人入門 Operator 開發。 目標讀者 對 Kubernete…

Java基礎知識面試題(已整理Java面試寶典pdf版)

什么是Java Java是一門面向對象編程語言&#xff0c;不僅吸收了C語言的各種優點&#xff0c;還摒棄了C里難以理解的多繼承、指針等概念&#xff0c;因此Java語言具有功能強大和簡單易用兩個特征。Java語言作為靜態面向對象編程語言的代表&#xff0c;極好地實現了面向對象理論…

科學視角下的打坐:身心獲益的實證探究

在快節奏的現代生活中&#xff0c;人們在追求物質豐富的同時&#xff0c;也愈發關注身心的健康與平衡。古老的打坐修行方式&#xff0c;正逐漸走進科學研究的視野&#xff0c;并以大量實證數據展現出對人體多方面的積極影響。? 什么是打坐&#xff1a; 打坐是一種養生健身法…

javaSE————網絡編程套接字

網絡編程套接字~~~~~ 好久沒更新啦&#xff0c;藍橋杯爆掉了&#xff0c;從今天開始爆更嗷&#xff1b; 1&#xff0c;網絡編程基礎 為啥要有網絡編程呢&#xff0c;我們進行網絡通信就是為了獲取豐富的網絡資源&#xff0c;說實話真的很神奇&#xff0c;想想我們躺在床上&a…

MySQL性能調優(三):MySQL中的系統庫(mysql系統庫)

文章目錄 MySQL性能調優數據庫設計優化查詢優化配置參數調整硬件優化 MySQL中的系統庫1.5.Mysql中mysql系統庫1.5.1.權限系統表1.5.2.統計信息表1.5.2.1.innodb_table_stats1.5.2.2.innodb_index_stats 1.5.3.日志記錄表1.5.3.1. general_log1.5.3.2. slow_log 1.5.4.InnoDB中的…

多個路由器互通(靜態路由)無單臂路由(簡單版)

多個路由器互通&#xff08;靜態路由&#xff09;無單臂路由&#xff08;簡單版&#xff09; 開啟端口并配ip地址 維護1 Router>en Router#conf t Router(config)#int g0/0 Router(config-if)#no shutdown Router(config-if)#ip address 192.168.10.254 255.255.255.0 Ro…

關于 AI驅動的智慧家居、智慧城市、智慧交通、智慧醫療和智慧生活 的詳細解析,涵蓋其定義、核心技術、應用場景、典型案例及未來趨勢

以下是關于 AI驅動的智慧家居、智慧城市、智慧交通、智慧醫療和智慧生活 的詳細解析&#xff0c;涵蓋其定義、核心技術、應用場景、典型案例及未來趨勢&#xff1a; 一、AI智慧家居 1. 定義與核心功能 定義&#xff1a;通過AI與物聯網&#xff08;IoT&#xff09;技術&#…

【ESP32|音頻】一文讀懂WAV音頻文件格式【詳解】

簡介 最近在學習I2S音頻相關內容&#xff0c;無可避免會涉及到關于音頻格式的內容&#xff0c;所以剛開始接觸的時候有點一頭霧水&#xff0c;后面了解了下WAV相關內容&#xff0c;大致能夠看懂wav音頻格式是怎么樣的了。本文主要為后面ESP32 I2S音頻系列文章做鋪墊&#xff0…

端側大模型綜述On-Device Language Models: A Comprehensive Review

此為機器翻譯&#xff0c;僅做個人學習使用 設備端語言模型&#xff1a;全面回顧 DOI&#xff1a;10.48550/arXiv.2409.00088 1 摘要 大型語言模型 &#xff08;LLM&#xff09; 的出現徹底改變了自然語言處理應用程序&#xff0c;由于減少延遲、數據本地化和個性化用戶體驗…

推流265視頻,網頁如何支持顯示265的webrtc

科技發展真快&#xff0c;以前在網頁上&#xff08;一般指谷歌瀏覽器&#xff09;&#xff0c;要顯示265的視頻流&#xff0c;都是很雞肋的辦法&#xff0c;要么轉碼&#xff0c;要么用很慢的hls&#xff0c;體驗非常不好&#xff0c;而今谷歌官方最新的瀏覽器已經支持265的web…

redis的sorted set的應用場景

Redis 的 Sorted Set&#xff08;有序集合&#xff0c;簡稱 ZSet&#xff09; 結合了 Set 的去重特性 和 按分數&#xff08;score&#xff09;排序 的特性&#xff0c;非常適合需要 高效排序 或 范圍查詢 的場景。以下是它的典型應用場景及示例&#xff1a; 實時排行榜 場景&…

18-21源碼剖析——Mybatis整體架構設計、核心組件調用關系、源碼環境搭建

學習視頻資料來源&#xff1a;https://www.bilibili.com/video/BV1R14y1W7yS 文章目錄 1. 架構設計2. 核心組件及調用關系3. 源碼環境搭建3.1 測試類3.2 實體類3.3 核心配置文件3.4 映射配置文件3.5 遇到的問題 1. 架構設計 Mybatis整體架構分為4層&#xff1a; 接口層&#…