进阶的内容比较庞大,但请各位耐心看完,我相信一定会有所收获,如果我有错误的地方希望大家可以指正我的错误,万分感谢
防止有些博友没有看过前一篇我的博客,在此简单概括一下那篇博客的内容有哪些。
- 指针变量是用来存放地址的,对其解引用就可以找到该地址唯一标识的内存空间
- 指针大小在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"和存放于数组中的字符串不同,是不可更改的,被称为常量字符串,它在内存里只会存储一份。
不理解只存一份的可以点我哦
2.指针数组
初阶已介绍指针数组,即存放指针的数组就是指针数组
在此就不再赘述,主要是为了区分下面的数组指针
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])
{}//同test2
void 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