>
> 输出结果如下:
>
>
> 
>
>
>
| |
| --- |
| 为什么会是这样的结果呢,其实呢!数组arr1和arr2它们在开辟空间的时候是分别开不一样的空间,所以它们两的地址是不可能相同的,而arr3和arr4,就是我们上面讲的,它们是不可被修改的,也就是说它们在开辟空间的时候,没必要开两个不一样的空间了,反正它们的内容也不会被修改,那为什么还要浪费空间开新的呢,这时候编译器就很聪明了,它就把两个指针都指向同一块内存空间。因此arr3=arr4! |
---
###
2.📌指针数组
>
> 指针数组和数组指针其实很多人都容易混淆,怎么区分呢?
>
>
> 很简单,捉住主语!
>
>
> 比如:好孩子,那他的主语是不是“孩子”,”好“这个词是修饰孩子的。
>
>
> 同样:指针数组,它是个数组,指针是修饰它的类型
>
>
>
我们常用的有整型数组,字符数组等,整型数组就是存放整型的数组,而字符数组就是存放字符的数组,
**所以指针数组,就是存放指针的数组!!**
那它是如何在代码中定义的呢?
>
> 
>
>
>
###
3.📌数组指针
**数组指针 主语:是指针!**
我们知道int\*是指向int型的指针,char\*是指向char型的指针
所以数组指针是指向数组的指针!
p1, p2分别是什么?
>
> int \*p1[10];
>
>
> int (\*p2)[10];
>
>
>
p1上面我们讲过是指针数组
而p2就是我们的数组指针了!
为什么呢?
>
> int (\*p)[10]; //解释:p先和\*结合,说明p是一个指针变量,然后指向的是一个大小为10个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针。
> //这里要注意:[]的优先级要高于\*号的,所以必须加上()来保证p先和\*结合。
>
>
>
>
> 写出下面的数组指针
>
>
> 
>
>
> 我们知道定义一个指针是要根据它的类型,然后在类型后面+\*就可以定义一个指针了,比如int \*a,所以说我们在定义数组指针的时候也要知道数组的类型,那么数组的类型是什么呢?
>
>
> 举个例子,int a=0;去掉变量名后就是它的类型,比如a的类型就是int,所以数组也是同样的方式,int a[5],去掉变量名后
>
>
> 就是int [5],这个就是它的类型吗?
>
>
> 是的,别怀疑,这个就是它是类型,然后呢,我们就像普通类型一样在加个\*和指针名就可以
>
>
> 只不过它的指针名跟int那些不一样,数组指针的名字是放在**int 和[]之间的**,而且必须要用括号括起来,不然就变成这样了
>
>
> int \*pa [5],它是一个指针数组,因为[]优先级高与\*,所以呢数组a的指针最终的结果就是int (\*pa)[5],b的也是同样的分析方法。
>
>
>
**那数组指针怎么用呢?它有什么作用呢?**
我给大家写一个数组指针错误的使用方式!
#include <stdio.h> int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; int(*p)[10] = &arr;//把数组arr的地址赋值给数组指针变量p
//但是我们一般很少这样写代码 一个数组指针的比较笨的使用方法
int i = 0;
for (i; i < 10; i++)
{
printf("%d ", \*((\*p) + i) );
}
printf("\n");
//上面的写法我们完全可以用一个整型指针来完成
int\* parr = arr;
for (i = 0; i < 10; i++)
{
printf("%d ", \*(parr + i));
}
return 0;
}
上面这个列子就是数组指针错误的使用方式,为什么说它错误呢!因为它是的用一个数组指针来实现数组打印,而这个功能完完全全可以用整型指针来实现,可它偏偏用了数组指针,这就将它复杂化,简直就是脱裤子放屁,多此一举!所以我们学数组指针可不是用来实现这样的功能的。
**相对正确的使用方式是用于二维数组传参的时候使用的**
举个例子
#include <stdio.h> //以二维数组的形式接受地址 void print_arr1(int arr[3][5], int row, int col) { int i = 0; int j = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { printf("%d ", arr[i][j]); }
printf("\n");
} } //以数组指针的形式接受地址 void print_arr2(int(*arr)[5], int row, int col) { int j = 0; int i = 0; for (i = 0; i < row; i++) { for (j = 0; j < col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 }; print_arr1(arr, 3, 5); //数组名arr,表示首元素的地址 //但是二维数组的首元素是二维数组的第一行 //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址 //可以数组指针来接收 print_arr2(arr, 3, 5); return 0; }
**学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:**
int arr[5]; int *parr1[10]; int (*parr2)[10]; int (*parr3[10])[5]; //思考一下上面的代码是表示数组呢,还是数组指针呢?还是其他什么呢?

int arr[5];//一个整型数组,数组有5个元素,每个元素的类型是整型 int *parr1[10];//一个指针数组,数组里有10个元素,每个元素的类型是一个整型指针(int*) int (*parr2)[10];//数组指针,指向的对象是一个拥有10个元素的数组,每个元素的类型是整型(int) int (*parr3[10])[5];//一个数组指针数组,数组有10个元素,每个元素的类型是数组指针(int(*)[5])
>
> 第四个再解释一下,因为[]的优先级比\*要高,所以parr3先和[]结合,所以它是个数组,又因为我们把parr3[]去掉之后,就剩下了int(\*)[5],这不就是数组指针类型吗,其实跟第三个对比一下,parr2去掉之后,就是int(\*)[10],这就是数组指针。所以第四个的类型是数组指针,而它是一个数组,所以最后其实就是一个数组指针数组
>
>
>
---
###
4.📌函数指针
“函数指针”我想大家首先得知道它是一个指针吧!
**而且是一个指向函数的指针**
可能有人会问—>什么鬼?函数也有地址吗?

有没有呢我给你测试一下就知道了,看下面一段代码
#include <stdio.h> void test() { printf("hehe\n"); } int main() { printf("%p\n", test); printf("%p\n", &test); return 0; }
>
> 
>
>
> 你看到了吗!显然在这里,这个函数test()的地址为004E13BB,而且用不用&符号都能把该函数地址取出来!
>
>
>
那问题来了,整型数,字符,数组等的地址我们都已经学会如何保存在相对应的指针中了。
**那我们的函数的地址要想保存起来,怎么保存?**
我有个选项想让大家选出正确的函数指针:
看下面代码
void test() { printf("hehe\n"); } //下面pfun1和pfun2哪个有能力存放test函数的地址? void (*pfun1)(); void *pfun2();
首先,能给存储地址,就要求pfun1或者pfun2是指针,那哪个是指针?
其实我想大家应该都能选出来,答案就是第一个pfun1,因为类似上面的数组指针嘛!\*符号一般都要括号括起来的,因为它的优先级比较低
**那怎么解释这个(void (\*pfun1)())呢?**
>
> 其实是这样的,pfun1先和\*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。!
>
>
>
举个例子
//写出下面函数的函数指针 int add(int x, int y) { return x + y; }
>
> 答案应该是:int (\*pf)(int,int)=&add,&符号可加可不加
>
>
> 解释:因为add函数的返回值是int ,参数类型为int x,int y,所以函数指针的类似为int(\*)(int x,int y),但是x,y可以省略不写
>
>
>
**接下来给大家阅读两段有趣的代码:**
//代码1 (*(void (*)())0)(); //代码2 void (*signal(int , void(*)(int)))(int);
知道上面的代码是什么意思吗?这可不是我胡编乱造出来的,上面的代码来自于《C陷阱和缺陷》这本书。
解释一下这两行代码的意思
>
> 第一行代码其实就是一个函数调用,为什么呢?
>
>
> 别急,首先我们把括号拆分一下,然后把外壳(\*)(),先去掉,先留下(void(\*)() )0,然后这时候就要擦亮你的眼睛了,void(\*)(),不就是函数指针吗?,它是一个类型,然后放在0的前面代表什么呢?不就是强制类型转换吗,比如(char)0,就将0强制类型转换成char类型,同样的(void(\*)())0,就是将0强制类型转换成函数指针类型,而这个函数的返回值是void,参数为空。
>
>
> 那么现在0就相当于这样的一个:函数的返回值是void,参数为空,的函数指针,然后再把外壳装回来就是(\*0)(),就是将0进行解引用操作,然后就相当于调用函数的操作。比如我把0换成pf,最后的形式就是(\*pf)(),不就是类似于test(),这样的函数调用吗!
>
>
> 第二行代码其实就是一个函数声明,为什么呢?
>
>
> 首先还是先把括号拆分开,看红色框中的两个进行类比,下面add我相信大家都知道是一个函数声明吧,它的返回类型是int,参数有两个,一个是int,另一个也为int,add为它的函数名
>
>
> 同样的signal其实也是一个函数名,它的参数也有两个,一个是int,另一个是void(\*)(int),不知道大家看到这里了,能不能看得出这是一个函数指针呢,这个函数指针的返回值为void,参数为int,其实就是一个传指针的操作,没有什么高大上的,只不过它的形式有点复杂。
>
>
> 而最后的外壳的void(\* )(int),我们跟add类比,add的前面是它的返回类型int,所以呢,signal这个函数的返回类型就是一个函数指针,void(\*)(int).
>
>
> 
>
>
>
**有没有什么函数指针的使用场景呢?**
有,比如c语言自带的标准库中有一个可以排序任何类型的排序算法qsort,它就涉及了函数指针的参数和使用!
而像qsort这样通过调用函数指针的函数就被称为回调函数
>
> 回调函数不是由该函数 的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
>
>
>
感兴趣的可以翻一下我之前的文章有模拟实现过qsort函数,和qsort函数的介绍!
---
###
5.📌函数指针数组
为了防止大家学懵了,给大家回忆一下什么是数组!
**数组是一个存放相同类型数据的存储空间**
int *arr[10];//数组的每个元素是int* int arr1[5];//数组每个元素是int char* arr[6];//数组每个元素是char*
那我们已经学习了指针数组,现在学这个**函数指针数组**我相信是不难理解的!
**那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组**
那函数指针的数组如何定义呢?
还是给几个选项吧!
int (*parr1[10])(); int *parr210; int (*)() parr3[10];
答案是:**parr1**
**parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢? 是 int (\*)() 类型的函数指针。**\*
教一下大家如何正确的写好一个函数指针数组
#include<stdio.h>
int add(int x, int y) { return x + y; } int sum(int x, int y) { return x - y; } int main() { //先写函数指针
收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!