c语言学习--函数与指针

发布时间:2026/10/15 23:06:06

c语言学习--函数与指针 指针传递地址传递、引用传递值传递实参的值是非地址类型实参将自己的值的拷贝传递给形参形参与实参只有值相等是两个独立的变量。指针传递实参是地址指针变量、数组名形参接收到时地址函数内部使用形参可以修改外部的数据。#include stdio.h void fn1(int n) { n100; } void fn2(int *n) { *n100; } int main() { int num10; 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 num; } int main() { int *ptr get_value(); printf(ptr%p \n , ptr); printf(*ptr%d \n , *ptr); return 0; }错误现象: 编译的时候这串代码是没有问题的但是函数返回局部变量地址后运行时出现内存访问错误原因num是局部变量调用完之后其中的数据会被销毁。调用完之后是能拿到地址但是拿到的地址是销毁后的地址。现代系统会检测到这种错误并将地址重置为0NULL指针如何解决呢设置为静态局部变量。静态局部变量函数调用结束不销毁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内部代码。嵌入式开发中大量使用回调按键中断回调、定时器回调框架触发条件到了自动调用我们写好的回调函数。
延伸阅读

更多相关文章

2026/10/14 7:34:11

一杯奶茶钱,让五个专业设计师帮我做电商主图

我把一个20W的USB充电器插头放在桌上,拍了两张照片,传进了我们平台的AI电商推广。 想看看这支分了五个角色的团队,怎么对付一个没什么设计预算、也没电商经验的新手卖家。 结果有点出乎意料。它先审计了我。 先填表,六个格 打…

2026/10/15 23:06:01

61850客户端软件实战:从协议栈选型到联调避坑

简介:61850客户端软件是面向电力自动化工程师与智能变电站调试人员的一款IEC 61850标准通信工具,用于与保护继电器、测控单元等IED设备对接,完成数据访问、远程监控、配置读取及故障分析等任务。资源包共7个文件,约724KB&#xff…

2026/10/15 23:06:01

2022秋离散数学试卷精讲:谓词逻辑、群论与图论避坑指南

简介:中国地质大学(武汉)2022年秋季学期离散数学期末试卷,试卷类别为A卷,考试方式为开卷笔试,是面向计算机科学与数学专业本科生的典型期末考核资料。PDF完整收录100分试题,覆盖谓词逻辑、集合等势与偏序集、二元关系与…

2026/10/15 23:06:01

三相空载线路合闸过电压的PSCAD仿真与抑制策略对比

三相空载输电线路的合闸过电压,在PSCAD里跑一次仿真并对比几种抑制策略,是电力系统电磁暂态分析里特别典型的一个课题。我之所以说“典型”,是因为它同时覆盖了三个核心关注点:一是空载线路呈现的容性特征如何影响合闸瞬间的暂态过…

2026/10/15 23:01:01

Flutter+SQLite宠物生命周期管理App开发实战:AI辅助从零搭建

1. 项目缘起与整体设计思路1.1 为什么选宠物生命周期管理这个方向做任何一个从零起步的项目,第一步永远不是写代码,而是想清楚"这东西到底给谁用、解决什么痛点"。宠物生命周期管理这个方向,乍看像是个小众赛道,但实际拆…

2026/10/14 11:31:03

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/14 17:41:07

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/15 13:42:16

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/15 0:02:22

防关联浏览器哪个好?三款产品实测差异解析

新手做多店铺,最纠结的就是防关联浏览器选哪个。飞跨、网易、站斧这三款被问得最多,先给结论:要一体化省心、长期便宜,选站斧;要网易招牌和稳,选网易;预算紧做东南亚 Lazada,选飞跨。…

2026/10/15 0:02:22

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成

影刀RPA深度实战:大模型Fine-tuning定制RPA专用Prompt与指令生成 一、技术背景 大模型(LLM)在通用场景下表现强大,但当我们将 GPT-4 或 Qwen 直接用于生成影刀RPA操作指令和 XPath 定位时,准确率通常只有 40%-60%。原因…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑