【c语言】指针进阶

目录

1.字符指针

2.指针数组

3.数组指针 

3.1 数组指针的定义

3.2 数组指针的使用 

4.数组参数,指针参数

4.1 一维数组传参 

4.2 二维数组传参 

4.3 一级指针传参 

4.4 二级指针传参

5.函数指针

 6.函数指针数组

6.1函数指针数组的定义

6.2 函数指针数组的使用

7.指向函数指针数组的指针

8.回调函数

 8.1 回调函数的用例——qsort


在指针初阶中,我们知道了指针的概念:内存单元有编号,编号=地址=指针
1. 指针(口头语)就是个变量,用来存放地址,地址唯一标识一块内存空间。
2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
3. 指针是有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
4. 指针的运算。

1.字符指针

字符指针的常见用法:

int main() {char ch = 'w';char* pc = &ch;*pc = 'w';return 0;
}

可以使用常量字符串给字符指针赋值:常量字符串表达式的值就是首字符的地址,在内存中连续存放且可以通过下标访问,可以看成是数组,但是不能改变

#include <stdio.h>
int main() {char* p = "abcdef";//加const修饰指针更安全printf("%s\n", p);printf("%c\n", *p);//*p得到的是'a',需要用%c的格式打印*p = 'e';//err:常量字符串不能改变printf("%c\n", "abcdef"[3]);//通过下标访问return 0;
}

常量字符串给字符指针赋值时,使用const修饰指针,如果解引用改变字符值会报编译错误,更安全合理 

 练习题:

#include <stdio.h>
int main()
{char str1[] = "hello bit.";char str2[] = "hello bit.";const char* str3 = "hello bit.";const char* str4 = "hello bit.";if (str1 == str2)printf("str1 and str2 are same\n");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

输出:

str1 and str2 are not same

用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。

数组名表示首元素地址,str1,str2分别指向两块内存区域


str3 and str4 are same

注意:常量区就是一直存在的,只读的,不可更改的数据区域,并且一个字符串只会有一份

str3,str4指向同一个常量字符串,但str3,str4是不同的内存区域

2.指针数组

指针数组是一个存放指针的数组: int* arr[10] = {0};//表示arr数组有10个元素,且每一个元素都为int*类型
int main() {int a = 0;int b = 0;int c = 0;int d = 0;//指针数组不会这样使用int* arr[] = { &a,&b,&c,&d };return 0;
}

指针数组的用法:

  1. 模拟一个二维数组
  2. 管理多个字符串
#include <stdio.h>
//指针数组模拟二维数组
int main() {int arr1[] = { 1,2,3,4,5 };int arr2[] = { 2,3,4,5,6 };int arr3[] = { 3,4,5,6,7 };//数组名表示首元素地址int* arr[] = { arr1,arr2,arr3 };//访问int i = 0;for (i = 0; i < 3; i++) {int j = 0;for (j = 0; j < 5; j++) {printf("%d ", arr[i][j]);(*(arr+i)//?}printf("\n");}return 0;
}

#include <stdio.h>
//管理多个字符串
int main() {char* arr[4] = { "wo","shi","hao","dan" };//访问int i = 0;for (i = 0; i < 4; i++) {printf("%s\n", arr[i]);
//arr[i]->*(arr+i),arr是指针数组,数组名表示首元素地址,解引用得到char*访问字符串,以%s打印,不同于*p(p为字符指针)只能以%c格式打印}return 0;
}

3.数组指针 

3.1 数组指针的定义

能够指向数组的指针。 

数组名是首元素的地址

但存在两个例外:

1.sizeof(数组名),这里的数组表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节

2.&数组名,这里的数组名表示整个数组,取出的是数组的地址

数组的地址怎么理解,看下面的代码:

#include <stdio.h>
int main()
{int arr[10] = { 0 };printf("arr = %p\n", arr);//int*printf("arr+1 = %p\n", arr + 1);printf("&arr[0] = %p\n", &arr[0]);//int*printf("&arr[0]+1 = %p\n", &arr[0] + 1);printf("&arr= %p\n", &arr);//p存放数组的地址,是数组指针printf("&arr+1= %p\n", &arr + 1);return 0;
}

运行结果:

arr = 00000056FEAFFC68
arr+1 = 00000056FEAFFC6C

//加4个字节
&arr[0] = 00000056FEAFFC68
&arr[0]+1 = 00000056FEAFFC6C

//加4个字节
&arr= 00000056FEAFFC68
&arr+1= 00000056FEAFFC90

//加40个字节

指针类型决定了指针+1的步长是几个字节

数组指针的写法由语法规定

#include <stdio.h>
int main() {//整型指针int a = 0;int* p = &a;//数组指针int arr[] = { 0 };int (*p)[1] = &arr;return 0;
}

注意:数组指针的大小不能省略,数组即使初始化没有指定大小,大小也是固定的

p的类型是int (*)[1],大小不写默认为0

3.2 数组指针的使用 

访问一维数组使用数组指针?

#include <stdio.h>
int main() {int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int (*p)[10] = &arr;int i = 0;for (i = 0; i < 10; i++) {printf("%d ", (*p)[i]);}return 0;
}

(*p)->*&arr->*&可以抵消,使用数组指针访问数组过于繁琐,不建议

一般使用整型指针访问数组: 

#include <stdio.h>
int main() {int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };int* p = arr;int i = 0;for (i = 0; i < 10; i++) {printf("%d ", p[i]);//*(p+i)}return 0;
}

 数组传参:

一维数组:

#include <stdio.h>
//一维数组传参——形参为数组形式
void Print(int arr[], int sz) {int i = 0;for (i = 0; i < sz; i++) {printf("%d ", arr[i]);}
}
int main() {int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);Print(arr, sz);return 0;
}

形参数组的大小可以不写或者写错,因为数组传参的本质是传数组首元素的地址 

#include <stdio.h>
//一维数组传参——形参为指针形式
void Print(int* arr, int sz) {int i = 0;for (i = 0; i < sz; i++) {printf("%d ", arr[i]);}
}
int main() {int arr[] = { 1,2,3,4,5,6,7,8,9,10 };int sz = sizeof(arr) / sizeof(arr[0]);Print(arr, sz);return 0;
}

二维数组:

注意:二维数组是一维数组的数组,二维数组的数组名为首元素地址,二维数组的首元素是第一行的地址;二维数组传参,形参为数组时,形参的行可以省略,列不行

#include <stdio.h>
//二维数组传参——参数为数组形式
void Print(int arr[3][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 ", arr[i][j]);}printf("\n");}
}
int main() {int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6}, {3,4,5,6,7} };Print(arr, 3, 5);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]);}printf("\n");}
}
int main() {int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6}, {3,4,5,6,7} };Print(arr, 3, 5);return 0;
}

注意:p为二维数组指针,表示二维数组的第一行地址,加i表示第i行的地址,解引用得到第i行的数组名,*p=arr,*(p+i)=arr[i];第i行的数组名既不与&结合,也没有单独放在sizeof中,所以表示第i行首元素的地址,再使用j访问第i行的每个元素,p[i][j]表示*(*(p+i)+j)

 分析:

4.数组参数,指针参数

4.1 一维数组传参 

#include <stdio.h>
//一维数组传参
void test(int arr[])//ok?
{
}
void test(int arr[10])//ok?
{
}
void test(int* arr)//ok?
{
}
void test2(int* arr[20])//ok?
{
}
void test2(int** arr)//ok?//使用二级指针存放一级指针
{
}
int main()
{int arr[10] = { 0 };int* arr2[20] = { 0 };test(arr);test2(arr2);
}

全部正确

4.2 二维数组传参 

#include <stdio.h>
//二维数组传参
void test(int arr[3][5])//ok?
{
}
void test(int arr[][])//ok?//err
{
}
void test(int arr[][5])//ok?
{
}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int* arr)//ok?//err
{
}
void test(int* arr[5])//ok?//err
{
}
void test(int (*arr)[5])//ok?
{
}
void test(int** arr)//ok?//err
{
}
int main()
{int arr[3][5] = { 0 };test(arr);
}

二维数组传参,形参为数组时列不能省略,形参为指针时,只能用数组指针,来接收二维数组第一行的地址

4.3 一级指针传参 

#include <stdio.h>
void print(int* p, int sz) {int i = 0;for (i = 0; i < sz; i++){printf("%d\n", *(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;
}

一级指针传参,形参用一级指针接收就行

注意:反向思考,一级指针的形参,可以接收什么实参

void test(int *p) {}
int main() {int a = 10;int* ptr = &a;int arr[5];test(&a);//传整型变量的地址test(ptr);//传整型指针test(arr);//传整型一维数组的数组名return 0;
}

4.4 二级指针传参

#include <stdio.h>
void test(int** ptr) {
printf("num = %d\n", **ptr);
}
int main()
{int n = 10;int* p = &n;int** pp = &p;test(pp);test(&p);return 0;
}

反向思考:

void test(int** p) {}
int main() {int a = 10;int* p = &a;int** pp = &p;int* arr[6];test(&p);//传一级指针的地址test(pp); // 传二级指针变量test(arr);//传指针数组的数组名return 0;
}

5.函数指针

数组指针——指向数组的指针——存放数组的地址——&数组名就是数组的地址

函数指针——指向函数的指针——存放函数的地址——如何得到函数的地址?

#include <stdio.h>
int Add(int x, int y) {return x + y;
}
int main() {printf("%p\n", &Add);printf("%p\n", Add);//函数指针int (*pf1)(int, int) = Add;int (*pf2)(int, int) = &Add;//通过函数指针解引用调用函数int ret = (*pf2)(2, 3);printf("%d\n", ret);//直接通过函数指针调用//函数直接通过函数名调用,函数名就是函数指针,所以指针可以不解引用来调用函数,*是摆设,可以没有,可以有多个//注意:要么不解引用,解引用一定要加括号ret = pf1(4, 6);printf("%d\n", ret);return 0;
}

通过运行调试以及将Add和&Add都赋值给相同的类型int (*)(int,int)没有警告可以看出,Add,&Add类型相同,都表示函数地址

函数指针的写法: 

int                (*)                                            (int,int)

返回类型     表示类型为指针    参数类型(参数名一般省略,只有函数定义时才用到参数名)

分析:

int main() {//分析//代码1 (*(void (*)())0)();//void (*)()是函数指针类型,在括号中表示强制类型转换,对0进行从整型强转为指针类型,存放一个函数地址,表示0地址处放一个函数,返回类型为void,没有参数//表示解引用调用0地址处函数,函数没有参数,没有传参//代码2void (*signal(int, void(*)(int)))(int);//是一个函数声明,声明函数signal,参数类型为int,和void(*)(int),该函数指针参数为int,返回类型为void,signal函数的返回类型为函数指针void (*)(int),该函数指针参数为int,返回类型为void//简化//void(*)(int) signal(int, void(*)(int));//error:错误写法,语法不支持;函数指针作为返回值,名字要写在*旁边//typedef void (*)(int) pfun_t;//简化:重命名函数指针类型,但是pfun_t要放到*旁边typedef void (*pfun_t)(int);//去掉typedef,pfun_t是函数指针变量,加上typedef,pfun_t是函数指针类型pfun_t signal(int, pfun_t);return 0;
}

 6.函数指针数组

6.1函数指针数组的定义

int Add(int x, int y) {return x + y;
}
int Sub(int x, int y) {return x - y;
}
int main() {int (*pf1)(int, int) = Add;int (*pf2)(int, int) = Sub;//数组中存放多个类型相同的元素//函数指针数组int (*pfArr[2])(int, int) = { Add,Sub };//pfArr是函数指针数组,是存放函数指针的数组//数组名:pfArr;数组元素个数:2;数组元素类型:int (*)(int, int)return 0;
}

6.2 函数指针数组的使用

完成一个计算器

switch语句实现:

#include <stdio.h>
void menu() {printf("***************************\n");printf("****   1.add   2.sub   ****\n");printf("****   3.mul   4.div   ****\n");printf("****   0.exit          ****\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 = 0;int y = 0;int ret = 0;do {menu();printf("请选择:");scanf("%d", &input);switch (input){case 1:printf("请输入两个数:");scanf("%d %d", &x, &y);ret = Add(x, y);printf("ret=%d\n", ret);break;case 2:printf("请输入两个数:");scanf("%d %d", &x, &y);ret = Sub(x, y);printf("ret=%d\n", ret);break;case 3:printf("请输入两个数:");scanf("%d %d", &x, &y);ret = Mul(x, y);printf("ret=%d\n", ret);break;case 4:printf("请输入两个数:");scanf("%d %d", &x, &y);ret = Div(x, y);printf("ret=%d\n", ret);break;case 0:printf("退出计算机\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;
}

函数指针数组实现:转移表

#include <stdio.h>
void menu() {printf("***************************\n");printf("****   1.add   2.sub   ****\n");printf("****   3.mul   4.div   ****\n");printf("****   0.exit          ****\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 = 0;int y = 0;int ret = 0;do {menu();printf("请选择:");scanf("%d", &input);int (*pfArr[])(int, int) = { NULL,Add,Sub,Mul,Div };//函数指针数组大小可以省略//                            0    1   2   3   4if (input == 0)printf("退出游戏\n");else if (input <= 4 && input >= 1) {printf("请输入两个数:\n");scanf("%d %d", &x, &y);ret = pfArr[input](x, y);//通过函数名(函数地址)调用函数printf("ret = %d\n", ret);}elseprintf("选择错误,请重新选择\n");} while (input);return 0;
}

后续增加运算时,只需要增加pfArr中的元素即可,其他main函数中的语句不变

约束:放入pfArr中的函数必须类型一样

7.指向函数指针数组的指针

不重要,拓展

#include <stdio.h>
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 = 0;int b = 0;int c = 0;int arr[] = { &a,&b,&c };int* (*p)[3] = &arr;//指向整型指针数组的指针//函数指针数组int (*pfArr[])(int,int) = {NULL,Add,Sub,Mul,Div};int (*(*p)[5])(int, int) = &pfArr;//指向函数指针数组的指针//p指针,指向的类型为int (* [5])(int, int),是一个函数指针数组return 0;
}

8.回调函数

回调函数就是一个通过函数指针调用的函数。
如果把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

使用switch语句实现一个计算器程序时太过冗余,把相同代码封装:

#include <stdio.h>
void menu() {printf("***************************\n");printf("****   1.add   2.sub   ****\n");printf("****   3.mul   4.div   ****\n");printf("****   0.exit          ****\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;
}
void calc(int (*pf)(int, int)) {int x = 0;int y = 0;int ret = 0;printf("请输入两个数:\n");scanf("%d %d", &x, &y);ret = pf(x, y);printf("ret = %d\n", ret);
}
int main() {int input = 0;do {menu();printf("请选择:");scanf("%d", &input);switch (input){case 1:calc(Add);break;case 2:calc(Sub);break;case 3:calc(Mul);break;case 4:calc(Div);break;case 0:printf("退出计算机\n");break;default:printf("选择错误,请重新选择\n");break;}} while (input);return 0;
}

当通过函数指针pf调用Add,Add就称为回调函数,通过回调函数,可以将函数calc变得更加通用

 8.1 回调函数的用例——qsort

qsort函数是一个库函数,底层使用的是快速排序的方式,对任意类型数据进行排序

这个函数可以直接使用

数据排序:冒泡排序、选择排序、插入排序、快速排序...(数据结构)

qsort函数:利用了函数指针实现回调函数的机制

 p1指向的元素>p2指向的元素,返回>0的数字

 p1指向的元素<p2指向的元素,返回<0的数字

 p1指向的元素==p2指向的元素,返回0

qsort函数的使用:

比较整型数组: 

#include <stdio.h>
#include <stdlib.h>
void Print(int arr[], int sz) {int i = 0;for (i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");
}
//由程序员编写
int cmp_int(const void* e1, const void* e2) {return *(int*)e1 - *(int*)e2;
//强转为int*类型才能解引用
}
void test1() {int arr[] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);Print(arr, sz);qsort(arr, sz,sizeof(arr[0]),cmp_int);Print(arr, sz);
}
int main() {test1();return 0;
}

 注意:

  1. 需要包含头文件stdlib.h
  2. void* 类型的指针-不能进行解引用,也不能进行+-整数的操作,void*是无具体类型的指针,用来存放任意类型的数据的地址,以保证qsort函数可以排序任意类型数据

比较结构体数据:

  1. 按照年龄
  2. 按照名字(字典序) 

按照年龄:

#include <stdio.h>
#include <stdlib.h>
struct Stu {char name[20];int age;
};
int cmp_stu_by_age(const void* e1, const void* e2) {return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}void test2() {struct Stu arr[] = { {"zhangsan",20},{"lisi",18},{"wangwu",30} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
int main() {test2();return 0;
}

按照名字:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Stu {char name[20];int age;
};
int cmp_stu_by_name(const void* e1, const void* e2) {return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}
void test3() {struct Stu arr[] = { {"zhangsan",20},{"lisi",18},{"wangwu",30} };int sz = sizeof(arr) / sizeof(arr[0]);qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
int main() {test3();return 0;
}

排序后: 

快速排序与冒泡排序相比,将比较方法抽离出来,使其能比较任意类型

冒泡排序:

#include <stdio.h>
//冒泡排序算法——给一组整型数据,使用冒泡排序,两两相邻元素比较,对数据进行升序
//n个元素,比较n-1趟,每趟排序比较n-1-i对
void Print(int arr[], int sz) {int i = 0;for(i = 0; i < sz; i++) {printf("%d ", arr[i]);}
}
void bubble_sort(int arr[], int sz) {//趟数int i = 0;for (int i = 0; i < sz - 1; i++) {int j = 0;//对数for (j = 0; j < sz - 1 - i; j++) {if (arr[j] > arr[j + 1]) {int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}}}
}
int main() {int arr[] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz);Print(arr, sz);return 0;
}

函数缺陷:只能排序整型数组 

使用冒泡排序模拟一个排序,可以排序任意类型数据:

使用冒泡排序模拟,对不同类型数据的排序来说,排序的趟数和每趟要比较的对数是不变的,只需要改变比较的方法

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//使用冒泡排序模拟任意类型的排序
void Print(int arr[], int sz) {int i = 0;for (i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");
}
struct Stu {
char name[20];
int age;
};
//固定函数
void swap(char* buf1, char* buf2, size_t size) {int i = 0;for (i = 0; i < size; i++) {char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}
//固定函数
//e1,e2是指针,存放一个要比较的元素
//e1指向的元素 > e2指向的元素,返回 > 0的数字
//e1指向的元素 < e2指向的元素,返回 < 0的数字
//e1指向的元素 == e2指向的元素,返回0
void bubble_sort(void* base, size_t sz,size_t size,int (*cmp)(const void* e1,const void* e2)) {//趟数int i = 0;for (int i = 0; i < sz - 1; i++) {int j = 0;//对数for (j = 0; j < sz - 1 - i; j++) {/*if (arr[j] > arr[j + 1]) {int tmp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = tmp;}*///不能直接用>比较//arr[j]和arr[j+1]的地址如何表示?//base是void*的指针,指向数组首元素,不能+-数字,可以转成char*类型后加跳过的字节数得到需要的地址 //char* + 1 = 1 * sizeof(char)//int* + 1 = 1 * sizeof(int)if (cmp((char*)base + j * size, (char*)base + (j + 1) * size)>0) {//交换:参数为两个元素的地址以及元素的大小,将每个元素的对应字节一一交换swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}
//使用该排序函数的人提供一个比较方法
int cmp_stu_by_name(const void* e1, const void* e2) {return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//注意:strcmp对应字节逐一比较,不能比较汉字,一个汉字占两个字节
}
int cmp_stu_by_age(const void* e1, const void* e2) {return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
int cmp_int(const void* e1, const void* e2) {return *(int*)e1 - *(int*)e2;
}
//需要排序的人编写
void test3() {struct Stu arr[] = { {"zhangsan",20},{"lisi",18},{"wangwu",30} };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
}
void test2() {struct Stu arr[] = { {"zhangsan",20},{"lisi",18},{"wangwu",30} };int sz = sizeof(arr) / sizeof(arr[0]);bubble_sort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
}
void test1() {int arr[] = { 9,8,7,6,5,4,3,2,1,0 };int sz = sizeof(arr) / sizeof(arr[0]);Print(arr, sz);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);Print(arr, sz);
}
int main() {test1();test2();test3();return 0;
}

bubble_sort()函数只能排序为升序且不能改变,如何排为降序?

#include <stdio.h>
//如何不改变bubble_sort函数的情况下将数组排为降序
void Print(int arr[], int sz) {int i = 0;for (i = 0; i < sz; i++) {printf("%d ", arr[i]);}printf("\n");
}
void swap(char* buf1, char* buf2, size_t size) {int i = 0;for (i = 0; i < size; i++) {char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}
void bubble_sort(void* base, size_t sz, size_t size, int (*cmp)(const void* e1, const void* e2)) {int i = 0;for (int i = 0; i < sz - 1; i++) {int j = 0;for (j = 0; j < sz - 1 - i; j++) {if (cmp((char*)base + j * size, (char*)base + (j + 1) * size) > 0) {swap((char*)base + j * size, (char*)base + (j + 1) * size, size);}}}
}
int cmp_int(const void* e1, const void* e2) {return *(int*)e2 - *(int*)e1;//后者比前者大时,交换两元素
}
void test1() {int arr[] = { 0,1,2,3,4,5,6,7,8,9};int sz = sizeof(arr) / sizeof(arr[0]);Print(arr, sz);bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);Print(arr, sz);
}
int main() {test1();return 0;
}

可以处理任意类型的编程——泛型编程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/77845.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

极狐GitLab 项目 API 的速率限制如何设置?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;关于中文参考文档和资料有&#xff1a; 极狐GitLab 中文文档极狐GitLab 中文论坛极狐GitLab 官网 项目 API 的速率限制 (BASIC SELF) 引入于 15.10 版本&#xff0c;功能标志为rate_limit_for_unauthenticated_projects_api_…

【xlog日志文件】怎么删除里面包含某些字符串的行(使用excel)

将log日志,复制到单独一行 B列&#xff08;可能一行很长&#xff0c;所以将整合后的放在A列&#xff09; 使用公式可以筛选出 包含某些字符串的行 为true&#xff0c;将这些行直接删除 IF(COUNT(FIND("MediaMuxterThreadRussia",B2,1))>0,"包含",&quo…

STM32提高篇: CAN通讯

STM32提高篇: CAN通讯 一.CAN通讯介绍1.物理层2.协议层二.STM32CAN外设1.CAN控制器的3种工作模式2.CAN控制器的3种测试模式3.功能框图三.CAN的寄存器介绍1.环回静默模式测试2.双击互发测试四.CAN的HAL代码解读一.CAN通讯介绍 CAN(Controller Area Network 控制器局域网,简称…

Java写数据结构:栈

1.概念&#xff1a; 一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈&#xff1a;栈的插…

单页面应用的特点,什么是路由,VueRouter的下载,安装和使用,路由的封装抽离,声明式导航的介绍和使用

文章目录 一.什么是单页面应用?二.什么是路由?生活中的路由和Vue中的路由 三.VueRouter(重点)0.引出1.介绍2.下载与使用(5个基本步骤2个核心步骤)2.1 五个基本步骤2.2 两个核心步骤 四.路由的封装抽离五.声明式导航1.导航链接特点一:能跳转特点二:能高亮 2.两个高亮类名2.1.区…

【C++】模板2.0

最近学习了一些模板的知识&#xff0c;速写本博客作为学习笔记&#xff0c;若有兴趣&#xff0c;欢迎垂阅读&#xff01; 1.非类型模板参数 模板参数分类类型形参与非类型形参。 类型形参即&#xff1a;出现在模板参数列表中&#xff0c;跟在class或者typename之类的参数类型名…

目标检测中的损失函数(二) | BIoU RIoU α-IoU

BIoU来自发表在2018年CVPR上的文章&#xff1a;《Improving Object Localization With Fitness NMS and Bounded IoU Loss》 论文针对现有目标检测方法只关注“足够好”的定位&#xff0c;而非“最优”的框&#xff0c;提出了一种考虑定位质量的NMS策略和BIoU loss。 这里不赘…

如何在 Amazon EC2 上部署 Java(Spring Boot 版)

让我们学习如何将 Java Spring Boot Web 服务器部署到 Amazon EC2。每月只需 3 美元。 使用 Azure&#xff0c;您可能不知道要花费多少钱。 Spring Boot 项目示例 在本教程中&#xff0c;我们将重点介绍如何将 Java Spring Boot 服务器部署到 Amazon EC2&#xff0c;因此我们不…

Git常用命令分类汇总

Git常用命令分类汇总 一、基础操作 初始化仓库git init添加文件到暂存区git add file_name # 添加单个文件 git add . # 添加所有修改提交更改git commit -m "提交描述"查看仓库状态git status二、分支管理 创建/切换分支git branch branch_name …

mysql——基础知识

关键字大小写不敏感 查看表结构中的 desc describe 描述 降序中的 desc descend 1. 数据库的操作 1. 创建数据库 create database 数据库名;为防止创建的数据库重复 CREATE DATABASE IF NOT EXISTS 数据库名;手动设置数据库采用的字符集 character set 字符集名;chars…

Redis 哨兵与集群脑裂问题详解及解决方案

Redis 哨兵与集群脑裂问题详解及解决方案 本文将深入探讨Redis在哨兵模式和集群模式下可能出现的脑裂问题&#xff0c;包括其发生场景、原因以及有效的解决策略。同时&#xff0c;我们还将提供相应的代码示例和配置方案来帮助读者理解和实施。 一、脑裂问题概述 脑裂&#x…

国内网络设备厂商名单(List of Domestic Network Equipment Manufacturers)

国内网络设备厂商名单 运维工程师必须广泛熟悉国内外各大厂商的设备&#xff0c;深入掌握其应用场景、功能特点及优势。这不仅有助于在故障排查时迅速定位问题&#xff0c;还能在系统设计、优化与升级中做出更合理的决策。对设备特性的精准把握&#xff0c;能够显著提升运维效…

2、SpringAI接入ChatGPT与微服务整合

2、SpringAI接入ChatGPT与微服务整合 小薛博客AI 大模型资料 1、SpringAI简介 https://spring.io/projects/spring-ai Spring AI是一个人工智能工程的应用框架。其目标是将Spring生态系统的设计原则&#xff08;如可移植性和模块化设计&#xff09;应用于人工智能领域&#…

基于ubuntu24.10安装NACOS2.5.1的简介

基于ubuntu24.10安装NACOS2.5.1的简介 官方网站地址&#xff1a; https://nacos.io 可访问nacos站点 https://nacos.io/zh-cn/ 2025年04月记录发布 V2.5.1 版本 一、环境预准备 64 bit JDK 1.8&#xff1b; sudo apt update sudo apt install openjdk-8-jdk sudo apt upda…

神经网络:从基础到应用,开启智能时代的大门

在当今数字化时代&#xff0c;神经网络已经成为人工智能领域最热门的技术之一。从语音识别到图像分类&#xff0c;从自然语言处理到自动驾驶&#xff0c;神经网络的应用无处不在。它不仅改变了我们的生活方式&#xff0c;还为各个行业带来了前所未有的变革。本文将带你深入了解…

[k8s实战]Containerd 1.7.2 离线安装与配置全指南(生产级优化)

[k8s实战]Containerd 1.7.2 离线安装与配置全指南&#xff08;生产级优化&#xff09; 摘要&#xff1a;本文详细讲解在无外网环境下部署 Containerd 1.7.2 容器运行时的完整流程&#xff0c;涵盖二进制包安装、私有镜像仓库配置、Systemd服务集成等关键步骤&#xff0c;并提供…

【CPU】结合RISC-V CPU架构回答中断系统的7个问题(个人草稿)

结合RISC-V CPU架构对中断系统七个关键问题的详细解析&#xff0c;按照由浅入深的结构进行说明&#xff1a; 一、中断请求机制&#xff08;问题①&#xff09; 硬件基础&#xff1a; RISC-V通过CLINT&#xff08;Core Local Interrupter&#xff09;和PLIC&#xff08;Platfor…

[密码学实战]国密算法面试题解析及应用

以下是密码学领域常见的面试题及其详细解析,涵盖基础理论、算法实现与应用场景,帮助系统化备战技术面试 一、基础概念类 1. 密码学的主要目标是什么? 答案: 确保数据的机密性(加密防止窃听)、完整性(哈希校验防篡改)、认证性(数字签名验证身份)和不可否认性(签名防…

Spring Boot 实现 Excel 导出功能(支持前端下载 + 文件流)

&#x1f9e0; 一、为什么用 EasyExcel&#xff1f; 在 Java 开发中&#xff0c;操作 Excel 的框架主要有&#xff1a; Apache POI&#xff08;经典但慢、内存占用大&#xff09; JXL&#xff08;老旧不维护&#xff09; Alibaba EasyExcel&#xff08;阿里出品&#xff0c;…

【论文速递】2025年06周 (Robotics/Embodied AI/LLM)

目录 SMOLLM2&#xff1a;当Smol变得大 - 以数据为中心的小语言模型英文摘要中文摘要 OmniHuman-1&#xff1a;重新考虑一阶段的人类动画模型的扩展英文摘要中文摘要 S1&#xff1a;简单的测试时间缩放英文摘要中文摘要 直接对齐算法间的差异日渐模糊英文摘要中文摘要 VideoJAM…