指针详解(回调函数引入)

在初识指针后,开始了指针的进阶,在指针进阶的这部分,又接触了许多关于指针知识,指针真不愧是C语言的精髓所在


一、字符指针

在指针的类型中,已经知道有一种指针类型叫做字符指针char*

一般使用

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

此外,还有一种使用方法

int main()
{
	char* ps = "hello word";
    //”hello word“为常量字符串
	printf("%s\n", ps);
	return 0;
}

那  char* ps = "hello word",是指什么呢?

首先,ps属于字符指针,而在一个32位平台上,指针的大小只有四个字节,是不可能存放字符串的所有元素的地址,本质上就是ps内存放的是字符串中首元素的地址,并且该字符串不能够修改,最好用个const修饰下。

例题:该代码的输出结果                                                                                                                   

#include <stdio.h>
int main()
{
	const char* p1 = "abcd";
	const char* p2 = "abcd";
	if (p1 == p2)
		printf("smple\n");
	else
		printf("not smaple\n");
	return 0;
}

答案:sample 原因是"abcd"是一个常量字符串,不会再更改,所以机器在分配内存的时候,让p1与p2内存的是相同的地址

二、指针数组

所谓指针数组,就是存放指针的数组。

    int arr[5];//整型数组
	char str[5];//字符数组
	int* parr[5];//存放整形指针的数组 —— 指针数组
	char *pstr[5];//存放字符型指针的数组——指针数组

案例:可以用一个指针数组去维护多个数组

#include <stdio.h>
int main()
{
	int arr1[5] = { 1,2,3,4,5 };
	int arr2[5] = { 2,3,4,5,6 };
	int arr3[5] = { 3,4,5,6,7 };
	int* arr[] = { arr1,arr2,arr3 };

	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 5; j++)
			printf("%d ", *(arr[i] + j));
		printf("\n");
	}
	return 0;
}

三、数组指针

顾名思义,数组指针,就是能够指向数组的指针,那么又该如何的去定义一个数组指针呢

    int *pa[10];
	// '[' 与pa结合优先级比 '*'的要高,这样写只能代表pa表示一个数组,应当保证pa先于'*'结合
	int(*pa)[10];
	//pa与'*'结合,表示pa为一个指针,"int(* )[10]"表示一个指向数组的指针,
    //并且该数组的类型,存十个元素

数组指针在一维数组中没啥用,但它可以用来维护多维数组

以一个二维数组为例:已经知道,一维数组的数组名代表的是该数组的首个元素的地址,而二维数组的数组名代表的是该数组第一行所有的元素

样例(该样例目的为,按照行和列打印一个二维数组)

#include <stdio.h>
//未使用数组指针
void Printf1(int arr[][4], int m, int n)
//在一般情况下传二维数组的行可以省略,而列不可以省略
{
	for (int i = 0; i < m; i++)
	{
		for (int j = 0; j < n; j++)
			printf("%d ", arr[i][j]);
		printf("\n");
	}
}
//使用数组指针
void Printf2(int(*pa)[4], int m, int n)
{
	for (int i = 0; i < m; i++)
	{
		for (int j = 0; j < n; j++)
			printf("%d ", *(*(pa + i)+j));
		//pa+i表示第几行的地址,然后解引用,再加j表示该行第j个元素的地址
            //printf("%d ", pa[i][j]);
    //也可以选择用这种方式因为pa与数组名arr等价都是首元素地址
            //printf("%d ", *(pa[i] + j));
			//printf("%d ",(*(pa + i))[j]);
     //这几种暑促方法的结果均相同
		printf("\n");
	}
}
int main()
{
	int arr[4][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6},{4,5,6,7} };
	Printf1(arr, 4, 4);
	Printf2(arr, 4, 4);
	return 0;
}

了解:int  ( * pa [10] ) [5]    这里pa表示一个数组,内部储存了十个元素,每种元素的类型是数组指针,每个数组指针指向的数组内部储存了十个元素,每种元素的类型数组整型,pa即存储数组指针的数组。

四、函数指针

函数指针,即能够指向函数的指针

一个函数的函数名,代表了一个函数在内存中的位置

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%p\n", Add);
	printf("%p\n", &Add);
	return 0;
}

两种方法的输出结果相同,说明函数也可以用指针来表示,那么又该如何去定义一个函数指针类型,继续以加法函数为例。

#include <stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	//int 表示该函数的返回类型为整型,*padd 表示 padd是一个指针,
     (int,int)表示该函数有两个整型参数
	int (*padd)(int, int) = Add;
	//简单使用
	printf("%d\n", (*padd)(3, 5));
	printf("%d\n", padd(3, 5));
	return 0;
}

注意在定义函数指针时,int (*padd)(int,int),由于'*'的优先级低于'(',故应当保证padd与‘*’优先结合,否则padd与‘(’结合说明padd为一个函数

两个阴间代码所表述的意思

1.(*(void(*)())0)();

2.void(*signal(int, void(*)(int)))(int);

1.0前面的的一串唯一的函数指针类型,该指针指向一个参数为空,返回值为空的函数,在0前面表示将0强制转换为函数指针类型,然后对0进行解引用操作,调用该函数;

2.signal是一个函数名,它有两个参数,一个是整型,另外一个是函数指针,该函数指针的指向的返回类型为空,该函数的参数为整型,signal函数的返回类型是一个函数指针,该函数指针的指向的返回类型为空;

对于第二个阴间代码可以用typedef简化一下;

void(*signal(int, void(*)(int)))(int);
//可以对这个函数指针进行简化
typedef void(* )(int)  p_fun;
//这种写法是错误的
//正确写法
typedef void(*p_fun)(int);
//简化结果
p_fun signal(int,p_fun);

五、函数指针数组

函数指针数组即存放函数指针的数组,是一个数组,可以去存放一些参数类型相同,返回值类型相同的函数指针

int (*pa[4])(int,int)
//pa是内部可以存放四个函数指针,每个函数指针所指向的参数均为两个整形,返回值均为整型

应用案例(简易计算器)

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

void menu()
{
	printf("******************************\n");
	printf("*******1.+        2.- ********\n");
	printf("*******3.*        4./ ********\n");
	printf("*******     0.exit    ********\n");
	printf("******************************\n");
}

int main()
{
	int(*pa[4])(int, int) = { Add,Sub,Mul,Div };
	int n;
	do
	{
		menu();
		printf("请选择>");
        //选择计算类型
		scanf("%d", &n);
		if (n >= 1 && n <= 4)
		{
			int x, y;
			printf("请输入两个整数:");
         //需要计算的数
			scanf("%d%d", &x, &y);
			printf("%d\n", pa[n-1](x, y));
          //进行计算并输出结果
		}
		else if (n == 0)
			printf("退出\n");
		else
			printf("选择错误\n");
	} while (n);
	
	return 0;
}

(了解)指向函数指针数组的指针(持续套娃)

    //函数指针数组
    int(*parr[10])(int, int);
	//存放函数指针数组的指针
	int(*(*ppadd)[10])(int, int) = &parr;
	//padd表示一个指针,它所指向的地址是一个数组,该数组有十个元素,
	//每个元素的类型是函数指针,函数指针指向的函数有两个整型参数,返回类型为整型

六、回调函数

1.qsort函数的使用 

qsort可以对任何类型的数据进行排序,在使用qsort函数时需要引头文件stdlib.h;同时,qsort函数使用时有四个参数 数组名、需要排序的个数、数据类型所占的字节数、compare函数(需要自己定义)

_ACRTIMP void __cdecl qsort(
    _Inout_updates_bytes_(_NumOfElements * _SizeOfElements) void*  _Base,
    _In_                                                    size_t _NumOfElements,
    _In_                                                    size_t _SizeOfElements,
    _In_                _CoreCrtNonSecureSearchSortCompareFunction _CompareFunction
    );

案列,对整型数据进行排序

#include <stdio.h>
#include <stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
 
}
//此函数的作用是为了判断在使用qsort函数时,qsort函数对该函数的回调目的是为了判断所传地址指向
//的两个数的大小,如果第一个数大于给第二个数,返回小于零的数,相等返回0,小于返回小于零的数
//因此使用return *(int*)e1 - *(int*)e2可完美实现。
int main()
{
	int arr[] = { 12,121,34,2,33,45,87,91,21,12 };
	int sz = sizeof(arr) / sizeof(arr[0]);
//判断元素个数
	qsort(arr, sz, sizeof(int), cmp_int);
	return 0;
}

对于结构体类型也可使用

案例目的,根据年龄大小进行排序

#include <stdio.h>
#include <stdlib.h>

struct stu
{
	char namr[30];
	int age;
};

int cmp_age(const void* e1, const void* e2)
{
	return ((((struct stu*)e1)->age) - (((struct stu*)e2)->age));
//若需要对名字按照字典序进行排列,只需要将情形转化为结构体类型的指针指向name部分
}


int main()
{
	struct stu s[3] = { {"zhangsan",13},{"lisi",15},{"wangwu",12} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_age);
	return 0;
}

2.模拟实现该函数(排序方法:冒泡排序)

#include <stdio.h>

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

int cmp_int(const void* e1, const void*e2)
{
	return *((int*)e1) - *((int*)e2);
}

int cmp_age(const void* e1, const void* e2)
{
	return ((((struct stu*)e1)->age) - (((struct stu*)e2)->age));
}

void swap(char* buf1,char*buf2,int wide)
//由于不知道两个元素的类型,需要知道数组元素的宽度,每次只交换一个字节,交换宽度次
{
	for (int i = 0; i < wide; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

void bubble_sort(void* base, int sz, int wide,int(*cmp)(void* e1,void* e2))
//用void* 指针可以接受任何类型的数据,用一个函数指针接受不同类型数据的比较方法函数
{
	for (int i = 0; i < sz - 1; i++)
//判断趟数
	{
		for (int j = 0; j < sz - i - 1; j++)
//每个元素需要与其他元素比较的次数
		{
			
			if (cmp(((char*)base + j * wide), ((char*)base + (j + 1)*wide)) > 0)
//由于不知道所接受元素的类型,不能够直接对地址进行处理,所以将base强行转化为字符指针类型
//字符数据只占一个字节,又知道数组元素的宽度,通过这种方法,可以找到下一个元素,进行比较
			{
				swap(((char*)base + j * wide), ((char*)base + (j + 1)*wide),wide);
//如果满足条件,交换两个元素
			}
		}
	}
}

void test()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);

	bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
}

void test1()
{
	struct stu s[3] = { {"zhangsan",12},{"lisi",11},{"wangwu",13} };
	int sz = sizeof(s) / sizeof(s[0]);

	bubble_sort(s, sz, sizeof(s[0]), cmp_age);

}

int main()
{
	test1();

	test();
	
	return 0;
}

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

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值