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