概述
指针进阶(qsort的用法和模拟实现)
- 字符指针
- 指针数组
- 数组指针
- 数组参数 指针参数
- 一维数组传参
- 二维数组传参
- 一级指针传参
- 二级指针传参
- 函数指针
- 函数指针数组
- 用法
- 回调函数
- qsort函数
- 模拟实现qsort函数
字符指针
在指针的类型中我们知道有一种指针类型为字符指针 char* ;
#include<stdio.h>
int main()
{
char s = 'a';
char* p = &s;
*p = 'b';
return 0;
}
//我们可以将单个字符的地址放在字符指针中
char* t = "hello world!";
printf("%s", t);
//我们还可以将字符串储存在字符指针中 用%s打印就可以打印出字符串
第二种使用场景实际上不是将整个字符串储存在字符指针中,而是将字符串首元素的地址存放在字符指针中
以%s打印就是找到字符串中的 才终止打印;
观察这段代码
#include <stdio.h>
int main()
{
char str1[] = "hello world.";
char str2[] = "hello world.";
const char *str3 = "hello world.";
const char *str4 = "hello world.";
//分别用数组的形式和字符指针的方式储存字符串
if(str1 ==str2)//数组的方式给创建的str1和str2是否相同
printf("str1 and str2 are samen");
else
printf("str1 and str2 are not samen");
if(str3 ==str4)//字符指针的方式给创建的str1和str2是否相同
printf("str3 and str4 are samen");
else
printf("str3 and str4 are not samen");
return 0;
}
结果如下
可以看到数组方式创建的两个是不同,字符指针创建的相同
原因如图所示
1.以数组的方式创建是开辟了两块不同的空间来存放数据,且数组内数据是可变的,这就要求必须开辟不同空间;
2.以字符指针的方式是开辟了一块空间放置“hello world.”,两个字符指针都指向第一个元素的位置,且字符串本质上是常量字符串,不可与被更改,所以只需要开辟一块空间
指针数组
指针数组顾名思义就是存放指针变量的数组
int* arr[10];
/arr先与[]结合,说明是数组,然后去掉 arr[], int*就是每个元素的类型
char* arr2[10];
数组指针
数组指针顾名思义是指向数组的指针
int *p
//指向整型的指针
char*p
//指向字符的指针
int (*p)[10]
//指向数组的指针 数组类型为int[10]
例如
#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 i = 0;
int j = 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];
//arr是数组 可存放5个元素 每个元素类型为int
int* parr1[10];
//parr1是指针数组 可存放十个元素 每个元素类型为int*
int(*parr2)[10];
//parr2是数组指针 指向的数组有10个元素 每个元素类型为int
int(*parr3[10])[5];
//parr3是一个数组 可存放10个元素 每个元素类型为int(*)[5]
数组参数 指针参数
一维数组传参
#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int* arr)
{}
void test2(int* arr[20])
{}
void test2(int** arr)
{}
//一维数组传参 参数可以写成一维数组,元素个数可以省略
//也可以写成一级指针的方式,因为数组名表示数组首元素的地址
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
}
二维数组传参
void test(int arr[3][5])//ok?
{}
void test(int arr[][])//ok?
{}
void test(int arr[][5])//ok?
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int (*arr)[5])//ok?
{}
//二维数组首元素地址是第一行的地址 是一个一维数组的地址,所以可以用数组指针来接受
int main()
{
int arr[3][5] = {0};
test(arr);
return 0;
}
一级指针传参
#include <stdio.h>
void print(int* p, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%dn", *(p + i));
}
}
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9 };
int* p = arr;
int sz = sizeof(arr) / sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}
二级指针传参
#include <stdio.h>
void test(int** ptr)
{
printf("num = %dn", **ptr);
}
int main()
{
int n = 10;
int* p = &n;
int** pp = &p;
test(pp);
//pp是二级指针 可用二级指针接受
test(&p);
//p是一级指针 &p取出一级指针的地址 可用二级指针接受
return 0;
}
函数指针
函数指针顾名思义就是存放函数地址的指针
例如
#include<stdio.h>
void test(int a, int b)
{
printf("a+b=%dn", a + b);
}
int main()
{
int a = 2, b = 3;
void (*pfun1)(int, int) = &test;
//pfun1先与*结合说明是指针变量
//剩下的void (int,int)就是函数的参数和返回类型
(*pfun1)(a, b);
return 0;
}
看两个有趣的代码
//代码1
(*(void (*)())0)();
//这个代码从0开始分析,先将0强制类型转换为
//(void (*)())的函数指针类型然后在解引用调用这个
//函数指针指向的函数
//代码2
void (*signal(int , void(*)(int)))(int);
//这个代码从signal分析首先和()结合说明是
//一个函数 函数的参数一个是int 一个是void(*)(int)
//返回类型是void(*)(int)
//把第二个代码简化一下就是将void(*)(int)重命名为pfun_t
typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);
//这样看起来更加明了
函数指针数组
顾名思义就是存放函数指针的数组
例如
int (*parr1[10])();
//parr1先和[]结合说明是个数组
//parr1[]就剩int(*)()说明数组中每个元素都是函数指针类型
//这样就定义了一个函数指针数组
用法
计算器的实现
如果不使用函数指针类型的话,计算器的实现如下
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
do
{
printf("*************************n");
printf(" 1:add 2:sub n");
printf(" 3:mul 4:div n");
printf("*************************n");
printf("请选择:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %dn", ret);
break;
case 2:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret = %dn", ret);
break;
case 3:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret = %dn", ret);
break;
case 4:
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret = %dn", ret);
break;
case 0:
printf("退出程序n");
break;
default:
printf("选择错误n");
break;
}
} while (input);
return 0;
}
这样我们就实现了这个计算器的基本逻辑
但是这样写有几个问题
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret = %dn", ret);
break;
//1.switch语句中类似这样的语句多次重复出现
//2.如果还需要加其他运算方法时需要修改的地方很多
这时我们就可以使用函数指针数组的方式,将每个运算逻辑的函数放在一个函数指针数组中
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a * b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf("*************************n");
printf(" 1:add 2:sub n");
printf(" 3:mul 4:div n");
printf("*************************n");
printf("请选择:");
scanf("%d", &input);
if ((input <= 4 && input >= 1))
{
printf("输入操作数:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf("输入有误n");
printf("ret = %dn", ret);
}
return 0;
}
这样重复的代码就只需要写一次,且我要想往内容中加其他运算逻辑只需要写好函数,然后加入数组中,非常方便
回调函数
回调函数就是一个通过函数指针调用的函数 如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
qsort函数
库函数中的qsort函数就使用了回调函数
且qsort函数不止可以对整型数组排序,根据我们写的比较函数,可以给结构体元素等等一系列数组排序
具体用法如下
#include <stdio.h>
#include <stdlib.h>
//qosrt函数的使用者得实现一个比较函数
int int_cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
int main()
{
int arr[] = { 1, 3, 5, 7, 9, 2, 4, 6, 8, 0 };
int i = 0;
qsort(arr, sizeof(arr) / sizeof(arr[0]), sizeof(int), int_cmp);
//第一个参数是数组的第一个元素的地址
//第二个参数是需要排序的元素的个数
//第三个参数是数组中元素的大小
//第四个参数是一个函数指针 需要自己实现一个比较函数 这里就是回调函数的用法
//用指针的方式调用函数,而不是直接调用
for (i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d ", arr[i]);
}
printf("n");
return 0;
}
模拟实现qsort函数
库函数中的qsort函数内部是使用快速排序的算法来实现的
我们在这里使用冒泡排序的方式模拟实现qsort函数
首先根据资料确定函数的参数和返回类型
void bubble_sort(void* base, int count, int size, int(*cmp)(void*, void*))
//这里参数中出现的void*可以接受任何类型的指针
//因为我们无法确定需要排序的数组元素类型是什么所以采用void*
//void*指针在使用时需要强制类型转换为需要的类型再使用
{
}
函数内部先实现冒泡排序的基本思想
void bubble_sort(void* base, int count, int size, int(*cmp)(void*, void*))
{
int i = 0;
int j = 0;
for (i = 0; i < count - 1; i++)
{
for (j = 0; j < count - i - 1; j++)
{
if (//是否交换的判断条件)
{
//交换-
}
}
}
}
//这里因为是实现任意类型数组的排序 所以要自己设计一个判断交换的条件和一个交换函数
下面就是判断条件和交换的实现
判断条件
//因排序的内容不同,所以判断条件也不同
struct stuent
{
int age;
char name[20];
};//例如对结构体进行排序
//如果按年龄排序
int cmp_age(const void* e1, const void* e2)
{
return ((struct stuent*)e1)->age - ((struct stuent*)e2)->age;
}
//将p1和p2强制类型转换为struct stuent*再指向age相减
//如果按名字排序
int cmp_name(const void* e1, const void* e2)
{
return strcmp(((struct stuent*)e1)->name, ((struct stuent*)e2)->name);
//字符串比较函数比较大小
}
//将p1和p2强制类型转换为struct stuent*再指向name相减
//p1>p2 返回大于0
//p1=p2 返回等于0
//p1<p2 返回小于0
交换
void swap(char* e1, char* e2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char temp = *e1;
*e1 = *e2;
*e2 = temp;
e1++;
e2++;
}
//因为不知道具体类型 所以必须使用char类型一个字节一个字节交换
}
所有加起来就是整个qsort的模拟实现
void swap(char* e1, char* e2,int width)
{
int i = 0;
for (i = 0; i < width; i++)
{
char temp = *e1;
*e1 = *e2;
*e2 = temp;
e1++;
e2++;
}
}
void bubble_sort(void* base, size_t num, size_t width, int(*cmp)(const void* e1, const void* e2))
{
size_t i = 0;
for (i = 0; i < num - 1; i++)
{
size_t j = 0;
for (j = 0; j < num - 1 - i; j++)
{
//判断函数 j j+1
if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
{
//交换函数
swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
struct stuent
{
int age;
char name[20];
};
int cmp(const void* e1, const void* e2)
{
return *(int*)e1 - *(int*)e2;
//e1 > e2 return>0
}
int cmp_age(const void* e1, const void* e2)
{
return ((struct stuent*)e1)->age - ((struct stuent*)e2)->age;
}
int cmp_name(const void* e1, const void* e2)
{
return strcmp(((struct stuent*)e1)->name, ((struct stuent*)e2)->name);
}
int main()
{
//整型数组排序
int arr[10] = { 1,5,9,3,7,8,4,2,6 };
int sz = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr, sz, sizeof(arr[0]), cmp);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
//***********************************
//结构体排序(age)
struct stuent s[3] = { {20,"zhangsan"},{40,"lisi"},{30,"wangwu"} };
sz = sizeof(s) / sizeof(s[0]);
qsort(s, sz, sizeof(s[0]), cmp_age);
//***********************************
//结构体排序(name)
qsort(s, sz, sizeof(s[0]), cmp_name);
return 0;
}```
最后
以上就是迷路铃铛为你收集整理的【C语言】指针进阶(qsort的用法和模拟实现)字符指针指针数组数组指针数组参数 指针参数函数指针函数指针数组回调函数的全部内容,希望文章能够帮你解决【C语言】指针进阶(qsort的用法和模拟实现)字符指针指针数组数组指针数组参数 指针参数函数指针函数指针数组回调函数所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复