指针的进阶(2)

函数指针

先看一段代码:

#include <stdio.h>
void test()
{
    
    
 printf("hehe\n");
}
int main()
{
    
    
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0;
}

结果:

0000000000401550
0000000000401550

我们可以看到函数名可以表示成地址,那么对于函数的地址,我们就可以用函数指针来存储。

void test()
{
    
    
 printf("hehe\n");
}
void (*pfun1)();

星号先与变量名结合,表示指针,后面加上小括号就表示指针指向的是函数的地址。

当函数里面有参数时,我们也需要跟着写进去。

void test(char* pc, int arr[10])
{
    
    
}

int main()
{
    
    
	void (*pf)(char *, int [10]) = test;

	return 0;
}

只需要写数据类型即可,可以不跟变量名。

下面看一下函数指针的用例:

int Add(int x, int y)
{
    
    
	return x + y;
}

int main()
{
    
    
	
	int (*pf)(int, int) = Add;

	int r = Add(3, 5);
	printf("%d\n", r);

	int m = (*pf)(4, 5);

	printf("%d\n", m);

	return 0;
}

8
9

这里我们可以直接调用函数的地址,对它进行解引用,还需要对其函数进行传参,就能实现一个函数的调用;这里*和pf需要用下括号括起来,**倘若没有下括号,那么pf会先与(4,5)先结合,**那么星号在这里就会变成无效的引用,程序将会报错。

函数指针数组

这是一个存放函数地址的数组;

int (*parr1[10])();
我们可以先写出一个函数指针int(*parr1)(),然后通过优先级将【】与parr1结合,就变成了函数指针数组。

下面我们用计算器这个例子,分别用switch和函数指针数组进行对比;

#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 = %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;
	}

这是用switch写出来的计算器,可以实现加减乘除的功能,但我们会发现,倘若我们要对该程序进行补充功能时,那么就得重复写case中的语句,当然我们可以直接粘贴复制,但我们看起来就会比较冗余,认为重复的地方出现过太多次了;

函数指针数组的方式:

#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 = %d\n", ret);
        }    
    }
 return 0;
}

我们先在主函数创建一个函数指针数组int(*p[5])(int x, int y) = { 0, add, sub, mul, div };调用时,只需要对指针p解引用,输入正确的下标和参数,就能实现不必要的冗余

指向函数指针数组的指针

void test(const char* str)
{
    
    
 printf("%s\n", str);
}
int main()
{
    
    
 //函数指针pfun
 void (*pfun)(const char*) = test;
 //函数指针的数组pfunArr
 void (*pfunArr[5])(const char* str);
 pfunArr[0] = test;
 //指向函数指针数组pfunArr的指针ppfunArr
 void (*(*ppfunArr)[5])(const char*) = &pfunArr;
 return 0;
}

对于这么长的描述,我们可以从函数指针下手,然后拓展到函数指针数组,最后用指针指向函数指针数组即可,我们在写的时候要注意,各符号的优先级当有[]和星号时,如果没有小括号将变量名和星号括起来,那么它最终就是一个数组,且星号放在变量名前面,[]放在变量名后面,不能随意乱放。

总的来说,对于数组和指针,我们可以组成很长数组指针数组,对于这种写法,我们重点在于要学会看懂怎么读,懂得各符号的优先级,明白哪个与哪个相结合,拆开分解后就一目了然了。

回调函数

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

qsort函数各类的使用

void qsort (void* base, size_t num, size_t size,
int (compar)(const void,const void*))

这是一个快排函数,我们可以看到,它的参数包含函数指针,当我们调用这个函数时,必须对函数指针指向的函数进行调用完,该函数才得以实现,表明qsort函数是一个回调函数。下面我们来看看qsort函数各种类型数据的使用。

整型类型数据:

#include <stdio.h>
int int_cmp(const void * p1, const void * p2)
{
    
    
    return (*( int *)p1 - *(int *) p2);
}
void test1()
{
    
    
    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");
}
int main()
{
    
    
    test1();

 return 0;
}

结果:0 1 2 3 4 5 6 7 8 9

在这里,对于qsort函数的最后一个参数,需要我们自己创建一个符合条件的函数,使它对应的返回值能够判断不同元素的大小即可。

浮点类型的:

int float_cmp(const void* p1,const void* p2)
{
    
    
    if((*(float*)p1-*(float*)p2)>0.000000)
    {
    
    
        return 1;
    }
    else if((*(float*)p1-*(float*)p2)<0.000000)
    {
    
    
        return -1;
    }
    else
    {
    
    
        return 0;
    }
}
void test2()
{
    
    
    float arr[]={
    
    3.14,2.56,6,78,5.32,4.02};
    int i=0;
    qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(float),float_cmp);
    
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
    
    
    printf( "%.2f ", arr[i]);
    }
    printf("\n");
    
}
int main()
{
    
    
   // test1();
    test2();

 return 0;
}

2.56 3.14 4.02 5.32 6.00 78.00

在这里,对于最后一个参数,浮点数由于具有精度,函数类型为整型,所以需要与具有精度的0进行判断,所以需要加上条件判断语句。倘若使用第一种方法的强制换类型,那么将会丢失精度。

字符串:

//字符串的大小
int str_cmp_size(const void* p1,const void* p2)
{
    
    
    return strcmp(*(char**)p1,*(char**)p2);
}
//字符串的长度
int str_cmp_len(const void* p1,const void* p2)
{
    
    
    return strlen(*(char**)p1)-strlen(*(char**)p2);
}
void test3()
{
    
    
    char* arr[]={
    
    "abc","VPN","RTx","Rtx","rtxbbb","abbb"};
    int i=0;
    qsort(arr,sizeof(arr)/sizeof(arr[0]),sizeof(arr[0]),str_cmp_len);
    for (i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
    
    
    printf( "%s ", arr[i]);
    }
    printf("\n");
}
int main()
{
    
    
   // test1();
    //test2();
    test3();

 return 0;
}

大小:RTx Rtx VPN abbb abc rtxbbb
长度:VPN RTx Rtx abc abbb rtxbbb

在这里,为何不直接调用(char*)p1,而要用*(char**),这是因为如果用(char*)的话,对于形参p1来说,存的是arr数组名的地址,strcmp()会将p地址对应的内容转换为字符串,这就不符合我们的想法,所以应该是将数组中对应的字符串的地址传过去,再对其解引用,这样才能得到真正的字符串。

struct Stu
{
    
    
	char name[20];
	int age;
};

int cmp_stu_by_age(const void* p1, const void* p2)
{
    
    
	return ((struct Stu*)p1)->age - ((struct Stu*)p2)->age;
}

void test4()
{
    
    
	struct Stu arr[] = {
    
     {
    
    "zhangsan", 20}, {
    
    "lisi", 50},{
    
    "wangwu", 15} };
	int sz = sizeof(arr) / sizeof(arr[0]); 
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
    for (int i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
    
    
    printf( "%d ", arr[i].age);
    }
    printf("\n");
}

int cmp_stu_by_name(const void* p1, const void* p2)
{
    
    
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

void test5()
{
    
    
	struct Stu arr[] = {
    
     {
    
    "zhangsan", 20}, {
    
    "lisi", 50},{
    
    "wangwu",15}};
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
    for (int i = 0; i< sizeof(arr) / sizeof(arr[0]); i++)
    {
    
    
    printf( "%s ", arr[i].name);
    }
    printf("\n");
}
int main()
{
    
    
   // test1();
    //test2();
    //test3();
    test4();
    test5();

 return 0;
}

15 20 50
lisi wangwu zhangsan

对于结构体的使用,要用结构体指针,指向对应的比较变量,就能得到最终答案。

模拟实现sqort函数

在这里,我们用的是冒泡排序实现sqort函数的思想。

void bubble_sort(int arr[], int sz)
	{
    
    
		int i = 0;
		//趟数
		for (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;
				}
			}
		}
	}

这是一个整型类型的冒泡排序,当我们要用到浮点型,结构体类型时,就无法使用该函数,所以,我们根据sqort函数的参数进行模拟实现。
我们可以对上面函数进行改装,首先不能改变冒泡排序的思想,也就是两层循环不变,而我们要改变的是,对于不同类型的比较方式,还有不同类型是怎么交换的。
对于不同类型的比较,我们用到sqort的第四个参数,我们要解决的是怎么传参?我们要清楚,我们传的是每个元素的地址,不同类型意味着它们每个元素所占字节不一样,那么我们可以利用元素的大小乘上对应的j,就能实现元素地址的传递了

(cmp((char*)base+jsize, (char)base+(j+1)size)>0)
我们用char
强制类型转换,因为char*指针移动时,刚好是一个字节一个字节过去的;

对于转换,我们已经知道每个不同类型元素的地址了,我们只需要交换它们的地址即可,对于char*类型的,我们可以一个字节一个字节循环转换;那么就写一个交换函数来进行实现

void Swap(char* buf1, char* buf2, int size)//交换arr[j],arr[j+1]这两个元素
{
    
    
	int i = 0;
	char tmp = 0;
	for (i = 0; i < size; i++)
	{
    
    
		tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

原代码:

void Swap(char* buf1, char* buf2, int size)
{
    
    
	int i = 0;
	char tmp = 0;
	for (i = 0; i < size; i++)
	{
    
    
		tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base, int num, int size, int (*cmp)(const void*, const void*))
{
    
    
	int  i = 0;
	for (i = 0; i < num - 1; i++)
	{
    
    
		int j = 0;
		for (j = 0; j < num - 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);
			}
		}
	}
}

这就是利用冒泡排序实现sqort函数的思想,对于如何使用,使用方法和上面的sqort函数是一模一样的,这里就不在过多展示。

猜你喜欢

转载自blog.csdn.net/m0_74068921/article/details/131597113