c语言指针进阶


前言

越学越多才会知道自己的无知,然后努力学习,请不要自以为是的沾沾自喜。


一、字符指针

字符指针不是存储一串字符串,而是存储首字符的地址。从而能够找到这一串字符。而且注意这是常量字符串,不能够被修改。

	const char* p = "abcdef";
	printf("%s\n", p);//abcdef

题目扩展

题目如下:

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");
	else
		printf("str1 and str2 are not same\n");

	if (str3 == str4)
		printf("str3 and str4 are same\n");
	else
		printf("str3 and str4 are not same\n");

	return 0;
}

求输出为多少?

结果
第一个输出不相等,第二个相等。
思路:
str1和str2都是char类型的数组变量,肯定独自分配空间,首地址肯定不同。
str3,str4都是常量字符串,而且字符串相同。由于常量字符串在内存中不能被改变,因此只存一份就ok了。所以str3和str4都指向同一个内存地址。因此str3==str4。
在这里插入图片描述

二、指针数组

存放指针的数组。是数组类型。
在这里插入图片描述
整形指针数组:
在这里插入图片描述
字符指针数组:
在这里插入图片描述

三、数组指针

存放数组地址的指针。是指针类型。
在这里插入图片描述

数组指针的表示方法:
把*p先()强调它一定是指针类型,然后再指向数组。
	int arr[10];
	//pa就是一个数组指针
	int (*pa)[10] = &arr;

题目扩展1

观察下面输出:
(1)
在这里插入图片描述
结果都相同。
观察下面输出:
(2)
在这里插入图片描述
你会发现结果并不相同。为什么呢?

(1)分析
arr是数组名,表示首元素的地址,它的类型是int *
&arr[0]就是首元素的地址,它的类型也是int *
&arr是整个数组的地址,它的类型是int [10]。

因此(1)中虽然结果相同,数组首元素的地址和数组的地址从值的角度来看是一样的,但是意义不一样。

(2)分析
arr+1,地址+4字节,跳过一个整型。
&arr[0],与上面同理。
&arr+1,经计算后发现跳过40个字节,正好是整个数组所占内存。更加说明了它的类型是int [10]。

数组指针的应用

void print2(int(*p)[4], 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("%d ", p[i][j]);
		}
		printf("\n");
	}
}

int main()
{
	int arr[3][4] = { {1,2,3,4}, {2,3,4,5} , {3,4,5,6} };
	//print1(arr, 3, 4);
	print2(arr, 3, 4);

	return 0;
}
int (*arr2)[10]:
arr2是一个数组指针,该指针指向一个数组,数组是10个元素每个元素是int类型。

int (*parr3[10])[5]:

在这里插入图片描述

在这里插入图片描述

四、函数指针

指向函数的指针。

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

int main()
{
	
	//int arr[10];
	//int (*pa)[10] = &arr;//数组指针的表示方法。函数指针类比推之。1):int (*pf)(int, int) = &Add;2):int (* pf)(int, int) = Add;
}
int (*pf)(int, int) = Add;
&函数名和函数名都是函数的地址
所以(1)=(2)。

pf 是一个存放函数地址的指针变量 -  函数指针

题目扩展

解读( * ( void ( * )( ) ) 0 )();

该代码是一次函数调用
调用0地址处的一个函数
首先代码中将0强制类型转换()为类型为void (*)()的函数指针
然后去调用0地址处的函数

解读void (* signal( int, void(*)(int) ) )(int);

该代码是一次函数的声明
声明的函数名字叫signal
signal函数的参数有2个,第一个是int类型,第二个是函数指针类型,该函数指针能够指向的那个函数的参数是int
返回类型是void
signal函数的返回类型是一个函数指针,该函数指针能够指向的那个函数的参数是int,返回类型是void

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

可以用typdef换一种写法:

	typedef void(*pf_t)(int) pf_t;
	pf_t signal(int, pf_t);

五、函数指针数组

在这里插入图片描述
函数指针数组的表示:

int my_strlen(const char* str)
{
	return 0;
}

int main()
{
	//指针数组
	char* ch[5];

	//pa是数组指针
	int (*pa)[10] = &arr;
	
	//pf是函数指针
	int (*pf)(const char*) = &my_strlen;

	//函数指针数组
	int (*pfA[5])(const char*) = { &my_strlen};
	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;
}

方法一:传统方法
利用switch-case来调用函数。不过如果每新增加一个函数功能,就会导致需要添加一个case。

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("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

方法二:优秀方法
用函数指针数组存放单个函数地址。在调用时直接利用函数数组下标就可以调用。若要新增一个函数功能,直接把数组扩大即可,比较便于维护。

//函数指针数组存放上述函数的地址
//转移表
int (*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div };

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input>=1 &&input<=4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pf[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);

	return 0;
}

指向函数指针数组的指针

int main()
{
	int arr[10];
	int (*pA)[10] = &arr;//指向数组的指针

	//函数指针数组
	int (* pf[5])(int, int);

	//ppf是指向函数指针数组的指针
	int (*(* ppf)[5])(int, int) = &pf;

	return 0;
}

ppf是指向pf一整个数组的地址哦!
在这里插入图片描述

六、回调函数

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

以下是上面所写过的代码,重复了很多内容,太过繁琐。
在这里插入图片描述
可以用回调函数对其简化。

void calc(int (*pf)(int, int))//这就是一个回调函数,参数是函数指针,
{							//通过调用此回调函数而达到调用其他函数的目的
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数:>");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%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;
}

七、排序任意类型数据

1.冒泡排序

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

不过此种排序比较有局限性,只能够实现整数的排序,不能够实现其他各种类型的排序。如:结构体,字符串等。

使用qsort()函数排序任意类型数据

要包含头文件:#include <stdlib.h>
温馨提示:想要知道qsort函数怎么使用,可以搜索cplusplus.com,在其界面找旧版点击进入,在里面直接搜索qsort。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
上面<0,就是说实现升序,>0实现降序。

qsort函数运用格式:

void qsort(void* base, //待排序的数组的起始地址
	      size_t num,    //元素个数
	      size_t width,  //一个元素的大小
	      int (*cmp)(const void* e1, const void* e2)//两个元素的比较函数
          );
void *
是无具体类型的指针。
好处:可接受任何类型的地址。什么地址都可以用此类型接收。
坏处:放进去的没办法使用。
用途:传未知类型地址时可以用void*接收。但使用时要强制类型转为需要的类型。

其中比较函数需要自己写。如:比较整形的函数。

实现一个比较整型的函数,实现升序排序。
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

完整代码:

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

实现一个比较整型的函数
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

使用qsort对数组进行排序,升序
void test1()
{

	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	库函数中一个排序函数:qsort
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	0 1 2 3 4 5 6 7 8 9
	打印
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

对结构体排序:

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

按照学生的年龄来排序
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
按照学生的姓名来排序
int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);//字符串比较函数
															//头文件#include <string.h>				  				  	 
}

void test2()
{
	struct Stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);//实现不同类型的比较。
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}

编程,实现排序任意类型数据的功能

改造冒泡排序函数,使得这个函数可以排序任意指定的数组
void* base:数组首元素地址。
size_t sz:数组长度。
size_t width: sizeof(arr[0]),一个元素所占字节。

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

改造冒泡排序函数,使得这个函数可以排序任意指定的数组

void bubble_sort(void* base, size_t sz, size_t width, int (*cmp)(const void* e1, const void* e2))
{
	趟数
	size_t i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		一趟冒泡排序的过程
		size_t j = 0;
		for (j = 0; j < sz - 1 - i; j++)
		{
			if (cmp((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
			{
				交换
				Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}

实现整数排序

实现一个比较整型的函数
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

void test3()
{
	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);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

使用我们自己写的bubble_sort函数排序结构体数组:

按照学生的年龄来排序
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}

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

void test4()
{
	struct Stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_age);
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}


总结

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值