【C语言重点难点精讲】C语言指针
文章目錄
- 一:指針入門
- 二:數(shù)組入門
- (1)數(shù)組的內(nèi)存空間布局
- (2)區(qū)分&arr[0]和&arr
- 三:指針和數(shù)組的關(guān)系
- (1)以指針的形式訪問和以數(shù)組形式訪問
- (2)為什么C語言要這樣設(shè)計(jì)?
- 四:指針數(shù)組和數(shù)組指針
- 五:多維數(shù)組和多級(jí)指針
- (1)二維數(shù)組
- (2)二級(jí)指針
- (3)多級(jí)指針
- 六:數(shù)組參數(shù)和指針參數(shù)
- (1)一級(jí)指針傳參
- (2)一維數(shù)組傳參
- (4)二維數(shù)組傳參
- 七:函數(shù)指針
- (1)函數(shù)指針
- (2)函數(shù)指針數(shù)組
- (3)指向函數(shù)指針數(shù)組的指針
指針是C語言中的一大重點(diǎn),有關(guān)指針的理解一些基礎(chǔ)性使用不在本篇文章介紹范圍內(nèi),讀者可以閱讀站內(nèi)其它博主的文章,寫得都比較棒,本文主要是針對(duì)指針中的一些重點(diǎn),難點(diǎn)做一些總結(jié),以及最重要的是和數(shù)組的關(guān)系
一:指針入門
如果讀者能夠較深刻理解下面語句的含義,那么對(duì)于指針就算是達(dá)到入門的標(biāo)準(zhǔn)了
//第一組 int a=10; int* p=&a; //第二組 p=10; int* q=p; //第三組 *p=10; int b=*p;- 第一組:定義一個(gè)整形變量a,并賦值10,然后定義一個(gè)指針變量p,用a的地址初始化
- 第二組:把10賦值給指針變量p,此時(shí)指針p指向一個(gè)地址為10的地址,然后把p的內(nèi)容同樣賦值給一個(gè)同樣類型的指針變量q
- 第三組:*p表示解引用,將p所指向的空間里的內(nèi)容改為10,然后賦值給變量b
二:數(shù)組入門
(1)數(shù)組的內(nèi)存空間布局
數(shù)組是整體申請(qǐng)空間的,然后將地址最低的空間,作為a[0]元素
這就是為什么我們?cè)L問數(shù)組或者用指針訪問數(shù)組時(shí)采用的是++,本質(zhì)就是其元素排布時(shí)按照地址增大方向排布
(2)區(qū)分&arr[0]和&arr
首先我們需要搞清楚指針+1究竟是什么含義,看如下代碼
int main() {char* c = NULL;short* s = NULL;int* i = NULL;double* d = NULL;printf("%d\n", c);printf("%d\n\n", c + 1);printf("%d\n", s);printf("%d\n\n", s + 1);printf("%d\n", i);printf("%d\n\n", i + 1);printf("%d\n", d);printf("%d\n\n", d + 1);}其運(yùn)行結(jié)果如下
因此,指針+1實(shí)際加上的是該指針類型的大小
回到正題
int main() {char arr[10] = { 0 };printf("%p\n", &arr[0]);printf("%p\n", &arr[0]+1);printf("%p\n", &arr);printf("%p\n", &arr+1); }- &arr,sizeof(arr):arr表示整個(gè)數(shù)組,
- &arr[0]:表示數(shù)組首元素的地址,因?yàn)閇]的優(yōu)先級(jí)更高
- &arr[0]+1:由于是char類型的數(shù)組,所以+1
- &arr+1:這是數(shù)組的地址,可以理解橫跨整個(gè)數(shù)組
還有,當(dāng)數(shù)組名做右值時(shí),代表數(shù)組首元素的地址,本質(zhì)等價(jià)于&arr[0];但是,數(shù)組名不可以充當(dāng)左值,能夠充當(dāng)左值的,必須是有空間且可被修改的
三:指針和數(shù)組的關(guān)系
指針和數(shù)組沒有關(guān)系,他們是完全不同的兩套規(guī)范,只不過在操作上有交集
(1)以指針的形式訪問和以數(shù)組形式訪問
C語言中保存字符串中有兩種形式,如下
int main() {char* str = "abcdef";//str指針在棧上保存,“abcdef”在字符常量區(qū),不可修改char arr[] = "abcdef";//整個(gè)數(shù)組都在棧上保存,可以被修改 }1:分別以指針和以下標(biāo)的方式訪問指針
int len = strlen(str); for (int i = 0; i < len; i++) {printf("%c ", *(str + i));//以指針方式訪問printf("%c ", str[i]);//以數(shù)組方式訪問 } printf("\n");指針和數(shù)組指向或者表示一塊空間的時(shí)候,訪問方式是可以互通的,具有相似性,但注意它們不是同一個(gè)東西
(2)為什么C語言要這樣設(shè)計(jì)?
我們知道數(shù)組傳參時(shí)可以采用這種方式
void Show(int arr[], int num) {for (int i = 0; i < num; i++) {printf("%d ", arr[i]);}printf("\n"); }int main() {int arr[] = { 1,2,3,4,5 };int num = sizeof(arr) / sizeof(arr[0]);Show(arr, num); }其中的sizeof(arr)/sizeof(arr[0])語句不能放入在函數(shù)內(nèi)進(jìn)行,否則將會(huì)只打印一個(gè)元素,相信初學(xué)者在這里也犯過很多錯(cuò)誤。
指針和數(shù)組互通的好處之一就在這里,因?yàn)槿绻换ネ?#xff0c;那么在傳參時(shí)對(duì)于數(shù)組這樣龐大的東西就會(huì)浪費(fèi)非常多的額外空間,所以干脆在傳參時(shí)將其降維成指針,直接讓指針訪問即可
那么既然這樣,形參中的int arr[]其實(shí)就可以寫作int* arr
因此:所有的數(shù)組在傳參時(shí)都會(huì)降維成指針——指向其內(nèi)部類型的指針,一維數(shù)組當(dāng)然就是對(duì)應(yīng)數(shù)據(jù)類型的指針,二維數(shù)組就是一維數(shù)組的數(shù)組指針
四:指針數(shù)組和數(shù)組指針
指針數(shù)組: 它是一個(gè)數(shù)組,里面的元素均是指針,即int* p1[10]
數(shù)組指針: 它是一個(gè)指針,指向了一個(gè)數(shù)組,即int (*p2)[10]
- []的優(yōu)先級(jí)大于“*”
- int* p1[10]可以理解為int* [10] p1
- int (*p2)[10]可以理解為int[10]* p2
- int (*p[4])[5]中[4]是一個(gè)數(shù)組,這個(gè)數(shù)組存放了四個(gè)數(shù)組指針p,分別指向含有5個(gè)int類型的數(shù)組
數(shù)組指針賦值時(shí)一定要注意
int a[10]={0}; int(*p)[10]=&a;五:多維數(shù)組和多級(jí)指針
(1)二維數(shù)組
需要注意多維數(shù)組其內(nèi)存空間布局仍然是線性連續(xù)遞增的
int main() {char c[4][3] = { 0 };for (int i = 0; i < 4; i++){for (int j = 0; j < 3; j++){printf("&a[%d][%d]:%p\n", i, j, &c[i][j]);}} }
就二維數(shù)組而言,我們認(rèn)為:二維也可以看做一維數(shù)組,只不過其內(nèi)部元素仍然是一維數(shù)組
因此下面這三種地址雖然值是相同的但是其含義完全不同
char c[4][3] = { 0 }; printf("%p\n", &c);//這個(gè)“二維”數(shù)組的地址 printf("%p\n", c);//“二維”數(shù)組中第一個(gè)元素,也即第一個(gè)數(shù)組的地址 printf("%p\n", &c[0][0]);//“二維”數(shù)組第一個(gè)元素的第一個(gè)元素的地址
那么相應(yīng)的+1操作也有各自的含義
下面的練習(xí)題可以幫助你很好理解上面的概念
int a[3][4] = {0};//求整個(gè)數(shù)組的大小 printf("%d\n",sizeof(a));//(3×4)×4=48 //求單個(gè)元素的小 printf("%d\n",sizeof(a[0][0]))//1×4=4 //二維數(shù)組第一個(gè)“元素”,即第一個(gè)一維數(shù)組的大小 printf("%d\n",sizeof(a[0]))//4×4=16 //當(dāng)sizeof()內(nèi)部只含有一個(gè)數(shù)組名時(shí)表示整個(gè)數(shù)組,當(dāng)內(nèi)部數(shù)組名參與運(yùn)算時(shí)表示單個(gè)元素 //因此這里就是第一個(gè)元素也即第一個(gè)數(shù)組的首元素地址進(jìn)行偏移,仍然是一個(gè)指針 printf("%d\n",sizeof(a[0]+1))//4 //第一個(gè)元素也即第一個(gè)數(shù)組的首元素偏移至第一個(gè)數(shù)組的第二個(gè)元素進(jìn)行解引用,也即a[0][1] printf("%d\n",sizeof(*(a[0]+1)))//a[0][1],4 //表示該二維數(shù)組的第一個(gè)數(shù)組的地址,然后偏移至第二個(gè)數(shù)組的地址處,注意仍然是地址 printf("%d\n",sizeof(a+1));//4 //這是第二個(gè)數(shù)組的地址,對(duì)齊解引用相當(dāng)于第二個(gè)數(shù)組的數(shù)組名 printf("%d\n",sizeof(*(a+1)));//4×4=16 //a[0]表示第一個(gè)數(shù)組,&a[0]第一個(gè)數(shù)組的地址,然后+1,偏移值第二個(gè)數(shù)組的地址處 printf("%d\n",sizeof(&a[0]+1));//4 //同上,它是第二個(gè)數(shù)組的地址,然后解引用就是第二個(gè)數(shù)組的大小 printf("%d\n",sizeof(*(&a[0]+1))); //4×4=16 //相當(dāng)于*(a+0),于是表示第一個(gè)數(shù)組,然后解引用就是第一個(gè)數(shù)組的大小 printf("%d\n",sizeof(*a)); //4×4=16 //第四個(gè)一維數(shù)組 printf("%d\n",sizeof(a[3])); //16
其實(shí)以下寫法是等價(jià)的
- a[2]等價(jià)于*(a+2);
- a[2][3]等價(jià)于*(*(a+2)+3)
(2)二級(jí)指針
準(zhǔn)確理解以下例子的含義即可
int main() { int a = 10; int* p = &a; int** pp = &p;p = 100;//將p指針的內(nèi)容改為100 *p = 100;//將p指針指向的空間也即變量a的內(nèi)容改為100 pp = 100;//pp之前指向了一級(jí)指針,現(xiàn)在更改為指向100 *pp = 100;//pp保存的是p的地址,因此現(xiàn)在相當(dāng)于將p的指向改為了100 **pp = 100;//兩次解引用就是修改變量a的內(nèi)容100}(3)多級(jí)指針
下面的這個(gè)例子有助于你深刻理解多級(jí)指針,其輸出結(jié)果為“ink”
#include<stdio.h>int main() {static char *s[] = {"black", "white", "pink", "violet"};char **ptr[] = {s+3, s+2, s+1, s}, ***p;p = ptr;++p;printf("%s", **p+1);return 0; }六:數(shù)組參數(shù)和指針參數(shù)
(1)一級(jí)指針傳參
由于指針變量的特殊性,所以才傳參時(shí)很多人就會(huì)將其特殊化,但是指針傳參也會(huì)拷貝,函數(shù)內(nèi)部和外部?jī)蓚€(gè)指針不是一個(gè)指針
void test(char* p) {printf("test函數(shù)內(nèi)的p的地址%p\n", &p); }int main() {char* p = "hello world";printf("main中p的地址:%p\n", &p);test(p); }
因此,很多人會(huì)犯下面這樣的錯(cuò)誤
結(jié)果顯而易見,什么也不會(huì)打印,這是因?yàn)閭鬟^去的是一個(gè)一級(jí)指針,依然用一級(jí)指針接受的話,發(fā)生的是值拷貝,那個(gè)字符串拷貝函數(shù)只是對(duì)局部變量進(jìn)行拷貝,函數(shù)調(diào)用結(jié)束,局部變量銷毀相當(dāng)于什么也沒用做
因此,正確的做法是采用二級(jí)指針
(2)一維數(shù)組傳參
void test(int arr[])//比較常用的寫法。注意傳過來的是數(shù)組,但本質(zhì)已經(jīng)降維為了指針 {} void test2(int arr[10])//同上 {} void test3(int* arr)//數(shù)組名就是首元素地址,可以 {} void test4(int** arr)//指針數(shù)組每個(gè)元素都是指針,可以用二級(jí)指針 {} int main() {int arr[10] = { 0 };//整形數(shù)組int* arr2[20] = { 10 };//指針數(shù)組 }(4)二維數(shù)組傳參
數(shù)組傳參一定要發(fā)生降維,降維成指針,對(duì)于二維數(shù)組就會(huì)降維成數(shù)組指針
void test(int(*a)[5])//數(shù)組指針 {}int main() {int a[6][5] = { 0 };test(a); }傳參時(shí)當(dāng)然可以使用int arr[6][5]這樣的形式,由于它是依靠列來確定組數(shù)的,所以行可以省略,但是列不能省略,因此二維數(shù)組傳參還可以這樣寫
void test(int a[][5])//數(shù)組指針 {}int main() {int a[6][5] = { 0 };test(a); }七:函數(shù)指針
(1)函數(shù)指針
函數(shù)是代碼的一部分,程序運(yùn)行時(shí)也要加載進(jìn)內(nèi)存,以供CPU后續(xù)尋址。
函數(shù)指針的定義和數(shù)組指針基本類似,其定義和調(diào)用方式如下
int add(int x, int y) {int z = x + y;return z; }int main() {int a = 10;int b = 20;int(*p)(int, int) = &add;//這個(gè)指針指向了一個(gè)函數(shù),其參數(shù)有兩個(gè)類型分別為int和int,所指向函數(shù)的返回值為intprintf("%d\n", (*p)(a, b));//第一種調(diào)用方式printf("%d\n", p(a, b));//第二種調(diào)用方式 }(2)函數(shù)指針數(shù)組
函數(shù)指針數(shù)組本質(zhì)是一個(gè)數(shù)組,存放的元素類型是函數(shù)指針
如下Add()、Sub()這兩個(gè)函數(shù)形參列表相同,返回值相同,因此可以將他們的地址存放在函數(shù)指針數(shù)組中
int Add(int x, int y) {int z = x + y;return z; }int Sub(int x, int y) {int z = x - y;return z; }int main() {int(*parr[2])(int, int) = { &Add, Sub };//函數(shù)指針數(shù)組的定義for (int i = 0; i < 2; i++){printf("%d\n", parr[i](2, 3));//函數(shù)指針數(shù)組的使用} }函數(shù)指針數(shù)組能夠很好的保存一組具有相同參數(shù)類型,相同返回值的函數(shù)的地址。它的一個(gè)經(jīng)典例子就是“轉(zhuǎn)移表”。比如在計(jì)算器例子中,使用switch case語句,如果使用普通方式,要增加一些其他運(yùn)算時(shí),其case語句要多次增加,顯得很臃腫,而運(yùn)用函數(shù)指針數(shù)組,則能避免這種情況,且在后期增加新的相同類型的運(yùn)算時(shí),在主函數(shù)內(nèi)只需增加新函數(shù)地址
(3)指向函數(shù)指針數(shù)組的指針
int arr[10] = { 0 }; int(*p) = arr;//指向整形數(shù)組的指針int(*parr[4])(int, int);//函數(shù)指針數(shù)組 int(*(*pparr)[4])(int, int) = &parr;//指向函數(shù)指針數(shù)組的指針總結(jié)
以上是生活随笔為你收集整理的【C语言重点难点精讲】C语言指针的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: swift如何动态创建对象
- 下一篇: 2-3:套接字(Socket)编程之UD