C語言進(jìn)階學(xué)習(xí)之指針
1.指針概念回顧
指針的基本概念:
指針是一個(gè)變量,用來存放地址,地址唯一標(biāo)識(shí)一塊內(nèi)存空間。指針的大小是固定的4/8個(gè)字節(jié)(32位平臺(tái)/64位平臺(tái))。指針是有類型,指針的類型決定了指針的±整數(shù)的步長,指針解引用操作的時(shí)候的權(quán)限。不能對(duì)沒有初始化的指針或者是空指針進(jìn)行間接訪問
2.字符指針
指針類型為 char*
的指針就是字符指針
字符指針指向的對(duì)象的類型為字符型
一般使用方法:
char ch = 'a'; char* p = &ch; //取ch的地址放到指針變量p中
另外一種常見使用方法:
char* p2 = "bcdef";
對(duì)于第二種使用方法我們需要注意的是
p2實(shí)際存放的只是字符串首字符的地址,即p2存放的是字符b的地址。
我們來看一道經(jīng)典的面試題:
#include <stdio.h> int main() { char str1[] = "hello yang."; char str2[] = "hello yang."; char* str3 = "hello yang."; char* str4 = "hello yang."; if (str1 == str2) printf("str1 and str2 are same\n"); else printf("str1 and str2 are not same\n"); if (str3 == str4) printf("str3 and str4 are same\n"); else printf("str3 and str4 are not same\n"); return 0; }
str3和str4指向的都是字符串H的地址
當(dāng)指針指向同一個(gè)字符串的時(shí)候,它們實(shí)際會(huì)指向同一塊內(nèi)存。但是用相同的常量字符串去初始化不同的數(shù)組的時(shí)候就會(huì)開辟出不同的內(nèi)存塊。
因此它的答案為
3.數(shù)組指針和指針數(shù)組
指針數(shù)組是一個(gè)存放指針的數(shù)組
例如
int* arr1[10]; //整形指針的數(shù)組 char *arr2[4]; //一級(jí)字符指針的數(shù)組 char **arr3[5]; //二級(jí)字符指針的數(shù)組
3.1數(shù)組指針的含義
數(shù)組指針是指針?還是數(shù)組?
答案是:指針。
int *p1[10]; int (*p2)[10]; //p1, p2分別是什么?
這里就不得不談?wù)摰絻?yōu)先級(jí)了,優(yōu)先級(jí)從大到小的順序?yàn)椋?“( )” > “[ ]” > “ * ”
首先看int *p1[10];
, 由于[ ]的優(yōu)先級(jí)大于*,因此p1先與“[” 結(jié)合,故而p1首先它是一個(gè)數(shù)組,它是一個(gè)存放了10個(gè)指針變量的數(shù)組。
再來看int (*p2)[10];,
由于()的優(yōu)先級(jí)大于 [ ], 先看()里的,p2與 * 結(jié)合,故而篇是一個(gè)指針,它指向了一個(gè)存放了10個(gè)整型的數(shù)組
3.2&數(shù)組名vs數(shù)組名
對(duì)于下面這個(gè)代碼
#include <stdio.h> int main() { int arr[5] = {0}; printf("%p\n", arr); printf("%p\n", &arr); return 0; }
arr 和 &arr 有什么區(qū)別呢?
其運(yùn)行結(jié)果如下:
如果你認(rèn)為它們代表的含義是正確的,不妨試著運(yùn)行下面這個(gè)代碼
#include <stdio.h> int main() { int arr[5] = { 0 }; printf("arr = %p\n", arr); printf("&arr= %p\n", &arr); printf("arr+1 = %p\n", arr + 1); printf("&arr+1= %p\n", &arr + 1); return 0; }
我的編譯器下運(yùn)行結(jié)果如下:
如果 arr 和 &arr 代表的含義一樣,那么后面兩個(gè)的結(jié)果應(yīng)該也是一致的,但是卻不一樣。
實(shí)際上: &arr 表示的是整個(gè)數(shù)組的地址,而不是數(shù)組首元素的地址。
數(shù)組的地址+1,跳過整個(gè)數(shù)組的大小,所以 &arr+1 相對(duì)于 &arr 的差值是40
3.3數(shù)組指針
數(shù)組指針就是一個(gè)指向數(shù)組的指針,意味著,這個(gè)指針內(nèi)存放的是數(shù)組的地址
#include <stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,0}; int (*p)[10] = &arr;//把數(shù)組arr的地址賦值給數(shù)組指針變量p return 0; }
我們來使用下指向一維數(shù)組的數(shù)組指針
//使用函數(shù)打印數(shù)組內(nèi)的內(nèi)容 # include <stdio.h> void print_arr(int(*p)[10], int sz) //傳過來的是數(shù)組的地址,用指針來接收 { int i = 0; for (i = 0; i < sz; i++) { printf("%d ", (*p)[i]); //先解引用找到數(shù)組 } } int main(void) { int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; int sz = sizeof(arr) / sizeof(arr[0]); //求出數(shù)組的元素個(gè)數(shù) print_arr(&arr, sz); return 0; }
4.數(shù)組傳參和指針傳參
在寫代碼的過程中,為了更好地利用模塊化的特點(diǎn),我們會(huì)使用大量的函數(shù)。但是在將一個(gè)功能封裝成函數(shù)的過程中,我們就不可避免的會(huì)傳過去數(shù)組和指針,那么函數(shù)是如何接受的呢?
4.1一維數(shù)組傳參
以下是常見的一維數(shù)組傳參
#include <stdio.h> void test(int arr[]) //直接用數(shù)組接收,空間大小可以省略 {} void test(int arr[10]) //直接用數(shù)組接收,空間大小可以指明 {} void test(int *arr) //接收數(shù)組首元素的地址 {} void test2(int *arr[20]) //直接用數(shù)組接收 {} void test2(int **arr) //傳過來一級(jí)指針的地址,使用二級(jí)指針接收 {} int main() { int arr[10] = {0}; int *arr2[20] = {0}; //存放了20個(gè)int*的數(shù)組 test(arr); //傳過去的是首元素的地址 test2(arr2); }
4.2二維數(shù)組傳參
當(dāng)我們?cè)谡務(wù)摱S數(shù)組首元素的時(shí)候指的是第一行!
數(shù)組名是首元素的地址,在二維數(shù)組中指的是第一行的地址
void test(int arr[3][5])//直接用二維數(shù)組接收 {} void test(int arr[][])//錯(cuò)誤的接收!列不能省略 {} void test(int arr[][5])//行是可以省略的,但是列一定不能省略 {} //總結(jié):二維數(shù)組傳參,函數(shù)形參的設(shè)計(jì)只能省略第一個(gè)[]的數(shù)字。 //因?yàn)閷?duì)一個(gè)二維數(shù)組,可以不知道有多少行,但是必須知道一行多少元素。 //這樣才方便運(yùn)算。 void test(int *arr)//錯(cuò)誤接收!傳過來的是第一行一維數(shù)組的地址 {} void test(int* arr[5])//錯(cuò)誤接收!這是一個(gè)存放int*類型的數(shù)組,而數(shù)組內(nèi)的元素是int {} //由于二維數(shù)組的首元素是第一行,每一行是一個(gè)一維數(shù)組 //因此可以寫成指向一維數(shù)組的指針 void test(int (*arr)[5]) {} void test(int **arr) //錯(cuò)誤! {} int main() { int arr[3][5] = {0}; test(arr); }
4.3一級(jí)指針傳參
#include <stdio.h> void print(int *p, int sz) { int i = 0; for(i=0; i<sz; i++) { printf("%d\n", *(p+i)); } } int main() { int arr[10] = {1,2,3,4,5,6,7,8,9}; int *p = arr; int sz = sizeof(arr)/sizeof(arr[0]); //一級(jí)指針p,傳給函數(shù) print(p, sz); return 0; }
4.4二級(jí)指針傳參
#include <stdio.h> void test(int** ptr) { printf("num = %d\n", **ptr); } int main() { int n = 10; int* p = &n; int** pp = &p; test(pp); test(&p); return 0; }
5.函數(shù)指針
在之前的學(xué)習(xí)當(dāng)中不會(huì)每天使用函數(shù)指針,但是請(qǐng)不要忘記它的存在。
什么是函數(shù)指針?顧名思義,函數(shù)指針就是指向一個(gè)函數(shù)的指針。既然函數(shù)指針是指針,那么它也具有指針的一些特性。比如:在對(duì)指針進(jìn)行間接訪問之前必須要先初始化
//代碼一 int f(int);//f函數(shù) int (*pf)(int) = &f;
這是函數(shù)初始化的一種方法。
//代碼二 int f(int); int (*pf)(int) = f;
代碼一和代碼二是等效的。我們用下面一個(gè)代碼查看,發(fā)現(xiàn)其打印出來的地址是一樣的。
在數(shù)組中,數(shù)組名是數(shù)組首元素的地址。而函數(shù)名則是函數(shù)的首地址。在函數(shù)名使用的時(shí)候我們所用的編譯器會(huì)將函數(shù)名轉(zhuǎn)化為一個(gè)函數(shù)指針,&操作符只是顯示地說明了函數(shù)將隱式執(zhí)行的任務(wù)。
如果你需要將函數(shù)的地址存儲(chǔ)起來,這個(gè)時(shí)候你就可能會(huì)需要用到函數(shù)指針了
void test() { printf("hehe\n"); } //下面的哪一個(gè)語句有能力存放函數(shù)test的地址呢? void (*pfun1)(); //語句1 void *pfun2(); //語句2
存儲(chǔ)地址應(yīng)該需要用到的是指針
語句1:
pfun1首先和 * 操作符結(jié)合,那么它是一個(gè)指針。后面有一個(gè)函數(shù)調(diào)用操作符,表明它是一個(gè)函數(shù)指針。它所指向的函數(shù)
返回值類型是void
語句2:
pfun2首先和()結(jié)合,那么它是一個(gè)函數(shù)。 *和前面的void結(jié)合,表明這個(gè)函數(shù)的返回值類型是void*
所以選擇語句1。
6.函數(shù)指針數(shù)組
我們知道數(shù)組是存儲(chǔ)一組相同類型的數(shù)據(jù),在此之前我們?cè)賮砘仡櫼幌轮羔様?shù)組
int *arr[10]; //數(shù)組的每個(gè)元素是int*
這是一個(gè)數(shù)組,數(shù)組存放了10個(gè)int*類型的數(shù)據(jù)。
按照這個(gè)思路,我們把很多函數(shù)的地址存放在數(shù)組中,那么它就是一個(gè)函數(shù)指針數(shù)組。
下面我們一起來實(shí)現(xiàn)它
首先它是一個(gè)數(shù)組 [ ], 存放的數(shù)據(jù)類型為函數(shù)指針,int (*)()類型的是函數(shù)指針
int (*parr1[10])(); int *parr2[10](); int (*)() parr3[10];
7.指向函數(shù)指針數(shù)組的指針
經(jīng)過我們上面這么多的推導(dǎo),這個(gè)也就很容易就能表示出來
指向函數(shù)指針數(shù)組的指針是一個(gè) 指針, 指針指向一個(gè) 數(shù)組 ,數(shù)組的元素都是 函數(shù)指針
void test(const char* str) { printf("%s\n", str); } int main() { //函數(shù)指針pfun void (*pfun)(const char*) = test; //函數(shù)指針的數(shù)組pfunArr void (*pfunArr[5])(const char* str); pfunArr[0] = test; //指向函數(shù)指針數(shù)組pfunArr的指針ppfunArr void (*(*ppfunArr)[10])(const char*) = &pfunArr; return 0; }
8.回調(diào)函數(shù)
回調(diào)函數(shù):回調(diào)函數(shù)就是將一個(gè)函數(shù)的函數(shù)指針作為參數(shù)傳遞給另一個(gè)函數(shù),而這個(gè)函數(shù)就是回調(diào)函數(shù)。
回調(diào)函數(shù)不是由該函數(shù)的實(shí)現(xiàn)方直接調(diào)用,而是在特定的事件或條件發(fā)生時(shí)由另外的一方調(diào)用的,用于對(duì)該事件或條件進(jìn)行響應(yīng)。
庫函數(shù)中的qsort函數(shù)就是一個(gè)典型的回調(diào)函數(shù)
總結(jié)
本篇文章就到這里了,希望能夠給你帶來幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
C語言數(shù)據(jù)結(jié)構(gòu)樹的雙親表示法實(shí)例詳解
這篇文章主要介紹了C語言數(shù)據(jù)結(jié)構(gòu)樹的雙親表示法實(shí)例詳解的相關(guān)資料,需要的朋友可以參考下2017-06-06C++使用鏈表存儲(chǔ)實(shí)現(xiàn)通訊錄功能管理
這篇文章主要為大家詳細(xì)介紹了C++使用鏈表存儲(chǔ)實(shí)現(xiàn)通訊錄功能管理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06C++實(shí)現(xiàn)一個(gè)簡單消息隊(duì)列的示例詳解
消息隊(duì)列在多線程的場(chǎng)景有時(shí)會(huì)用到,尤其是線程通信跨線程調(diào)用的時(shí)候,就可以使用消息隊(duì)列進(jìn)行通信。本文將利用C++實(shí)現(xiàn)一個(gè)簡單的消息隊(duì)列,感興趣的可以了解一下2022-12-12visual studio 建立dll類型工程、控制臺(tái)程序
這篇文章主要介紹了visual studio 建立dll、控制臺(tái)類型工程的相關(guān)知識(shí),感興趣的朋友跟隨腳本之家小編一起學(xué)習(xí)吧2018-05-05手把手教你實(shí)現(xiàn)一個(gè)C++單鏈表
鏈表是一種數(shù)據(jù)結(jié)構(gòu),用于數(shù)據(jù)的存儲(chǔ)。這篇文章主要為大家介紹了如何實(shí)現(xiàn)一個(gè)C++單鏈表,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以嘗試一下2022-11-11json error: Use of overloaded operator [] is ambiguous錯(cuò)誤的解決方
今天小編就為大家分享一篇關(guān)于json error: Use of overloaded operator [] is ambiguous錯(cuò)誤的解決方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-04-04