C语言:指针3(函数指针与指针函数)

函数指针

定义:函数指针本质上是指针,它是函数的指针(定义了一个指针变量,变量中存储了函数的地

址)。函数都有一个入口地址,所谓指向函数的指针,就是指向函数的入口地址。这里函数名就代

表入口地址。

函数指针存在的意义:

  1. 让函数多了一种调用方式

  2. 函数指针作为形参,可以形式调用(回调函数)

定义格式:

返回值类型 (*变量名) (形式参数列表);

举例:

int (*p)(int a,int b)

函数指针的初始化:

  1. 定义的同时赋值

    // 函数指针需要依赖于函数,先有函数,再有指针
    // 定义一个普通的函数
    int add(int a,int b){ return a + b;}
    // 定义一个函数指针,并给他赋值
    // 通过以下代码我们发现:函数指针的返回类型和依赖函数的返回类型一致,函数指针的参数个数类型和依赖函
    数一致
    int (*p)(int a,int b) = add;// 赋值一定要注意:函数不能带有()

  2. 先定义后赋值

    // 定义一个普通的函数
    int add(int a,int b){ return a + b;}
    // 定义一个函数指针
    // int (*p)(int a,int b);
    int (*p)(int,int); // 一般写作这种
    // 给函数指针赋值
    p = add;

注意:

  1. 函数指针指向的函数要和函数指针定义的返回值类型,形参列表对应,否则编译报错

  2. 函数指针时指针,但不能指针运算,如p++等,没有实际意义

  3. 函数指针作为形参,可以形成回调

  4. 函数指针作为形参,函数调用时的实参只能是与之对应的函数名,不能带小括号()

  5. 函数指针的形参列表中的变量名可以省略

案例

#include <stdio.h>
/**
 * 定义一个函数指针
 */ 
int max(int a,int b)
{
    if(a > b)
        return a;
    return b;
}
int main(int argc,char *argv[])
{
    // 定义测试数据
    int a = 3,b = 2,c;
    // 直接函数调用
    c = max(a,b);
    printf("%d,%d两个数中的最大值是%d\n",a,b,c);
    // 定义一个函数指针
    int (*p)(int,int) = max;
    // 间接函数调用
    c = p(a,b);
    printf("%d,%d两个数中的最大值是%d\n",a,b,c);
    c = (*p)(a,b);
    printf("%d,%d两个数中的最大值是%d\n",a,b,c);
    
    return 0;
}

回调函数

概念

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

为什么要用回调函数

因为可以把调用者与被调用者分开,所以调用者不关心谁是被调用者。它只需知道存在一个具有特 定原型和限制条件的被调用函数。

简而言之,回调函数就是允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数 在处理相似事件的时候可以灵活的使用不同的方法。

实现

#include <stdio.h>
/**
 * 回调函数1
 */
int callback_1(int a)
{
    printf("hello,this is callback_1:a=%d\n",a);
    return 0;
}
/**
 * 回调函数2
 */ 
int callback_2(int b)
{
    printf("hello,this is callback_2:b=%d\n",b);
}
/**
 * 实现回调函数
 */ 
int handle(int x,int (*callback)(int))
{
    printf("开始执行!\n");
    
    callback(x);
    printf("执行结束!\n");
}
int main(int argc,char *argv[])
{
    handle(100,callback_1);
    handle(200,callback_2);
    return 0;
}

指针函数

定义:本质上是函数,这个函数的返回值类型是指针,这个函数称为指针函数。

语法:

指针类型 函数名(形参列表)
{
    函数体;
    return 指针变量;
}

举例:

// int *get(int a)
int* get(int a)
{
    int *b = &a;
    return b;
}
int main()
{
    int *a = get(5);
    printf("%d\n",*a);
}

注意:

在函数中不要直接返回一个局部变量的地址,因为函数调用完毕后,局部变量会被回收,使得返回 的地址就不明确,此时返回的指针就是野指针。

解决方案:

如果非要访问,可以给这个局部变量添加 static ,可以延长它的生命周期,从而避免野指针(尽 量少用,因为存在内存泄漏)

二级指针

说明:指针除了一级指针,还有多级指针,但是我们一般开发中最多用到二级指针。三级指针本质

上用法和二级指针差不多。

定义:二级指针,又被称之为多重指针,引用一级指针的地址,此时这个指针变量就得定义成二级

指针。

int a = 10;    // 普通变量
int *p = &a;   // 一级指针
int **w = &p;  // 二级指针
int ***x = &w; // 三级指针

定义格式:

数据类型 **变量名 = 指针数组的数组名或者一级指针的地址

举例:

// 指针数组
int arr = {11,22,33};
int* arr_ = {&arr[0],&arr[1],&arr[2]};
// 一级指针
int a = 10;
int* p = &a;
// 二级指针和指针数组
// 字符型指针数组,本质上是一个二维的char数组
char* str[3] = {"abc","aaa034","12a12"};
// 如果要用一个指针变量来接收,就需要一个二级指针
char **p_ = str;// 正常编译,str指向的是首元素的地址

// 二级指针和二维数组
int arr[2][3] = {{1,2,3},{11,22,33}};
// 二级指针
int **k = arr;  // 编译报错,数据类型不相符(二维数组不等于二级指针)

int a = 90;// 变量
int *p = &a; // p存放a的地址
int **k = &p; // k存放p的地址
printf("%p,%p,%d\n",k,*k,**k);// k->p的地址,*k->p的值->a的地址,**k->p的值->a的地址->a的值

结论:

  1. 二级指针和指针数组是等效,和二维数组不等效。

  2. 二维数组和数组指针是等效,和二级指针不等效。

二级指针的用法:

  1. 如果是字符的二级指针,可以像遍历字符串数组一样遍历它

  2. 如果是其他的二级指针,就需要解引用两次访问它所指向的数据

案例

#include <stdio.h>
void fun1()
{
     // 字符指针
     char *name[]={"Follow me","BASIC","Great Wall","FORTRAN","Computer design"};
     
     // 定义一个二级指针(字符的二级指针)
     char **p;    
     // 定义循环变量
     int i = 0;
     // 遍历指针数组
     do
     {
        p = name + i;
        printf("%s\n",*p);
        i++;
     }while(i < 5);
     
     printf("\n");
          
     
}
void fun2()
{
    int arr1[5] = {11,12,13,14,15};
    // 创建一个指针数组
    int *arr[] = {&arr1[0],&arr1[1],&arr1[2],&arr1[3],&arr1[4]};
    int **p = arr,i = 0;
    // 遍历
    for(;i < 5; i++)
   {
       // printf("%5d",**(p+i));
       printf("%5d",**p);
       p++;
   }    
    
    printf("\n");
    
}
int main()
{
    fun1();
    fun2();
}
相关推荐
云上的阿七1 小时前
《云计算能不能真正实现按需付费?》
开发语言·云计算·perl
xserver21 小时前
hadoop搭建
大数据·linux·hadoop
breaksoftware2 小时前
Windows Subsystem for Linux——设置默认登录用户名
linux·运维·服务器
梁萌5 小时前
Linux安装Docker
linux·运维·docker·helloworld·容器化部署
彩虹糖_haha5 小时前
Linux高并发服务器开发 第五天(压缩解压缩/vim编辑器/查找替换/分屏操作/vim的配置)
linux·运维·服务器
旺仔学IT5 小时前
Centos7中使用yum命令时候报错 “Could not resolve host: mirrorlist.centos.org; 未知的错误“
linux·运维·centos
Yhame.5 小时前
深入理解 Java 中的 ArrayList 和 List:泛型与动态数组
java·开发语言
编程之路,妙趣横生5 小时前
list模拟实现
c++
qq_433618446 小时前
shell 编程(五)
linux·运维·服务器
mazo_command7 小时前
【MATLAB课设五子棋教程】(附源码)
开发语言·matlab