• 函数指针一站式理解(C++ primer plus),包括指针数组,数组指针,胆小慎入


             本文的所有文字均需仔细阅读,本文知识点来源于C++ primer plus指针篇,其重要性不言而喻,文中加入了些许本人的观点简化阅读障碍,但也可能因此产生表述不清的问题,如有疑问,请直接阅读C++ primer plus 第六版第198页7.10函数指针

    函数指针原理跟范例

    1. #include
    2. #include
    3. /*
    4. * 函数指针
    5. * 函数的地址是存储其机器语言代码的内存的开始地址.
    6. * 可以编写将另一个函数的地址作为参数的函数,这样第一个函数能够找到第二个函数.这种方法的优势在于,可以在不同的场景传入不同的函数地址,提高函数的灵活性
    7. * 1、获取函数地址
    8. * 获取函数的地址很简单:只要使用函数名(不带参数)即可,例:void think(); 是一个函数,则 think就是该函数的地址
    9. * 2、声明函数指针
    10. * 函数指针的声明应像函数原型一样指出函数的相关信息,包括函数的返回类型,以及函数的特征标志(参数列表)
    11. * 例如: 某函数原型为 double pam(int); 则该函数的正确指针类型声明为 double (*pf)(int);
    12. * 可以看到该函数指针与原型极其相似,只是将pam替换为(*pf),由于pam是函数,因此(*pf)也是函数,则pf就是函数指针
    13. * 这里涉及到指针相关的知识比如 * () 的优先级,右左原则
    14. * 括号的优先级比 * 高,因此 *pf(int) 意味着pf()是一个返回指针的函数,而(*pf)(int)意味着pf是一个指向函数的指针
    15. * double (*pf)(int);
    16. * double pam(int);
    17. * pf = pam;
    18. *
    19. * 函数说明 : 该函数模拟了需要写lines行代码,不同的人需要多少时间
    20. * 入参:
    21. lines : 行数
    22. pf : 函数指针,每个人把自己需要的时间计算出来
    23. * 出参: 无
    24. * 返回值: void
    25. void estimate(int lines, double (*pf)(int));
    26. * 3、使用指针来调用函数
    27. * 如何来调用被指针指向的函数,也就是如何知道自己该传怎样一个函数做参数才能正确调用 estimate函数呢
    28. * 线索来自指针声明,在2中说了,(*pf)扮演的角色与函数名相同,因此使用(*pf)时,只需将它看做函数名即可
    29. * double pam(int);
    30. * double (*pf)(int);
    31. * pf = pam;
    32. * double x = pam(4);
    33. * double y = (*pf)(4);
    34. * 指针使用函数还有另一种方法: C++允许像使用函数名一样使用pf;
    35. * double y = pf(4);
    36. * 关于如上两种函数指针的用法,其由来可以往前追溯历史: 为何 pf 和 (*pf)等价呢?
    37. * 一种学派认为,由于pf是函数指针,所以*pf是函数,因此调用函数应为 (*pf)()
    38. * 另一种学派认为,由于函数名本身就是指向该函数的指针,所以指向函数的指针的行为应该是跟函数名相似的,而pf是指向函数的指针,因此可以将pf()当函数名来使用.
    39. * 以上两种方式C++均支持且运行一致.
    40. */
    41. using std::cout;
    42. using std::cin;
    43. double pam(int);
    44. double tony(int);
    45. void estimate(int lines, double (*pf)(int));
    46. int main()
    47. {
    48. int code;
    49. cout << "你需要多少行代码? ";
    50. cin >> code;
    51. cout << "pam 的预估:\n";
    52. estimate(code, pam);
    53. cout << "tony 的预估:\n";
    54. estimate(code, tony);
    55. return 0;
    56. }
    57. double pam(int lns)
    58. {
    59. return lns * 0.05;
    60. }
    61. double tony(int lns)
    62. {
    63. return 0.03 * lns + 0.004 * lns * lns;
    64. }
    65. void estimate(int lines, double (*pf)(int))
    66. {
    67. cout << lines << "行需要 ";
    68. cout << pf(lines) << " 小时 \n";
    69. }

    上述代码运行结果:

    键盘输入数字9

    运行结果如下:

    你需要多少行代码? 9
    pam 的预估:
    9行需要 0.45 小时
    tony 的预估:
    9行需要 0.594 小时

    深入探讨函数指针(附带讨论指针数组跟数组指针)

    1. #include
    2. #include
    3. /*
    4. * 深入探讨函数指针
    5. * 如下三个函数原型
    6. * 1、const double * f1(const double ar[],int n);
    7. * 2、const double * f2(const double [],int);
    8. * 3、const double * f3(const double *,int);
    9. * 这些函数的特征标志(参数列表)看似不同,但实际上相同.C++的函数原型,参数 const double ar[] 跟 const double * ar 含义完全一样,其次,函数原型可以省略标识符,
    10. * 因此 const double ar[] 可以简化为const double [],const double * ar可以简化为 const double *
    11. * 不过对于函数定义,必须提供显式的参数名,因此必须使用const double ar[]或者const double * ar
    12. * 有了函数原型,那么定义函数指针就十分方便了
    13. * const double * (*p1)(const double *,int); 表示指向上述三个函数的函数指针
    14. * 可在声明的同时进行初始化
    15. * const double * (*p1)(const double *,int) = f1;
    16. * 使用C++的自动类型推导,代码将要简单很多
    17. * auto p2 = f2;
    18. * 下面看如下语句
    19. * cout << (*p1)(av,3) << ":" << *(*p1)(av,3) << endl;
    20. * cout << p2(av,3) << ":" << *p2(av,3) << endl;
    21. * 如何理解上述语句? 根据前面函数指针的原理,这里可以知道 (*p1)(av,3) 和 p2(av,3) 都调用指向的函数,也就是 f1() 跟 f2(),av,3是两个参数,
    22. * 这俩函数返回值是double * ,因此显示出来的是地址,那后半部分(*(*p1)(av,3) 和 *p2(av,3))显示的就是这些地址里面的值
    23. * 因为上述三个函数都需要使用函数指针,那选择就有两种,一是声明三个函数指针分别指向三个函数,二是通过函数指针数组
    24. * 如何声明这样的一个数组呢? 这种数组的声明应该类似单个函数指针的声明,但是必须在某个地方加上[3]用于指出这是一个包含三个函数指针的数组,答案如下:
    25. * const double * (*pa[3])(const double *,int) = {f1,f2,f3};
    26. * 为何这样声明呢? 这涉及到 () [] * 三者的优先级以及指针的右左原则
    27. * 首先可以确定的是我们需要一个数组,因此必须有 pa[3],由于[] 的优先级高于 * ,因此 *pa[3] 表示pa是一个包含三个指针的数组,
    28. * 其余的特征标志(参数列表)和返回类型用以修饰该指针数组的指针类型(可通过指针的右左原则来反解pa的类型)
    29. * 这里可否使用auto来简化函数指针数组的声明呢? 不行,自动类型推导只能使用于单值初始化,不能用于初始化列表,但声明pa后可通过auto声明同类型的pb:
    30. * auto pb = pa;
    31. * 因为数组名是指向数组第一个元素的指针,所以这里的pa,pb 是指向函数指针的指针
    32. * 如何使用pa,pb 调用函数呢?
    33. * const double * px = pa[0](av,3);
    34. * const double * py = (*pb[1])(av,3);
    35. * 如何理解上述语句?
    36. * 在上面的 函数指针跟范例中解释了两种函数指针用法的历史
    37. * 因为函数名本身就是指向该函数地址的指针,因此调用函数有两个理解: 1、函数指针调用函数 2、函数名调用函数
    38. * 因为pa数组的每个元素都是函数指针,因此可以直接使用数组元素(函数指针)调用函数,即pa[0](av,3)
    39. * pb跟pa一样,每个元素都是函数指针,既然元素是指针,那通过对指针解引用即可得到函数名本身,通过函数名调用即 (* pb[1])(av,3)
    40. * 现在尝试来做另一件事: 创建一个指向pa数组的指针.
    41. * 由于数组名pa本身是指向函数指针的指针,因此指向该数组的指针将是一个指向 指针的指针 的指针
    42. * 上面的话听起来就够绕了,不过幸运的是,由于可以使用单个的值而不是初始化列表对其初始化,因此可以使用 auto:
    43. * auto pc = &pa;
    44. * 那么该如何手动声明这个类型呢? 假设指针名叫pd
    45. * 参考pa的声明 : const double * (*pa[3])(const double *,int)
    46. * 现在知道了pd是一个指针,由于*的优先级相对()[]而言最低,因此 一定存在 (*pd),其指向一个数组,根据右左原则,一定会有(*pd)[3],
    47. * 这个数组的元素都是指针,则 (* (*pd)[3]),数组元素指针的类型可以参考pa的声明,则最终的结果如下:
    48. * const double * (* (*pd)[3])(const double *,int) = &pa;
    49. * 那么该如何使用pd调用函数呢? 既然pd指向一个数组,那*pd就是数组,而(*pd)[i]就是数组元素,也就是函数指针,因此可以简单的使用函数指针方式调用
    50. * 即 (*pd)[i](av,3),那么 *(*pd)[i](av,3)就是函数返回的指针指向的值
    51. * 也可以使用第二种方法 (*(*pd)[i])(av,3) ,该方法使用的是函数名,那么 *(*(*pd)[i])(av,3)就是函数返回的double值
    52. * 需要注意的是pa 跟 &pa的区别
    53. * pa是一个数组名,在大多数情况下,数组名就是数组第一个元素的地址,即pa = &pa[0],因此,pa是单个指针的地址
    54. * 但是 &pa是整个数组(即三个指针块)的地址.从数字上说,pa和&pa的值相同,但是它们的类型不同.
    55. * 区别是,pa+1为数组中下一个元素的地址,而&pa + 1 表示的则是pa后面12个字节内存块的地址(对指针使用sizeof的结果为4,数组一共三个指针)
    56. * 另一个区别是,要得到第一个元素的值,只需要对pa解除一次引用,但需要对&pa解除两次引用
    57. * **&pa = *pa = pa[0];
    58. */
    59. using std::cout;
    60. using std::cin;
    61. using std::endl;
    62. const double* f1(const double ar[], int n);
    63. const double* f2(const double[], int);
    64. const double* f3(const double*, int);
    65. typedef const double* (*p_fun)(const double*, int);
    66. int main()
    67. {
    68. double av[3]{ 1112.3,1542.6,2227.9 };
    69. const double* (*p1)(const double*, int) = f1;
    70. auto p2 = f2;
    71. cout << "使用函数指针 \n";
    72. cout << "函数返回地址 函数返回值 \n";
    73. cout << (*p1)(av, 3) << " : " << *(*p1)(av, 3) << endl;
    74. cout << p2(av, 3) << " : " << *p2(av, 3) << endl;
    75. const double* (*pa[3])(const double*, int) { f1, f2, f3 };
    76. auto pb = pa;
    77. cout << "使用函数指针数组\n";
    78. cout << "函数返回地址 函数返回值 \n";
    79. for (int i = 0;i < 3;i++)
    80. {
    81. cout << pa[i](av, 3) << " : " << *pa[i](av, 3) << endl;
    82. }
    83. for (int i = 0; i < 3; i++)
    84. {
    85. cout << pb[i](av, 3) << " : " << *pb[i](av, 3) << endl;
    86. }
    87. cout << "使用函数指针数组指针\n";
    88. cout << "函数返回地址 函数返回值 \n";
    89. //auto pc = &pa;
    90. const double* (*(*pc)[3])(const double*, int){&pa};
    91. cout << (*pc)[0](av, 3) << " : " << *(*pc)[0](av, 3) << endl;
    92. auto pd = &pa;
    93. const double* pdb = (*pd)[1](av, 3);
    94. cout << pdb << " : " << *pdb << endl;
    95. cout << (*(*pd)[2])(av, 3) << " : " << *(*(*pd)[2])(av, 3) << endl;
    96. cout << "使用函数指针别名\n";
    97. cout << "函数返回地址 函数返回值 \n";
    98. p_fun pf1 = f1;
    99. cout << pf1(av, 3) << " : " << *pf1(av, 3) << endl;
    100. p_fun pe[3]{ f1,f2,f3 };
    101. cout << "使用函数指针别名数组\n";
    102. cout << "函数返回地址 函数返回值 \n";
    103. for (int i = 0; i < 3; i++)
    104. {
    105. cout << pe[i](av, 3) << " : " << *pe[i](av, 3) << endl;
    106. }
    107. cout << "使用函数指针别名数组指针\n";
    108. cout << "函数返回地址 函数返回值 \n";
    109. p_fun(*pf)[3]{ &pe };
    110. for (int i = 0; i < 3; i++)
    111. {
    112. cout << (*pf)[i](av, 3) << " : " << *(*pf)[i](av, 3) << endl;
    113. }
    114. return 0;
    115. }
    116. const double* f1(const double ar[], int n)
    117. {
    118. return ar;
    119. }
    120. const double* f2(const double ar[], int n)
    121. {
    122. return ar + 1;
    123. }
    124. const double* f3(const double* ar, int n)
    125. {
    126. return ar + 2;
    127. }

    上述代码运行如下 :

    使用函数指针
    函数返回地址  函数返回值
    0000009ACE70FA38 : 1112.3
    0000009ACE70FA40 : 1542.6
    使用函数指针数组
    函数返回地址  函数返回值
    0000009ACE70FA38 : 1112.3
    0000009ACE70FA40 : 1542.6
    0000009ACE70FA48 : 2227.9
    0000009ACE70FA38 : 1112.3
    0000009ACE70FA40 : 1542.6
    0000009ACE70FA48 : 2227.9
    使用函数指针数组指针
    函数返回地址  函数返回值
    0000009ACE70FA38 : 1112.3
    0000009ACE70FA40 : 1542.6
    0000009ACE70FA48 : 2227.9
    使用函数指针别名
    函数返回地址  函数返回值
    0000009ACE70FA38 : 1112.3
    使用函数指针别名数组
    函数返回地址  函数返回值
    0000009ACE70FA38 : 1112.3
    0000009ACE70FA40 : 1542.6
    0000009ACE70FA48 : 2227.9
    使用函数指针别名数组指针
    函数返回地址  函数返回值
    0000009ACE70FA38 : 1112.3
    0000009ACE70FA40 : 1542.6
    0000009ACE70FA48 : 2227.9

    上述的示例看起来比较深奥,但是指向函数指针数组的指针并不少见,类的虚方法实现通常都采用了这种技术,不过这些细节由编译器实现

    typedef 可以将p_fun声明为函数指针类型,通过使用p_fun大大减少输入量,让代码更不容易出错,易于理解

  • 相关阅读:
    VUE 级联菜单cascader 的options动态加载页面无变化
    让学前端不再害怕英语单词(一)
    大话CAS
    第四章 ObjectScript 宏预处理器指令
    案例分享|智慧化的西部机场
    【工具】html请求 Content-Encoding=br 返回值乱码的问题 解码返回值
    消息队列 记录
    HarmonyOS非线性容器特性及使用场景
    关于pytorch的数据处理-数据加载Dataset
    霸榜Github三个月的「架构师成长手册」成为架构师也有捷径
  • 原文地址:https://blog.csdn.net/qq_37059136/article/details/125875174