指针进阶详解(字符指针)(指针数组)(数组指针)(函数指针)(函数指针数组)(函数指针数组指针)(回调函数)(qsort函数)

字符指针

#include <stdio.h>

int main()
{

	char ch = 'a';
	char *p = &ch;

	char *p1 = "hello world!";
	const char *p1 = "hello world!";


	return 0;
}

字符指针就是指向一个char类型变量的指针。
通常我们会这样直接引用

char *p = “abcdef”;

这时的abcdef是一个字符串常量,是不可修改的值。p也仅仅是存放abcdef这个字符串的首元素的地址

在这里插入图片描述
当解引用时,仅仅是显示h,也就是这个字符串的首元素。

另外,因为指针指向的是一个常量字符串,所以我们会用const常量修饰一下,这样会更好。

下面看看一个例题:

	char str1[] = "I like study";
	char str2[] = "I like study";

	char *str3 = "I like study";
	char *str4 = "I like study";

	if (str1 == str2)
	{
		printf("str1地址与str2地址相同。\n");
	}
	else
	{
		printf("str1地址与str2地址不相同。\n");
	}

	if (str3 == str4)
	{
		printf("str3地址与str4地址相同。\n");
	}
	else
	{
		printf("str3地址与str4地址不相同。\n");
	}

在这里插入图片描述
由此可知,char数组创建时,会开辟两个不同的空间,所以地址会不同。

而char * 指向的字符串是一个常量,所以指针只是在指向它,所以只开辟了一次,不同的指针在指向它、

指针数组

指针数组:顾名思义就是一个存放指针的数组,这个数组中的元素都是一个个指针(也就是地址)。

int *arr[10];

上面这个就是一个叫arr的数组中存放了指向int 类型变量的指针,这个会和马上介绍的数组指

针混淆。

指针数组本质是一个数组,因为arr变量名先和 [ ] 结合arr[]说明这是一个数组,再看前面有一
个int 所以说明arr数组中存放的 int 类型的元素。

数组指针

数组指针:指针有指向字符元素的,整形元素的,而数组指针就是指向一个数组的指针,它存放的是一个数组的地址,是不是很神奇昂呀。

#include <stdio.h>

int main()
{
	int arr[10] = { 0 };
	int(*p)[10] = &arr;

	return 0;
}

int(*p)[10] ;

这就是数组指针的定义形式,是不是和int *p[10]很像,但是就是一对()就改变了他的性质,

因为()是先和结合,这就决定了(*p)是一个指针,去掉指针定义的部分剩下的就是int [10]这不

就是数组吗。所以他指向的就是包含10个int元素的数组,而前面的指针数组变量先和[]结

合,所以就是个数组,是不是很草率,就是结合性优先级的缘故,所以一个是指针,一个是

数组。

下面在介绍一下二维数组的真正本质

	//二维数组的深度解析
	int     (arr2[3])      [5];
	//二维数字的数组名为行指针
	//arr有三个元素 - 有三行
	//再往外看 - 每一行中又是一行一维数组 - 一维数组中有5个int类型的元素

我们要搞清楚无论什么数组,他的数组名字是这个数组的首元素的地址,一维数组的首元素是一个变量的,但是二维数组的首元素比较奇怪,他的首元素是第一行元素的地址,也就是一个行指针,他不是一个二级指针,他存放不是一级指针的地址,而是一个一维数组的(一行元素)地址,所以二级指针的传参可以用一个数组指针接收。

int main()
{
	void print(int(*p)[3], int r, int c);

	int arr[3][3] = { {1,2,3}, {2,3,4,}, {3,4,5} };
	print(arr, 3, 3);
	return 0;
}

void print(int(*p)[3], int r, int c)
{
	for (int i = 0; i < r; i++)
	{
		for (int j = 0; j < c; j++)
		{
			printf("%d ", *(*(p + i) + j));
		}
		printf("\n");
	}
}

大略的了解之后你看看你会不会下面的几道题目

#include <stdio.h>
int main()
{

	int  arr[5];            //一维数组
	int  *parr1[10];        //指针数组
	int  (*parr2) [10];     //数组指针
	int (* parr3[10] ) [10];//数组指针数组,一个存放数组指针的数组

	return 0;
}

如果你己经轻轻松松做对了上面的几个题,说明你已经初步入门了,要再接再厉哦。

下面总结一下函数传参时,一级指针和为二级指针的参数接收值应该是什么

一级指针:
1.普通变量的地址 (&a)
2.一级指针*p中的p
3.一维数组的数组名

二级指针:
1.int *arr[10]这种数组指针的数组名(也就是他的首元素的地址,一级指针的地址)
2.int**p这种二级指针

总结一下:

#include <stdio.h>

int main()
{
	//一维数组
	int arr1[10];
	int *p1 = arr1;        //存放的是一维数组的首元素,一个元素的地址
	int (*p2)[10] = &arr1; //存放的是一维数组的地址,p2是一个行指针
	//二维数组
	int arr2[2][3];
	int(*p3)[3] = arr2;    //存放的是二维数组的首元素,是一个一维数组的地址,是一个行指针
	int(*p4)[2][3] = &arr2;//存放的是二维数组的地址
	return 0;
}

函数指针

函数指针:指向一个函数的指针,存放函数的地址。

例如:

#include <stdio.h>

void function(char * str)
{
	printf("I like study!\n");
}

int main()
{
	void(*p1)(char*) = &function;
	void(*p2)(char*) = function;

	(*p1)("I like study");
	p2("I like study");

	return 0;
}

指向函数的类型名 (* p ) (指向函数的参数)
eg. int (*p)(int , int )
void (*p)(int , char)
.。。。。

强调:函数名应该是函数首元素的地址,但是函数哪来的首元素呀,所以函数名就是函数的地址,所以当把函数的地址传给指针的时候,可以 用&函数名 也可以直接用函数名当做函数的地址,另外,当把用指针调用函数的时候,可以**(p)(),也可以直接p()调用*,但是一般会用函数名当做函数的地址,用p当做函数名来用。

下面我们来看看两段代码,加深一下堆函数指针的理解

#include <stdio.h>

int main()
{


	(  *(void(*)())  0  )();
	//调用0地址处的函数,该函数无参数,返回值为void
	//(void (*)())0  是将0当做地址并强制类型转换成一个函数的地址,
	//*解引用调用这个函数(由上面介绍可知可以不用解引用,直接把函数名当做函数调用)
	//最外面的()是在调用这个函数



	void(*     signal(int,void(*)(int) )    )(int);
	//这是对signal函数的声明,
	//该函数的一个参数为int ,另一个参数为指向函数参数为int 返回值为void 的函数指针
	//signal函数的返回值为指向函数参数为int ,返回值为void 的函数指针


	return 0;
}

做完上面两道题目,是不是有一点心得啦,其实函数指针的难点就是无论是返回类型还是函数参数,他烦的地方都是最外面,这和我们平时的类型在前的代码风格不同,所以阅读起来会有一些障碍。
所我们可以用取别名的方式来更好的理解:

#include <stdio.h>

void function(int x, int y)
{

}

int main()
{
	typedef void(*pfun_t)(int, int);   //给void (*)(int , int);类型的函数指针起别名叫pfun_t

	typedef int(*pfun_c)(char*, char); //给int(*)(char*, char);类型的函数指针起别名叫pfun_c


	void(*p1)(int, int) = function;

	//void(*)(int, int) p = function;  这样写实错误的

	pfun_t p2 = function;          //上面两个完全等价

	return 0;
}

函数指针数组

函数指针数组:存放函数指针的数组

int (*pArr[10])(int, int) = {函数名1, 函数名2, 函数名3, 函数名4…}

这就是一个可以存放10个函数是指针的数组,而且函数指针都是指向参数为(int, int),返回类型为int的函数。
当我们写的多个函数都是接受一样的函数参数,返回一样的类型的时候,就可以考虑用一个函数指针数组来存放这些指针。
下面我来写一个案例:

简易计算器

#define _CRT_SECURE_NO_WARNINGS 1

#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(*pArr[5])(int, int) = { 0, Add, Sub, Mul, Div };//函数指针数组存放函数指针

	int input;
	do
	{
		menu();
		printf("请输入你要的操作:>");
		scanf("%d", &input);
		if (0 == input)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input >= 1 && input <= 5)
		{
			printf("请输入两个操作数:>");
			int num1, num2;
			scanf("%d%d", &num1, &num2);
			int ret = pArr[input](num1, num2); //函数指针数组中的函数指针在调用函数
			printf("ret = %d\n", ret);
		}
		else
		{
			printf("输入错误,请重新输入\n");
		}

	} while (input);

	return 0;
}

看完上面的栗子是不是感受到了函数指针数组的用途啦。

回调函数

回调函数:就是利用函数指针可以在一个函数体中使用另一个函数
还是那计算器举一个例子:

#define _CRT_SECURE_NO_WARNINGS 1
#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 (*p)(int, int ))//通过calc函数可以调用其他的函数,这就是回调函数
{
	printf("please input two numbers:>");
	int num1, num2;
	scanf("%d%d", &num1, &num2);
	int ret = p(num1, num2);
	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;
}

回调函数是不是在某些情况下,使用起来也很方便呀。

qsort函数

最后我们来介绍一下,同样利用了回调函数机制的qsort函数,而且这可函数可以排序任意的元素,这是一个C语言标准库里的函数
qsort函数要引用头文件
#include <stdlib.h>
在这里插入图片描述

先来看看这个函数参数和返回值

qsort(void * base, size_t num, size_t width, int (*compare)(const void *elem1, const void elem2));

下面我来一一介绍一下每个参数的含义:
第一个参数 void * base ,这是要接受一个函数的起始位置,也就是函数的地址,用void 的指针来接受函数的地址,是因为qsort函数为了可以排序任意的元素,所以用void的指针来接收,这就是用void *的好处,他可以接收任意类型的元素地址,但是这也是他最大的缺点,他在qsort函数内部无法识别出元素的类型。

第二函数参数num,就是数组中元素的个数,你必须把元素的个数告诉我,我才能给你排序几个元素。

第三个参数 width刚刚说了因为void *无法识别出元素类型,所以不需知道一个元素的字节数大小

第四个参数int(*compare)(const void *elem1, const void *elem2);这也是最重要的一个部分,这就用到了回调函数的机制,因为要排序任意的元素,但是不同元素比较方式可能不同,(整型元素是用int - int 比较,字符元素是用strcmp(char , char )比较 ),所以我们在调用这个qsort函数的同时,必须自己写一个比较函数

#define _CRT_SECURE_NO_WARNINGS 1

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

int cmp(const void *e1, const void *e2) //比较函数
{
	return (*(int *)e1 - *(int *)e2);   
	//因为e1,e2都是void *类型的参数,所以要强制类型转换成必要比较元素的类型
}


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

	qsort(arr, sz, sizeof(arr[0]), cmp);

	int i = 0;
	for ( i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	printf("\n");


	return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1

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


int cmp(const void *e1, const void *e2) //比较函数
{
	return (*(int *)e1 - *(int *)e2);   
	//因为e1,e2都是void *类型的参数,所以要强制类型转换成必要比较元素的类型
	//如果返回>0就交换,即元素升序,如果用e2 -e1 的话就是,后一个减前一个元素>0就交换,即降序
}



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


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


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

	qsort(arr, sz, sizeof(arr[0]), cmp);

	int i = 0;
	for ( i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}

	printf("\n");

	struct Stu sarr[] = { {"zhangsan", 19}, { "lisi", 18 }, { "wanwu", 20 } };
	int szs = sizeof(sarr) / sizeof(sarr[0]);

	qsort(sarr, szs, sizeof(sarr[0]), cmp_stu_by_name);

	qsort(sarr, szs, sizeof(sarr[0]), cmp_stu_by_age);

	return 0;
}



既然已经了解了qsort函数的原理,我们就可利用最简单的冒泡排序也实现一个可以排序任意类型元素的函数

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>


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

void swap_elem(char *e1, char *e2, int width)//传参时,将元素的字节大小也传过来,然后将元素的每个字节都交换,则元素就完成交换
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *(e1+i);
		*(e1+i) = *(e2 + i);
		*(e2+i) = tmp;
	}
}


void Bubble_sort(void *base, size_t sz, size_t width, int(*cmp)(const void *e1, const void *e2))
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			//arr[j] arr[j + 1]
			if (cmp((char *)base + j * width, (char *)base + (j + 1)*width) > 0)
				swap_elem((char *)base + j * width, (char *)base + (j + 1)*width, width);
		}
	}
}

int main()
{
	int arr[10] = { 2,1,9,5,3,6,7,8,4,0 };
	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]);
	}

	printf("\n");

	return 0;
}


写这个函数的年底按有两个:
第一是cmp比较函数接受的参数是要先将base用char强制类型转换,这样可以是指针的步长变成1,然后加jwidth,这样就跳过了一个待排序的元素的字节大小。

第二是swap_elem函数因为不知道元素的字节大小,所以就干脆一个字节一个字节换,知道把传过来的字节大小长度交换完毕。

以上就是指针进阶的全部内容了,希望可以对你有帮助。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hyzhang_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值