现在我们已经学习到了指针的进阶,这也可以分为两部分,主要的难点在于qsort的模拟实现。
- 进阶指针1(字符指针,数组指针,二维数组传参,函数指针变量(数组))
- 进阶指针2(回调函数,qsort使用,qsort的模拟实现)
进阶指针 1
字符指针变量:
字符变量的一般使用,但是这里需要注意,如果加入一个字符串,那么指针指向的是字符串第一个字符的地址。
数组指针变量:
之前我们学习了指针数组,指针数组是⼀种数组,数组中存放的是地址(指针)
那么数组指针是什么呢?
是指向一个数组的指针变量。
第一行就是指针数组里面存放的指针。
第二行就数组指针是指向一个数组为10个元素的指针变量。
解释:p先和*结合,说明p是⼀个指针变量变量,然后指着指向的是⼀个⼤⼩为10个整型的数组。所以p是⼀个指针,指向⼀个数组,叫数组指针。
这⾥要注意:[]的优先级要⾼于*号的,所以必须加上()来保证p先和*结合。
数组指针的初始化
数组指针是用来存放数组的地址的,我们可以存放地址数组的整个地址,在调试窗口下类型也是完全一致的。
二维指针传参的本质:
平时我们二维数组传参:
⼆维数组传参本质上也是传递了地址,传递的是第⼀⾏这个⼀维数组的地址,那么形参也是可以写成指针形式的。如下:
函数指针变量:
函数指针就是用来存放函数的地址的。
具体写法如下:
函数指针解析:
函数指针使用:
typedef关键字:
typedef是用来重命名的将复杂的类型简单化。
比如:unsigned int 可以改成uint
数组指针跟函数指针有点区别
⽐如我们有数组指针类型 int(*)[5] ,需要重命名为 parr_t ,那可以这样写:
函数指针:
函数指针数组:
我们已经学习了函数指针,那么函数指针数组就是在定义函数名的后面加上【】下标操作符就行了。
转移表:
转移表就是通过一个函数指针数组来储存自己自定义的函数,在需要调用函数时,就可以通过函数指针数组的下标来访问。
下面我们用转移表来实现一般的计算机:
int jf(int x, int y)
{
return x + y;
}
int jif(int x, int y)
{
return x - y;
}
int cf(int x, int y)
{
return x * y;
}
int cuf(int x, int y)
{
return x / y;
}
void cd()
{
printf("******************\n");
printf("**1:加法 2:减法**\n");
printf("**3:乘法 4:除法**\n");
printf("**0:退出**********\n");
printf("******************\n");
}
int main()
{
int x, y;
int a = 1;
int ret = 0;
do
{
cd();
int (*p[5])(int x, int y) = { 0,jf,jif,cf,cuf };
printf("请输入:");
scanf("%d", &a);
if (a >= 1 && a <= 4)
{
printf("请输入操作数:");
scanf("%d %d", &x, &y);
ret = (p[a])(x, y);
printf("%d\n", ret);
}
else if (a == 0)
{
printf("退出游戏\n");
}
else
{
printf("输入错误请重新输入\n");
}
} while (a);
}
进阶指针2
回调函数是什么?:
回调函数就是⼀个通过函数指针调⽤的函数。
我们上面所讲到的转移表其实就是用了回调函数功能,创建一个函数指针数组,然后通过我们输入的下标来访问函数。
当然函数指针也是可以做到上面的转移表的,只不过比函数指针数组麻烦了不少。
qsort的使用举例:
需要包含头文件<stdlib.h>
这是我在c++参考网站上找的关于qsort需要的参数,从上面可以看出qsort是用来排序升序数组的,算法都已经在内部帮我们写好了,所以用起来也非常简单。
以下是它需要的具体参数资料:
具体实现:
int px(const void *x, const void *y)
{
return *(int*)x - *(int*)y;
}
//判断大小,返回大于0就交换,等于小于不交换。
int main()
{
int a[] = {5,48,48,6,4,78,9,14,3,4,58,6,4,1,0,0 };
int si = sizeof(a) / sizeof(a[0]);
for (int c = 0; c < si; c++)
{
printf("%d ", a[c]);
}
printf("\n");
qsort(a, si, sizeof(a[0]), px);
for (int c = 0; c < si; c++)
{
printf("%d ", a[c]);
}
printf("\n");
return 0;
}
这里 注意如果需要排序字符串需要用到头文件#include <string.h>,strcmp来比较:
int name(const void* x, const void* y)
{
return strcmp((*(gaa*)x).name, (*(gaa*)y).name);
}
模仿qsort的功能实现一个通用的冒泡排序:
//结构体
struct gaa
{
char name[10];
int age;
};
//比较大小并返回
int sx(const void *x, const void *y)
{
return strcmp(((struct gaa*)x)->name, ((struct gaa*)y)->name);
}
//交换
void jh(char* x, char* y,size_t si)
{
for (int a = 0; a < si; a++)
{
char c = *x;
*x= *y;
*y = c;
x++;
y++;
}
}
//循环判断
void qs(void* a, size_t se, size_t si,int (*sx)(void*x,void*y))
{
for (int c = 0; c < se-1;c++)//第一趟
{
for (int d = 0; d < se - 1 - c; d++)//每一趟判断出一个数
{
if (sx((char*)a + d * si , (char*)a + (d + 1) * si) > 0)
{
jh((char*)a + d * si, (char*)a + (d + 1) * si, si);
}
}
}
}
//打印
void acc( struct gaa a[], int se)
{
for (int c = 0; c < se; c++)
{
printf("%s ", a[c].name);
printf("%d \n",a[c].age);
}
printf("\n");
}
int main()
{
//int a[] = { 1,5,4,8,9,6,3,4,1 };
//char a[] = {"cbafhesi"};
//float a[] = { 1.9,2.9,6.4,7.8,9.0 };
struct gaa a[] = {{"zhangsan",20},{"lisi",19},{"wangwu",22} };
int se = sizeof(a) / sizeof(a[0]);
qs(a, se, sizeof(a[0]), sx);
acc(a, se);
return 0;
}
关于这里的强制转换成char*解释:
这里强制转换成char类型是方便我们后期用字符类型进行排序,d*si是跳过一个4个字节进行比较,后面的d+1就是比前面多跳4个字节。
比如int类型前面d=0,si肯定等于4 ,a+0*4就是0,传过去的就是a第一个数据
那么第二个就是d+1=1,a+1*4(char等于一个字节,加4个字节),就是a第二个数据
关于交换传每个数的大小解释:
这里还是如上是int类型的,假如条件成立进入函数,我们传给了jh一个char类型的函数所以,也得用char类型的来接收,而因为char类型是一个字节,所以每次只能交换一个字节,而int是4个字节(x86),所以就需要循环4次。
运行结果(通过名字首字母排序):