你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

函数指针与指针函数

[复制链接]
gaosmile 发布时间:2020-8-31 23:03
1 什么是指针?

定义:指针是程序数据在内存中的地址,而指针变量是用来保存这些地址的变量;

微信图片_20200831230135.png

上面一个 4GB 的内存可以存放 2^32 字节的数据。左侧连续的十六进制编号就是内存地址,每个内存地址对应一个字节的内存空间。而指针变量保存的就是这个编号,也即内存地址。

指针的声明:

指针其实就是一个变量,指针的声明方式与一般的变量声明类似,如下:

int *p;         // 声明一个 int 类型的指针 p,该指针指向一个int类型的对象
4 p' Y/ e2 {) S8 \! A5 ychar *p         // 声明一个 char 类型的指针 p,该指针指向一个int类型的对象- l" F: h  m3 H# H6 E- l
int *arr[10]    // 声明一个指针数组,该数组有10个元素,其中每个元素都是一个指向 int 类型对象的指针
/ H7 h  h2 J7 M# R3 eint (*arr)[10]  // 声明一个数组指针,该指针指向一个 int 类型的一维数组
" c9 c# u, C& `4 z- ?; |) _int **p;        // 声明一个指针 p ,该指针指向一个 int 类型的指针
: u# H8 {6 K# B8 n9 U2 e

声明一个指针变量并不会自动分配任何内存。在对指针进行间接访问之前,指针必须进行初始化:或是使他指向现有的内存,或者给他动态分配内存,否则我们并不知道指针指向哪儿,这个问题需要特别关注。

2 什么是函数指针?

函数指针定义:函数指针是指向函数的指针变量。因此“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。

其通用表达式为:类型说明符 (*函数名) (参数)

int (*fun)(int x)  //函数指针的定义( a9 F% J( N: J: l" w
int (*fun)(int x,int y) //函数指针的定义
$ r9 P4 h0 g0 y" Z! k

函数指针在PC软件开发中使用较少,在嵌入式行业使用较多,但是无论是PC软件还是嵌入式软件,理解函数指针的定义和使用,对于理解程序设计都是很有好处的。

函数指针的赋值

函数指针和其他指针一样定义之后使用之前也是需要初始化。

函数指针有两个用途:调用函数和做函数的参数

int (*fun)(int x,int y) //函数指针的定义9 |" g# D( l; `) j2 f  \
fun = &Function          //函数指针的赋值方式1
3 K* [6 ]7 v. N( H2 Q: gfun = Function           //函数指针的赋值方式29 o, ~& L. B7 d- ], x, a, |% W  t
x = (*fun)()             //函数指针的调用方式1
' g7 b) l0 H  g' _- Px = fun()                //函数指针的调用方式2# ]. _+ m. @! x4 |5 r; _+ s3 Z( o

函数赋值的时候取地址运算符&不是必需的,因为一个函数标识符就表示了它的地址,并且赋值的时候函数不需要带圆括号;

如果是函数调用,还必须包含一个圆括号括起来的参数表。

函数指针的用法

我们使用指针的时候,需要通过钥匙 * 来取其指向的内存里面的值,函数指针使用也如此。通过用(*pf)取出存在这个地址上的函数,然后调用它。

char*  fun(char* p1,char* p2)
3 C# R$ E7 e# U# Z{% d) ~5 i6 B4 r. r/ T' D
  int i = 0;: I; M! P- w9 j, N
  i = strcmp(p1,p2);
, d: q, P7 B8 w& f9 ?  if(0 == i)
9 v- d, N% ^2 W3 i# d8 L  {
3 D* z+ f% _: x5 p/ _4 O    return p1;
' H. e" `& E5 I0 ^& `) E  }
. P, a2 q0 U8 G" d8 d  E+ t  else% U  R/ w  R# W! l( w
  {$ N. K4 k+ M8 E
     return p2;
9 \1 H& C6 B2 O" @  }3 z4 ?7 l( r* i
}. |# g# e; q6 J3 f
int main()
! y8 y! H# f& b2 d% N{3 ?# n6 x2 s' x
  char * (*pf)(char* p1,char* p2);; U8 Q+ O* ]5 i$ C* d$ d+ q* V
  pf = &fun;
5 {9 b7 {5 ^4 O  (*pf)("aa","bb");
. V4 r# R& `4 G' x  return 0;
. z6 X) g! d% j& _; j. H+ B1 L}, B6 f) O+ ~7 j/ L

这里需要注意到是,在Visual C++6.0里,给函数指针赋值时,可以用&fun或直接用函数名fun。这是因为函数名被编译之后其实就是一个地址,所以这里两种用法没有本质的差别。

用法延申

当我们不满足于函数指针上面如此简单的用法时,这时候需要一个高级用法来扩展我们对于函数指针的认知边界。

感兴趣的同学可以看看下面这个用法,并尝试理解该表达式是如何使用的函数指针。

(* (void(*)()) 0)(); //出自《C Trap and Pitfalls》这本经典的书
: {+ m! E% C4 \& Q, V

答案如下:   ``

  • 第一步:通过void(*) (),可以明白这是一个函数指针类型。这个函数没有参数,没有返回值。
  • 第二步:通过(void(*) ())0,可以明白这是将0强制转换为函数指针类型,0是一个地址,也就是说一个函数存在首地址为0的一段区域内。
  • 第三步:通过(*(void(*) ())0),可以明白这是取0地址开始的一段内存里面的内容。
  • 第四步:最终理解(*(void(*) ())0)(),这是函数调用。
    # _5 f0 e  I6 ^$ j8 f8 I
让程序跳转到绝对地址为0x0113F90C

方法一:

  • 将0x0113F90C地址强制转换为函数指针类型,即: (void (*)())0x0113F90C
  • 然后调用:((void (*)())0x0113F90C)()' t5 r+ O$ |2 y2 M* n" i  U2 z

方法二:

typedef (void (*)())  VoidFuncPtr;
: `0 F) s9 u$ R! B: n! U! }) _((VoidFuncPtr)0x0113F90C)();
1 ^6 ]/ ?3 d  \& }) u" a% g& p
面试题:指出程序的错误#include<stdio.h>
& ~9 X4 `/ @+ t5 ]3 n5 C4 U$ F- C5 `! \0 }+ S1 u% B: B$ x
void main(void)) |5 t2 ]4 S7 j$ P4 k/ K0 q! `
{* J- P( o9 ^; b1 H4 z
   int max(x,y);
& F% q0 o- U' R% l- g' `5 o   int *p=max;
* `* d" U# A0 ^, B, H6 Q" ~   int a,b,c,d;4 J& V( W, b! [2 `& |, o! ~
   scanf("%d %d %d",a,b,c);
  K! ]+ D8 a; s8 S   d=p(p(a,b),c);
( x6 b7 Y& x" @1 d   printf("d:%d\n",d);' n0 q5 ^$ M8 P- H% G
   return;
! H$ M9 N6 Y% z- f$ ?4 m+ G}
& ]6 f0 U+ [8 U* Y! F7 Cint max(int a,int y)
( ~* {! K" n( e% t0 W5 y- w4 i{
9 L# T2 H) m! v+ e+ N/ u   return(x > y ?x:y);
3 U1 {  w# A, i$ V1 X5 ^! i7 y}& N1 y4 Y! r2 j6 F2 |5 B

答案:

  • int max(x ,y);函数声明错误,改为:int max(int x,int y);+ U: R+ u# Q, W5 Q) o- N4 R4 p

解析:max函数声明只是写出了函数的形参的名称,这对参数的类型来说是毫无意义的,编译器会把x和y当做数据类型来看,编译时会出错,max的调用肯定也会出错。

  • int *p=max;指针定义错误,改为:int (*p)(int ,int)=max;
    6 s. D& P# }7 {8 K% x9 h, A' r& r

解析:函数的指针是不能直接赋值给int型指针.

  • scanf("%d %d %d",a,b,c);库函数使用错误,改为scanf("%d %d%d",&a,&b,&c);: h+ L5 ~2 w  \% D3 j" j1 ~2 W8 Z

解析:库函数使用错误,第二部分应该是接收数据的地址,这里却写成了变量。

  • d=p(p(a,b),c);函数指针调用函数错误,改为d=(*p)((*p)(a,b),c);`
    # l5 X% M, ]' u0 `0 L* q6 i

解析:用函数指针调用函数的格式如下:(【*】【函数指针名称】)(【参数列表】);不能直接用函数指针加上参数就直接调用

3 什么是指针函数?

指针函数定义:指针函数的落脚点是一个函数,这个函数的返回值是一个指针,与普通函数int function(int,int)类似,只是返回的数据类型不一样而已。

_type_ *function(int, int) //返回的是指针地址" P$ ^0 h$ _! V* _% i* i
int function(int,int)     //返回的是int型数据。
1 ~0 F- K% Q# Z0 `' i
int  *fun(int x)        //指针函数的定义9 p3 v: Q$ k% B% f
int * fun(int x,int y)  //指针函数的定义
4 ?3 H9 J5 ]) x) X9 @; [' Z+ m7 kint* fun(int x,int y)   //指针函数的定义
; X) |( R( h+ x

以上三种写法均正确,但是*靠近返回值一点更容易理解。

指针函数的调用

在调用指针函数时,需要一个同类型的指针来接收其函数的返回值。

typedef struct _Data{1 d' T5 v) ]" S0 X/ M8 {7 X9 B
    int a;' y% r3 T4 i5 t/ p
    int b;+ c- b% d  K: I) E) u
}Data;" @4 B1 \/ r; Y

8 c" w6 C) M+ I; P2 s  e% x//指针函数0 [3 O8 b/ q) h
Data* f(int a,int b){
7 W* }# O* t- Z2 D2 E, Z& V& ~    Data * data = new Data;
0 D* O: _6 ~( [- r. F; }8 ?! S0 j    data->a = a;& x, B9 q/ F2 Z
    data->b = b;
& c7 u, V) b. o6 A+ P    return data;
  A/ U' X& t' d; P) [0 b- h}* b& H! p! l* D' o: x6 v& \1 E

: j0 t" {% m. x+ m: T1 w. Aint main(int argc, char *argv[])' F  ?; j5 N9 K& w; A" O: P
{
9 p$ @: ?; X! d; B; d+ l8 x    QApplication a(argc, argv);0 {" ^! q7 F) i1 G6 n
    //调用指针函数- `! Q# e* Q" O. g/ R( Z$ T
    Data * myData = f(4,5);& N% J* t8 _; e, S1 A2 t
    qDebug() << "f(4,5) = " << myData->a << myData->b;" P# U6 a! `8 s0 p8 I3 w
0 {6 W+ Q4 A2 ^0 o4 F6 ^
    return a.exec();
4 U, g# \* C6 ?& Q8 F+ U# Z4 q2 z, h}# X' x  X# I) C& O4 |- l4 g5 g
% m$ U: z5 Q, c' X: w0 ]

不过也可以将其返回值定义为 void* 类型,在调用的时候强制转换返回值为自己想要的类型。

其输出结果是一样的,不过不建议这么使用,因为强制转换可能会带来风险。返回类型可以是任何基本类型和复合类型。返回指针的函数的用途十分广泛。

事实上,每一个函数,即使它不带有返回某种类型的指针,它本身都有一个入口地址,该地址相当于一个指针。

比如函数返回一个整型值,实际上也相当于返回一个指针变量的值,不过这时的变量是函数本身而已,而整个函数相当于一个“变量”。

4 函数指针与指针函数区别

通过以上的介绍,小伙伴应该都能理解二者的定义。那么简单的总结下二者的区别:

1. 定义不同
  • 指针函数本质是一个函数,其返回值为指针。
  • 函数指针本质是一个指针,其指向一个函数。
    # O: O4 @, R/ A6 o" ~3 Z% q
2. 写法不同
  • 指针函数:int* fun(int x,int y);
  • 函数指针:int (*fun)(int x,int y);$ d8 N/ v) M& U1 h. b5 P4 e* l

可以简单粗暴的理解为,指针函数的*是属于数据类型的,而函数指针的星号是属于函数名的。

再简单一点,可以这样辨别两者:函数名带括号的就是函数指针,否则就是指针函数。

3. 用法不同

上面函数指针和指针函数的用法都有,但是函数指针的用法会更多,相对而言难度也更大,例如函数指针与回调函数,如果是C++非静态成员函数指针,其用法也会有一些区别,感兴趣的同学可以关注后续推文或自行查阅相关书籍。

总而言之,这两个东西很容易搞混淆,一定要深入理解其两者定义和区别,避免犯错。

3 \6 T+ Z9 I6 X7 `+ m7 g, o
赞 收藏 1 评论0 发布时间:2020-8-31 23:03

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版