函数与指针

指针传递(地址传递、引用传递)

值传递:实参的值是非地址类型,实参将自己的值的拷贝传递给形参,形参与实参只有值相等,是两个独立的变量。

指针传递:实参是地址(指针变量、数组名),形参接收到时地址,函数内部使用形参可以修改外部的数据。

#include <stdio.h>

void fn1(int n)
{
    n+=100;
}

void fn2(int *n)
{
    *n+=100;
}


int main()
{
    int num=10;
    printf("num=%d\n",num);// 10

    fn1(num);
    printf("num=%d\n",num);// 10

    fn2(&num);
    printf("num=%d\n",num);// 110

    
    return 0;
}

为什么调用fn1之后num的值还是10呢?为什么fn2就是110呢?

调用 fn1(num);,这是值传递

  1. main里面 num = 10
  2. 调用 fn1 的时候:把 num 里面的 10 复制一份,给到 fn1 的形参变量n

⚠注意:这里是复制一份!fn1 里面的n,是 fn1 函数自己栈上的局部变量,和 main 的num是两块完全独立的内存,两个盒子。

调用 fn2(&num);

  1. &num拿到 main 中 num 的内存地址,把这个地址传给形参指针n
  2. fn2 的指针变量n保存的是 main 里面 num 的地址。
  3. *n +=100*解引用,顺着地址,直接找到 main 函数的 num 变量,修改它里面的数据
  4. main 的 num 直接变成 110。

fn2 的指针变量n本身,函数结束之后同样会销毁,但是它已经通过地址把外面 num 的值改完了。

指针函数

是个函数,返回指针(地址)

注意: 指针函数不能返回局部变量的地址,函数调用结束局部变量被销毁;可以返回静态局部变量的地址。

#include <stdio.h>
#include <string.h>

// 定义指针函数 返回两个字符串中较长的一个
char *get_longger_string(char *str1, char *str2)
{
    return strlen(str1) > strlen(str2) ? str1 : str2;
}

int main()
{
    char *city = "Shanghai";
    printf("较长字符串:%s \n", get_longger_string(city, "Beijingnan"));
    return 0;
}

注意:

#include <stdio.h>
// 定义函数
int *get_value()
{
    // num 是局部变量,函数调用结束会被销毁
    int num = 250;
    return &num;
}

int main()
{
    int *ptr = get_value();
    printf("ptr=%p \n ", ptr);
    printf("*ptr=%d \n ", *ptr);
    return 0;
}

错误现象: 编译的时候这串代码是没有问题的,但是函数返回局部变量地址后,运行时出现内存访问错误

原因:num是局部变量,调用完之后其中的数据会被销毁。调用完之后是能拿到地址,但是拿到的地址是销毁后的地址。现代系统会检测到这种错误并将地址重置为0(NULL指针)

如何解决呢?

设置为静态局部变量。

静态局部变量,函数调用结束不销毁

    static int num = 250;

函数指针

是个指针,指向一个函数

#include <stdio.h>

// 定义函数
double avg(int a, int b)
{
    return (a + b) / 2.0;
}

int main()
{
    
    // 打印地址
    printf("avg: %p \n", avg);
    printf("&avg: %p \n", &avg);
    printf("*avg: %p \n", *avg);
    return 0;
}

打印出来的全是地址这是为什么?

1. avg

在 C 语言中,函数名,会隐式转换为函数的入口地址,代表函数代码在内存中存放的起始地址。

avg →拿到函数代码块的首地址。

2. &avg

&取函数的地址,对函数名取地址,得到的还是函数入口地址avg&avg,输出结果完全一样。

普通变量:num是值,&num是地址,两者不一样。 函数特殊:avg&avg得到同一个函数入口地址。

3. *avg

*解引用函数指针。 函数指针解引用,得到的还是这个函数本身,又会再次转换成函数地址。 所以*avg依然打印函数入口地址。

函数指针这套规则很特殊:avg&avg*avg**avg***avg全部等价,全都是函数入口地址! 你写多少层*解引用,结果不变。

// 调用函数
printf("%.2lf \n", avg(10, 40));
printf("%.2lf \n", (&avg)(10, 40));
printf("%.2lf \n", (*avg)(10, 40));

算出来的结果都是25.00

对于函数:avg&avg*avg****avg,全部是同一个函数入口地址,调用的是同一个函数,传入同样参数,返回值完全一样

#include <stdio.h>

// 定义函数
double avg(int a, int b)
{
    return (a + b) / 2.0;
}

int main()
{

    // 函数指针 ---------------------------------------------
    // 定义函数指针 指针函数 avg 一下三种写法都一样
    // double (*ptr)(int, int) = &avg;
    // double (*ptr)(int, int) = *avg;
    double (*ptr)(int, int) = avg;

    // 打印
    printf("ptr: %p \n", ptr);
    printf("*ptr: %p \n", *ptr);
    printf("&ptr: %p \n", &ptr);

    // 使用函数指针调用avg
    printf("%.2lf \n", (*ptr)(70, 40));
    printf("%.2lf \n", ptr(70, 40));
    // printf("%.2lf \n", (&ptr)(70, 40));// 这个不行因为这个是指针自己的地址
    
    return 0;
}

为什么打印printf("&ptr: %p \n", &ptr);结果跟前面两个不一样呢?

因为指针ptr存放的值是函数的地址,但是指针有他自己的地址,所以取址后得到的是ptr自己的地址。

总而言之:有了函数的地址就可以调用函数

总结:

  1. 函数的代码存放于内存的代码区,调用函数的本质就是使用函数代码的地址去调用
  2. 函数名、&函数名、*函数名 都可以获取到函数代码的地址,都可以调用函数。

回调函数

函数指针可以作为某个函数的参数来使用的,简单来讲,回调函数就是一个通过函数指针调用的函数。就是当参数的才是回调函数。
回调函数作为另一个函数的参数,在另一个函数的内部可以调用回调函数。

// 函数指针声明格式

返回值类型 (*指针名)(参数列表);

#include <stdio.h>
#include <math.h>
// 定义函数, 需要接收一个函数作为参数
// cb 的类型是 void (*)(), 是个指针,指向 void () 类型的数据
void A(void (*cb)())
{
    // 调用cb指向的函数
    cb();
}

void fn1()
{
    printf("hello fn1");
}

// 定义函数,实现对数组进行初始化赋值,需要接收一个函数作为参数
void initarray(double *arr, int len, double (*cb)(double))
{
    for (int i = 0; i < len; i++)
    {
        arr[i] = cb((double)i);
    }
}

void fn2()
{
    printf("hello fn2");
}

// 定义 initArray 的回调函数
double half(double n)
{
    return n / 2;
}

int main()
{
    A(fn1);
    printf("\n");

    double nums[6];
    initarray(nums, 6, half);
    for (int i = 0; i < 6; i++)
    {
        printf("%.2lf\n", nums[i]);
    }

    printf("\n");

    initarray(nums, 6, sqrt);
    for (int i = 0; i < 6; i++)
    {
        printf("%.2lf ", nums[i]);
    }
    printf("\n");

    initarray(nums, 6, cbrt);
    for (int i = 0; i < 6; i++)
    {
        printf("%.2lf ", nums[i]);
    }
    printf("\n");
    return 0;
}
  • A(fn1); fn1函数名转换为函数入口地址,赋值给形参函数指针cb; 在A函数内部执行cb(),等价执行fn1()

  • initarray(nums,6,half);half函数地址传给形参double (*cb)(double); 循环中arr[i] = cb((double)i);,实际调用half(i),把返回值赋值给数组。

  • 库函数也可以作为回调

回调函数思想

回调的核心思想:框架函数只负责流程,把具体业务逻辑交给外部传入的回调函数。 例子:initarray只负责循环遍历数组(框架);生成什么数字交给回调函数half/sqrt/cbrt(业务逻辑)。 后续想要新的数组赋值规则,只需要写新回调函数,不用修改initarray内部代码。

嵌入式开发中大量使用回调:按键中断回调、定时器回调,框架触发条件到了,自动调用我们写好的回调函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值