
函数这东西干我们这行的天天都在写、天天都在用——写程序要看函数查表格要找函数做量化回测还得强调指标里不能有未来函数。可要真问一句“函数到底是什么”很多干了三五年的朋友反而会卡壳能说出来“就是把一段逻辑封装起来反复调用”但再往深一层函数在内存里怎么存的、为什么有的语言里函数能当参数传来传去、为什么Excel里的LEFT和VLOOKUP放一起会出怪问题就未必讲得清了。这篇文章我就把“函数”这个概念从头到尾掰开揉碎从编程语言里的函数、内置函数、箭头函数、回调函数一路讲到办公软件里的常用函数、嵌入式里的中断函数、信号处理里的互相关函数和量化指标里的“无未来函数”。适合刚入行的学生、写业务代码的工程师、玩Excel报表的运营朋友以及自己折腾软硬件项目的爱好者。不管你是哪种身份读完至少能把“函数”这两个字从模糊的感觉变成清晰的知识框架下次再遇到LOCATION的问题也知道往哪儿排查。1. 函数到底是什么从“输入-处理-输出”说起1.1 函数的本质与“JS中函数是对象吗”这个经典疑问我先用一个最朴素的类比。你开了一台榨汁机把苹果从入口放进去按下开关出口流出苹果汁。榨汁机就是函数苹果是输入苹果汁是返回结果。不同榨汁机参数不同有的要切块、有的要加冰对应函数的不同参数有的榨汁机带自动清洗对应函数内部执行一些副作用操作。这个类比能解释大多数场景下的函数但遇到JavaScript社区那个经典问题——“JS中函数是对象吗”——就不够用了。答案是是而且不仅是对象还是“一等公民”。这意味着函数可以像普通变量一样被赋值给变量可以作为参数传给另一个函数也可以作为另一个函数的返回值。// 函数作为对象体现在可以加属性 function greet(name) { return Hello, name; } greet.defaultName World; console.log(greet(greet.defaultName)); // Hello, World // 更关键的是函数可被传递 const fn greet; console.log(fn(Tom));理解了这一点你就能明白为什么JavaScript有函数声明、函数表达式、箭头函数这么多样写法。它们本质上都在做同一件事创建一个函数对象只是声明的时机和this绑定规则有区别。// 函数声明会提升 function add(a, b) { return a b; } // 函数表达式不提升 const sub function(a, b) { return a - b; }; // 箭头函数写法更简洁且不绑定自己的this const mul (a, b) a * b;这里的重点不是背语法而是要建立起“函数是一种值”的心理模型。有了这个模型回调函数、高阶函数、函数组件这些进阶概念就全通了。React里函数组件本质就是一个接收props对象、返回元素的函数这跟C语言里把函数指针传给排序算法如出一辙。1.2 内置函数、自定义函数与“函数是积木”按来源分函数一般两大类内置函数和自定义函数。内置函数是语言或工具自带的比如Python的abs、len、rangeC的sqrt、fabsSQL的MD5、JSON_EXTRACTExcel的VLOOKUP、LEFT——它们解决的是高频通用问题。自定义函数则是你为了解决业务里的特殊逻辑而封装的代码块。我见过不少朋友写代码没有封装习惯一个脚本从头到尾几百行中间重复的代码有七八处。一旦需求变了要去修改这七八处漏改一处就出事。函数真正的价值就是“一次定义、多处复用”你只需要修改函数体所有调用位置自动生效。拿C语言举例判断一个整数是不是偶数这种逻辑写个函数#include stdio.h #include stdbool.h bool is_even(int n) { return n % 2 0; } int main(void) { for (int i 0; i 10; i) { if (is_even(i)) { printf(%d is even\n, i); } } return 0; }把逻辑命名成函数后代码读起来就像一份说明文档main函数里看到is_even(i)就知道这是在判断偶数。这种“自文档化”的价值在项目大了以后特别明显。1.3 参数、返回值和作用域函数的三个核心部件一个函数要正常工作通常关心三件事入参怎么传、结果怎么给、内部变量能不能污染外部。参数方面有个经典细节——C语言的scanf函数签名是int scanf(const char *format [, argument]...);。第二个参数是const char *为什么格式化串要const因为scanf只读取这个字符串不会修改它声明const可以让编译器帮忙检查你有没有往格式化串里写入同时允许传入字符串字面量。这里还牵出另一个问题很多人以为scanf是“带格式的输入”而忽略了它返回的是成功匹配并赋值的参数个数。不检查返回值一旦输入类型不对变量就是未初始化的随机值这是新手最常见的内存安全问题之一。返回值方面有个在C里反复踩的坑函数返回字符串。直接返回局部char数组指针是未定义行为因为函数结束后栈内存已被回收// 错误示范返回指向局部变量的指针 const char* bad() { char buf[64] hello; return buf; // 悬垂指针 }正确做法是返回std::string或者用静态存储区或者让调用者传入缓冲区。这个坑后面我会在排查章节里详细展开。作用域则决定了函数内外变量的可见关系。Python、C、JS都用花括号或缩进建立起作用域规则。函数内部的局部变量默认不污染全局但全局变量在函数内可以被读取和修改——这个特性是方便也是隐患回调函数、闭包、嵌套函数都跟作用域强相关。2. 不同领域函数的实战用法代码、表格、数据与信号2.1 Python定义函数、嵌套调用和几个高频内置函数Python定义函数用def关键字这个大家都会。但有几个细节值得单独说。第一个是input函数多个输入。很多人想一下接收多个值直接写a, b input()结果只能拿到第一个字符。正确做法是配合splitx, y input(请输入两个数字用空格分隔: ).split() x, y int(x), int(y)如果你想让输入更健壮可以写一个小函数来循环处理直到用户输入合法数据def get_two_numbers(): while True: parts input(请输入两个数字: ).split() if len(parts) ! 2: print(必须输入两个数请重新来) continue try: a, b map(int, parts) # map返回迭代器自动拆包 return a, b except ValueError: print(输入的不是整数请重新来) x, y get_two_numbers() print(x y)第二个是嵌套定义和嵌套调用。Python允许在函数内部再定义函数内部函数能访问外部函数的局部变量这个特性叫闭包。嵌套调用则是你在函数A里调用函数BB里再调用C。多层调用时要注意栈深度递归函数如果没写好退出条件会撑爆栈内存。第三个是高频内置函数的真实用途。abs函数返回绝对值处理负数和浮点数都行upper函数用于把字符串转成大写比如做不区分大小写的用户名比对时可以if input().strip().upper() ADMIN。这些工具函数单独看不值钱组合起来就是实用功能。还有exit函数和flush。Python脚本里遇到严重错误要提前退出可以import sys; sys.exit(1)非零状态码表示异常终止。print函数的flush参数则用于强制刷新输出缓冲区在写实时日志或进度条时特别有用import time for i in range(10): print(f\r进度: {i*10}%, end, flushTrue) time.sleep(0.2)如果不加flushTrue你会看到输出“卡顿”或者挤成一坨因为标准输出默认是块缓冲的只有缓冲区满或者程序结束才真正写出来。2.2 JavaScript箭头函数、回调函数与函数组件JS的函数体系非常灵活。普通函数function箭头函数回调函数套在一起时新手容易晕。我建议记住一条主线凡是“某个动作完成后要执行的函数”就是回调函数。比如数组的forEach、map、filter参数里都放一个回调const nums [1, 2, 3, 4, 5]; const doubled nums.map((n) n * 2); console.log(doubled); // [2, 4, 6, 8, 10] const evens nums.filter((n) n % 2 0); console.log(evens); // [2, 4]箭头函数写法上比function表达式简练但它不绑定自己的this会从外层作用域继承this。在React函数组件里这个特性配合Hooks就很好用——把一个组件定义成一个接收props的函数内部用useState、useEffect等Hooks管理状态代码比类组件直白得多。这里还有个跟“内置函数”相关的高频操作JSON查询函数。在JS里没有独立的JSON查询函数但JSON.parse和JSON.stringify是处理JSON的标准工具。想在嵌套对象里安全取属性可以封装一个小的查询函数function safeGet(obj, path, defaultVal null) { return path.split(.).reduce((acc, key) (acc acc[key] ! undefined ? acc[key] : defaultVal), obj); } const user { profile: { name: Tom, address: { city: Hangzhou } } }; console.log(safeGet(user, profile.address.city)); // Hangzhou console.log(safeGet(user, profile.age, 18)); // 18这不单单解决“查数据”的问题也体现了一个重要思路内置函数不够用时用自定义函数补齐语义。2.3 C/C与嵌入式中断函数、滤波函数、回调与零拷贝嵌入式里用GCC编译CH32V芯片时中断函数的定义方式跟普通函数不一样。普通函数是调用者用call指令跳进来中断则是硬件触发时自动进入。为了正确返回并恢复现场需要告诉GCC这是一个中断处理函数#include ch32v30x.h void TIM1_UP_IRQHandler(void) __attribute__((interrupt(WCH-Interrupt-fast))); void TIM1_UP_IRQHandler(void) { if (TIM_GetITStatus(TIM1, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM1, TIM_IT_Update); // 处理定时中断逻辑 } }这里的__attribute__((interrupt))是GCC对中断函数的标准扩展能保证函数保存必要寄存器并用iret类指令返回。如果你忘了加这个属性中断返回后寄存器可能被破坏程序直接跑飞。嵌入式里另一个高频需求是对ADC采样值做滤波。C语言里最常用的是滑动平均滤波#define FILTER_LEN 8 static uint16_t buffer[FILTER_LEN]; static uint8_t index 0; static uint32_t sum 0; uint16_t adc_filter(uint16_t new_sample) { sum - buffer[index]; buffer[index] new_sample; sum new_sample; index (index 1) % FILTER_LEN; return (uint16_t)(sum / FILTER_LEN); }滑动平均的优点是平滑度高、响应快缺点是对脉冲干扰不够敏感。如果信号里毛刺很多可以先做一次中值滤波再去滑动平均。这种滤波函数在写传感器采集逻辑时几乎是标配。再说C里的几个“函数名”热词。vector函数其实不是函数而是vector容器的方法比如push_back、size、empty。sort函数的用法在C和Java里都要注意排序稳定性C的sort不保证稳定要稳定排序得用stable_sortJava的Collections.sort进入Java 7以后用的TimSort保证稳定。fun函数在C语境里常指functor仿函数——用一个类重载operator()让对象能像函数一样被调用这在STL算法里非常常见#include vector #include algorithm #include iostream struct Greater { bool operator()(int a, int b) const { return a b; } }; int main() { std::vectorint v {4, 1, 8, 3}; std::sort(v.begin(), v.end(), Greater()); for (int x : v) std::cout x ; }Linux层面的select函数和sendfile/splice函数是另一个热门话题。select是早期的IO多路复用函数可以同时监控多个文件描述符的就绪状态sendfile和splice则用于零拷贝数据传送——正常读文件再写socket数据要从内核态拷贝到用户态再拷回去而sendfile让内核直接把数据从文件描述符送往socket省掉中间拷贝。做个简单的文件传输函数可以这样// 用sendfile实现零拷贝文件发送 #include fcntl.h #include sys/sendfile.h #include sys/socket.h #include unistd.h ssize_t sendfile_all(int out_fd, int in_fd, off_t *offset, size_t size) { ssize_t sent 0; while ((size_t)sent size) { ssize_t ret sendfile(out_fd, in_fd, offset, size - (size_t)sent); if (ret 0) { return -1; } if (ret 0) break; sent ret; } return sent; }还有LD_PRELOAD劫持这个技术热词。LD_PRELOAD是Linux动态链接器的一个环境变量它允许在程序启动时预先加载指定共享库。很多人借此“替换”系统中的标准函数——比方说写一个自己的ptrace函数在别人程序执行ptrace调用时插入自己的逻辑。这通常用于系统调用追踪、兼容问题诊断、调试程序崩溃等合法用途。它让我印象最深的一点是“函数在动态链接层面也是可替换的”这对理解函数解析机制很有帮助。2.4 Excel与SQL函数组合的注意事项办公领域函数的热度不亚于编程。Excel里最有名的组合之一是LEFT函数和VLOOKUP函数一起用。很多同学想用LEFT提取编号前几位再去VLOOKUP查询结果老是查不到原因通常有三个。第一个原因是类型不一致。LEFT返回的是文本哪怕它返回的内容全是数字也是文本型数字。VLOOKUP的查找值如果是数值型的单元格就匹配不上文本型结果。解决办法是查完之后用--LEFT(...)或者VALUE(LEFT(...))把它转回数值。第二个原因是查找值里有不可见字符。LEFT提取的字符串可能带空格或者其他非打印字符此时用TRIM函数包一层再查。第三个是通配符问题。当LEFT的结果包含星号或问号时VLOOKUP会把它当成通配符处理导致匹配错乱。这种情况下改用VLOOKUP的精确匹配第4个参数写FALSE还不够最好把查找值里的~转义或干脆用INDEX加MATCH。VLOOKUP(VALUE(LEFT(A2, 5)), 数据表!$A:$B, 2, FALSE)新版Excel还出了个REGEXEXTRACT函数支持用正则表达式提取文本。做日志清洗时这个函数非常好用比如提取一串文本里所有手机号。但它只在新版Excel和WPS部分版本里支持老版本还得用MID加FIND的组合或者直接用Power Query。要注意版本兼容别在共享报表里写了一个别人电脑上根本无法执行的新函数。SQL侧MySQL函数也是高频话题。MD5加密函数做数据脱敏很方便SELECT MD5(password123);JSON函数也是现代MySQL的重要能力JSON_EXTRACT可以取JSON字段里的值配合JSON_TABLE可以实现更复杂的查询SELECT JSON_EXTRACT(info, $.name) AS name FROM users WHERE JSON_EXTRACT(info, $.age) 18;有些人把“select函数”理解成MySQL里的SELECT关键字其实SELECT是SQL语句不是函数。如果是Python里那个select函数那是IO多路复用模块。这两个名字撞在一起经常闹误会排查报错前先把概念分清楚能省很多时间。2.5 数据与信号处理损失函数、互相关函数、拟合函数和无未来函数函数的概念在数据分析和信号处理里同样无处不在。深度学习里YOLO损失函数就是一个典型的多目标损失组合定位损失坐标框的回归、置信度损失判断框里有没有目标、分类损失判断目标类别三部分加权求和组成最终训练目标。理解损失函数怎么把模型输出和真实标签的差异变成可优化的数值是理解深度学习的关键一步。信号处理领域有相位噪声和互相关函数。相位噪声描述的是振荡器输出信号在频域上的相位抖动工程上用单边相位功率谱密度衡量。要测量极低噪底的相位噪声常用互相关法用两套独立的测量链路分别测量同一个被测源的相位噪声然后做互相关运算。因为两套链路的内部噪声互不相关经过多次平均后会相互抵消被测源的相位噪声则因为同源相关而保留下来。这个原理简洁漂亮也解释了为什么互相关函数在信号检测里地位这么高。海洋水文里的波浪拟合函数则让我想起了谐波叠加法。理想波面可以被看作很多个不同频率、振幅、相位的余弦函数叠加h(t) Σ Ai·cos(ωi·t φi)通过频谱分析从实测波面数据中提取出各频率成分的振幅和初相再用这些参数拟合出波浪时间序列。这套思路跟“函数逼近”是一回事用一组基函数的线性组合去逼近复杂信号。最后是量化交易里高频出现的“无未来函数”。所谓未来函数指的是某些技术指标在计算当前周期数值时偷偷使用了未来周期的数据导致回测时信号看起来很准实盘却完全失效。典型例子是用未来K线的高低价去计算当前均线的上下穿越。真正的波段高低点指标必须严格只用当前及之前的数据常用的做法是先识别已经确认的局部极值点再画线段连接。这个约束听起来简单实际写指标时很容易在不经意间踩雷。3. 函数实战演练三套可以抄作业的代码3.1 用Python封装一个“多输入数据清洗日志输出”的函数综合前面讲的input多输入、abs、upper、flush等知识点我写一个实用的小demo让用户输入姓名和大写金额校验并规范化后输出。def normalize_name(name): name name.strip() return name.title() # 首字母大写 def parse_amount(text): 把用户输入的金额文本转成正数。 text text.replace(,, ).replace(¥, ).strip() if text.startswith(-): print(f警告金额输入为负已取绝对值) amount abs(float(text)) else: amount float(text) return round(amount, 2) def process_one(): name normalize_name(input(请输入姓名: )) amount parse_amount(input(请输入金额: )) print(f{name}: {amount:.2f}, flushTrue) return name, amount entries [] for _ in range(3): entries.append(process_one()) print(处理完成共录入, len(entries), 条数据)这个小函数组合里process_one调用了normalize_name和parse_amount是嵌套调用parse_amount里用了abs和float内置函数flushTrue保证每一条录入都实时显示。它是前面所有知识点的汇总。3.2 用JavaScript写一个简单的查找/映射函数再写一个JS版通过对象数组模拟Excel查找同时展示数组内置函数和自定义函数的配合。const staff [ { id: 101, dept: 研发, salary: 15000 }, { id: 102, dept: 市场, salary: 12000 }, { id: 103, dept: 运维, salary: 13000 }, ]; function findStaff(id) { return staff.find((item) item.id id) || null; } function calcBonus(allStaff, ratio 1) { return allStaff .filter((item) item.dept ! 管理) .map((item) ({ id: item.id, bonus: item.salary * ratio })) .reduce((acc, cur) acc cur.bonus, 0); } const target findStaff(102); console.log(target); console.log(总奖金, calcBonus(staff, 0.2));这里find、filter、map、reduce都是内置函数它们接受的回调函数又可以是我自定义的箭头函数。一个统计总奖金的需求用函数式写法六行就完成了。换成命令式for循环虽然也能写但读起来的抽象层级明显不同。3.3 用Excel公式实现文本拼接后的数据匹配最后给一个Excel组合公式例子。假设A列是订单编号格式类似“ORD-2025-001”B表里有订单详情我想提取“ORD-2025-001”中最后的3位数字作为短编号再匹配短编号对应的客户。VLOOKUP(VALUE(RIGHT(A2, 3)), 短编号表!$A:$C, 3, FALSE)要注意的是如果A列不是统一格式提取位置不一样需要先用FIND函数定位“-”的位置再RIGHT提取一句话公式会变成VLOOKUP(VALUE(RIGHT(A2, LEN(A2) - FIND(-, A2, FIND(-, A2) 1))), 短编号表!$A:$C, 3, FALSE)这种嵌套公式写起来麻烦但由于Excel不是编程语言咱们很多时候只能用这种“函数套函数”的方式表达逻辑。我的建议是如果嵌套超过三层先在空白单元格里逐层验证中间结果再合并成最终公式不要一次性写完。否则出了问题你根本不知道是哪层参数错了。4. 常见函数化报错与排查技巧实录4.1 pip无法识别cmdlet、函数、脚本文件或可运行程序这个报错几乎每个刚装Python的人都会遇到。完整提示是“pip : 无法将“pip”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写……”。它跟函数没关系但天天见我顺手拆一下。这句报错背后的原因九成是pip.exe所在目录没有加进系统的Path环境变量。安装Python时如果没勾选“Add Python to PATH”pip就只在Python安装目录的Scripts子目录里躺着PowerShell和cmd都找不到它。最直接的解法不是手动去改环境变量而是以后都用python -m pip install 包名这个形式。因为只要python命令能找到python -m pip就一定能运行它绕开了pip命令本体的路径问题。如果你想彻底解决再把Python安装目录下的Scripts目录加到Path里重启终端就生效。4.2 CLion无法跳转到函数定义处CLion是基于CMake的IDE它跳转函数靠的是代码索引。跳转失效的原因按可能性排序CMake没有重新加载、项目没编译过导致没有生成compile_commands.json、文件被排除了索引范围、索引还在后台构建中。我的处理顺序是先等右下角索引进度条跑完再执行File → Reload CMake Project让它重新生成编译数据库还不行的话Build → Rebuild Project强制编译一次最后检查该项目文件是否被右键标记成“Excluded”。如果某个源码文件没有被任何CMake target引用CLion默认不建索引这时候跳转自然失效。这个问题最容易被忽视尤其是测试代码或者脚本工具类文件。4.3 VMware Workstation报“在部分链上无法执行所调用的函数请打开父虚拟磁盘”这个报错涉及的是虚拟机磁盘快照链。VMware的虚拟磁盘常常由父vmdk和多个子vmdk组成子磁盘依赖父磁盘。如果某个快照的父vmdk文件被移动、改名、删除或者被清理工具误删启动虚拟机时就会报你这个错误。我建议先不要反复点击“打开”而是去虚拟机目录里检查.vmx文件引用的vmdk文件名是否存在再借助vmware-vdiskmanager命令行工具检查磁盘链完整性vmware-vdiskmanager.exe -R F:\vm\Windows10\Windows10.vmdk-R参数会尝试修复磁盘链。如果提示父磁盘路径失效要找到正确的父vmdk位置并重新指定或者选择放弃某个损坏快照。整个过程操作前记得备份vmdk宁可多留一份也不要在没有备份的情况下去修复链因为修复失败可能连现有磁盘都打不开。4.4 LVGL打印函数重复定义LVGL在嵌入式GUI项目里很常用新手经常遇到“lv_log_print_text重复定义”或者“printf重复定义”的链接错误。原因通常是LVGL内部已经实现了一个日志打印函数而你又在自己的代码里重定向printf到串口两边同名冲突。解决方案有两个。一个是在lv_conf.h里关掉LVGL自带日志LV_USE_LOG设置为0。另一个是保留LVGL日志但把输出回调注册成你自己的打印函数lv_log_register_print_cb(my_printf);看到“重复定义”先别急着删代码弄清楚冲突的是哪个符号再决定是关闭功能还是改注册回调。4.5 C函数返回字符串的悬垂指针问题前面讲过函数里直接返回局部char数组会得到悬垂指针。这是典型的静态分析工具能抓出来的问题。最佳实践是用std::string返回值#include string std::string build_message(const std::string name) { return Hello, name !; }C11以后std::string的移动语义让返回代价很低参考opencv的solvepnp这类库函数的用法也能看到它们普遍使用输出参数和智能指针来规避裸指针问题。如果你维护的旧代码非要返回const char*至少用static局部变量同时心里清楚这个返回值下一次调用就会被覆盖不能长期持有。4.6 sizeof到底是不是函数要不要头文件严格说sizeof是运算符不是函数。它对类型和表达式都能求大小。那为什么有人问“sizeof函数需要头文件”其实sizeof本身不需要头文件但你如果写sizeof(int)代码里用了size_t类型时需要包含stddef.h或stdio.h之类的标准头文件否则size_t未定义会报错。#include stdio.h #include stddef.h int main() { size_t n sizeof(int); double arr[10]; printf(int: %zu bytes, array: %zu bytes\n, n, sizeof(arr)); return 0; }如果你把sizeof当成函数去记忆就会误以为要include某个“函数声明”的头文件。这是概念不清导致的连锁反应。4.7 回调函数与事件驱动系统常见问题回调函数写多了以后容易遇到两个问题回调里抛异常没人接回调里修改共享状态导致数据竞争。在JS里回调函数配合闭包还会产生经典的“循环中打印i”问题for (var i 0; i 5; i) { setTimeout(function() { console.log(i); // 实际全部打印 5 }, 100); }原因就是var声明的作用域是函数级所有回调共享同一个i回调执行时循环已经跑完。解决办法是把var改成let让每次循环有独立的块级作用域或者用闭包立即执行函数传参。这个问题你理解了“函数是对象、闭包捕获的是变量引用而非值”之后就完全通透。5. 我对函数思维的理解函数的使用范围远远超出编程语言本身。Excel公式是函数SQL的聚合函数是函数数学里的平方根、对数、余弦是函数信号处理里的互相关函数、损失函数是函数连波浪拟合都需要一组基函数。所有这些东西的底层思想高度一致把输入映射到输出封装行为组合复用。我在实际项目里发现一个规律代码写得乱的人往往不是语法不会而是“函数拆分的度”没把握好。拆得太碎满屏小函数读起来反而费劲拆得太粗一个函数几百行参数十几二十个改起来心惊胆战。没有万能标准我的经验贴是如果一个函数超过50行或者缩进超过三层或者你发现自己要给这个函数写很长的注释才能说明白它干了什么就说明该拆了。反过来一个函数只有一两行、只被调用一次它存在的意义也存疑。排查函数相关问题时我也攒了个小习惯先看调用点传进来的参数是否符合函数预期再看函数内部有没有提前return的路径最后才怀疑函数本身。很多诡异问题的源头不在函数体内部而在调用方——比如把文本型字符串当成数值传进去了或者回调函数在错误的事件循环里执行。函数只是个加工机器你得先检查送进去的料对不对再怀疑机器本身。函数这话题越往里挖越觉得有意思。无论是上古的C语言还是现代的框架函数始终是把复杂问题拆小、把独立逻辑复用的最基础手段。掌握函数概念不只是学会一个语法更是建立一种“输入-处理-输出”的思考方式。回到文章开头那个榨汁机的比喻你真正要学的是知道苹果要先切块、知道温度不能太高、知道出汁口要接杯——这些经验就是在一次次写函数、调函数、修函数的过程中攒下来的。