进阶的内容比较庞大,但请各位耐心看完,我相信一定会有所收获,如果我有错误的地方希望大家可以指正我的错误,万分感谢
防止有些博友没有看过前一篇我的博客,在此简单概括一下那篇博客的内容有哪些。
- 指针变量是用来存放地址的,对其解引用就可以找到该地址唯一标识的内存空间
- 指针大小在32位平台上为4字节,在64位平台上为8字节
- 指针具有类型之分,指针类型决定了其进行解引用操作时的访问权限及+/-整数的步幅
- 指针的几种运算(指针加减整数,指针减指针,指针关系运算)
本文章主要内容
1.字符指针
我们知道字符指针的使用方法为
#include<stdio.h>int main(){char a='0';char *p=&a;*p='a';return 0;}
那下面这种是什么意思呢
#include<stdio.h>int main(){char *p="Thanks for browsing";return 0;}
我们知道char*只能存一个元素的地址,但怎么可以和一个字符串相等呢?
有次我们看到,原来上面这个代码的意思是存放该字符串首元素的地址(即‘T’的地址存放在p中)
那么我们就可以这样使用它了
但是这样写有个小问题,这里的"Thanks for browsing"和存放于数组中的字符串不同,是不可更改的,被称为常量字符串,它在内存里只会存储一份。
不理解只存一份的可以点我哦
初阶已介绍指针数组,即存放指针的数组就是指针数组
在此就不再赘述,主要是为了区分下面的数组指针
int *arr1[10];//存放一级整形指针的数组int **arr2[10];//存放二级整形指针的数组
3.数组指针
数组指针重点说的是指针
例如整形指针是指向整形的指针、字符指针是指向字符的指针
那么数组指针就是指向数组的指针
int arr[10]={0};int (*p1)[10]=&arr;int *p2=arr;//*p代表这是个指针,因为[]比*的结合性要高,会与指针数组产生歧义,所以需要加上()//即int (*p1)[10]=&arr的完整解释为p是一个指向有十个整形元素的指针
&arr是指整个数组的首地址
arr是指首元素的地址
两者虽数值上相同,但加1后移动的步幅不同!(如下图)
数组名只有在两种情况下是指整个数组的首地址
- &arr
- sizeof(arr)
除此之外数组名均指代数组首元素地址
接下来我们来介绍一下数组指针的使用方法
#include<stdio.h>int main(){int arr[10] = { 0 };int i = 0;int(*p)[10] = &arr;for (i = 0; i < 10; i++){printf("%d ", (*p)[i]);printf("%d ", *((*p)+i));printf("%d ", (*p)[0][i]);//以上三种均可找到数组中的元素//*p等价于arr,即arr[i]==(*p)[i]//&具有‘升维’的作用,*具有‘降维’的作用}return 0;}
#include<stdio.h>void print(int (*p)[5],int r,int c){int i = 0;for (i = 0; i < r; i++){int j = 0;for (j = 0; j < c;j++){printf("%d ", *(*(p+i)+j));//p+i相当于&arr+i 第i行的地址//*(p+i)+j相当于arr+j 第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, 3, 5);//二维数组传参,传的是第一行的地址//传参数也会发生降维//传二维数组,函数得到的为第一行的地址//传一维数组,函数得到的为首元素地址return 0;}
这里做一个小练习,可以测试一下自己是否真的明白了指针数组和数组指针
int arr[5]; 有五个整形元素的数组int *p[5]; 有五个整形指针的数组int (*p)[5]; 指向有五个整形元素的数组的指针int (*p[5])[10]; 存放五个指向有十个整形元素的数组的指针的数组 //p先和[]结合成为数组,然后拆解为int (*)[10],//现在即使数组中的元素类型,每个元素为指向有十个整形元素的数组指针
4.数组传参和指针传参
一维数组传参void test1(int arr[]){}//该方法就是普通的数组传参void test2(int arr[10]){}//该方法与test1相同,10并没有实际意义,只是形式上感觉上下匹配void test3(int *p){}//该方法就是利用了数组传参会发生降维,就利用整形指针接收void test4(int *p[10]){}//同test2void test5(int **p){}//传的元素为一级指针,所以可以用二级指针接收int main(){int arr[10] = { 0 };int *p[10] = { 0 };test1(arr);test2(arr);test3(arr); test4(p); test5(p);return 0;}
二维数组传参void test1(int arr[3][5]){}//该方式可行void test2(int arr[][5]){}//该方法可行void test3(int arr[][]){}//该方法不可行,对二维数组而言,可以不知道行数,但必须知道列数void test4(int *arr){}//该方法不可行经降维后得到是一维数组的地址,而此处却用整形指针接收void test5(int* arr[]){}//该方法不可行,这里用的是指针数组接收,要想用数组接收只能用上面两种void test6(int (*p)[5]){}//该方法可行,利用数组指针指向一个一维数组void test7(int **p){}//该方法不可行,该方法是为了接收一级指针。利用指针接收只能用上面这种int main(){int arr[3][5] = { 0 };test1(arr);test2(arr);test3(arr);test4(arr);test5(arr);test6(arr);test7(arr);return 0;}
我们知道一级指针传值,可以用一级指针接收 那么,反过来想想,都有哪些传过去可以用指针接收呢?void test(int *p){}int main(){int a = 10;int *pa = &a;int arr[5] = { 0 };test(&a);test(pa);test(arr);//传参基本原则:传过去的参数和形参类型相同return 0;}同理,二级指针呢?void test(int **p){}int main(){int a = 10;int *pa = &a;int **ppa = &pa;int *arr[10] = { 0 };test(ppa);test(&pa);test(arr);return 0;}
5.函数指针
#include<stdio.h>int test(int x,int y){return x*y;}int main(){int a=0;int *pa=&a;//指向整形的指针int arr[10]={0};int (*parr)[10]=&arr;//指向数组的指针int (*pf)(int ,int)=&test;//指向函数的指针//函数返回值类型 参数类型 函数名取地址即可获得函数地址test==&test 两者相同,上面可替换,这里可没有函数首元素一说 函数指针如何调用函数呢?int b=(*pf)(2,3);//和正常使用指针是类似的,只需解引用即可使用return 0;}
但上面已经写过,将test赋给pf,那么是否意味着pf也可以和test一样直接调用函数呢?
答案是肯定的,可以。pf前面加星号是为了初学者可以更快了解C语言语法,其实 并未起到解引用的用处,但如果写成(pf)(2,3)的形式就一定要带括号,否则意思就变成了对调用函数的结果解引用
现在我们已经初步认识函数指针,接下来我们来看看两句很“有趣”的代码
1.(*(void(*)())0)()
- 这个的确很不好看明白,但首先我们知道void(*)()是一个不需要参数且返回值为空的函数指针类型,而 ()0就是将0强制类型转换为函数指针类型,接着就是解引用调用0地址处的函数,该函数无参数,返回类型为void。
(该代码源自C陷阱和缺陷)
2.void(* signal(int, void(*)(int) ) )(int);
- 首先要肯定的是这是一个函数声明,signal是函数名,需要整形和函数指针(该函数指针参数为int,返回值类型为void)两个参数,signal的返回值类型为void(* )(int)(该函数指针参数为int,返回值类型为void)。
上面这个其实不容易理解,所以我们可以利用typedef让它变得好看一点typedef void(*new_pf)(int) ;注意new_pf为新名字,void(*)(int)为旧名字 这样写是语法规定,不可以写成typedef void(*)(int) new_pf;所以上面的函数声明就可以写成 new_pf signal(int,new_pf);
6.函数指针数组
这个应该可以举一反三了吧,函数指针数组即存放函数指针的数组
#include<stdio.h>void test1(){printf("hello\n");}void test2(){printf("world\n");}int main(){void(*pt1)() = test1;void(*pt2)() = test2;void(*parr[2])() = (test1, test2);//函数指针数组其实就是在函数指针的名字后面加 [元素个数]return 0;}
运用函数指针数组很经典的案例函数指针数组实现计算器(可以点进来了解一下)
7.指向函数指针数组的指针函数指针数组终归是个数组,既然是数组,那就可以用指针指向它
int (*p)(int ,int);//函数指针int (*parr[5])(int ,int);//函数指针数组int (*(*pparr)[5])(int ,int)= &parr;//指向函数指针数组的指针(*pparr)证明是个指针,[5]说明指向的是个数组,元素类型为int (*)(int ,int)
8.回调函数
注意重点来了
回调函数就是一个通过函数指针调用的函数,如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行相应。
举个例子void test1(){ printf("hello");}void test2(void (*p)()){ p();}int main(){test2(test1);并没有主动调用test1,而是将test1地址传给test2,在适当情况下test2调用test1 此时test1被称为回调函数return 0;}
如果想加深一下对回调函数的理解可以点下面两个链接
回调函数计算器
快速排序的使用(qsort就是一个很好的回调函数)
大家好,上次的初阶版让我第一次感受到了写博客喜悦,原来可以有这么多人看我的博客呀,非常感谢大家对我的支持与鼓励,我一定会再接再厉,继续创作出优质的内容来向大家分享知识。——2021.3.23
今天终于把它磨出来了,虽然有些小细节还是可能还需更改,但内容上还是很充实的,非常感谢点进来的每个人。——2021.4.4