深入淺出C語言指針:從數組到函數指針的進階之路(中)

指針是C語言的靈魂,也是初學者最頭疼的知識點。它像一把鋒利的刀,用得好能大幅提升代碼效率,用不好則會讓程序漏洞百出。今天這篇文章,我們從數組與指針的基礎關系講起,一步步揭開指針進階類型的神秘面紗,最后用實戰案例鞏固所學——保證通俗易懂,還會標注所有重點和坑點。

一、數組與指針:繞不開的基礎關系

1.1 數組名的本質:不是簡單的地址

很多人以為"數組名就是首元素地址",這句話對但不完整。數組名的本質有兩個例外,這是初學者最容易掉的坑!

重點結論:
  • 一般情況:數組名表示數組首元素的地址。
    例:int arr[10] = {1,2,...,10}; 中,arr&arr[0] 地址相同。
  • 兩個例外(必須牢記):
    • sizeof(數組名):數組名代表整個數組,計算整個數組的字節大小。
      例:sizeof(arr) 結果為 40(10個int,每個4字節),而非指針大小(4/8)。
    • &數組名:數組名代表整個數組,取出的是整個數組的地址(與首元素地址值相同,但偏移量不同)。
      例:&arr + 1 偏移40字節(跳過整個數組),而 arr + 1 偏移4字節(跳過一個元素)。
實戰驗證:
#include <stdio.h>
int main() {int arr[10] = {1,2,3,4,5,6,7,8,9,10};printf("&arr[0] = %p\n", &arr[0]);  // 首元素地址printf("arr     = %p\n", arr);      // 首元素地址(等價于上一行)printf("&arr    = %p\n", &arr);     // 整個數組的地址(值相同,意義不同)// 關鍵差異:+1操作printf("&arr[0]+1 = %p\n", &arr[0]+1);  // 跳過1個元素(+4字節)printf("arr+1     = %p\n", arr+1);      // 跳過1個元素(+4字節)printf("&arr+1    = %p\n", &arr+1);     // 跳過整個數組(+40字節)return 0;
}
輸出結果解析:

前三個地址值相同,但&arr+1會跳過整個數組(10個int,共40字節),而前兩者只跳過1個元素(4字節)。這證明&arr指向的是整個數組,而非單個元素。

1.2 用指針訪問數組:靈活但要謹慎

有了對數組名的理解,我們可以用指針靈活訪問數組元素。核心邏輯是:數組元素的訪問本質是"首地址+偏移量"

等價關系(重點):
  • arr[i] 等價于 *(arr + i)
  • 指針p指向首元素時,p[i] 等價于 *(p + i)
示例代碼:
#include <stdio.h>
int main() {int arr[5] = {10,20,30,40,50};int* p = arr;  // p指向首元素// 兩種訪問方式等價printf("arr[2] = %d\n", arr[2]);      // 30printf("*(p+2) = %d\n", *(p + 2));    // 30printf("p[2] = %d\n", p[2]);          // 30(指針也支持下標)return 0;
}

1.3 一維數組傳參:別被"數組形式"騙了

當數組作為參數傳遞給函數時,形參看似是數組,本質是指針。這也是為什么在函數內部用sizeof求不出數組長度的原因。

  • 數組傳參實際傳遞的是首元素地址,而非整個數組。
  • 函數形參兩種寫法(等價):
    void test(int arr[]); // 數組形式(本質是指針)
    void test(int* arr);  // 指針形式(更直觀)
    
易錯點演示:
#include <stdio.h>
// 形參寫成數組形式,本質還是指針
void test(int arr[]) {printf("函數內sizeof(arr) = %d\n", sizeof(arr));  // 4或8(指針大小)
}int main() {int arr[10] = {0};printf("主函數內sizeof(arr) = %d\n", sizeof(arr));  // 40(整個數組大小)test(arr);return 0;
}

1.4.冒泡排序(指針應用)

  • 核心:相鄰元素比較交換,通過指針訪問數組元素。
  • 優化版(提前終止有序數組):
    void bubble_sort(int* arr, int sz) {for(int i=0; i<sz-1; i++) {int flag = 1; // 假設本趟有序for(int j=0; j<sz-i-1; j++) {if(arr[j] > arr[j+1]) {int tmp = arr[j];arr[j] = arr[j+1];arr[j+1] = tmp;flag = 0; // 發生交換,無序}}if(flag) break; // 無交換,直接退出}
    }
    

二、指針的進階類型:從二級指針到數組指針

2.1 二級指針:指針的指針

指針變量也是變量,它的地址需要用"二級指針"存儲。可以理解為:一級指針指向數據,二級指針指向一級指針

示例圖解:
int a = 10;       // 數據
int* pa = &a;     // 一級指針(指向a)
int** ppa = &pa;  // 二級指針(指向pa)
操作邏輯:
  • *ppa 等價于 pa(通過二級指針獲取一級指針)
  • **ppa 等價于 *pa 等價于 a(通過二級指針獲取數據)

2.2 指針數組:存放指針的數組

指針數組是數組,其元素類型是指針。比如int* arr[5]表示:一個有5個元素的數組,每個元素是int*類型的指針。

用途:存儲多個同類型地址
#include <stdio.h>
int main() {int arr1[] = {1,2,3};int arr2[] = {4,5,6};int arr3[] = {7,8,9};// 指針數組存儲三個一維數組的首地址int* parr[3] = {arr1, arr2, arr3};// 訪問arr2的第2個元素(5)printf("%d\n", parr[1][1]);  // 等價于*(parr[1] + 1)return 0;
}

2.3 數組指針:指向數組的指針

數組指針是指針,它指向一個完整的數組。比如int (*p)[5]表示:一個指針,指向"有5個int元素的數組"。

易混淆對比(重點):
定義本質解讀
int* p[5]指針數組先與[]結合,是數組,元素為int*
int (*p)[5]數組指針先與*結合,是指針,指向int[5]數組
數組指針的用法:
#include <stdio.h>
int main() {int arr[3][5] = {{1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15}};int (*p)[5] = arr;  // arr是首行地址(指向第一行數組)// 訪問第二行第三列元素(8)printf("%d\n", *(*(p + 1) + 2));  // 等價于p[1][2]return 0;
}

三、字符串與字符指針:藏著坑的常量

3.1 字符指針的兩種用法

字符指針(char*)既可以指向單個字符,也可以指向字符串的首字符。后者更常見,但要注意常量字符串的特性。

示例:
#include <stdio.h>
int main() {// 指向單個字符char ch = 'a';char* pc = &ch;// 指向字符串首字符(重點)const char* pstr = "hello";  // "hello"是常量字符串,不可修改printf("%s\n", pstr);  // 打印整個字符串(從首字符開始直到'\0')return 0;
}

3.2 常量字符串的存儲:節省空間的小技巧

C/C++會把相同的常量字符串存儲在同一塊內存中,這是容易踩坑的點。

示例(面試常考):
#include <stdio.h>
int main() {char str1[] = "hello";  // 數組:開辟新空間,存儲"hello"char str2[] = "hello";  // 數組:再開辟新空間,存儲"hello"const char* str3 = "hello";  // 指針:指向常量區的"hello"const char* str4 = "hello";  // 指針:指向同一塊常量區空間printf("str1 == str2 ? %d\n", str1 == str2);  // 0(地址不同)printf("str3 == str4 ? %d\n", str3 == str4);  // 1(地址相同)return 0;
}
結論:
  • 用常量字符串初始化數組時,每次都會開辟新空間
  • 用常量字符串初始化字符指針時,多個指針可能指向同一塊空間(節省內存)

四、二維數組傳參:首行地址是關鍵

二維數組可以理解為"數組的數組"(每個元素是一維數組)。因此,二維數組的數組名表示首行的地址(即第一個一維數組的地址),類型是數組指針。

二維數組傳參的正確方式:
#include <stdio.h>
// 形參可寫成二維數組形式,或數組指針形式
void print_arr(int (*p)[5], int row, int col) {for (int i = 0; i < row; i++) {for (int j = 0; j < col; j++) {printf("%d ", p[i][j]);  // 等價于*(*(p+i)+j)}printf("\n");}
}int main() {int arr[3][5] = {{1,2,3,4,5}, {6,7,8,9,10}, {11,12,13,14,15}};print_arr(arr, 3, 5);  // 傳遞首行地址return 0;
}

五、函數指針:讓指針指向代碼

函數也有地址(函數名就是地址),用函數指針可以存儲函數地址,實現更靈活的調用(比如回調函數)。

5.1 函數指針的定義與使用

定義格式:返回類型 (*指針名)(參數類型列表)
#include <stdio.h>
int add(int a, int b) {return a + b;
}int main() {// 定義函數指針,指向add函數int (*pf)(int, int) = add;  // 等價于&add// 兩種調用方式printf("add(2,3) = %d\n", add(2,3));       // 直接調用printf("pf(2,3) = %d\n", pf(2,3));         // 用指針調用printf("(*pf)(2,3) = %d\n", (*pf)(2,3));   // 等價寫法return 0;
}

5.2 函數指針數組與轉移表:簡化多分支邏輯

函數指針數組是存儲函數指針的數組,適合實現"菜單-功能"類邏輯(如計算器),替代冗長的switch-case。

實戰案例:用函數指針數組實現計算器
#include <stdio.h>
// 四則運算函數
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }int main() {int x, y, input;// 函數指針數組(轉移表):下標1-4對應加減乘除int (*operate[5])(int, int) = {0, add, sub, mul, div};do {printf("1:加 2:減 3:乘 4:除 0:退出\n");printf("請選擇:");scanf("%d", &input);if (input >= 1 && input <= 4) {printf("輸入操作數:");scanf("%d %d", &x, &y);// 用數組下標調用對應函數printf("結果: %d\n", operate[input](x, y));} else if (input != 0) {printf("輸入錯誤!\n");}} while (input != 0);return 0;
}
優勢:
  • 新增功能只需添加函數并更新數組,無需修改分支邏輯
  • 代碼更簡潔,可讀性更高

六、typedef:給復雜類型起"小名"

typedef可以為復雜類型(如指針、數組指針、函數指針)重命名,簡化代碼。但要注意與#define的區別。

6.1 用法示例:

#include <stdio.h>
// 重命名基本類型
typedef unsigned int uint;// 重命名指針類型
typedef int* int_ptr;// 重命名數組指針
typedef int (*arr_ptr)[5];  // 指向int[5]數組的指針// 重命名函數指針
typedef int (*calc_func)(int, int);  // 指向"int(int,int)"函數的指針int main() {uint a = 10;              // 等價于unsigned intint_ptr p1, p2;           // p1和p2都是int*(指針)arr_ptr parr;             // 等價于int (*parr)[5]calc_func pf = add;       // 等價于int (*pf)(int,int)return 0;
}

6.2 與#define的區別(易錯點):

#define是簡單替換,而typedef是真正的類型重命名。

#include <stdio.h>
typedef int* int_ptr;       // 類型重命名
#define INT_PTR int*        // 宏替換int main() {int_ptr p1, p2;  // p1和p2都是int*(正確)INT_PTR p3, p4;  // 替換后為int* p3, p4; → p4是int(錯誤)return 0;
}

七、總結與易錯點回顧

  1. 數組名的兩個例外sizeof(數組名)&數組名表示整個數組
  2. 數組傳參本質:形參是指針,需額外傳遞長度
  3. 指針數組vs數組指針:前者是數組(存指針),后者是指針(指向數組)
  4. 常量字符串存儲:相同常量字符串可能共享內存,數組初始化則不共享
  5. 函數指針數組:適合實現多功能菜單,替代switch-case
  6. typedef與#define:typedef是類型重命名,#define是文本替換

指針雖然復雜,但只要抓住"地址"和"類型"兩個核心(地址決定指向哪里,類型決定+1跳過多少字節),就能逐步掌握。多寫代碼驗證,少死記硬背,才是學好指針的關鍵!

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

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

相關文章

java web Cookie處理

java web 設置cookie更改啟動端口// Directory tree (5 levels) ├── src\ │ ├── a.txt │ └── com\ │ └── zhang\ │ └── ServletContext\ │ ├── cookie\ │ └── servletContext.java └── web\├─…

機器學習—線性回歸

一線性回歸線性回歸是利用數理統計中回歸分析&#xff0c;來確定兩種或兩種以上變量間相互依賴的定量關系的一種統計分析方法。相關關系&#xff1a;包含因果關系和平行關系因果關系&#xff1a;回歸分析【原因引起結果&#xff0c;需要明確自變量和因變量】平行關系&#xff1…

Spring Boot Admin 監控模塊筆記-實現全鏈路追蹤

一、概述Spring Boot Admin&#xff08;SBA&#xff09;是一個用于監控和管理 Spring Boot 應用程序的工具。它提供了一個 Web 界面&#xff0c;可以集中管理多個 Spring Boot 應用程序的健康狀態、指標、日志、配置等信息。通過 SBA&#xff0c;你可以輕松地監控和管理你的微服…

容器化與Docker核心原理

目錄 專欄介紹 作者與平臺 您將學到什么&#xff1f; 學習特色 容器化與Docker核心原理 引言&#xff1a;為什么容器化成為云計算時代的基石&#xff1f; 容器化技術全景與Docker核心原理&#xff1a;從概念到實踐 文章摘要 1. 引言&#xff1a;為什么容器化成為云計算…

掌握Python三大語句:順序、條件與循環

PS不好意思各位&#xff0c;由于最近筆者在參加全國大學生電子設計大賽&#xff0c;所以最近會出現停更的情況&#xff0c;望大家諒解&#xff0c;比賽結束后我會加大力度&#xff0c;火速講Python的知識給大家寫完&#x1f396;?&#x1f396;?&#x1f396;?&#x1f396;…

JAVA結合AI

Java 與人工智能&#xff08;AI&#xff09;的結合正經歷從技術探索到深度融合的關鍵階段。以下從技術生態、應用場景、工具創新、行業實踐及未來趨勢五個維度展開分析&#xff0c;結合最新技術動態與企業級案例&#xff0c;揭示 Java 在 AI 時代的獨特價值與發展路徑。一、技術…

本土DevOps平臺Gitee如何重塑中國研發團隊的工作流

本土DevOps平臺Gitee如何重塑中國研發團隊的工作流 在數字化轉型浪潮席卷各行各業的當下&#xff0c;軟件開發效率已成為企業競爭力的核心指標。Gitee DevOps作為專為中國開發團隊打造的本土化研發管理平臺&#xff0c;正在改變國內技術團隊的工作方式。該平臺通過從代碼管理到…

5G MBS(組播廣播服務)深度解析:從標準架構到商用實踐

一、MBS技術背景與核心價值 1.1 業務需求驅動 隨著超高清視頻(4K/8K)、多視角直播、XR元宇宙應用爆發式增長,傳統單播傳輸面臨帶寬浪費(相同內容重復發送)與擁塞風險(萬人并發場景)的雙重挑戰。5G MBS通過點對多點(PTM)傳輸實現內容一次發送、多終端接收,頻譜效率提…

如何將照片從 realme 手機傳輸到電腦?

對于 realme 用戶來說&#xff0c;將照片傳輸到電腦可以有多種用途&#xff0c;從釋放設備空間到在單獨的存儲設備上創建備份。這個過程不僅有助于高效管理設備內存&#xff0c;還可以讓您利用電腦上強大的照片編輯軟件進行高級增強和創意項目。了解如何將照片從 realme 手機傳…

Centos 7部署.NET 8網站項目

簡介 本文詳細介紹了在CentOS 7系統上部署.NET 8網站項目的完整流程&#xff0c;主要內容包括&#xff1a;系統版本更新與檢查、PostgreSQL數據庫的安裝配置&#xff08;含防火墻設置、數據庫初始化及遠程訪問配置&#xff09;、Nginx Web服務的安裝與防火墻配置。文章通過分步…

Windows 11下IDEA中使用git突然變得卡慢及解決辦法

1. 表象 使用idea的git進行update、commit、push等操作時&#xff0c;極度卡慢。需等待幾十秒到幾分鐘。修改文件后&#xff0c;git刷新也不及時。update命令有時候無法點擊。 2.解決方法 停止PC Manager ServiceCtrl shift esc : 打開任務管理器找到服務&#xff1a; 服務中…

MyBatis 的兩級緩存機制

現實分布式項目中會不會開啟mybatis的二級緩存&#xff1f; 在分布式項目中&#xff0c;是否開啟MyBatis的二級緩存需結合具體場景和技術方案綜合評估。 以下是關鍵考量因素&#xff1a; 一、默認二級緩存的局限性 隔離性問題&#xff1a;MyBatis默認的二級緩存基于HashMap實…

分布式原子序列(Distributed Atomic Sequence)

這段內容是關于 Apache Ignite 中的 分布式原子序列&#xff08;Distributed Atomic Sequence&#xff09;&#xff0c;也就是一個分布式 ID 生成器。我們來一步步深入理解它的原理、用途和使用方式。&#x1f539; 一、核心概念&#xff1a;什么是分布式 ID 生成器&#xff1f…

VSCode——插件分享:Markdown PDF

該插件可以將markdown編寫內容轉成PDF。 ? 支持渲染圖表、代碼高亮、表格等 Markdown 內容 安裝 Visual Studio Code安裝插件&#xff1a;Markdown PDF 打開擴展商店&#xff0c;搜索 Markdown PDF 并安裝 打開你的 .md 文件右鍵 → 點擊 Markdown PDF: Export (pdf)自動生成 …

rust-模塊樹中引用項的路徑

模塊樹中引用項的路徑 為了告訴 Rust 在模塊樹中如何找到某個項&#xff0c;我們使用路徑&#xff0c;就像在文件系統中導航時使用路徑一樣。要調用一個函數&#xff0c;我們需要知道它的路徑。 路徑有兩種形式&#xff1a; 絕對路徑是從 crate 根開始的完整路徑&#xff1b…

mac n切換node版本報錯Bad CPU type in executable

該node版本僅支持intel芯片&#xff0c;不支持Apple 芯片&#xff08;M1/M2/M3/M4&#xff09;&#xff0c;所以需要下載Rosetta 2 &#xff0c;讓node可以在搭載 Apple 芯片的 Mac 上運行。 env: node: Bad CPU type in executable /opt/homebrew/bin/n: line 753: /usr/local…

經典算法之美:冒泡排序的優雅實現

經典算法之美&#xff1a;冒泡排序的優雅實現基本概念工作原理介紹具體實現代碼實現總結基本概念 冒泡排序是一種簡單的排序算法&#xff0c;通過重復比較相鄰的元素并交換它們的位置來實現排序。它的名稱來源于較小的元素像氣泡一樣逐漸“浮”到數組的頂端。 工作原理 介紹…

click和touch事件觸發順序 糊里糊涂解決的奇怪bug

問題詳情 在嵌入式硬件設備里&#xff0c;測試 “點擊input密碼框&#xff0c;彈出第三方自帶鍵盤&#xff0c;點擊密碼框旁的小眼睛&#xff0c;切換輸入內容加密狀態&#xff0c;鍵盤收起/彈出狀態不變” 的功能邏輯&#xff1b;實際情況卻是 “點擊鍵盤或input框之外的任何地…

【0基礎PS】Photoshop (PS) 理論知識

目錄前言一、Photoshop 核心概念與定位?二、圖像基礎理論?三、圖層理論&#xff1a;PS 的核心工作機制?四、選區與蒙版?五、調色核心理論?六、常用文件格式?學習建議?總結前言 在數字圖像編輯領域&#xff0c;Photoshop&#xff08;簡稱 PS&#xff09;無疑是行業標桿級…

數據庫 設計 pdm comment列表顯示和生成建表sql

按如下步驟 生成見建表語句 comment非空使用comment 生成字段注釋&#xff0c; 空的時候使用name 生成字段注釋 sql腳本模板編輯 參考 PowerDesigner生成mysql字段comment 注釋-騰訊云開發者社區-騰訊云 版本不同這邊的設置不同 這個勾打上