C 语 言 - - - 函 数 指 针、函 数 指 针 数 组 和 回 调 函 数

C 语 言 - - - 函 数 指 针、函 数 指 针 数 组 和 回 调 函 数

💻作 者 简 介:曾 与 你 一 样 迷 茫,现 以 经 验 助 你 入 门 C 语 言
💡个 人 主 页:@笑口常开xpr 的 个 人 主 页
📚系 列 专 栏:C 启新程
✨代 码 趣 语:好 的 代 码 会 促 生 好 的 代 码,糟 糕 的 代 码 也 会 促 生 糟 糕 的 代 码。
💪代 码 千 行,始 于 坚 持,每 日 敲 码,进 阶 编 程 之 路。
📦gitee 链 接:gitee

在这里插入图片描述

         在 编 程 的 世 界 里,每 一 行 代 码 都 可 能 隐 藏 着 无 限 的 可 能 性 。你 是 否 想 过,一 个 小 小 的 程 序 究 竟 能 改 变 什 么?它 可 以 是 解 决 复 杂 问 题 的 工 具 ,也 可 以 是 实 现 梦 想 的 桥 梁。今 天,就 让 我 们 一 起 走 进 C 语 言 指 针 的 世 界,探 索 它 的 无 限 潜 力。

函 数 指 针

定 义

         函 数 指 针 是 指 向 函 数 的 指 针 变 量。它 跟 普 通 指 针 有 所 不 同,普 通 指 针 指 向 的 是 数 据,而 函 数 指 针 指 向 的 是 函 数 的 代 码,函 数 指 针 存 储 的 是 函 数 的 地 址。

格 式

return_type (*pointer_name)(parameter_list);

return_type - - - 函 数 的 返 回 类 型

pointer_name - - - 函 数 指 针 的 名 称

parameter_list - - - 函 数 的 参 数 列 表

&函 数 名 和 函 数 名 的 地 址

下 面 展 示代 码 示 例

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

在这里插入图片描述

总 结

         函 数 名 和 & 函 数 名 可 以 互 换,它 们 的 地 址 是 相 同 的。 但 是 &Add 能 更 清 晰 地 表 明 是 在 获 取 函 数 地 址。

使 用 函 数 指 针 来 输 出 两 数 之 和

温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。

下 面 展 示代 码 示 例

#include<stdio.h>
int Add(int x, int y)
{
	return x + y;
}
int main()
{
	int (*pf)(int, int) = &Add;
	//pf存放函数地址的指针变量
	//&函数名和函数名都是函数的地址
	int ret = (*pf)(2, 3);
	printf("%d\n", ret);
	return 0;
}

在这里插入图片描述

补 充

         int (*pf)(int, int) = &Add;这 句 代 码 中 pf 是 存 放 函 数 地 址 的 指 针 变 量,也 是 函 数 指 针 的 变 量 名。因 为 函 数 名 和 & 函 数 名 的 地 址 是 相 同 的,所 以 这 句 代 码 也 可 以 写 成 int (*pf)(int, int) = Add;

函 数 指 针 数 组

定 义

         在 C 语 言 里,函 数 指 针 数 组 是 一 种 特 殊 的 数 组,其 每 个 元 素 均 为 函 数 指 针。借 助 函 数 指 针 数 组,能 够 依 据 不 同 的 条 件 或 者 索 引,便 捷 地 调 用 不 同 的 函 数。

格 式

return_type (*array_name[size])(parameter_list);

return_type - - - 函 数 的 返 回 类 型

array_name - - - 数 组 的 名 称

size - - - 数 组 的 大 小

parameter_list - - - 函 数 的 参 数 列 表

使 用 函 数 指 针 数 组 来 输 出 两 数 的 加 减 乘 除

温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。

下 面 展 示代 码 示 例

#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 x, int y)
{
	return x / y;
}
int main() 
{
    //声明一个函数指针数组
    int (*arr[4])(int, int) = { add, sub, mul, Div };
    //通过函数指针数组调用函数
    int result1 = arr[0](3, 4);
    int result2 = arr[1](3, 4);
    int result3 = arr[2](3, 4);
    int result4 = arr[3](3, 4);
    printf("3 + 4 = %d\n", result1);
    printf("3 - 4 = %d\n", result2);
    printf("3 * 4 = %d\n", result3);
    printf("3 / 4 = %d\n", result4);
    return 0;
}

在这里插入图片描述

代 码 解 释

下 面 两 段 代 码 来 自《 C 陷 阱 和 缺 陷 》这 本 书 中 的 第 15 页 和 第 19 页。

下 面 展 示第 一 段 代 码

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

温 馨 提 示:读 者 们 ,先 自 己 分 析 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。

代 码 解 析

(void (*)())

         这 是 一 个 强 制 类 型 转 换 操 作 符。void ( * )( ) 定 义 了 一 个 函 数 指 针 类 型,该 类 型 的 函 数 指 针 指 向 的 函 数 没 有 返 回 值(void),并 且 没 有 参 数( 空 括 号 表 示 无 参 数)。所 以 (void (*)()) 表 示 把 一 个 值 强 制 转 换 为 这 种 函 数 指 针 类 型。

(void (*)())0

         这 里 将 整 数 0 强 制 转 换 为 void ( * )( ) 类 型 的 函 数 指 针。因 为 函 数 指 针 存 储 一 个 函 数 的 地 址,把 内 存 地 址 0 当 作 一 个 函 数 的 起 始 地 址,认 为 在 这 个 地 址 处 存 放 着 一 个 没 有 返 回 值 且 无 参 数 的 函 数。

*( void ( * )( ) )0

         对 前 面 得 到 的 函 数 指 针 进 行 解 引 用 操 作。解 引 用 操 作 会 获 取 该 指 针 所 指 向 的 函 数。

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

         最 后 加 上 括 号 ( ),表 示 调 用 这 个 解 引 用 得 到 的 函 数。也 就 是 尝 试 调 用 位 于 内 存 地 址 0 处 的 函 数。

代 码 整 体 含 义

         ( * ( void ( * )( ) ) 0 )( ); 这 段 代 码 的 作 用 是 尝 试 调 用 内 存 地 址 为 0 处 的 函 数。不 过,在 大 多 数 操 作 系 统 里,内 存 地 址 0 是 受 保 护 的 区 域,程 序 一 般 没 有 权 限 访 问 这 个 区 域。所 以 执 行 这 段 代 码 通 常 会 引 发 段 错 误。

下 面 展 示第 二 段 代 码

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

温 馨 提 示:读 者 们 ,先 自 己 分 析 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。

代 码 解 析

void( * )( int )

         这 是 一 个 函 数 指 针 类 型。该 函 数 指 针 指 向 的 函 数 接 受 一 个 int 类型 的 参 数,并 且 没 有 返 回 值(void)。

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

         signal 是 函 数 名。signal 函 数 有 两 个 参 数,第 一 个 参 数 是 int 类型,第二 个 参 数 是 void( * )( int )。表 示 signal 函 数 接 收 一 个 整 数 和 一 个 函 数 指 针 作 为 参 数。

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

         signal 函 数 的 返 回 类 型 是 一 个 函 数 指 针,该 函 数 指 针 能 够 指 向 的 那 个 函 数 的 参 数 是 int,返 回 类 型 是 void。

代 码 缩 写

下 面 展 示代 码 缩 写

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

回 调 函 数 - - - qsort

在这里插入图片描述

定 义

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

qsort 的 函 数 原 型

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

qsort 函 数 包 含 在 stdlib.h 头 文 件 中。

base - - - 指 向 待 排 序 数 组 起 始 位 置 的 指 针,由 于 使 用 void * 类 型,所 以 它 能 处 理 任 意 类 型 的 数 据

num - - - 数 组 里 元 素 的 数 量

size - - - 每 个 元 素 的 大 小( 以 字 节 为 单 位 )。

compar - - - 回 调 函 数,用 于 比 较 两 个 元 素 的 大 小。该 函 数 接 收 两 个 const void * 类 型 的 指 针,分 别 指 向 要 比 较 的 两 个 元 素,返 回 一 个 整 数 值 来 表 示 它 们 的 大 小 关 系。

size_t

         size_t 是 一 种 无 符 号 整 数 类 型,这 意 味 着 它 只 能 表 示 非 负 整 数。因 为 数 组 元 素 的 数 量 和 元 素 的 大 小 不 可 能 是 负 数,所 以 使 用 无 符 号 类 型 是 合 理 的。

回 忆 冒 泡 函 数

温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。

下 面 展 示代码示例

#include<stdio.h>
void 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 temp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = temp;
			}
		}
	}
}
int main()
{
	int arr[] = { 4,3,2,7,5,8,6,1,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	sort(arr, sz);
	int i = 0;
	for (i = 0;i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

在这里插入图片描述

使 用 qsort 函 数 升 序

温 馨 提 示:读 者 们 ,先 自 己 写 代 码,这 是 提 升 编 程 能 力 的 好 机 会。若 未 达 要 求 ,别 气 馁 ,参 考 下 文 解 释 会 有 新 收 获。

下 面 展 示代 码 示 例

#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;//升序
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0;i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

在这里插入图片描述

代 码 解 析

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

参 数 意 义

arr - - - 指 向 待 排 序 数 组 起 始 位 置 的 指 针

sz - - - 数 组 中 元 素 的 数 量

sizeof(arr[0]) - - - 每 个 元 素 的 大 小(以 字 节 为 单 位)。

cmp_int - - - 比 较 函 数 的 指 针,用 于 定 义 元 素 之 间 的 比 较 规 则

qsort 函 数 内 部 执 行 流 程

选 择 基 准 元 素

         qsort 函 数 采 用 快 速 排 序 算 法,快 速 排 序 的 第 一 步 是 选 择 一 个 基 准 元 素 。这 个 基 准 元 素 可 以 是 数 组 中 的 任 意 一 个 元 素,常 见 的 选 择 方 式 有 选 择 第 一 个 元 素、最 后 一 个 元 素 或 者 中 间 元 素 等。

分 区 操 作

         以 选 择 的 基 准 元 素 为 标 准,将 数 组 分 为 两 部 分:一 部 分 是 小 于 等 于 基 准 元 素 的 元 素,另 一 部 分 是 大 于 基 准 元 素 的 元 素。这 个 过 程 称 为 分 区 操 作。具 体 步 骤 如 下:

1、设 定 两 个 指 针,一 个 从 数 组 的 起 始 位 置 开 始(记 为 left),另 一 个 从 数 组 的 末 尾 位 置 开 始(记 为 right)。

2、从 left 指 针 开 始,向 右 移 动,直 到 找 到 一 个 大 于 基 准 元 素 的 元 素;从 right 指 针 开 始,向 左 移 动,直 到 找 到 一 个 小 于 等 于 基 准 元 素 的 元 素。

3、交 换 这 两 个 元 素 的 位 置。

4、重 复 上 述 步 骤,直 到 left 指 针 和 right 指 针 相 遇

5、最 后 将 基 准 元 素 与 left 指 针( 或 right 指 针)所 指 向 的 元 素 交 换 位 置,此 时 基 准 元 素 就 处 于 其 最 终 的 排 序 位 置。

递 归 排 序

         分 区 操 作 完 成 后,数 组 被 分 成 了 两 部 分,分 别 对 这 两 部 分 递 归 地 调 用 qsort 函 数 进 行 排 序,直 到 每 个 子 数 组 的 长 度 为 1 或 0,此 时 排 序 完 成。

比 较 函 数 cmp_int 的 作 用

         在 分 区 操 作 和 递 归 排 序 过 程 中,qsort 函 数 会 多 次 调 用 比 较 函 数 cmp_int 来 确 定 元 素 之 间 的 大 小 关 系。

cmp_int 函 数 的 代 码 如 下:

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

const void * e1 和 const void * e2

         这 两 个 参 数 是 指 向 要 比 较 的 两 个 元 素 的 指 针,由 于 qsort 是 一 个 通 用 的 排 序 函 数,所 以 使 用 void* 类 型 的 指 针 来 表 示 任 意 类 型 的 元 素。

*(int * ) e1 - *( int * ) e2

         因 为 参 数 为 const void * ,所 以 要 将 e1 和 e2 指 针 转 换 为 int* 类 型 的 指 针,然 后 解 引 用 得 到 它 们 所 指 向 的 整 数 值,最 后 返 回 它 们 的 差 值。如 果 差 值 小 于 0,则 表 示 e1 指 向 的 元 素 小 于 e2 指 向 的 元 素;如 果 差 值 等 于 0,则 表 示 两 个 元 素 相 等;如 果 差 值 大 于 0,则 表 示 e1 指 向 的 元 素 大 于 e2 指 向 的 元 素。

使 用 qsort 函 数 降 序

下 面 展 示代 码 示 例

#include<stdio.h>
#include<stdlib.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e2 - *(int*)e1;//升序
}
int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0;i < sz;i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
	return 0;
}

在这里插入图片描述

模 拟 实 现 qsort 函 数

下 面 展 示代 码 示 例

#include<stdio.h>
#include<string.h>
int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;//升序
}
void Swap(char* buf1, char* buf2, int width)
{
	int i = 0;
	for (i = 0;i < width;i++)
	{
		char temp = *buf1;
		*buf1 = *buf2;
		*buf2 = temp;
		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 main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	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]);
	}
	printf("\n");
	return 0;
}

通 用 冒 泡 排 序 函 数 bubble_sort

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

         bubble_sort 是 一 个 通 用 的 冒 泡 排 序 函 数,可 以 对 任 意 类 型 的 数 组 进 行 排 序。

         void * base 指 向 待 排 序 数 组 起 始 位 置 的 指 针,使 用 void* 类 型 是 为 了 让 该 函 数 可 以 处 理 任 意 类 型 的 数 据。

size_t sz 表 示 数 组 中 元 素 的 数 量。

size_t width 表 示 每 个 元 素 的 大 小(以 字 节 为 单 位)。

         int( * cmp)(const void * e1, const void * e2) 这 是 一 个 函 数 指 针,指 向 一 个 比 较 函 数,用 于 定 义 元 素 之 间 的 比 较 规 则。

外 层 for 循 环 控 制 排 序 的 轮 数,总 共 需 要 进 行 sz - 1 轮。

         内 层 for 循 环 用 于 比 较 相 邻 的 元 素,如 果 前 一 个 元 素 大 于 后 一 个 元 素,则 调 用 Swap 函 数 交 换 它 们 的 位 置。

交 换 函 数 Swap

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

Swap 函 数 用 于 交 换 两 个 元 素 的 内 容。

         char * buf1 和 char * buf2 这 两 个 参 数 是 指 向 要 交 换 的 两 个 元 素 的 指 针,使 用 char* 类 型 是 为 了 按 字 节 进 行 交 换,从 而 可 以 处 理 任 意 类 型 的 数 据。

int width 表 示 每 个 元 素 的 大 小( 以 字 节 为 单 位 )。

通 过 一 个 for 循 环,逐 字 节 交 换 buf1 和 buf2 所 指 向 的 元 素 的 内 容。

在这里插入图片描述

总结

         至 此,关 于 C 语 言 指 针 的 探 索 暂 告 一 段 落,但 你 的 编 程 征 程 才 刚 刚 启 航。写 代 码 是 与 机 器 深 度 对 话,过 程 中 虽 会 在 语 法、算 法 困 境 里 挣 扎,但 这 些 磨 砺 加 深 了 对 代 码 的 理 解。愿 你 合 上 电 脑 后,灵 感 不 断,在 C 语 言 的 世 界 里 持 续 深 耕,书 写 属 于 自 己 的 编 程 传 奇,下 一 次 开 启 ,定 有 全 新 的 精 彩 等 待。小 编 期 待 重 逢,盼 下 次 阅 读 见 你 们 更 大 进 步,共 赴 代 码 之 约!

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值