C语言——指针(下)

引言

在前面的两篇文章中,我们一起探索了C语言指针的一些知识,今天我们将继续学习指针,相信在学习完这节内容后,我们会对指针有更加深刻的理解

函数指针变量

1.函数指针变量的创建

函数指针是一种指向函数的指针,其本质是一个指针变量,存储的值是函数的入口地址。

可能会有友友感到疑惑:函数也有地址?

我们可以写个代码来测试一下:

#include<stdio.h>

void test()
{
	printf("hello\n");
}

int main()
{
	printf("test:	%p\n", test);
	printf("&tets	%p\n", &test);
    return 0;
}

运行结果如下:

因此我们得知:函数确实是有地址的,函数名就是函数的地址,当然也能通过 &函数名 的方式获得函数的地址。

2.函数指针变量的使用

如果我们想要将函数的地址存放起来,就需要创建函数指针变量,函数指针变量的写法和数组指针十分相似。

先看看函数指针的一般形式:

return_type (*pointer_name)(parameter list);

其中:

return_type:函数返回值的类型

pointer_name:函数指针变量的名称

parameter list:函数参数列表,包括参数类型和参数名(参数名可以省略)

来看个具体的例子:假设我们有一个函数 int add(int x, int y),它接受两个整数参数并返回它们的和。我们可以创建一个指向这个函数的函数指针变量

代码如下:

#include<stdio.h>

int add(int x, int y) 
{  
    return x + y;  
}  
  
int main() 
{  
    int (*func_ptr)(int, int); // 创建函数指针变量  
    func_ptr = add;            // 将函数add的地址赋给函数指针变量  
                               // 或者直接初始化  
                               // int (*func_ptr)(int, int) = add;        
    int sum = func_ptr(2, 3);  // 通过函数指针调用函数  
    printf("The sum is: %d\n", sum);  
      
    return 0;  
}

阅读两段有趣的代码

在阅读这两段代码前,我们先来了解一下typedef

3.typedef

typedef 关键字在C语言中用于为已有的类型创建别名,使用 typedef 可以使代码更简洁、更易读。

举个例子:如果我们觉得 unsigned int 写起来十分不方便,那我们就可以使用 typedef 这个关键字

typedef unsigned int uint;
//将unsigned int重命名为uint

同样的,我们也可以对指针类型重命名,例如,把 int* 重命名为ptr_t,可以这样子写:

typedef int* ptr_t;

我们也能对数组指针和函数指针重命名,不过与以上两种略有区别

比如,我们有数组指针类型int(*)[5],需要重命名为 parr_t ,那可以这么写:

typedef int(*parr_t)[5];    //新的类型名需要放在*的右边

函数指针类型的重命名也是如此,如将void(*)(int)类型重命名为pfun_t,就可以这么写:

typedef void(*pfun_t)(int);    //新的类型名需要放在*的右边

4.两段有趣的代码

注:两段代码均出自《C陷阱和缺陷》

代码1:

(*(void(*)())0)();

要如何解释这段代码呢?

我们分析一下这段代码:

(*(void(*)())0)();
//void(*)()--函数指针类型
//(void(*)())--强制类型转换
//(void(*)())0--将0强制类型转换为void(*)()的函数指针类型
//这意味着我们假设0地址处放着无参,返回值为void的函数
//最终是调用0地址处放着的这个函数

我们从里往外拆分,最里面void(*)()是一个函数指针类型,它的返回类型是空,参数也为空,我们可以将其简化为pf 。

那么我们就可以把代码改写成这样:

(*(void (*)())0)();

// 定义一个指向不接受参数且返回void的函数的指针类型
typedef void(*pf)();
// 将整数0强制转换为pf类型的指针,并调用该地址处的函数
(*(pf)0)();

这段代码是先将0强制类型转换为函数指针类型,然后对其解引用

解引用之后相当于调用在0地址的函数,因为其参数为空所以只有一个单独的()。

代码2:

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

我们同样来分析一下这段代码

void (*signal(int, void(*)(int)))(int);
//signal 是一个函数
//这个函数接受两个参数:
//一个 int 类型的信号编号和一个 void(*)(int) 类型的函数指针
//这个函数返回一个 void(*)(int) 类型的函数指针

通过typedef进行化简:

// 定义一个新的类型别名 pfun_t,它是指向接受 int 参数并返回 void 的函数的指针  
typedef void (*pfun_t)(int);  
  
// 使用新的类型别名 pfun_t 来简化 signal 函数的声明  
pfun_t signal(int, pfun_t);

计算器的实现

如果我们想要实现计算器的部分功能,我们可以这样:

#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(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \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;
}

我们观察到:这个代码十分的冗杂,我们可以使用以下两种方法进行优化:

5.函数指针数组

函数指针数组是一个数组,其每个元素都是一个函数指针,即这个数组用于存放指向函数的指针

这些函数指针指向的函数具有相同的返回类型和参数列表,以确保类型匹配和正确使用。

那么函数指针数组要如何使用呢?

语法如下:

return_type (*pointer_name[num])(parameter list);

其中:

return_type 是函数返回的类型

pointer_name 是函数指针数组的名称

num 是数组中函数指针的数量

parameter list 是函数接受的参数列表

这表示 pointer_name 是一个数组,它包含 num 个元素,每个元素都是一个指向接受特定 parameter list 并返回 return_type 类型结果的函数的指针。

函数指针数组的一重要用途就是转移表

接下来我们来修改优化一下计算器的代码:

#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 }; 
	do
	{
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输入操作数:");
			scanf("%d %d", &x, &y);
			ret = (*p[input])(x, y);
			printf("ret = %d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出计算器\n");
		}
		else
		{
			printf("输⼊有误\n");
		}
	} while (input);
		return 0;
}

6.回调函数

回调函数是一种通过函数指针调用的函数。在编程中,我们通常把一个函数的指针(即地址)作为参数传递给另一个函数,当这个指针被用来调用它所指向的函数时,我们就称之为回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

简单来说就是通过函数来调用函数

计算器的优化,代码如下:

#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;
}
void calc(int(*pf)(int, int))
//用函数指针来接收函数地址
{
	int ret = 0;
	int x, y;
	printf("输入操作数:");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("ret = %d\n", ret);
}
int main()
{
	int input = 1;
	do
	{
		printf(" 1:add 2:sub \n");
		printf(" 3:mul 4:div \n");
		printf(" 0:exit \n");
		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函数

7.qsort的语法

qsort函数是C语言标准库中的一个函数,用于对数组进行快速排序。

其函数原型如下:

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

下面是对qsort函数参数的详细解释:

void *base:指向要排序的数组的第一个元素的指针。由于qsort函数用于对任何类型的数组进行排序,因此使用void指针作为参数,以便能够接受任何类型的数组。

size_t nitems:数组中元素的个数。

size_t size:数组中每个元素的大小(以字节为单位)。

int (*compar)(const void *, const void *):一个指向比较函数的指针,该函数用于确定数组中元素的排序顺序。比较函数应该接受两个指向要比较的元素的指针,并返回一个整数来指示它们的相对顺序。如果第一个元素小于第二个元素,则返回负数;如果两个元素相等,则返回零;如果第一个元素大于第二个元素,则返回正数。

8.qsort的使用

8.1 使用qsort排列整型数据

代码如下:

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

//void* 类型的指针是无具体类型的指针,这种类型的指针不能直接解引用,也不能进行整数的运算
//需要强制类型转换
int compare(const void* p1, const void* p2)
{
	return (*(int*)p1 - *(int*)p2);
	//(*(int*)p1 - *(int*)p2)>0 返回1
	//(*(int*)p1 - *(int*)p2)=0 返回0、
	//(*(int*)p1 - *(int*)p2)<0 返回-1
}
int main()
{
	int arr[10] = { 3,2,4,5,1,7,8,9,0,6 };
	int n = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, n, sizeof(int), compare);
	//arr:指向数组的地址
	//n:数组的大小
	//sizeof(int):数组元素的大小
	//compare:指向函数
	for (int i = 0; i < n; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
8.2 使用qsort排列结构数据

我们还能借助qsort实现排列结构数据

代码如下:

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

//结构体成员的间接访问
//使用方式:结构体指针->成员名
int cmp_stu_by_name(const void* p1, const void* p2)
{
	return strcmp(((struct Stu*)p1)->name, ((struct Stu*)p2)->name);
}

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

//结构体成员的直接访问是通过点操作符(.)访问的
//使用方式:结构体变量.成员名
int main()
{
	struct Stu arr[3] = { {"zhangsan",20},{"lisi",18},{"wangwu",16} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_name);
	printf("按照名字首字母排列:\n");
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
	qsort(arr, sz, sizeof(arr[0]), cmp_stu_by_age);
	printf("按照年龄排列:\n");
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
	return 0;
}
8.3 冒泡排序

冒泡排序是一种计算机科学领域的较简单的排序算法。它重复地走访过要排序的元素列,依次比较两个相邻的元素,如果它们的顺序错误就把它们交换过来。这个走访元素的工作会重复进行,直到没有相邻元素需要交换,也就是说该元素列已经排序完成。

冒泡排序的基本原理是两两比较待排序数据的大小,当两个数据的次序不满足顺序条件时即进行交换,反之则保持不变。这样每次最小(或最大)的结点就像气泡一样浮到序列的最前位置。因此,冒泡排序也因此得名。

代码的实现:

void bubble_sort(int arr[], int sz)
{
	for (int i = 0; i < sz - 1; i++)
	{
		int flag = 0;
		for (int j = 0; j < sz - i - 1; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				flag = 1;
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
		if (flag == 0)
		{
			break;
		}
	}
}

int main()
{
	int arr[10] = { 3,4,2,1,0,9,8,6,7,5 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}
8.4 qsort的模拟实现

使用回调函数,模拟实现qsort(使用冒泡排序的方式)

参数部分:

void bubble(void *base, int count , int size, int(*cmp )(void *, void *))

为了方便我们对不同的数据类型进行比较,我们可以用char*类型进行比较

像这样实现内容的交换:

void bubble_sort(void* arr,size_t sz,size_t width,int (*cmp)(const void*,const void*))
{
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - i - 1; j++)
		{
			if (cmp((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)
			{
				// 交换两个元素
				Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);
			}
		}
	}
}

我们也能用这种方式实现对结构体数据的排列

完整代码如下:

struct Stu {
	char name[10];
	int age;
};
int cmp_int(const void*p1,const void* p2)
{
	return *(int*)p1 - *(int*)p2;
}

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

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

void Swap(char* s1, char* s2, size_t width)
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *s1;
		*s1 = *s2;
		*s2 = tmp;
		s1++;
		s2++;
	}	
}
void bubble_sort(void* arr,size_t sz,size_t width,int (*cmp)(const void*,const void*))
{
	for (int i = 0; i < sz - 1; i++)
	{
		for (int j = 0; j < sz - i - 1; j++)
		{
			if (cmp((char*)arr + j * width, (char*)arr + (j + 1) * width) > 0)
			{
				// 交换两个元素
				Swap((char*)arr + j * width, (char*)arr + (j + 1) * width, width);
			}
		}
	}
}

int test1()
{
	int arr[10] = { 0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (int i = 0; i < sz; i++)
	{
		scanf("%d", &arr[i]);
	}
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

int test2()
{
	struct Stu arr[3] = { {"zhangsan",17},{"lisi",18},{"wangwu",15} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int_age);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
}

int test3()
{
	struct Stu arr[3] = { {"zhangsan",17},{"lisi",18},{"wangwu",15} };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_char_name);
	for (int i = 0; i < sz; i++)
	{
		printf("%s %d\n", arr[i].name, arr[i].age);
	}
}

int main()
{
	//对整型数据进行排列
	test1();

	//对结构体进行排列
	//年龄
	//test2();

	//对结构体进行排序
	//姓名首字母
	//test3();
	return 0;
}

结束语

花了一段时间,终于是将指针的内容简单写了一下。

希望看到的友友们能点赞收藏加关注!!!

十分感谢!!!

  • 27
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值