指针传递(地址传递、引用传递)
值传递:实参的值是非地址类型,实参将自己的值的拷贝传递给形参,形参与实参只有值相等,是两个独立的变量。
指针传递:实参是地址(指针变量、数组名),形参接收到时地址,函数内部使用形参可以修改外部的数据。
#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);,这是值传递。
main里面num = 10- 调用 fn1 的时候:把 num 里面的 10 复制一份,给到 fn1 的形参变量
n。⚠注意:这里是复制一份!fn1 里面的
n,是 fn1 函数自己栈上的局部变量,和 main 的num是两块完全独立的内存,两个盒子。调用
fn2(&num);
&num拿到 main 中 num 的内存地址,把这个地址传给形参指针n。- fn2 的指针变量
n保存的是 main 里面 num 的地址。*n +=100:*解引用,顺着地址,直接找到 main 函数的 num 变量,修改它里面的数据。- 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 #
}
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自己的地址。
总而言之:有了函数的地址就可以调用函数
总结:
- 函数的代码存放于内存的代码区,调用函数的本质就是使用函数代码的地址去调用
- 函数名、&函数名、*函数名 都可以获取到函数代码的地址,都可以调用函数。
回调函数
函数指针可以作为某个函数的参数来使用的,简单来讲,回调函数就是一个通过函数指针调用的函数。就是当参数的才是回调函数。
回调函数作为另一个函数的参数,在另一个函数的内部可以调用回调函数。
// 函数指针声明格式
返回值类型 (*指针名)(参数列表);
#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内部代码。
嵌入式开发中大量使用回调:按键中断回调、定时器回调,框架触发条件到了,自动调用我们写好的回调函数。

476

被折叠的 条评论
为什么被折叠?



