【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;
}
指针数组的用法:
- 模拟一个二维数组
- 管理多个字符串
#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;
}
注意:
- 需要包含头文件stdlib.h
- void* 类型的指针-不能进行解引用,也不能进行+-整数的操作,void*是无具体类型的指针,用来存放任意类型的数据的地址,以保证qsort函数可以排序任意类型数据
比较结构体数据:
- 按照年龄
- 按照名字(字典序)
按照年龄:
#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;
}
可以处理任意类型的编程——泛型编程
相关文章:
【c语言】指针进阶
目录 1.字符指针 2.指针数组 3.数组指针 3.1 数组指针的定义 3.2 数组指针的使用 4.数组参数,指针参数 4.1 一维数组传参 4.2 二维数组传参 4.3 一级指针传参 4.4 二级指针传参 5.函数指针 6.函数指针数组 6.1函数指针数组的定义 6.2 函数指针数组…...
使用FastAPI与OpenAI构建多模态分析API服务
引言 随着多模态AI模型的普及(如Qwen-Omni-Turbo),开发者可以轻松构建支持图像、音频、视频分析的API服务。本文将通过一个FastAPI示例,展示如何通过Base64编码传输媒体文件,并结合OpenAI API实现异步分析。这一方案适…...
集成学习实际案例
一、算法竞赛经典:Kaggle & 国际赛事 1. 泰坦尼克号生存预测(Random Forest) 场景:Kaggle 入门级经典赛题,基于乘客信息预测生存概率。方案: 基模型:决策树(CART)&…...
Linux421用户、组
参考...
树模型与集成学习(决策树核心算法:ID3/C4.5/CART、随机森林、GBDT/XGBoost)
树模型与集成学习 一、决策树 决策树核心算法:ID3/C4.5/CART ID3算法(基于信息增益) 核心原理 ID3(Iterative Dichotomiser 3)是最早的决策树算法之一,由Ross Quinlan于1975年提出。其核心思想是通过信…...
Netdata 监控多台服务器
一、多服务器监控方案选择 1. Netdata Cloud(官方推荐,免费) 特点:无需自建中心节点,通过 Netdata 官方云平台集中查看所有服务器。步骤: 在每台服务器上安装 Netdata(参考上一指南࿰…...
CTF web入门之SQL注入使用工具sqlmap
详细说明:https://blog.csdn.net/qq_41701460/article/details/146391515 web201: 查看数据库 获取不到数据库信息 https://9556eca3-d69a-40f4-b2a4-c89c2d2f8f12.challenge.ctf.show/api/?id1题目有提到 使用–user-agent 指定agent,因为对于 sqlm…...
spark–sql项目实验
数据读取与格式转换 读取JSON数据:使用Spark提供的读取接口(如 spark.read.json() ,在不同编程语言接口下使用方式类似)将给定的JSON格式数据读入Spark中,形成 DataFrame 。 格式转换:按照题目要求&…...
gnome中删除application中失效的图标
什么是Application 这一块的东西应该叫application,准确来说应该是applications。 正文 系统级:/usr/share/applications 用户级:~/.local/share/applications ying192 ~/.l/s/applications> ls | grep xampp xampp.desktoprm ~/.local…...
华为设备命令部分精简分类汇总示例
华为网络设备的命令体系庞大且复杂,不同设备系列(如交换机、路由器、防火墙)和不同操作系统版本(如VRP5、VRP8)的命令可能存在差异。以下是一个 精简分类汇总,涵盖常用配置场景和命令示例: 一、…...
Java 自动装箱与拆箱:基本数据类型与包装类的转换
在Java编程中,自动装箱(Autoboxing)和自动拆箱(Unboxing)是两个重要的概念。它们使得基本数据类型与其对应的包装类之间的转换更加方便,同时也提高了代码的可读性和可维护性。 什么是自动装箱和拆箱&#…...
论文阅读HARIVO: Harnessing Text-to-Image Models for Video Generation
h-space对比损失(DC)的设计细节 目标:确保视频的所有帧在语义上保持一致(例如,同一视频中的不同帧应描述相同的主体和场景,避免物体突变或语义漂移)。 1. h-space的定义 h-space 是U-Net最深…...
OpenCV基础函数学习4
【大纲笔记见附件pdf】 目录 一、基于OpenCV的形态学操作 二、基于OpenCV的直方图处理 三、基于OpenCV霍夫变换 四、基于OpenCV模板匹配 一、基于OpenCV的形态学操作 二、基于OpenCV的直方图处理 三、基于OpenCV霍夫变换 四、基于OpenCV模板匹配...
大数据系列 | 详解基于Zookeeper或ClickHouse Keeper的ClickHouse集群部署--完结
大数据系列 | 详解基于Zookeeper或ClickHouse Keeper的ClickHouse集群部署 1. ClickHouse与MySQL的区别2. 在群集的所有机器上安装ClickHouse服务端2.1. 在线安装clickhouse2.2. 离线安装clickhouse 3. ClickHouse Keeper/Zookeeper集群安装4. 在配置文件中设置集群配置5. 在每…...
【leetcode题解】算法练习
目录 分治-快排算法 颜色分类 移动零 排序数组 数组中的第K个最大元素 最小K个数 分治-归并排序 排序数组 交易逆序对的总数(困难) 计算右侧小于当前元素的个数(困难) 翻转对(困难) 字符串 最…...
大模型要被特定行业所用,从难到易有四种方式:重新训练或从头构建模型、微调模型、动态提示(如 RAG 技术)、简单提示工程
大模型在特定行业应用的四种方式详解 根据提供的信息,大模型要被特定行业所用,从难到易有四种方式:重新训练或从头构建模型、微调模型、动态提示(如 RAG 技术)、简单提示工程。以下是每种方式的详细解析及实际案例说明…...
[Python] 入门核心笔记
目录 一、Python简介重点 二、编程语言基础重点 三、Python安装重点 四、第一个Python程序重点 五、Python解释器重点 六、Python开发环境重点 一、Python简介重点 起源:1989年Gudio van Rossum开发,1991年诞生,名字源于电视剧《Monty Python…...
TensorFlow中使用Keras
目录 前言创建模型配置layers训练和评估配置模型训练评估和预测 前言 keras集成在tf.keras中。 创建模型 创建一个简单的模型,使用tf.keras.sequential。 model tf.keras.Sequential() # 创建一层有64个神经元的网络: model.add(layers.Dense(64, activationrelu)) # 添加…...
【Flask】Explore-Flask:早期 Flask 生态的实用指南
开源项目:explore-flask/README.rst at master rpicard/explore-flask (github.com) 一、Coding conventions Summary Try to follow the coding style conventions laid out in PEP 8. Try to document your app with docstrings as defined in PEP 257. def…...
Canvas入门教程!!【前端】
目录 canvas是什么?使用场景:canvas使用:引入:获取2D的上下文:坐标轴: 绘制:beginPath() :moveTo() :lineTo():stroke():fillRect() :strokeStyle 属性&#…...
通过规范化模型自训练增强医学图像分割中的无监督域自适应|文献速递-深度学习医疗AI最新文献
Title 题目 Enhancing source-free domain adaptation in Medical Image Segmentationvia regulated model self-training 通过规范化模型自训练增强医学图像分割中的无监督域自适应 01 文献速递介绍 深度卷积神经网络对训练数据分布(源域)和测试数…...
Linux常见指令介绍中(入门级)
1. man 在Linux中,man命令是用于查看命令手册页的工具,它可以帮助用户了解各种命令、函数、系统调用等的详细使用方法和相关信息。 用法:在终端中输入man加上要查询的命令或工具名称,例如man ls,就会显示ls命令的手册…...
一文详解卷积神经网络中的卷积层和池化层原理 !!
文章目录 前言 一、卷积核大小(Kernel Size) 1. 卷积核大小的作用 2. 常见的卷积核大小 3. 选择卷积核大小的原则 二、步长(Stride) 1. Stride的作用 三、填充(Padding) 1. 填充的作用 四、通道数ÿ…...
神经网络直接逆控制:神经网络与控制的结合入门级结合
目录 1. 前言 2. 什么是直接逆控制? 2.1 直接逆控制的优点 2.2 直接逆控制的局限性 3. 直接逆控制的实现步骤 3.1 数据准备 3.2 神经网络设计 3.3 训练神经网络 3.4 控制实现 4. 使用 PyTorch 实现直接逆控制 4.1 问题描述 4.2 数据生成 4.3 神经网络设…...
使用tabs组件搭建UI框架
本节任务 使用tabs组件搭建ui框架 包含页签:首页、动态、发布,会员购、我的。 涉及内容: Tabs、TabContent组件Builder装饰器属性模型封装,包括:接口、枚举、常量 界面原型 1 Tabs布局 在MainPage(如果…...
jmeter跟踪重定向和自动重定向有什么区别?
在 JMeter 中,跟踪重定向和自动重定向有以下区别: 概念 跟踪重定向:指的是 JMeter 会按照服务器返回的重定向信息,继续发送请求到重定向的目标地址,并记录下整个重定向的过程,包括重定向的地址、响应信息…...
unity3d实现物体闪烁
unity3d实现物体闪烁,代码如下: using UnityEngine;public class Test : MonoBehaviour {//创建一个常量,用来接收时间的变化值private float shake;//通过控制物体的MeshRenderer组件的开关来实现物体闪烁的效果private MeshRenderer BoxColliderClick…...
(三十)安卓开发中的MVP模式详解
在安卓开发中,MVP(Model-View-Presenter) 是一种常见的软件架构模式,它通过将应用程序的逻辑与用户界面分离,使得代码更加模块化、易于维护和测试。本文将详细讲解MVP模式的组成部分、工作流程、优点,并结合…...
独立ADC和MCU中ADC模块的区别
以图中两种方案为例: 使用独立ADC和使用MCU的内部ADC来实现模数转换,有什么性能、技术上的区别吗? 集成和独立芯片各有优劣势: 1、集成的节约了板子空间,减少了外围设计。工艺也不一样,集成的工艺相对高一…...
微软Entra新安全功能引发大规模账户锁定事件
误报触发大规模锁定 多家机构的Windows管理员报告称,微软Entra ID新推出的"MACE"(泄露凭证检测应用)功能在部署过程中产生大量误报,导致用户账户被大规模锁定。这些警报和锁定始于昨夜,部分管理员认为属于误…...
Ray Tracing(光线追踪)与 Ray Casting(光线投射)
Ray Casting(光线投射) 定义:一种从观察点(如摄像机)向场景中每个像素投射单条光线,找到最近可见物体的渲染技术。 核心任务:确定像素对应的物体表面颜色,通常仅计算直接光照&#…...
Shell脚本-变量的分类
在Shell脚本编程中,变量是存储数据的基本单位。它们可以用来保存字符串、数字甚至是命令的输出结果。正确地定义和使用变量能够极大地提高脚本的灵活性与可维护性。本文将详细介绍Shell脚本中变量的不同分类及其应用场景,帮助你编写更高效、简洁的Shell脚…...
go for 闭环问题【踩坑记录】
Go 中的for 循环闭包问题,是每个 Go 程序员几乎都踩过的坑,也是面试和实际开发中非常容易出错和引起 bug 的地方。这里我会通过原理、示例、修正方法、背后机制等角度详细为你讲解。 一、问题描述 当你在 for 循环里写匿名函数(闭包…...
【分布式理论17】分布式调度3:分布式架构-从中央式调度到共享状态调度
文章目录 一、中央式调度器1. 核心思想2. 工作流程3. 优缺点4. **典型案例:Google Borg** 二、两级调度器1. **核心思想**2. **工作流程**3. 优缺点4. **典型案例:Hadoop YARN** 三、共享状态调度器1. **核心思想**2. **工作流程**3. 优缺点4. **典型案例…...
Java高频面试之并发编程-04
hello啊,各位观众姥爷们!!!本baby今天来报道了!哈哈哈哈哈嗝🐶 面试官:调用 start()方法时会执行 run()方法,那为什么不直接调用 run()方法? 多线程中调用 start() 方法…...
2025Java面试指南(附答案)
Java全家桶 Java基础 1. Java为什么被称为平台无关性语言? 2. 解释下什么是面向对象?面向对象和面向过程的区别 3. 面向对象的三大特性?分别解释下? 4. Java 中的参数传递时传值呢?还是传引用? 5. JD…...
springboot对接阿里云大模型
阿里云百炼文档地址: 百炼控制台 设置账号 首先跟着文档设置账号,新建一个api key 文档地址: 百炼控制台 对接会话API 你可以使用sdk来对接,但没有必要,因为所有接口对接都是http形式的,直接使用http库来对接就行了ÿ…...
理性决策与情绪偏差
“在愤怒中做决策,你会在懊悔中收拾残局。”—本杰明富兰克林 在情绪激动时,我们往往容易做出冲动的决定。但等情绪平复,回过头来看,常常会发现这些决定并不如我们当初所想的那样明智。诺贝尔经济学奖得主在其行为经济学研究中提…...
基于LLM的响应式流式处理实践:提升用户体验的关键技术
基于LLM的响应式流式处理实践:提升用户体验的关键技术 前言:当AI生成遇到用户等待焦虑 在人工智能应用井喷式发展的今天,大语言模型(LLM)的文本生成延迟问题始终是开发者需要直面的挑战。想象这样一个场景࿱…...
2025年渗透测试面试题总结-拷打题库09(题目+回答)
网络安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 2025年渗透测试面试题总结-拷打题库09 1. Linux系统加固降权思路 2. 系统后门检测工具 3. 绕过CDN获…...
批量替换多个 Word 文档中的指定图片
在 Word 文档中,我们可以插入各种各样的图片,比如插入 logo、插入设计图、施工图等等。在某些情况下,我们也会碰到需要将 Word 文档中某张图片替换成其它图片的场景,比如将旧的 Logo 替换成新的 Logo。当我们有大量的 Word 文档需…...
海外版高端Apple科技汽车共享投资理财系统
这一款PHP海外版高端Apple、科技汽车、共享投资理财系统phplaravel框架。...
【Unity iOS打包】报错解决记录
打包报错1: Invalid Bundle. The bundle at ProductName.app/Frameworks/UnityFramework.framework contains disallowed file Frameworks. (ID: 87a95518-52e2-4ce0-983d-aab8d8006f11) 解决: Target > UnityFramework > Build Settings > Bu…...
新能源汽车零部件功率级测试方案搭建研究
摘要:本文旨在针对新能源汽车核心零部件功率级测试需求,提出基于Python与PyVISA的自动化测试方案。通过集成主流设备(如Keysight 34980A、功率分析仪等),构建多协议兼容(CAN、RS485等)的测试平台…...
DeepSeek与WPS的动态数据可视化图表构建
摘要 在数据驱动决策的时代,动态数据可视化对于信息的高效传递与分析至关重要。本文聚焦于利用DeepSeek和WPS实现近百种动态数据可视化图表的技术应用,详细阐述其操作流程、技术原理及潜在价值。通过深入剖析这一技术组合的应用场景与实践意义࿰…...
XCTF-web(五)
Web_php_unserialize 当通过KaTeX parse error: Expected group after _ at position 42: …erialize,触发魔术方法_̲_wakeup和__destr…this->file)输出文件内容,若KaTeX parse error: Expected group after _ at position 17: …ile可控࿰…...
数字ic后端设计从入门到精通2(含fusion compiler, tcl教学)
上篇回顾 上一篇文章需要讨论了net,pin的基础用法,让我们来看一下高级一点的用法 instance current_instance current_instance 是 Synopsys 工具(如 Fusion Compiler 或 Design Compiler)中用于在设计层次结构中导航的关键命令。它允许用…...
Vue2集成ElementUI实现左侧菜单导航
文章目录 简介静态导航安装element-ui,vue-router,vuex编写router/index.jsmain.js中引入elementui,router编写左侧导航返回的菜单数据 动态导航编写router/index.js左侧菜单通过for循环生成通过for循环递归生成 store/index.jsmain.js中引入store登录页面代码菜单返回数据 总结…...
Flask API 项目 Swagger 版本打架不兼容
Flask API 项目 Swagger 版本打架不兼容 1. 问题背景 在使用 Flask 3.0.0 时遇到以下问题: 安装 flask_restful_swagger 时,它强制将 Flask 降级到 1.1.4,并导致其他依赖(如 flask-sqlalchemy、flask-apispec)出现版…...
spark和Hadoop的区别和联系
区别 计算模型 Hadoop:主要基于 MapReduce 计算模型,将任务分为 Map 和 Reduce 两个阶段,适合处理大规模的批处理数据,但在处理迭代式计算和交互式查询时性能相对较差。Spark:基于内存的分布式计算框架,采…...