. | . | . | . |
---|---|---|---|
1.字符指针 | 2.指针数组 | 3.数组指针 | 4.数组与指针参数 |
5.函数指针 | 6.函数指针数组 | 7.函数指针数组指针 | 8.回调函数 |
9.模拟qsort |
在这篇文章之前我还写了一篇指针基础
我们知道 字符指针指向字符地址,并且非常熟悉他的用法,比如下面:
#include
int main() { char arr[] = "abcdef"; char* p = arr; printf("%s\n", arr); printf("%s\n", p); return 0; } 输出结果是:
abcdef
abcdef
那如果我们这样写呢???
#include
int main() { char* p = "abcdef"; // 这是一个常量字符串 printf("%s\n", p); printf("%c\n", *p); return 0; } 运行结果是:
abcdef
a这个代码特别让人容易误会是把字符串 abcdef放到字符指针p里面,实际是把该字符串的首字符地址放到了指针p里面,
并且强调 !!! ,这样写就代表着 abcdef是一个常量字符串,不可修改.
比如我们再写一个语句 *p = ‘w’;
就会发生段错误,因为不可修改
因此,如果我们进行第二种写法,最好规范书写,就是加上const,以便我们理解
#include
int main() { const char* p = "abcdef"; printf("%s\n", p); printf("%c\n", *p); return 0; }
答案:
第一道 哈哈
第二道 呵呵
解析:
第一道: 因为arr1与arr2是两个数组,他们所占据的空间不同.而数组名是数组首元素地址,因此arr1与arr2的地址又不同,
所以 arr1 != arr2,所以回打印 哈哈
第二道: 因为这种写法代表着就是abcdef是常量字符串,而p1与p2的值都是abcdef,所以计算机为了节约空间,就不再开辟多的空间,只开辟一块
所以 p1 == p2,打印 呵呵
同理,我们为了规范书写,还是在char*前面加上const
指针数组:重点是后面,数组,所以指针数组是数组,即数组里面的元素类型是指针.
我们知道 一个数组的构成包括是三个部分 : 元素类型 数组符号[] 数组名(可以省略)
比如 **int arr [3] 意思是一个数组,他的名字是arr,该数组含有3个元素,每个数组类型是int **
char arr[4] 意思是一个数组,他的名字是arr,该数组含有4个元素,每个数组类型是char
那么,指针数组该怎么写呢??我们一步一步的来.
第一步,数组符号 []
第二步,写上数组名 arr
第三步,写上数组类型 (指针) int* char*…等
先在我们要求写一个数组名是pp,含有6个元素的整型指针数组.
int* pp[6];
运用:
#include
int main() { int a = 10; int b = 20; int c = 30; int* all[3] = {&a,&b,&c}; for(int i = 0;i<3;i++) { printf("%d\n",*all[i]); } return 0; } 运算结果:
10
20
30
#include
int main() { int arr1[] = {1,2,3}; int arr2[] = {4,5,6}; int arr3[] = {7,8,9}; int* all[3] = {arr1,arr2,arr3}; int i = 0; for(i = 0;i<3;i++) { int j = 0; for(j = 0;j<3;j++) { printf("%d ", *(all[i]+j));//利用了指针加减整数 //还可以这样写printf("%d ", all[i][j]); } printf("\n"); } return 0; } 运行结果:
1 2 3
4 5 6
7 8 9注释里面这样写
all[i][j]
的原因是all[i]
等于数组名,数组名再跟上数组符号[],就等于新数组
我们在2里面说了指针数组,现在我们讨论数组指针,注意,重点是指针,所以数组指针是 指针,指向的是数组,存放的是数组地址
我们知道指针的写法是 指针类型加上变量名.
比如 char p,称为字符指针 int p称为整型指针**
那么数组指针的类型怎么写呢???,假设有个整型数组叫arr
是这样吗? int *p = &arr 错,这是整型指针,差数组符号
是这样吗? int[] *p = &arr 错,int[]这种形式代表着int是一个数组名,而我们不能用数据类型做变量名
是这样吗? int *p[] = &arr 错,[]的结合性比
*
高,即p是数组,不是指针,即*p[]
代表着在解引用一个数组p[]综合上述,我们把(*p)括起来,这样就代表p是指针了.
所以,整型数组指针这样写
int (*p)[] = &arr
,意思是,指针p指向数组arr,arr的类型是int现在我们进行剖析 p是指针,那么剩下的就是 类型 即 int(* )[]
有人会问,为什么不这样写? int(* )[] p,这是c的规定,*与p必须在一起
出题,这里有一个数组
char arr[10] = "abcdefabc"
;请写出数组指针存放arr第一步,写出指针 (*p)
第二步,写出指向的类型 [10]
第三部,写出指向的数组的类型 char
所以就是 char (*p) [10] = &arr;
出题,这里有个整型指针数组 int* arr[3] = {&a,&b,&c};请写出数组指针存放arr
按照上面的步骤就是下面答案
int* (*p) [10] = &arr; 意思是,指针p,指向数组arr,arr的元素类型是
int*
#include
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int (*pa)[10] = &arr; //一定是存放数组地址哦,而不是arr首元素地址哦
for(int i = 0;i<10;i++)
{
printf("%d ",(*pa)[i]); //把(*pa)括起来是因为[]的结合性比*高,保证pa是指针
//因为 pa等于 &arr
//所以 *pa等于arr
// *pa+1 等于 arr+1
//所以,我们还可以这样写 printf("%d ",*(*pa + 1)); 利用指针加减整数访问
}
return 0;
}
结果 1 2 3 4 5 6 7 8 9 10
但是就是一个一维数组而已,我么完全不用这样写,太累了,所以,数组指针我么是用于二维数组
#include
void test(int(*p)[5],int a,int b)//因为arr是第一行的数组地址,就相当于是一个一维数组,所以用数组指针接收
{
int i = 0,j = 0;
for (i = 0;i
结果:
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
5 6 7 8 9
①
int arr[5];
这是一个数组,数组名是arr,有五个元素,每个元素类型是int,叫做 整型数组②
int *arr[10];
这是一个数组([]的结合性比*高),数组名是arr,有10个元素,每个元素的类型是int*
,所以叫做 指针数组③
int (*arr2)[10];
这是一个指针,指向一个数组名是arr2的数组,该数组有10个元素,每个元素类型是int 所以叫做 数组指针④
int(*arr3[10])[5]
这是一个数组([]的结合性比*高),数组名叫做arr3,有10个元素,每个元素类型是 数组指针,元素类型指向的是一个整形数组,所以叫做 数组指针数组
④有点不好理解,我画图分层写出来
我们能看见arr3由 三部分组成,数组名,数组标志[],元素类型
我在画细节图给大家理解
int arr1[3] = { 1, 2, 3 };
int arr2[3] = { 4, 5, 6 };
int arr3[3] = { 7, 8, 9 };
?????????? = { &arr1, &arr2, &arr3 };
答案: int(*parr[3])[3]
解析: &arr是 数组地址,数组地址用指 数组指针指向,一个指针指向一个数组地址
有三个所以需要三个数组指针
所以用数组存储
就是
int(*parr[3])[3]
而这也呼吁了上面的图
#include
void test(int arr[]);// 1
void test(int arr[10]);//2
void test(int* arr);//3
void test2(int* arr[20]);//4
void test2(int** arr);//5
int main()
{
int arr1[10] = {
0}; //整形数组
int* arr2[20] = {
0}; //数组指针
test(arr1);
test2(arr2);
return 0;
}
/*
以上传参均正确.
arr1是整形数组,他的每一个元素是int
所以1和2种接参法正确,表示接受的是int数组
因为arr1是一个地址,所以可以用指针接受,3也是正确的
arr2是指针数组,他的每一个元素都是指针int*
所以用4接受方法,表示接受一个数组指针, 正确
因为arr2是首元素地址,而首元素就是一个指针,所以要用二级指针接受,也是正确
*/
#include
void test(int arr[][]);//1
void test(int arr[3][5]);//2
void test(int* arr)//3
void test(int(*p)[5]);//4
int main()
{
int arr[3][5] = {
0};
test(arr);
return 0;
}
/*
解释:
1种 错误; 二维数组[][]里面的数字不能省略
2种 正确; 传的arr,arr的类型是int, 所以2的int arr[3][5]正确,表示接受二维整型数组
3种 错误; 二维数组的数组名是首元素地址,但是二维数组的首元素是 第一行 相当于一维数组&arr
所以,如果想要用指针接参,我们应该怎么弄?? 答案是 数组指针;
数组指针:指向----->数组地址 数组地址: 一维:&arr 二维:arr
*/
#include
void test(int(*p)[3]) /*一维数组名接收*/
{
for (int j = 0; j < 3; j++)
{
printf("%d ", (*p)[j]);
}
printf("\n");
}
void test1(int(*p)[5])/*二维数组名接收*/
{
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 5; j++)
{
printf("%d ", (*(p + i))[j]);
}
printf("\n");
}
}
int main()
{
int arr1[3] = { 1, 2, 3 };
//一个数组
int arr[3][5] = { {1,2,3,4,5} ,{2,3,4,5,6}, {3,4,5,6,7} };
test(&arr1);//传的数组地址;
test1(arr);//传的是一维的数组地址(二维数组本质上是多个一维数组连接在一起储存)
return 0;
}
/*运行结果:
1 2 3
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
*/
解释:
一维数组: &arr这个代表数组地址,不是首元素地址,而数组地址一般用数组指针接收,因为数组指针指向数组
p是指针,p等于&arr, 所以
*p
就等于arr, arr[] 就等于(*p)[]
二维数组:arr可以理解为就等于&arr,只是没有&. 就相当于 p = &arr,只是写的时候没有&
所以
*p
就是二维数组中真正的首元素地址,即第一个元素地址.在上面程序中i是行,所以*(p+i)
就是下一行中的第一个元素地址.所以
*(p+i) + j
就是第i行j列的某一个元素地址,所以*(*(p+i) + j)
就是i行j列某一个具体元素 也可以写成(*(p+i))[j]
看看下面两边的代码
可以很明显的看到,一级指针,我传的是一级指针,我函数就用一级指针接收
二级指针,我传的是二级指针,我就用二级指针接收
如果一个函数的参数是 一级指针,那么他可以接收什么?
如果一个函数的参数是 二级指针,那么他可以接收什么?
答案1:
如果一个函数的参数是一级指针,那么他可以接收 普通数组,变量地址,和一级指针
如果一个函数的参数是二级指针,那么他可以接收 二级指针,一级指针地址,和 一级指针数组 (int arr[10])*
函数指针:重点是指针,指向一个函数;
那么函数指针我们该怎么写呢??? 我们回顾一下在最开始写数组指针的时候是怎么写的.
先确定是一个指针(用括号括起来) 然后指向的东西是…(数组符号[]),再给数组写上元素类型
所以函数指针同样. 假设有一个函数是int add(int x, int y),那函数指针就是
int (*p)(int ,int )
例子:
#include
int add(int x,int y)
{
return x+y;
}
int main()
{
printf("%p\n", &add);
int a,b;
a = 4;b = 3;
int (*p)(int ,int);
printf("%d", (*p)(a,b));
return 0;
}
/*答案:
00F012BC
7
*/
void *p ();
与
void (*p) ();
这两个表达式的意思一样吗? 不一样. 上面是函数的声明,下面是函数指针
(*(void(*)())0)();
请问这个是啥???答案: 这是一个函数调用,并且该函数的地址在
0X00000000
处.解析: 首先看0左边括号里面的东西------
void(*)()
,这是什么?? ----函数指针类型 在他的外面又添加了一层(),然后在紧挨着0左边说明
(void(*)())
这是一个强制类型转换,即把0转换为某一个函数的地址,地址在0处.*(void(*)())
这是解引用地址为0的该函数最后
(*(void(*)())) ()
这就是再调用该函数. 之所以在解引用外面加一个(),是因为()的结合性比*高
void (*signal(int,void(*)(int)))(int);
请问这个是啥??答案: 这是一个返回类型为函数指针的函数
解析: 首先我们可以把他们拆开成
signal(int,void(*)(int))
和void (*)(int)
清晰的看到 前者是一个函数他的参数是一个整型和函数指针 后者是一个类型,函数指针类型. 所以这是一个返回类型为函数指针的函数.
typedef void (*)(int) a; a signal(int,a); 这样好理解了吗?? 好理解!!!! 但是这是有错误的写法哦~~~~~~~~~~,我只是为了大家好理解才这样写的 应该是这样写: typedef void (* a)(int); a signal(int,a); 即名字必须挨着*!!!!!!
这样是不是解非常好理解了呢??? 那么有人会问,既然
a signal(int,a);
可以这样写 那么原来为什么不这样写??? 看下面
void (*)(int)
signal(int,void(*)(int))
这个其实就和上面一样,即名字必须挨着*
,所以要揉在一起.
顾名思义:重点在最后面!!!**这是一个数组,**只是他的存储对象是 函数指针
为什么会有他呢??我们看看下面的例子:
#include
int add(int x,int y)
{
return x+y;
}
int sub (int x,int y)
{
return x-y;
}
int mul(int x,int y)
{
return x*y;
}
int div(int x,int y)
{
return x/y;
}
int main()
{
int a,b;
a = 4;b = 3;
/*现在我们需要调用上面的所有函数,如果一个一个的写就太麻烦了.因此现在就需要一个数组存储所有函数*/
return 0;
}
函数指针数组的写法::
我们知道一个数组由三个类型组成,分别是: 存储类型 数组名 数组符号[] 比如
int num[]
所以按照上面的定义我么可以知道这样写
int(*)(int,int) num[4]
是不是这样??? 对!!! 了90% 不要忘记我上面所说的*必须和名字结合所以应该是这样
int (*num[4])(int ,int)
char* my_strcpy(char* dest, const char* src);
my_strcpy
第二,请写一个函数指针数组,可以存放四个函数该地址第一:
char* (*p)(char*, const char*)
第二:
char* (*num[4])(char*, const char*)
下面,我们来编写一个小程序 (计算器),需要用到上面的东西
该计算器的作用是实现 基本加减乘除
1实现+ 2实现- 3实现* 4实现 0退出 其余数字报错,提醒重按.
按照上面的逻辑,我们可以很快想到用 do while循环 和 switch;
/*我们首先搭建结构*/
//结构搭建
#include
int main()
{
int input = 0;
int x,y;
do
{
remind();//提醒按键菜单
printf("请根据上面的提醒,按下命令数字1或2或3或4:\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
scanf("%d%d", &x,&y);
printf("输入完毕,结果是:\n");
printf("%d\n",add(x,y));
break;
case 2:
printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
scanf("%d%d", &x,&y);
printf("输入完毕,结果是:\n");
printf("%d\n",sub(x,y));
break;
case 3:
printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
scanf("%d%d", &x,&y);
printf("输入完毕,结果是:\n");
printf("%d\n",mul(x,y));
break;
case 4:
printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
scanf("%d%d", &x,&y);
printf("输入完毕,结果是:\n");
printf("%d", div(x,y));
break;
case 0:
printf("成功退出计算器\n\n\n");
break;
default:
printf("对不起,你输入的命令有误,请重新输入!\n");
break;
}
}while(input);
return 0;
}
#include
void remind()
{
printf("**********************************************\n");
printf("*********按1加 按2减 按3乘 按4除*********\n");
printf("************** 其他操作报告提示 ***************\n");
}
int add(int x,int y)
{
return x+y;
}
int sub (int x,int y)
{
return x-y;
}
int mul(int x,int y)
{
return x*y;
}
int div(int x,int y)
{
return x/y;
}
#include
void remind()
{
printf("**********************************************\n");
printf("****按1加 按2减 按3乘 按4除 按0退出*****\n");
printf("************** 其他操作报告提示 ***************\n");
}
int add(int x,int y)
{
return x+y;
}
int sub (int x,int y)
{
return x-y;
}
int mul(int x,int y)
{
return x*y;
}
int div(int x,int y)
{
return x/y;
}
int main()
{
int input = 0;
int x,y;
do
{
remind();//提醒按键菜单
printf("请根据上面的提醒,按下命令数字1或2或3或4或0:\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
scanf("%d%d", &x,&y);
printf("输入完毕,结果是:\n");
printf("%d\n",add(x,y));
break;
case 2:
printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
scanf("%d%d", &x,&y);
printf("输入完毕,结果是:\n");
printf("%d\n",sub(x,y));
break;
case 3:
printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
scanf("%d%d", &x,&y);
printf("输入完毕,结果是:\n");
printf("%d\n",mul(x,y));
break;
case 4:
printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
scanf("%d%d", &x,&y);
printf("输入完毕,结果是:\n");
printf("%d", div(x,y));
break;
case 0:
printf("您成功退出计算器\n\n\n");
break;
default:
printf("对不起,你输入的命令有误,请重新输入!\n");
break;
}
}while(input);
return 0;
}
代码如下:
#include
int main()
{
int input = 0;
int x,y;
int(*num[5])(int,int) = {0,add,sub,mul,div};
do
{
remind();//提醒按键菜单
printf("请根据上面的提醒,按下命令数字1或2或3或4或0:\n");
scanf("%d", &input);
if(input>=1 && input <= 4)
{
printf("系统已经识别到你的目的,请输入你想要操作该目的两个数字\n");
scanf("%d%d", &x,&y);
printf("输入完毕,结果是:\n\n");
printf("%d\n",num[input](x,y));
}
else if(input == 0)
printf("成功退出\n");
else
printf("对不起,你输入的命令有误,请重新输入!\n");
}while(input);
return 0;
}
我们首先把加减乘除函数封装在数组里面,然后利用下标进行访问
指向函数指针数组的指针是一个 指针 ,指针指向一个 数组 ,数组的元素都是 函数指针 ;
那么怎么定义呢? 我们同样可以回顾一下最开始我们是怎么定义 数组指针 —>> 函数指针----->>>数组指针数组等等的
现在我们需要定义函数指针数组指针, 所以需要明确 本体(指针) 指向类型(数组) 所指向的数组存的什么(函数指针)
#include
int add(); int main() { /*第一步,首先写出函数指针*/ int(* func_point)() = &add; //这是一个函数指针 /*第二步,写出一个数组,用来存放函数指针 (所以想想怎么写这个数组)*/ int (* )() num[] = { func_point,func_point,func_point}; //这样写对吗?对!!!!了90%,因为基本符合数组的规范, //但是我前面一直强调一个事情,*必须怎么样???*需要挨着名字.所以下面才是真正的写法 int (*num[])() = { func_point,func_point,func_point}; /*第三部,写出一个指针,用来存放函数指针数组*/ //第一步,先写出指针 (*point) //第二步,写出指针指向的数组 (*point)[] //第三部, 给所指向的数组添加数组类型 int (*)() (*point)[];// 成功了吗??对!!!!了90%,但是还是不要忘记我们所说的,*必须挨着名字,所以: int (*(*point)[])(); //大工告成!!!!!!!!!!!! return 0; } 所以,我们函数指针数组指针一般这样写 int (*(*point)[])();
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一
个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该
函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或
条件进行响应。
简而言之,就是一个函数的参数接收的是一个函数地址.被接收的函数,这时候就称为 回调函数
#include
int x = 2;
int y = 3;
int add(int x,int y)
{
return x+y;
}
int many( int(*func)(int,int), int b )
{
return func(x,y) + b;
}
int main()
{
printf("%d", many(add,6));
return 0;
}
/*
运行结果是:
11
*/
现在我们开始使用回调函数以及指针来模拟
qsort
,并且qsort可以排序一切
但是在模拟之前,我们首先需要知道qsort
函数是怎么使用的,现在我们来看官方文档.
官方的
qsort
是像上面一样声明的,我现在一一解释什么意思:
void* base
待排序元素的首元素地址,即数组首元素地址,即数组名
size_t num
数组元素数量
size_t width
数组单个元素的内存大小
int(_cdecl *compare)(const void* eleml,const void* elem2)
接收一个比较函数,返回 正数 负数 和 0
其中compare的写法是这样
//如果想要升序排列整型数组,就这样写
int compare (const void * a, const void * b)
{
return ( *(int*)a - *(int*)b );
}
//如果想要降序排列整型数组,就这样写
int compare (const void * a, const void * b)
{
return ( *(int*)b - *(int*)a );
}
#include
#include
struct info
{
char name[20];
int age;
int grade;
};
int compare(const void* a, const void* b)
{
return ((struct info*)a)->grade - ((struct info*)b)->grade;
}
int main()
{
struct info information[5] = {
{"夏敏",18,65},
{"李华",16,71},
{"杜美丽",17,85},
{"刘安",17,69},
{"李平",18,90}
};
qsort(information, sizeof(information) / sizeof(information[0]), sizeof(information[0]), compare);
for (int i = 0; i < 5; i++)
{
printf("%s\n", information[i].name);
}
return 0;
}
/*
运行结果:
李平
杜美丽
李华
刘安
夏敏
*/
qsort
函数了,因为我们知道了他的机制.现在我们开始写my_sqort()
;我们知道
qsort
的主要作用就是排序,所以我们自己设计的qsort
的核心程序就是排序,我们为了简单就选择通过冒泡思想来解决
#include
struct info
{
char name[20];
int age;
int grade;
};
/*因为指针就收的地址是第一个字节,所以需要挨个交换*/
void swap(char* a,char*b,int width)
{
for(int i = 0;i<width;i++)
{
char tmp = *b;
*b = *a;
*a = tmp;
a++;
b++;
}
}
/*第一步,首先按照标准qsort的写法,我们直接模拟一个与其一样的函数声明*/
void my_sort(void* base,int number,int size,int(*compare)(const void*,const void*))
{
/*第二步,写好冒泡排序的框架*/
int i = 0,j = 0;
for(i = 0;i<number-1;i++)
{
for(j = 0;j<number-1-i;j++)
{
/*第三步,我们需要通过调用compare知道他的返回值是大于0.还是小于0;如果大于0,我们就需要升序,反之,降序.*/
//那么,当有人在qsort外面写compare时候,他是知道自己需要排序什么类型的,但是我们模拟qsort的时候,我们是不知道的,所以我们需要实现某种方式来保证我们能够知道: 想要使用qsort排序的人的排序数组类型
//现在我们在my_qsort内部只知道 4 个参数 base number size 与compare
//那么怎么来利用这4个值确定我们一定可以知道待排序类型呢?? 那就是base与size,base是指针,首元素地址,size是一个元素的大小.
//那么 (char*)base就一定是第一个元素的地址
// (char*)base + size就一定是第二个元素地址
//所以,(char*)base + j*size就是前一个元素地址,
// (char*)base + (j+1)*size就是后一个元素地址.
//所以,我们可以开始自己使用compare
if(compare((char*)base + j*size,(char*)base + (j+1)*size)>0)
{
//如果返回值大于0,说明前面的值比后面大,所以我们需要交换前后两个值
swap((char*)base + j*size, (char*)base + (j+1)*size, size);
}
}
}
}
int main()
{
struct info information[5] = {
{
"夏敏",18,65},
{
"李华",16,71},
{
"杜美丽",17,85},
{
"刘安",17,69},
{
"李平",18,90}
};
my_qsort(information, sizeof(information) / sizeof(information[0]), sizeof(information[0]), compare);
for (int i = 0; i < 5; i++)
{
printf("%s\n", information[i].name);
}
return 0;
}