【C 语言】指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)
相關文章鏈接 :
1.【嵌入式開發】C語言 指針數組 多維數組
2.【嵌入式開發】C語言 命令行參數 函數指針 gdb調試
3.【嵌入式開發】C語言 結構體相關 的 函數 指針 數組
4.【嵌入式開發】gcc 學習筆記(一) - 編譯C程序 及 編譯過程
5.【C語言】 C 語言 關鍵字分析 ( 屬性關鍵字 | 常量關鍵字 | 結構體關鍵字 | 聯合體關鍵字 | 枚舉關鍵字 | 命名關鍵字 | 雜項關鍵字)
6.【C 語言】編譯過程 分析 ( 預處理 | 編譯 | 匯編 | 鏈接 | 宏定義 | 條件編譯 | 編譯器指示字 )
7.【C 語言】指針 與 數組 ( 指針 | 數組 | 指針運算 | 數組訪問方式 | 字符串 | 指針數組 | 數組指針 | 多維數組 | 多維指針 | 數組參數 | 函數指針 | 復雜指針解讀)
文章目錄
- 一. 指針
- 1. 指針 簡介
- ( 1 ) 指針 概念 ( 本質 | 占用內存 ① 32位 4字節 ② 64 位 8 字節 | * ① 聲明指針 ② 獲取指向的值 )
- ( 2 ) 指針 簡單示例 ( * 的讀寫內存作用 | 指針相關類型大小)
- 2. 傳值 和 傳址 調用
- ( 1 ) 相關概念 ( 傳值調用 復制實際值 | 傳址調用 復制地址值 )
- ( 2 ) 傳址調用 ( 改變外部變量值 )
- 3. 常量 和 指針
- ( 1 ) 相關概念 ( 核心原則 左數右指 | 左數 ① const int* p ② int const* p 數據時常量 | 右指 int* const 指針是常量 )
- ( 2 ) 驗證 常量 指針 相關概念 ( 左數右指 )
- 二. 數組
- 1. 數組 簡介
- ( 1 ) 數組 概念 ( 數組地址 | 數組大小 顯示 隱式 聲明 | 數組初始化 [ 效率比后期賦值高 ] )
- ( 2 ) 數組 示例 ( 定義 | 大小 | 初始化 )
- 2. 數組地址與名稱 概念
- ( 1 ) 數組 概地址 ( 數組名 [ 數組首元素地址 ] 和 &數組名 [ 數組地址 ] | 數組名 類似于 常量指針 | 數組拷貝 )
- ( 2 ) 數組 示例 ( 數組名 | 地址 | 數組拷貝禁止情況 )
- 3. 數組 與 指針 區別
- ( 1 ) 概念簡介 ( ① 數組名就是首元素地址 不需要尋址 | ② 指針 中保存一個地址 指向首元素地址 需要尋址 | printf 打印 數組 或 指針 : 根據占位符自動判斷打印地址還是打印內存中的具體內容 )
- ( 2 ) 代碼示例 ( 數組 | 指針 編譯器處理上的區別 )
- 三. 數組 指針 分析
- 1. 指針 加減 運算方式
- ( 1 ) 指針 加減法 運算 ( 指針指向的位置在同一個數組中改變才有意義 )
- (2) 數組大小計算示例
- ( 3 ) 指針 加法運算示例 ( 指針地址 + 4/8 * 被加數 )
- ( 4 ) 指針 減法 運算示例
- 2. 指針 比較 運算方式
- ( 1 ) 指針 比較 運算 ( 大于 小于 大于等于 小于等于 運算的前提是 必須指向同一數組 中的元素 | 任意兩指針只能進行 等于 不等于 的比較 )
- ( 2 ) 指針 比較 運算代碼示例 ( 用 指針 遍歷數組 )
- 3. 數組訪問方式
- ( 1 ) 下標 指針 訪問 ( 推薦使用下標訪問 )
- ( 2 ) 下標 指針 訪問 數組 性能 代碼示例
- 3. int array[]; array 和 &array 區別
- ( 1 ) int array[] 中 array 和 &array 意義 ( ① array 數組首元素地址 | ② &array 數組地址 )
- ( 2 ) array 和 &array 計算 代碼示例
- 4. 數組參數
- ( 1 ) 數組參數 概念 ( 退化成指針 | 需要帶上數組長度作為 附屬參數 )
- ( 2 ) 數組參數 代碼示例 ( 數組大小 | 數組參數大小 )
- 5. 數組 指針 對比 ( 內存分配 : ① 指針 分配 4 / 8 字節 ② 數組分配所有元素地址 | 作為參數 | 常量[ 數組 ] 變量[ 指針 ] 區別 )
- 四. 字符串
- 1. 字符串概念
- ( 1 ) 概念 ( 本質 是 char[] 數組 | '\0' 結尾 | 存儲位置 棧 堆 常量區 )
- ( 2 ) 示例代碼 ( 字符串概念 | 字符串 )
- 2. 字符串 長度
- ( 1 ) 字符串長度計算 ( 不包括 '\0' | 標準庫中有該函數)
- ( 2 ) 代碼示例 ( 字符串長度計算示例 )
- ( 3 ) 代碼示例 ( 自己實現 strlen 方法 )
- 3. 字符串函數 長度不受限制 情況
- ( 1 ) 不受限制的字符串函數 ( 函數自動尋找 '\0' 確定字符串大小 | stpcpy | strcat | strcmp )
- ( 2 ) 代碼示例 ( 自己實現字符串拷貝函數 )
- 4. 字符串函數 長度受限制 情況
- ( 1 ) 受限制的字符串函數 ( 推薦使用 降低錯誤率 )
- 五. 指針數組 與 數組指針
- 1. 數組指針
- ( 1 ) 數組類型介紹 ( 數組元素類型 | 數組大小 | 舉例 int[8] )
- (2) 數組指針簡介 ( 指向數組的 一個 指針 | 數組指針類型定義方式 : 數組元素類型 ( * 指針名稱 ) [數組大小] )
- ( 3 ) 代碼示例 ( 定義數組類型 | 數組指針用法 )
- 2. 指針數組
- ( 1 ) 指針數組簡介 ( 數組中存儲的元素是指針 | 數組指針 int (*array)[5] 本質是指針 | 指針數組 int* array[5] 本質是數組 )
- ( 2 ) 代碼示例 ( 指針數組使用案例 )
- 3. main 函數參數 分析
- ( 1 ) main 函數簡介
- (2) main 函數 代碼示例
- 六. 多維數組 和 多維指針
- 1. 二維指針 ( 指向指針的指針 )
- ( 1 ) 二維指針簡介 ( 指向指針的指針 )
- ( 2 ) 代碼示例 ( 指針的傳址調用 | 指向指針的指針 | 重置指針指向的空間 )
- 2. 二維數組
- ( 1 ) 二維數組 ( 存放方式 | 數組名 | 首元素類型 | 數組名 類似 常量指針 | )
- (2) 代碼示例 ( 以一維數組方式遍歷二維數組 | 體現二維數組的數據排列 )
- 3. 數組名
- ( 1 ) 數組名 簡介 ( 數組首元素地址 | &數組名 是 數組地址 )
- ( 2 ) 代碼示例 ( 數組名指針指向的內容 | 二維指針數組名對應的指針運算 )
- ( 3 ) 代碼示例 ( 一維數組遍歷 | 二維數組遍歷 )
- ( 4 ) 代碼示例 ( 為二維數組申請內存空間 )
- 五. 數組參數 與 指針參數
- 1. 數組參數退化為指針參數的意義
- ( 1 ) 數組參數退化的相關概念 ( 指針退化成數組 )
- ( 2 ) 代碼示例 ( 二維數組參數 的指針退化 | 外層指針退化 | 內層數組指針沒有退化 )
- 六. 函數指針
- 1. 函數類型 和 函數指針
- (1) 相關概念 ( 函數類型要素 ① 返回值, ② 參數類型, ③ 參數個數, ④ 隱含要素 : 參數順序 | 函數指針類型 返回值類型 (*變量名) (參數列表) )
- ( 2 ) 代碼示例 ( 定義函數指針 : ①typedef int(FUN)(int); FUN* p; 或者 ② void(*p1)(); | 給 函數指針 賦值 , 右值 可以直接使用 ① 函數名 或 ② &函數名 | 調用函數指針方法 : ① 函數指針變量名(參數) ② (*函數指針變量名)(參數) | 函數名 和 &函數名 是等價的 | 函數指針變量名(參數) 和 (*函數指針變量名)(參數) 也是等價的 )
- 2. 回調函數
- ( 1 ) 回調函數相關概念
- ( 2 ) 代碼示例 ( 回調函數示例 )
- 3. 解讀 復雜的 指針聲明 ( 難點 重點 | ①找出中心標識符 ②先右 后左 看 確定類型 提取 ③ 繼續分析 左右看 ... )
注意 : 博客中出現的關于指針的計算方式, 如果在 32 位電腦中, 指針的地址類型是 unsigned int 類型 , 占 4 字節 , 在 64 位電腦中 指針地址的類型是 unsigned long int , 占 8 個字節 ;
一. 指針
1. 指針 簡介
( 1 ) 指針 概念 ( 本質 | 占用內存 ① 32位 4字節 ② 64 位 8 字節 | * ① 聲明指針 ② 獲取指向的值 )
指針簡介 :
- 1.指針本質 : 指針本質也是一個變量 ;
- 2.占用內存 : 指針變量也要在內存中占用一定大小的空間, 不同 類型的指針占用的內存大小都是 相同的 ;
32位系統 指針 占用內存大小 4 字節, 64位系統 指針 占用內存大小 8 字節;
- 3.指針變量保存的值 : 指針變量中保存的是內存地址的值 ;
符號簡介 :
- 1.聲明指針 : 在 聲明指針變量時, * 表示聲明一個指定類型變量的指針 ;
- 2.使用指針 : 使用指針的時候, * 表示指針變量地址指向的內存中的值, 可以讀取該地址的實際數據值 或者 向地址中寫入實際數據值 ;
( 2 ) 指針 簡單示例 ( * 的讀寫內存作用 | 指針相關類型大小)
指針簡單示例 :
- 1.代碼示例 :
- 2.運行結果 :
2. 傳值 和 傳址 調用
( 1 ) 相關概念 ( 傳值調用 復制實際值 | 傳址調用 復制地址值 )
傳值調用 :
- 1.產生復制情況 : 傳值調用時 會發生 實參數據值 復制到 形參中 ;
傳址調用 :
- 1.實現方式 : 將指針當做函數的參數, 因為指針也是變量, 可以當做參數使用 ;
- 2.適用場景 : 如果需要在函數中修改實參的值, 并且執行函數完畢后保留下來, 這里就用到傳址調用, 使用指針作為函數參數 ;
- 3.適用場景2 : 參數數據類型較復雜, 如果參數很大, 傳值調用需要實參到形參的復制, 會浪費性能 ;
( 2 ) 傳址調用 ( 改變外部變量值 )
代碼示例1 :
- 1.代碼 :
- 2.執行結果 :
代碼示例2 :
- 1.代碼 :
- 2.執行結果 :
3. 常量 和 指針
( 1 ) 相關概念 ( 核心原則 左數右指 | 左數 ① const int* p ② int const* p 數據時常量 | 右指 int* const 指針是常量 )
參考 : const 關鍵字 ;
const 修飾指針 : 需要符合下面的規則 :
| const int* p | p指針地址可變 p指針指向的內容不可變 (const 在 * 左邊, 數據不可變) |
| int const* p | p指針地址可變 p指針指向的內容不可變 (const 在 * 左邊, 數據不可變) |
| int* const p | p指針地址不可變 p指針指向的內容不可變 (const 在 * 右邊, 地址不可變) |
| const int* const p | p指針地址不可變 p指針指向的內容不可變 (const 在 * 左邊 和 右邊, 數據和地址都不可變) |
const 修飾指針規則 : ***左數 右指 (左邊數據是常量, 右邊指針是常量)***;
左數 : const 出現在 * 左邊時, 指針指向的數據為常量, 指向的數據不可改變;
右指 : const 出現在 * 右邊時, 指針地址本身是常量, 指針地址不可改變;
( 2 ) 驗證 常量 指針 相關概念 ( 左數右指 )
參考 : const 關鍵字 ;
const 修飾指針規則 : 左數右指;
左數 : const 出現在 * 左邊時, 指針指向的數據為常量, 指向的數據不可改變;
右指 : const 出現在 * 右邊時, 指針地址本身是常量, 指針地址不可改變;
const 關鍵字 代碼示例 : 修飾指針
- 1.代碼示例1 : const 出現在 * 左邊, const int* p = &i;
- 2.代碼示例2 : const 出現在 * 左邊, int const* p = &i;
- 3.代碼示例3 : const 出現在 * 右邊, int* const p = &i;
- 4.代碼示例4 : const 同時出現在 * 左邊 和 右邊, const int* const p = &i;
二. 數組
1. 數組 簡介
( 1 ) 數組 概念 ( 數組地址 | 數組大小 顯示 隱式 聲明 | 數組初始化 [ 效率比后期賦值高 ] )
數組 簡介 :
- 1.概念 : 數組 是 相同類型 的 變量 的 有序集合 ;
- 2.數組示例 :
定義數組 int array[6];
意義 : 數組中包含 6 個 int 類型的數據 , 數組中每個元素都是 int 類型的 ;
第一個元素地址 : array 是數組中第一個元素的起始地址;
下標 : 可以通過下標來獲取數組中指定位置的元素, array[0] 是第一個元素的位置, array[5] 是第六個元素的位置 ;
數組大小 :
- 1.數組定義時必須聲明大小 : 數組在定義時, 必須顯示 或 隱式 的聲明數組的大小 ;
- 2.顯示聲明數組大小 : 定義數組時, 在數組名稱后的中括號中聲明數組大小 ;
- 3.隱式聲明數組大小 : 聲明數組時, 不在中括號中聲明數組大小, 只在初始化中初始化指定個數的元素, 那么元素的個數就是數組的大小 ;
數組初始化 :
- 1.完全初始化 : 數組大小為5, 將 5 個元素都在定義時指定位置 ;
- 2.部分初始化 : 數組大小為5, 如果初始化前 1 ~ 4 個元素, 剩余的元素默認初始化為 0 ;
- 3.初始化效率 : 初始化效率很高, 遠遠比依次賦值要高, 因此建議定義數組時最好初始化 ;
- 4.最佳實踐 :
( 2 ) 數組 示例 ( 定義 | 大小 | 初始化 )
數組 大小 初始化 示例 :
- 1.代碼 :
- 2.執行結果 :
2. 數組地址與名稱 概念
( 1 ) 數組 概地址 ( 數組名 [ 數組首元素地址 ] 和 &數組名 [ 數組地址 ] | 數組名 類似于 常量指針 | 數組拷貝 )
數組地址名稱 簡介 :
- 1.數組名稱 : 數組名稱 等價于 數組 首元素 地址 ;
- 注意 : 數組名 不是 數組的首地址 , &數組名 才是數組的首地址 , 但是這兩個的值是相同的 ;
- 2.數組地址 : 使用 & 取數組的地址, 才能獲取數組的地址 ;
- 3.值相同 : 數組的 首元素地址 與 數組地址是相同的 ;
- 4.數組地址 與 數組首元素地址 : 這兩個地址不是等價的, 其意義完全不同 ;
數組名稱 :
- 1.數組名稱的本質 : 數組名 類似于 常量指針, 數組名稱 不能作為左值, 不能被賦值 ; 數組名 只能作為右值, 被賦值給別的指針 , 數組名在***大多數情況下可以當做常量指針理解***, 但是 數組名絕對不是真正的常量指針 ;
- 2.數組名代表的地址 : 數組名稱 指向 數組首元素的地址, 其絕對值 與 數組地址 相同;
數組名稱不作為常量指針的場合 : 數組名類似于常量, 但不是常量, 下面兩種場合數組名與常量指針不同 ;
- 1.sizeof 取大小時 : 使用 sizeof 操作符獲取 array 數組大小時, sizeof 作用域常量指針獲取的是指針的大小, sizeof 作用于數組名, 獲取的是數組的大小 , 不是單個指針的大小;
- 2.作為 & 參數時 : & 只能用于變量, 不能用于常量, 因此 &數組名 是取數組的地址, 這種用法 不符合常量指針的特點 ;
數組拷貝禁用數組名直接賦值 :
- 1.禁止使用的方式 : 數組拷貝不能 直接使用 數組名1 = 數組名2 的方式進行拷貝 或者 賦值 ;
- 2.常量指針 : 數組名 類似于 常量指針, 其***不能作為賦值的左值, 只能做右值使用*** ;
- 3.數組大小 : 數組還有一個隱含的大小屬性, 如 sizeof(數組名) 就可以獲取整個數組的大小, 單純的數組名稱只是一個地址, 如果使用地址進行互相賦值, 數組的大小屬性無法體現, 因此 C 語言規范, 禁用數組名 作為左值 ;
( 2 ) 數組 示例 ( 數組名 | 地址 | 數組拷貝禁止情況 )
數組代碼示例 :
- 1.代碼示例 :
- 2.執行結果 :
3. 數組 與 指針 區別
( 1 ) 概念簡介 ( ① 數組名就是首元素地址 不需要尋址 | ② 指針 中保存一個地址 指向首元素地址 需要尋址 | printf 打印 數組 或 指針 : 根據占位符自動判斷打印地址還是打印內存中的具體內容 )
printf 打印 數組 與 指針 變量 :
- 1.處理數組 : 編譯器不會尋址 , 直接將 數組名代表的內存空間地址對應的數據打印出來 , 因為數組名就代表了數組的首地址, 不需要再次尋址 ; 數組名代表的地址 就是 內容的首地址 , 不用去尋址查找內容 ;
- 2.處理指針 : 編譯器會尋址 , 查找 指針變量的四個字節的內容, 指針變量的四個字節的地址指向的內容 , 然后將指針指向的內容打印出來 , 指針的地址 與 實際內容的地址 不連續, 是斷開的 ;
下面這張圖形象的說明了 指針 與 數組的 區別 :
指針的起始地址 和 數組的起始地址 :
- 1.指針起始地址 : 這里要區分 指針保存的地址 和 指針起始地址,
( 1 )指針保存的地址 : 是指 指針變量 4 字節 (32位系統的, 64位 8 個字節) , 這四個或 8個字節中保存了一個地址 , 這個地址指向另外一段內存空間, 這個地址是指針保存的地址, 又叫指針指向的地址, 在下圖中標注的 指針變量中保存(指向)的地址① , 這個地址還是 實際內容的起始地址① ;
( 2 )指針起始地址 : 是指指針變量所在的地址, 是 ***指針變量的四個字節的第一個字節所在內存的首地址 ***, 在下圖中標注的 指針起始地址② ; - 2.數組起始地址 : 數組名就是數組的起始地址, 又是數組首元素地址 , int array[10], array 是一個地址, 在下圖中標注的 數組首地址③, 這個地址還是數組 數組實際內容的首地址③ ;
- 3.圖示 :
printf 打印 數組 或 指針 的 內容 或 地址 : 針對 字符數組 和 字符指針, 根據占位符自動判斷打印地址還是打印內存中的具體內容 ;
- 1.打印字符串 : 如果想要打印出 數組或指針的 字符串, 那么使用 %s 作為占位符 ;
- 2.打印地址 : 如果想要打印出 數組或指針的地址 , 那么使用 %x 作為占位符 ;
- 3.智能判斷 : printf 時, 方法中會自動判斷 占位符 的類型, 來判斷是否要尋址, 如果 %x 則只打印地址, 如果使用 %s, 則會自動根據對應的地址打印出其內容 ;
- 4.代碼示例 :
- 5.執行結果 :
( 2 ) 代碼示例 ( 數組 | 指針 編譯器處理上的區別 )
代碼示例 :
- 1.代碼1 : 文件 test_1.c 內容 ;
- 2.代碼2 : 文件 test_2.c 中的內容 ;
- 3.執行結果 : 執行 gcc test_1.c test_2.c 命令進行編譯 , 執行 編譯后的可執行文件 ./a.out ;
三. 數組 指針 分析
1. 指針 加減 運算方式
( 1 ) 指針 加減法 運算 ( 指針指向的位置在同一個數組中改變才有意義 )
指針運算規則 :
- 1.指針是變量 : 只要是變量就可以進行運算, 可以加減運算, 指針 + 1 運算如下 ;
- 2.指針 + num 運算規則 : p + num(整數) , 反應到地址運算上 即 等價于 *(unsigned int)p + num * sizeof(p) , 其地址不是加1個字節, p 的地址的增量 是 所指向的數據類型的大小 乘以 被加數 的地址;
指針指向數組元素規則 :
前提 : 指針指向一個數組元素時有以下規則 :
加 : 指針 + 1 指向數組的下一個元素 ;
減 : 指針 - 1 指向數組的上一個元素 ;
數組運算規則 :
- 1.數組本質 : 數組的***元素存儲空間是連續的***, 從數組首地址(數組元素首地址 | 數組名)開始 ;
- 2.數組空間大小 : 數組的空間通過 sizeof(數組元素類型) * 數組大小 計算的, 這個數組元素類型是數組聲明的時候指定的, 數組大小是數組聲明或者初始化時指定的 ;
- 3.數組名 : 數組名 是 數組首元素的地址, 又是***數組地址***, 即***數組所在內存空間的首地址*** ;
- 4.數組名 看做 常量指針 : 數組名可以看做指向數組首元素的常量指針, 當數組名 + 1 時, 可以看做指針 進行了 加 1 運算, 其地址按照指針運算規則, 增加了 數組元素大小 * 1 ;
指針減法運算 :
- 1.指針之間的運算 : 兩個指針之間 只能進行 減法運算, 加法乘法除法不行, 并且 進行減法運算的兩個指針的類型必須相同 ;
- 2.指針減法運算的前提 : 進行減法運算的***兩個指針類型必須是同一個類型*** ;
- 3.指針減法運算的意義 : 指針減法運算時 兩個指針指向同一個數組才有實際的意義, 計算結果是 同一個數組 兩個指針指向位置的下標差 ;
- 4.同類型無意義減法 : 如果兩個指針指向相同類型的不同數組, 即使減法有結果, 這個結果也是沒有任何意義的;
指針減法的過程 : 指針1 - 指針2 = ( 指針1指向的地址 - 指針2指向的地址 ) / sizeof (指針1和指針2的相同類型)
(2) 數組大小計算示例
數組大小計算代碼示例 :
- 1.代碼示例 :
- 2.編譯執行結果 :
( 3 ) 指針 加法運算示例 ( 指針地址 + 4/8 * 被加數 )
數組名 指針 加法示例 :
- 1.代碼示例 :
- 2.編譯運行結果 : 示例中的 array + 1 比 array 的地址大 4 個字節 ;
指針運算 : int * p, p + 1 代表的地址是 p 的地址 加上 4, 即加上了 一個 int 類型大小的地址;
( 4 ) 指針 減法 運算示例
指針減法代碼示例 :
- 1.代碼示例 :
- 2.編譯運行結果 :
2. 指針 比較 運算方式
( 1 ) 指針 比較 運算 ( 大于 小于 大于等于 小于等于 運算的前提是 必須指向同一數組 中的元素 | 任意兩指針只能進行 等于 不等于 的比較 )
指針的比較運算 :
- 1.同一數組的比較運算 : 對于 大于 ( > ) , 小于 ( < ) , 大于等于 ( >= ) , 小于等于 ( <= ) 四種類型運算, 指針之間進行這四種運算的前提示 兩個指針 必須都指向同一個數組的元素 ;
- 2.任意指針的比較運算 : 對于 等于 ( == ) , 不等于 ( != ) 兩種比較運算, 指針之間進行這兩種比較運算, 可以是任意指針, 指針指向不同數組也可進行這兩種運算 ;
( 2 ) 指針 比較 運算代碼示例 ( 用 指針 遍歷數組 )
使用指針遍歷數組代碼示例 :
- 1.代碼示例 :
- 2.編譯執行結果 :
3. 數組訪問方式
( 1 ) 下標 指針 訪問 ( 推薦使用下標訪問 )
下標訪問數組 和 指針訪問數組 的示例 : 這兩種訪問數組的方式是等價的 ;
- 1.下標訪問數組 :
- 2.指針訪問數組 :
下標訪問 和 指針訪問 對比 :
- 1.可讀性 : 使用下標訪問數組, 數組的可讀性會大大的提高, 指針訪問數組不易理解 , 下標訪問在可讀性上優于指針訪問數組 ;
- 2.性能 : 當使用一個固定的增量訪問數組時, 指針訪問 的性能 優于 下標訪問;
推薦使用方式 : 現在的編譯器編譯出來的代碼, 性能上 指針訪問 與 下標訪問基本相同, 出于代碼可讀性考慮, 推薦使用下標訪問數組的方式 ;
下標 指針訪問數組性能分析 : 以 數組 中的元素互相賦值為例 ;
- 1.下標訪問 : 如訪問 array[3] ( 數組第 4 個元素 ) , 其首地址地址是 array 首地址 加上 3 個元素地址 ( 第三個元素的尾地址就是第四個元素的首地址 ) , 其預算方式是這樣的 : ( unsigned int ) array + sizeof(int) * 3 ;
- 2.指針訪問 : 如訪問 array[3] , 以指針的形式, 如果每次遞增 1 個下標, 那么運算方式是 ( unsigned int ) array + 4 即可, 這里每次只做加法, 下標訪問每次都要用乘法, 乘法運算要比加法運算費時 ;
( 2 ) 下標 指針 訪問 數組 性能 代碼示例
3. int array[]; array 和 &array 區別
( 1 ) int array[] 中 array 和 &array 意義 ( ① array 數組首元素地址 | ② &array 數組地址 )
數組 int array[] 中 array 和 &array 意義 :
- 1.數組元素首地址 : array 是數組首元素地址, sizeof ( *array ) 計算的是數組中單個元素的大小 ;
- 2.數組地址 : & 是數組的首地址, 代表的是整個數組的地址 ;
兩種指針的運算 :
- 1.array + 1 運算 : array + 1 的運算過程是 ( unsigned int ) array + sizeof (array) , 該運算相當于計算***數組中第二個元素的首地址** , 等價于 array[1] ;
- 2.&array + 1 運算 : &array + 1 的運算過程是 ( unsigned int ) ( &array ) + sizeof(&array)***, 其中 &array 結果是 array, sizeof ( &array) 的結果 等價于 sizeof ( array ), 這是整個數組的大小, 因此 &array + 1 的*結果是數組的尾地址* ;
( 2 ) array 和 &array 計算 代碼示例
代碼示例 :
- 1.代碼 :
- 2.編譯運算結果 :
- 3.圖示 :
- 4.p2 指針計算過程 : 由上圖可以看到 指針指向的位置開始取一個 int 類型, 地址由低到高 四個字節存儲的數據為 0 0 0 1, 由于是小端模式, 高位地址存放在高位 其大小為 0x01 00 00 00 , 轉為十進制是 16777216 ;
4. 數組參數
( 1 ) 數組參數 概念 ( 退化成指針 | 需要帶上數組長度作為 附屬參數 )
數組參數相關概念 :
-
1.數組作為參數時編譯器行為 : 數組作為參數時, 編譯器會將數組 退化成 指針, 此時這個指針就沒有數組的長度信息了 ;
示例 1 : ***void method(int array[]) 等價于 method(int p)**, 此時 *p 中是不包含數組的長度信息的 ;
示例 2 : *void method(int array[100]) 等價于 method(int p), 此時 *p 中是不包含數組的長度信息的 ; -
2.數組作為參數時的最佳用法 : 數組作為參數時, 應該定義另一個 int 類型的參數, 作為數組的長度信息 ;
( 2 ) 數組參數 代碼示例 ( 數組大小 | 數組參數大小 )
代碼示例 :
- 1.代碼 :
- 2.編譯執行結果 :
5. 數組 指針 對比 ( 內存分配 : ① 指針 分配 4 / 8 字節 ② 數組分配所有元素地址 | 作為參數 | 常量[ 數組 ] 變量[ 指針 ] 區別 )
內存空間分配區別 :
- 1.指針 ( 分配 4 或 8 字節 ) : 聲明指針的時候 只分配了 容納指針的 4字節 (32位系統) 或 8 字節 (64 位系統) ;
- 2.數組 ( 分配連續內存 ) : 聲明數組時 分配了一篇容納數組所有元素的一片連續內存空間 ;
參數上的區別 ( 等價 ) : 作為參數時, 數組 和 指針 參數時等價的, 數組會退化為指針, 丟失長度信息 ;
指針 數組 的 性質 :
- 1.數組 ( 常量 ) : 數組大部分情況下可以當做常量指針, 不能作為左值使用, 不能被賦值 ; (sizeof 和 & 作用域數組名時除外) ;
- 2.指針 ( 變量 ) : 指針是變量, 變量中保存的值 是 內存中的一個地址 ;
四. 字符串
1. 字符串概念
( 1 ) 概念 ( 本質 是 char[] 數組 | ‘\0’ 結尾 | 存儲位置 棧 堆 常量區 )
字符串相關概念 :
- 1.字符串本質 : C 語言中沒有字符串這個數據類型, 使用 char[] 字符數組來模擬字符串 ;
- 2.字符串要求 : 不是所有的字符數組都是字符串, 只有***以 ‘\0’ 結尾的字符數組***才是字符串 ;
- 3.字符串存儲位置 : 棧空間, 堆空間, 只讀存儲區 (常量區) ;
( 2 ) 示例代碼 ( 字符串概念 | 字符串 )
代碼示例 :
- 1.代碼 (正確的版本) :
- 2.編譯運行結果 ( 錯誤版本 報錯提示 ) : 取消 s3[0] = ‘h’; 注釋, 嘗試修改 只讀存儲區的數據 , 運行時會報段錯誤 ;
2. 字符串 長度
( 1 ) 字符串長度計算 ( 不包括 ‘\0’ | 標準庫中有該函數)
字符串長度 :
- 1.概念 : 字符串包含的字符個數, 不包含 ‘\0’ , 只包括有效字符 ;
- 2.計算字符串長度 : 根據從字符串開始到 ‘\0’ 結束, 計算不包括 ‘\0’ 的字符個數 ;
- 3.數組不完全使用 : 如果數組長度100, 在第50個元素位置出現了 ‘\0’, 那么這個字符串長度是 49, 數組長度是 100 ;
針對 C 標準庫已有的函數 :
- 1.不要自己實現 C 標準庫功能 : C 標準庫是優化到極致, 個人修改的效果比庫函數效果要差 ;
- 2.復用庫函數效率高 : 不要重復制造輪子 ;
( 2 ) 代碼示例 ( 字符串長度計算示例 )
代碼示例 :
- 1.代碼 :
- 2.編譯運行結果 :
( 3 ) 代碼示例 ( 自己實現 strlen 方法 )
實現 strlen 方法代碼示例 ( 普通版本 ) :
- 1.代碼 :
- 2.編譯運行結果 :
實現 strlen 方法代碼示例 ( 遞歸版本 ) :
- 1.代碼 :
- 2.編譯執行結果 :
3. 字符串函數 長度不受限制 情況
( 1 ) 不受限制的字符串函數 ( 函數自動尋找 ‘\0’ 確定字符串大小 | stpcpy | strcat | strcmp )
不受限制的字符串函數相關概念 :
- 1.字符串常用方式 : 一般在函數中使用字符串時, 需要指明字符串的大小, 因為字符串數組 一旦當做函數參數時, 就退化成指針, 失去了大小信息 ;
- 2.字符串相關的函數不需要大小信息 : 在 string.h 中的方法, 不需要傳入大小信息, 函數中會自動尋找 ‘\0’ 來計算字符串的長度 ;
- 3.參數不是字符串則出錯 : 不受限制字符串函數如果傳入的字符串沒有 ‘\0’ , 則會出錯 ;
不受限制的字符串函數示例 :
char *stpcpy(char *dest, const char *src);//字符串拷貝char *strcat(char *dest, const char *src);//字符串拼接int strcmp(const char *s1, const char *s2);//字符串比較不受限制字符串函數 的 相關注意事項 :
-
1.字符串必須以 ‘\0’ 結尾 : 此類函數相關的字符串必須以 ‘\0’ 結尾, 因為字符串長度是根據找到的 ‘\0’ 來計算的, 如果沒有 ‘\0’ 會報錯 ;
-
2.字符串長度改變相關 : strcpy ( 字符串拷貝 ) 和 strcat ( 字符串拼接 ) 必須保證 拷貝 或 拼接的 目標數組 有足夠的空間來保存結果字符串 ;
-
3.字符串比較函數 : strcmp 兩個字符串比較, 如果返回 0 , 表示兩個字符串相等 ;
- 函數 : int strcmp(const char *s1, const char *s2);
- ( 1 ) 返回值 等于 0 : 兩個字符串相等 ;
- ( 2 ) 返回值 大于 0 : 第一個字符串 大于 第二個字符串 ;
- ( 3 ) 返回值 小于 0 : 第一個字符串 小于 第二個字符串 ;
注意字符串要求 : strcmp 函數不會修改 s1 和 s2 字符串的值, 但是兩個字符串必須符合要求 以 ‘\0’ 結尾 ;
( 2 ) 代碼示例 ( 自己實現字符串拷貝函數 )
實現拷貝字符串函數 :
- 1.代碼 :
- 2.編譯運行結果 :
4. 字符串函數 長度受限制 情況
( 1 ) 受限制的字符串函數 ( 推薦使用 降低錯誤率 )
長度受限制的字符串函數 :
- 1.概念 : 長度受限制的字符串函數, 其 字符串參數 會 跟隨一個字符串先關的長度測參數, 一般為 size_t 類型, 用于限定字符串的字符數 ;
- 2.推薦使用 : 在函數調用的時候, 優先使用長度受限制的字符串函數, 這樣會減少出現錯誤的幾率 ;
長度受限字符串函數 舉例說明 :
- 1.字符串拷貝 : char *strncpy(char *dest, const char *src, size_t n) ;
- ( 1 ) 作用 : 拷貝 src 中 n 個字符 到 dest 目標字符串中 ;
- ( 2 ) src 長度 小于 n : 使用 ‘\0’ 填充剩余空間 ;
- ( 3 ) src 長度 大于 n : 只賦值 n 個字符, 并且不會使用 ‘\0’ 結束 , 因為已經復制了 n 個字符了 ;
- 2.字符串拼接 : char *strncat(char *dest, const char *src, size_t n) ;
- ( 1 ) 作用 : 從 src 字符串中賦值 n 個字符 到 dest 字符串中 ;
- ( 2 ) 始終 ‘\0’ 結尾 : 函數始終在 dest 字符串之后添加 ‘\0’;
- ( 3 ) 不填充剩余空間 : 對于拼接后剩余的數組空間, 不使用 ‘\0’ 填充 ;
- 3.字符串比較 : int strncmp(const char *s1, const char *s2, size_t n) ;
- ( 1 ) 作用 : 比較 src 和 dest 中前 n 個字符 是否相等 ;
五. 指針數組 與 數組指針
1. 數組指針
( 1 ) 數組類型介紹 ( 數組元素類型 | 數組大小 | 舉例 int[8] )
數組類型 :
- 1.數組類型要求 : 數組的類型有兩個決定要素, 分別是 ① 數組元素類型 和 ② 數組大小 ;
- 2.數組類型示例 : int array[8] 的類型是 int[8] ;
數組類型定義 :
- 1.數組類型重命名 : 使用 typedef 實現 , typedef type(數組名)[數組大小] ;
- 2.數組類型重命名示例 :
- ( 1 ) 自定義一個 int[5] 類型的數組 : typedef int(ARRAY_INT_5)[5] ;
- ( 2 ) 自定義一個 float[5] 類型的數組 : typedef float(ARRAY_FLOAT_5)[5] ;
- 3.根據自定義的數組類型聲明變量 :
- ( 1 ) 使用自定義的 ARRAY_INT_5 聲明變量 : ARRAY_INT_5 變量名 ;
- ( 2 ) 使用自定義的 ARRAY_FLOAT_5 聲明變量 : ARRAY_FLOAT_5 變量名 ;
(2) 數組指針簡介 ( 指向數組的 一個 指針 | 數組指針類型定義方式 : 數組元素類型 ( * 指針名稱 ) [數組大小] )
數組指針 : 本質是一個指針 ;
- 1.數組指針作用 : 數組指針 用于 指向一個數組 ;
- 2.數組名意義 : 數組名是數組首元素地址, 不是數組的首地址 , &數組名 是數組的首地址 ;
- 3.數組首地址 : & 數組名 是數組首地址 , 數組首地址 不是 數組名( 數組首元素地址 ) ;
- 4.數組指針定義 : 數組指針是通過 定義 數組類型 的指針;
- ( 1 ) 數組類型 : 是之前說過的 包含 ① 數組元素類型 , ② 數組大小 兩個要素, 定義數組類型 : typedef int(ARRAY_INT_5)[5] 定義一個 int[5] 類型的數組類型 ;
- ( 2 ) 定義數組指針 : ARRAY_INT_5* 指針名稱 ;
- 4.數組指針定義的另外方式 : 類型 ( * 指針名稱 ) [數組大小] ;
- ( 1 ) 示例 : *int (p)[5] , 定義一個指針 p, 指向一個 int[5] 類型的指針 ;
- ( 2 ) 不推薦此種寫法 : 可讀性很差 ;
數組指針 和 數組首元素指針 大小打印 :
- 1.代碼示例 :
- 2.編譯運行結果 :
( 3 ) 代碼示例 ( 定義數組類型 | 數組指針用法 )
代碼示例 :
- 1.代碼 :
- 2.編譯運行結果 :
2. 指針數組
( 1 ) 指針數組簡介 ( 數組中存儲的元素是指針 | 數組指針 int (array)[5] 本質是指針 | 指針數組 int array[5] 本質是數組 )
指針數組 相關概念 :
- 1.指針數組概念 : 指針數組是一個普通的數組, 其元素類型是 指針類型 ;
- 2.指針數組定義 : 類型* 數組名稱[數組大小] ;
- ( 1 ) 指針數組 : int* array[5] ;
- ( 2 ) 數組指針 : int (*array)[5] ;
( 2 ) 代碼示例 ( 指針數組使用案例 )
指針數組代碼示例 :
- 1.代碼 :
- 2.編譯執行結果 :
3. main 函數參數 分析
( 1 ) main 函數簡介
main 函數分析 :
- 1.main 函數 : main 函數是 ① 程序的 入口函數 , ② 操作系統調用的函數 ;
- 2.main 函數示例 :
- main 函數參數說明 :
- ( 1 ) int argc 參數 : 程序命令行參數個數 ;
- ( 2 ) char argv[] 參數* : 程序命令行字符串參數數組, 這是一個數組指針, 數組中每個元素都是指向一個字符串的指針 ;
- ( 3 ) char env[] 參數* : 環境變量數組, 這是一個數組指針, 數組中每個元素都是指向一個字符串的指針 ; 這個環境變量 在 Windows 中是配置的 環境變量, 在 Linux 中是配置在 /etc/profile ( 一種設置方式, 還有很多設置方式 ) 中定義的環境變量 ;
(2) main 函數 代碼示例
main 函數代碼示例 :
- 1.代碼示例 :
-
2.編譯運行結果 : 環境變量打印出來的東西太多了, 就不一一截圖查看 ;
六. 多維數組 和 多維指針
1. 二維指針 ( 指向指針的指針 )
( 1 ) 二維指針簡介 ( 指向指針的指針 )
指向 指針 的 指針 ( 二維指針 ) :
- 1.指針變量 : 指針變量會占用 內存空間 , 很明顯可以使用 & 獲取指針變量的地址 ;
- ( 1 ) 32 位系統 : 指針占 4 字節空間 ;
- ( 2 ) 64 位系統 : 指針占 8 字節空間 ;
- 2.指向 指針變量 的指針 : 定義一個指針, 這個指針 保存一個 指針變量 的地址 ( 不是 指針變量指向的地址, 是指針變量所在的本身的地址 ) ;
指針變量 的 傳值 和 傳址 調用 :
- 1.指針變量傳值調用 ( 一維指針 ) : 直接將指針值傳入, 修改的是 指針 指向的內存空間內容 ;
如 : void fun ( char *p ) , 這是相對于指針的傳值調用, 相對于 char 類型數據的傳址調用, 用于修改 p 指針指向的內存中的值 ;
- 2.指針變量傳址調用 ( 二維指針 ) : 在函數內部 修改 函數外部的變量, 需要傳入一個地址值, 如果要修改的是一個指針, 那么需要傳入指針的地址, 即參數是一個指向指針的指針 ; 指針變量傳址調用, 修改的是 指針 指向的 指針變量 ;
如 : void fun(char ** pp) 該傳址調用 即 傳入的是 char* 指針的地址, 修改的是 pp 二維指針 指向的 char* 類型指針 ;
- 3.函數中修改函數外部變量 : 只能使用指針 指向這個外部變量, 才可以修改這個外部變量 , 如果這個外部變量本身就是一個指針 , 那么就必須傳入這個指針的地址, 那么傳入的參數的內容就是一個二維指針 ;
( 2 ) 代碼示例 ( 指針的傳址調用 | 指向指針的指針 | 重置指針指向的空間 )
代碼示例 :
- 1.代碼 :
- 2.編譯運行結果 :
2. 二維數組
( 1 ) 二維數組 ( 存放方式 | 數組名 | 首元素類型 | 數組名 類似 常量指針 | )
二維數組 相關概念 : 二維數組 int array[5][5] ;
- 1.二維數組存放方式 : 二維數組在內存中以 一維數組 方式排布 ;
- 2.二維數組數組名 : 代表 二維 數組 首元素 地址, 其 首元素 是一個一維數組 , 即 array[0] ;
- 3.二維數組首元素類型 : 數組名 array 指向二維數組首元素, 那么其類型是 數組指針, 數組類型 為 int[5] ( ① int 類型數組, ② 含有 5 個元素 ) ;
- 4.數組名類似常量指針 : 二維數組的數組名可以看做常量指針, 除了兩種情況 sizeof 計算大小 和 & 獲取地址時 ;
- 5.具體的數據值存放 : 二維數組第一維是 數組指針, 第二圍才是具體的數據值 ;
- 6.二維數組圖示 :
一些注意點 :
1.編譯器沒有二維數組概念 : C語言中沒有二維數組改變, 編譯器 都按照一維數組來處理, 數組的大小在編譯時就確定了 ;
2.二維數組由來 : C 語言中的數組元素可以是任何類型, 即可以是一維數組, 這樣就產生了二維數組 ;
3.首元素地址確定時間 : 在編譯階段確定的 除了 數組大小外, 數組的首元素也是在編譯階段確定的, 在程序運行階段首元素地址不能被修改 (看做常量) ;
(2) 代碼示例 ( 以一維數組方式遍歷二維數組 | 體現二維數組的數據排列 )
代碼示例 :
- 1.代碼 :
- 2.編譯執行結果 :
代碼分析 :
將二維數組的首地址賦值給 類型相同 的一維數組, 遍歷該一維數組, 并且該數組的大小為 二維數組所有值得大小 , 由此可以看出, 二維數組的數據排布是按照索引, 先放入二維數組的第一個數組元素, 在按照索引依次將數組放入內存中 ;
3. 數組名
( 1 ) 數組名 簡介 ( 數組首元素地址 | &數組名 是 數組地址 )
數組名 相關概念 :
- 1.數組名 : 數組名代表了 數組首元素地址 ;
- 2.一維數組 數組名 : int array[2], array 指向數組首地址, 其指向了一個 int 類型首元素, array 類型為 int * ;
- 3.二維數組 數組名 : int array[2][3] , array 指向數組首地址, 其指向了 類型 int[3] 數組的首元素, array 的類型是 int(*)[5] ;
- ( 1 ) 類似常量指針 : 二維數組的數組名 可以看做為 常量指針 ;
- ( 2 ) 看做一維數組 : 二維數組可以看做一維數組, 只是這個一維數組內的元素 是 一維數組 ;
- ( 3 ) 二維數組元素 : 二維數組中每個元素都是 基礎類型同類型的 一維數組,
( 2 ) 代碼示例 ( 數組名指針指向的內容 | 二維指針數組名對應的指針運算 )
代碼示例 :
- 1.代碼 :
- 2.編譯運行結果 :
- 3.圖示 :
( 3 ) 代碼示例 ( 一維數組遍歷 | 二維數組遍歷 )
代碼示例 :
- 1.代碼 :
- 2.編譯運行結果 :
( 4 ) 代碼示例 ( 為二維數組申請內存空間 )
算法思想 : 為 int[3][3] 申請內存空間 ;
- 1.申請空間 : 分別申請 數組指針空間 和 數據空間 ;
- ( 1 ) 申請數組指針空間 : 申請三個 數組指針 空間, 只是三個普通的指針, 但是該指針類型是 int(*)[3], 即指向 int[3] 數組的指針 ;
- ( 2 ) 申請數據空間 : 申請 能存放 9個 int 值的數據空間 ;
- 2.分配指針 : 將申請的三個 數組指針 , 分別指向對應的 9 個int 值空間的對應位置 ;
代碼示例 :
- 1.代碼 :
- 2.編譯運行結果 :
- 3.圖示 :
五. 數組參數 與 指針參數
1. 數組參數退化為指針參數的意義
( 1 ) 數組參數退化的相關概念 ( 指針退化成數組 )
一維數組參數退化為指針 :
- 1.C語言中的拷貝方式 : C 語言中只會以 傳值拷貝 的方式來傳遞參數 ;
- ( 1 ) 傳遞指針也是傳值 ( 修改指針指向的地址的內容是用戶行為 ) : 只是傳的是指針變量的值, 但是這個變量中存放著地址, 函數中可以改變這個地址的值 ;
- 2.數組傳遞的方式 :
- ( 1 ) 傳遞整個數組 : 如果將整個數組傳遞過去, 如果數組中元素很多, 需要將數組所有元素都要拷貝一份 ;
- ( 2 ) 傳遞數組首元素地址 : 將數組名 ( 數組首元素的地址 ) 看做常量指針傳入函數 ;
- ( 3 ) C 語言針對數組參數的效率考慮 : 假如數組有 10000 個元素, 傳遞數組效率就非常低了, 如果傳遞數組首元素指針, 只用拷貝指針變量的值, 只拷貝 4 ( 32位系統 ) 或 8 ( 64位系統 ) 個字節, 這樣效率能大大提高 ;
- 3.數組參數退化的意義 : 數組參數退化為指針, 程序的執行效率能大大的提高 ;
二維數組參數退化問題 :
- 1.二維數組本質 : 二維數組也可以看做一維數組, 該一維數組中的每個數組元素都是一維數組 ;
- 2.數組退化過程 :
- ( 1 ) 一維數組參數退化過程 : void fun(int array[5]) <-> void fun(int array[]) <-> void fun(int* array) 以上的三種類型的參數都是等價的 ;
- ① 第一次退化 : 數組的個數可以省略掉, 只需要表明數組元素類型即可, 數組元素類型 int[] 類型;
- ② 第二次退化 : 只含有數組元素類型 不含數組個數的類型, 退化為 對應數組元素類型 的指針類型 ;
- ( 2 ) 二維數組參數退化過程 : void fun(int array[3][3]) <-> void fun(int array[][3]) <-> void fun(int (*array)[3])
- ① 第一次退化 : 數組的個數可以省略掉, 只需要表明數組元素類型即可, 數組元素類型 int[3] 類型;
- ② 第二次退化 : 直接退化為指向 一維數組的 數組指針, 該數組指針類型為 int(*)[3] 類型;
- ( 1 ) 一維數組參數退化過程 : void fun(int array[5]) <-> void fun(int array[]) <-> void fun(int* array) 以上的三種類型的參數都是等價的 ;
下面列舉數組參數與指針參數一些等價關系 : 去中括號 ( [] ), 變星號 ( * ) , 放左邊;
| 一維數組 int array[5] | 指針 *int array |
| 一維指針數組 int array[5]* | 指針 int* array* |
| 二維數組 int array[3][3] | 指針 *int (array)[3] |
注意事項 :
1.多維數組參數要求 : 傳遞多維數組參數時, 需要將除第一維之外的其它所有維度的大小都帶上 , 否則無法確定數組大小 和 類型, 編譯時會報錯 ;
2.數組參數限制 :
( 1 ) 一維數組 : 可以不帶數組長度, 但是必須指定數組的大小 ;
( 2 ) 二維數組 : 數組 第一維 長度可以不帶 ( 即 數組指針 元素個數可以省略 ) , 但是數組指針 指向的 數組類型大小必須指定 ( 第二維的大小必須指定 ) ;
( 3 ) 三維數組 : 數組 第一維 長度可不帶, 但是第二維 和 第三維 長度 必須帶上 ;
( 2 ) 代碼示例 ( 二維數組參數 的指針退化 | 外層指針退化 | 內層數組指針沒有退化 )
代碼分析 : 如 int array[3][3] ;
- 1.二維數組參數退化部分 : 二維數組本身 array 數組大小退化, 其退化為 int (*)[3] 類型, 指向一組數組指針的首地址 ;
- 2.二維數組參數沒有退化部分 : array 數組中, array 作為首元素, 其類型為 int[3] 類型, 該類型 包含 ① 其指向的一維數組 中的元素類型 int 和 ② 一維數組大小 3;
代碼示例 :
- 1.代碼 :
- 2.編譯執行結果 :
六. 函數指針
1. 函數類型 和 函數指針
(1) 相關概念 ( 函數類型要素 ① 返回值, ② 參數類型, ③ 參數個數, ④ 隱含要素 : 參數順序 | 函數指針類型 返回值類型 (*變量名) (參數列表) )
函數類型 :
- 1.函數類型引入 : 每個函數都有自己的類型 ;
- 2.函數類型要素 : ① 返回值, ② 參數類型, ③ 參數個數, ④ 隱含要素 : 參數順序 ;
- 示例 : void fun(int a, float b) 函數的類型為 void( int, float ) ;
- 3.函數重命名 : 使用 typedef 可以為函數重命名 , typedef 返回值類型 函數名稱(參數列表) ;
- 示例 : typedef void function(int, float), 就是將上面的 fun 函數重命名為 function ;
函數指針 :
- 1.函數指針概念 : 函數指針 指向一個 函數類型 變量 ;
- 2.函數名 : 函數名指向了 函數體 的 入口地址 ;
- 3.函數指針定義( 宏定義類型 ) : 函數類型* 變量名 ;
- 4.函數指針類型( 簡單類型 ) : 返回值類型 (*變量名) (參數列表) ;
( 2 ) 代碼示例 ( 定義函數指針 : ①typedef int(FUN)(int); FUN* p; 或者 ② void(*p1)(); | 給 函數指針 賦值 , 右值 可以直接使用 ① 函數名 或 ② &函數名 | 調用函數指針方法 : ① 函數指針變量名(參數) ② (*函數指針變量名)(參數) | 函數名 和 &函數名 是等價的 | 函數指針變量名(參數) 和 (*函數指針變量名)(參數) 也是等價的 )
代碼示例 :
- 1.代碼 :
- 2.編譯運行結果 :
2. 回調函數
( 1 ) 回調函數相關概念
回調函數簡介 :
- 1.回調函數實現 : 回調通過 函數指針 調用函數實現 ;
- 2.回調函數特點 : 調用者 和 被調用的函數 互不知情, 互不依賴 ;
- ( 1 ) 調用者 : 調用者不知道具體的函數內容, 只知道函數的類型 ;
- ( 2 ) 被調函數 : 被調用的函數不知道 調用者 什么時候調用該函數, 只知道要執行哪些內容 ;
- ( 3 ) 調用方式 : 調用者 通過 函數指針 調用具體的函數 ;
( 2 ) 代碼示例 ( 回調函數示例 )
代碼示例 :
- 1.代碼 :
- 2.編譯運行結果 :
3. 解讀 復雜的 指針聲明 ( 難點 重點 | ①找出中心標識符 ②先右 后左 看 確定類型 提取 ③ 繼續分析 左右看 … )
指針 定義 復雜性來源 :
- 1.數組指針 : 數組指針類型為 int (*) [5] , 即 一個指向 int[5] 的指針, 其指針變量名稱寫在中間的括號中
- 2.函數指針 : 函數指針類型為 int(*)(int, int), 即 一個指向 int(int, int) 類型函數的指針, 其指針變量名稱寫在中間的括號中 ;
- 3.數組指針混合函數指針 : 如果出現了 數組指針 指向一個函數, 這個指針可讀性很差, 理解需要一定的功力 ;
復雜指針閱讀技巧 ( 主要是 區分 函數指針 和 數組指針 ) 右左法則 :
-
1.最里層標示符 : 先找到最里層的圓括號中的標示符;
數組指針和函數指針的標示符 ( 指針變量名 ) 都在中間的圓括號中, 因此該步驟先找到指針變量名
-
2.右左看 : 先往右看, 再往左看 ;
-
3.確定類型 : 遇到 圓括號 “()” 或者 方括號 “[]” 確定部分類型, 調轉方向 ; 遇到 * 說明是指針 , 每次確定完一個類型 , 將該類型提取出來 , 分析剩下的 ;
一種可能性 :
int (*) [5] , 遇到中括號說明是數組指針類型,
int(*)(int, int) , 遇到圓括號 說明是函數指針類型 ; -
4.重復 2 , 3 步驟 : 一直重復, 直到 指針 閱讀結束 ;
指針閱讀案例 :
- 1.解讀案例 1 :
- 2.解讀案例 2 :
- 3.解讀案例 3 :
- 4.解讀案例 4 :
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的【C 语言】指针 与 数组 ( 指针 | 数组 | 指针运算 | 数组访问方式 | 字符串 | 指针数组 | 数组指针 | 多维数组 | 多维指针 | 数组参数 | 函数指针 | 复杂指针解读)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【C 语言】编译过程 分析 ( 预处理
- 下一篇: 【C 语言】内存管理 ( 动态内存分配