真实面经题目 · 原创解析

指针函数和函数指针的区别?

指针函数和函数指针的核心区别在于“谁是指针”。指针函数本质仍然是函数,只是返回值类型是指针;函数指针本质是指针变量,只是它保存的是某个函数的入口地址。区分它们时,重点看括号优先级、标识符先和谁结合,以及后续是被调用还是被赋值保存函数地址。

出现于:阿里巴巴 · 测开

60 秒回答模板

回答时可以先给结论:指针函数是返回指针的函数,例如 int* f();,这里 f 先和 () 结合,所以 f 是函数,返回值是 int*。函数指针是指向函数的指针,例如 int (*fp)(int);,括号让 *fp 先结合,说明 fp 是一个指针,它指向的目标是“参数为 int、返回 int 的函数”。然后补充使用方式:指针函数像普通函数一样调用,返回一个地址;函数指针通常先接收某个函数名,再通过 fp(参数) 或 (*fp)(参数) 调用。最后说明场景:指针函数常用于返回数组、结构体、缓冲区或对象地址;函数指针常用于回调、策略表、驱动表、状态机分发和测试桩替换。

考点 概念边界
主线 声明语法
易错点 把 int* f(); 误读成 f 是一个指针,忽略了…

深入解析

01

概念边界

指针函数的重点在“函数”,它描述的是一个函数的返回值类型。只要函数调用完成后返回的是某种指针类型,就可以称为指针函数。函数指针的重点在“指针”,它是一个变量或参数,里面保存函数入口地址,后续可以通过这个指针间接调用对应函数。两者名字相近,但抽象层次完全不同,一个是函数声明,一个是指针声明。

02

声明语法

判断声明时要先看标识符和运算符的结合关系。int* f(); 中,f 后面的 () 优先表示函数调用形式,所以 f 是一个函数,返回 int*。int (*fp)(int); 中,括号强制 *fp 先结合,表示 fp 是指针;括号外的 (int) 表示它指向的目标是一个函数,该函数接收 int 参数并返回 int。括号是区分两者最关键的语法信号。

03

调用方式

指针函数按普通函数调用,例如 p = f();,调用结果是一个地址,后续再通过 p 访问数据。函数指针通常先赋值,例如 fp = add;,再用 fp(1) 或 (*fp)(1) 完成间接调用。前者的调用产物是指针值,后者的指针本身代表可调用目标。如果只背定义而说不清调用过程,通常说明没有真正区分“返回地址”和“保存函数地址”。

04

使用场景

指针函数常见于需要返回动态内存、全局对象、结构体成员地址、数组首地址或资源句柄的场景,例如工厂函数返回某类对象指针。函数指针更多用于行为可替换的场景,例如回调函数、排序比较器、策略表、命令分发、状态机跳转表、测试中的 mock 钩子。一个偏数据结果返回,一个偏行为抽象和运行时分发。

05

优先级判断

C/C++ 声明中,() 的结合优先级高于 *,所以 int* f(); 不需要额外括号就会被理解为函数 f 返回 int 指针。如果写成 int (*f)();,含义就变成 f 是指向某个无参且返回 int 的函数的指针。读复杂声明时,可以从变量名开始,先处理括号内的结合,再向外看返回值或参数列表,这样能避免把函数声明和指针声明混在一起。

06

表达顺序

较完整的回答不应只说“一个是函数,一个是指针”,还要说明判断依据和实际用途。可以按“定义、例子、括号优先级、调用方式、典型应用、常见误区”的顺序作答。这样既能覆盖基础语法,又能体现工程理解。尤其在测试开发场景中,函数指针还可以联系回调测试、策略选择和依赖替换,回答会更贴近实际编码。

c

指针函数与函数指针最小对比

int *find_first(int *arr, int len, int target) {
  for (int i = 0; i < len; i++) {
    if (arr[i] == target) return &arr[i];
  }
  return NULL;
}

int add(int a, int b) {
  return a + b;
}

int (*operation)(int, int) = add;
  • 指针函数是返回指针的函数,重点看返回值类型里的 *。
  • 函数指针是指向函数的指针,重点看变量名先和 * 结合,再接参数列表。

易错点

  • 把 int* f(); 误读成 f 是一个指针,忽略了 f 后面的括号先表示函数声明。
  • 看到声明里有星号就直接判断为函数指针,没有先分析标识符与括号的结合关系。
  • 认为函数指针调用后一定返回指针,混淆了函数指针自身类型和目标函数返回类型。
  • 解释时只背“一个是函数一个是指针”,没有说明返回值、赋值方式和调用方式差异。
  • 在指针函数中返回普通局部变量地址,导致调用方拿到生命周期已经结束的无效地址。
  • 使用函数指针时忽略参数和返回值类型匹配,导致调用约定或类型转换出现隐患。

面试官追问

int *f(); 和 int (*f)(); 分别是什么意思?

int *f(); 表示 f 是一个函数,调用后返回 int 类型指针。int (*f)(); 表示 f 是一个函数指针,它指向一个无参且返回 int 的函数。关键差异是第二个声明中括号让 *f 先结合,因此 f 不再是函数名,而是指针变量。

函数名赋值给函数指针时为什么通常不用取地址符?

在多数表达式环境中,函数名会转换为指向该函数的指针,所以 fp = add; 通常就能成立,写成 fp = &add; 也常见且含义接近。真正需要注意的是函数指针类型必须匹配,包括返回值类型、参数个数和参数类型,否则可能导致编译错误或未定义行为。

函数指针和回调函数有什么关系?

回调是一种使用方式,函数指针是实现回调的常见手段之一。调用方把某个函数地址传给框架或通用逻辑,后者在合适时机通过函数指针反向调用该函数。这样可以把固定流程和可变行为拆开,常用于事件处理、排序比较、测试替身和策略选择。

指针函数返回局部变量地址有什么问题?

如果指针函数返回的是普通局部变量地址,函数结束后该变量生命周期已经结束,返回的指针会变成悬空指针,继续访问可能产生不可预期结果。更合理的做法是返回动态分配内存、静态存储对象、调用方传入缓冲区地址,或使用更安全的对象管理方式。

typedef 或 using 能怎样简化函数指针?

函数指针声明容易因为括号和参数列表变得难读,可以用 typedef 或 using 给函数类型起别名。例如先定义某种比较函数指针类型,再声明变量或参数,代码会更清晰。它不改变函数指针的本质,只是降低复杂声明带来的理解成本。