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 语 言 的 世 界 里 持 续 深 耕,书 写 属 于 自 己 的 编 程 传 奇,下 一 次 开 启 ,定 有 全 新 的 精 彩 等 待。小 编 期 待 重 逢,盼 下 次 阅 读 见 你 们 更 大 进 步,共 赴 代 码 之 约!