news 2026/10/5 3:26:51

函数到底是什么?从基础概念到工程实战一次讲透

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
函数到底是什么?从基础概念到工程实战一次讲透

函数这个词,我在写代码、处理数据、做算法调优的这几年里几乎天天碰。它不是什么高深的概念,但你拆开任何一个软件项目、一条SQL查询、甚至一张Excel表格,底层全是函数在撑场面。最近逛技术社区,跟函数相关的帖子特别密集:有人问pnpm为什么提示“无法将项识别为cmdlet、函数”,有人在折腾VSCode里C++函数跳转失效,还有人在研究SVM核函数参数、YOLO损失函数、openCV的solvePnP这些偏门方向。看起来问题五花八门,但往深处捋一捋,都落在同一根线上——我们怎么定义函数、怎么理解函数的机制、怎么在不同场景里用好函数,以及踩到函数相关的坑时怎么快速脱身。这篇文章就是把这些散落的点串起来,从函数最底层的逻辑讲到工程里的高阶用法,适合刚开始学编程的初学者,也适合写了几年业务代码但没系统捋过函数概念的开发者参考。

1. 先说清楚函数到底是什么:一个贯穿所有技术领域的通用抽象

很多人的第一个误区,是把函数当成编程语言专属的概念。实际上,函数是一种“输入-处理-输出”的通用抽象,它在Excel公式、SQL语句、股票指标、信号处理算法里都存在,只是长相和叫法不同。我经常用榨汁机打比方:你把水果(输入)塞进去,榨汁机按设定好的程序处理,然后吐出果汁(输出)。函数做的事情完全一样,只不过输入输出不一定是水果,可能是数字、字符串、数组、图像,甚至另一个函数。

1.1 从“重复劳动”说起:函数的本质是封装与复用

函数最核心的存在价值,是把一段逻辑封装起来,起个名字,然后随处调用。没有函数的代码会长什么样?假设你在一个项目里需要三次校验用户输入是否合法,如果不用函数,就要把同一段判断逻辑复制粘贴三遍。第一次写可能没问题,等产品经理说“校验规则要加一个空格过滤”,你得找出三处一模一样的代码改三遍,漏改一处就是线上事故。用函数的话,你只需要改一个地方,所有调用点自动生效。

这个场景放到生活里更容易理解。你做饭的时候,如果每个菜都从头琢磨火候、调料、下锅顺序,大概率翻车。但如果你有菜谱,第一次学会后存进脑子,以后每次只要报出菜名就能照方抓药。函数就是代码世界里的菜谱:定义一次,反复使用,而且能传不同的“食材”进去,得到对应的“成品”。

命名这件事也特别重要。一个叫check_collision的函数,你看名字就知道它做碰撞检测;一个叫send_order_notification的函数,你知道它用来发送订单通知。好的函数名本身就是注释,比大段解释性注释有用得多。我见过一些老项目里全是func1、func2这种命名,维护起来真的头大——函数把逻辑拆碎了,如果没有清晰的名字,拆得越碎越难读。

1.2 不同语言里函数的“长相”:同一件事,几种写法

函数的底层都是“输入-处理-输出”,但不同技术场景里写起来差异很大,我整理了一张表方便对比:

场景/语言函数的典型写法特点
Pythondef greet(name): return "Hello, " + name缩进控制块,动态类型,写起来最随性
JavaScriptconst greet = (name) => \Hello, ${name}``函数是一等公民,能当参数传递、当返回值返回
C/C++int add(int a, int b) { return a + b; }声明和实现分离,类型严格,要管头文件
SQLROW_NUMBER() OVER (PARTITION BY dept ORDER BY salary DESC)面向集合的计算,函数作用在一组行上
Excel=VLOOKUP(key, table, col, 0)内建函数直接操作单元格区域,人人可用

看清了这些差异,你以后遇到任何领域里的“函数式”概念,第一反应就不会是“这个我不会”,而是“我先看看它的输入是什么、输出是什么、内部大概做了什么”。比如热词里提到的股票SLOPE函数,本质就是把一段行情数据输入进去,输出一个线性回归斜率;MATLAB里的S函数,就是Simulink里定义模块行为的自定义函数接口。套路都是通的。

2. 函数的定义与声明:动手写第一个函数前必须搞懂的差异

我见过不少新手在Python里写函数很顺畅,一转到C++就懵了,再碰到JavaScript又觉得写法奇怪。其实不是函数变难了,而是每种语言对“定义”这件事的约束不同。这一部分把最常见的几种定义方式拆开讲,顺带解决几个高频误会。

2.1 Python函数:最友好的“入门款”

Python定义函数太省心了,一个def加上缩进就完事。默认参数、可变参数、关键字参数这些机制,能把很多复杂调用场景简化。我写个实际点的例子,它输入考试成绩列表,输出统计结果:

def analyze_scores(scores, threshold=60): passed = [s for s in scores if s >= threshold] return { "total": len(scores), "passed": len(passed), "fail": len(scores) - len(passed), "max": max(scores), "min": min(scores), "avg": sum(scores) / len(scores), }

调用一次就返回完整统计,不用每次写循环。这函数里有几个值得留意的点:默认参数threshold=60允许调用方不传第二个参数;返回值用字典,把多个结果打包在一起;函数内部用列表推导式做过滤,比手写循环更简洁。

顺带解答热词里的其中一个:“python中upper函数有什么用”。str.upper()是字符串对象自带的内置方法,作用是把所有英文字母转大写。实际开发里我经常拿它做输入规范化,比如用户注册时填的邮箱,统一转小写再存库,避免大小写不一致导致重复账号。

2.2 C/C++函数:声明、定义、模板,一个都不能少

C/C++为什么不学Python那样直接写函数体?因为它要编译链接。编译器在编译一个.c文件时,只看到了这个文件里的代码,如果你调用了别的文件实现的函数,必须先在当前文件里看到它的声明,否则编译器不知道函数存在。所以头文件里放声明,.cpp文件里放定义,链接阶段再把所有东西拼到一起。

提到头文件,很多人问“sizeof函数需要头文件吗”。注意,sizeof在C/C++里是运算符,不是函数,它不需要任何头文件。但你要用strlen、printf这些,就必须包含对应头文件,否则编译器报隐式声明或直接编译失败。这个区分挺典型——很多“函数”其实只是看起来像函数,底层是编译器内置行为。

C++里还有个好东西叫函数模板,它解决的是“同一套逻辑要支持多种类型”的问题:

template<typename T> T square(T x) { return x * x; } // 用法 int a = square(5); double b = square(3.14);

编译时编译器会分别为int和double生成两份代码。这就是为什么函数模板的定义通常也放在头文件里——编译器在调用点需要看到完整实现才能实例化。

还有一个高频问题:C++函数怎么返回字符串?最稳妥是返回std::string按值返回,移动语义会处理拷贝问题,别返回局部const char*指针,函数一结束那块内存就悬空了。

嵌入式方向的同学可以再往下看。C语言里做ADC值滤波,典型的做法是写一个滑动平均滤波函数:

#define FILTER_WINDOW 8 static int adc_filter(int new_value) { static int buffer[FILTER_WINDOW]; static int index = 0; static int sum = 0; sum -= buffer[index]; buffer[index] = new_value; sum += buffer[index]; index = (index + 1) % FILTER_WINDOW; return sum / FILTER_WINDOW; }

这个函数用静态数组保存最近8次采样值,每次都返回平均值。它是“有状态”的函数,靠static变量记住历史数据,这在嵌入式裸机环境里很常见。

2.3 JavaScript箭头函数与函数声明到底差在哪

热词里专门有人问“箭头函数写法”,其实箭头函数不只是写法更短,它和普通函数有本质区别。

// 函数声明 function double(x) { return x * 2; } // 箭头函数 const double = (x) => x * 2; // this指向差异 const obj = { value: 42, regular: function() { return this.value; }, arrow: () => this.value }; console.log(obj.regular()); // 42 console.log(obj.arrow()); // undefined,箭头函数的this来自定义时的外层作用域

箭头函数没有自己的this,它会捕获定义时所在上下文的this。这既是优点也是坑:在对象方法里如果用箭头函数,this不会指向对象本身;但在类字段里定义事件回调,箭头函数反而能避开this丢失问题。给三个实用判断标准:函数需要自己的this用普通函数;需要动态绑定this用普通函数;回调、简短纯计算逻辑用箭头函数。

3. 函数的高级机制:回调、闭包、函数对象与函数组件

如果只说定义和调用,函数只发挥了一半价值。真正让函数成为现代编程核心的,是它作为“值”可以被传递、被返回、被组合。很多初学者卡在回调函数和闭包上,这一部分我把它们彻底讲透。

3.1 JS里函数真的是对象,长期被误解的一等公民

热词里有“js中函数是对象吗”,答案是:是,也不完全是。typeof function(){}返回'function',但函数是Function构造函数的实例,Function又继承自Object,所以函数本质上是带有可调用行为的对象。正因为它是对象,它可以被赋值给变量、存入数组、作为对象的属性,还能给函数挂属性。

function greet(name) { return `Hello, ${name}`; } greet.language = 'en'; // 给函数挂属性 console.log(greet.language); // en const callWith = (fn, value) => fn(value); console.log(callWith(greet, 'Alice')); // Hello, Alice const makeCounter = () => { let count = 0; return () => ++count; }; const counter = makeCounter(); console.log(counter()); // 1 console.log(counter()); // 2

最后这个makeCounter就是闭包。内层匿名函数把外层的count变量夹带在自己的作用域链里,即使makeCounter执行结束,count也不会被回收。闭包的本质就是“函数记住了它出生时的环境”。

我在实际项目里用闭包最多的场景是私有状态管理——不想把变量暴露到全局,用闭包包一层就行。这种模式在防抖、节流函数里也大量出现,比如一个throttle函数内部保存上次执行时间,返回的新函数每次触发都会对比时间差。

3.2 回调函数:把函数当参数传出去的绝妙设计

回调函数的概念很简单:函数A接收函数B作为参数,等某个时机到了再调用B。这种设计把控制权反转给调用方,是异步编程的基石。最典型的场景是数组方法:

const items = [1, 2, 3, 4, 5]; const even = items.filter(num => num % 2 === 0); // [2, 4] const doubled = items.map(num => num * 2); // [2, 4, 6, 8, 10] const total = items.reduce((sum, num) => sum + num, 0); // 15

filter、map、reduce本身是函数,它们的参数是另一个函数,决定了对每个元素做什么。这就是“高阶函数”——接收或返回函数的函数。

回调的坑主要在异步场景。Node.js早期的fs.readFile(path, callback),回调层层嵌套会变成著名的“回调地狱”:

fs.readFile('a.txt', (err, dataA) => { fs.readFile('b.txt', (err, dataB) => { fs.readFile('c.txt', (err, dataC) => { // 再继续... }); }); });

现在用Promise和async/await解决,回调仍然存在,只是被语言层面的机制管理了。所以我建议初学者先把回调本身理解透,再去看Promise,不然老是觉得“为什么我有.then还要写回调”。

3.3 函数组件与内置函数:在前端与语言基础里的不同角色

热词里的“函数组件”,指的是React里的Function Component。老版本的React里函数组件只能做纯展示,没法管状态,所以叫“无状态组件”;React 16.8之后Hooks出现,函数组件也能用useState、useEffect,反而成了主流写法:

function Greeting({ name }) { const [count, setCount] = useState(0); return ( <div> <h1>Hello, {name}</h1> <button onClick={() => setCount(count + 1)}> Clicked {count} times </button> </div> ); }

函数组件本质就是“接收props对象,返回界面结构”的函数,它把UI表达成纯函数的形态,好测、好复用。理解了函数是一等公民后,再看React组件也会通。

内置函数则是另一类容易被忽略却天天在用的函数——语言或平台自带,不需要你自己写。Python里的abs、len、min、max,C标准库的printf、memcpy,JavaScript的parseInt、JSON.stringify,这些全是内置函数。内置函数的意义在于开箱即用,减少自造轮子。但有个不自觉的坑:很多人懒得查文档,自己造了质量不行的轮子,比如手写字符串拼接去拼JSON,不如直接用内置函数。

4. 藏在数据世界里的函数:从Excel到SQL再到股票指标

函数不只是写代码时才用。处理数据、做报表、分析行情,每一步都在跟各种各样的函数打交道。这一部分讲三个非常实际的数据处理场景,它们分别代表表格工具、数据库和金融分析里的函数玩法。

4.1 Excel函数实战:效率手册里那些“不加班”的秘密

网上有本很有名的书叫《早做完,不加班:Excel函数应用效率手册》,里面全是Excel函数的实战案列。我自己的体会是,Excel函数是把“从重复劳动里解放出来”这句口号执行得最彻底的场景。大部分人的日常报表工作,无非是求和、条件统计、匹配数据、提取信息,这些都有现成的函数。

需求函数写法说明
求和=SUM(A1:A100)最基础统计
条件计数=COUNTIF(B1:B100,"已完成")统计状态数量
查找匹配=VLOOKUP(D2,员工表!A:B,2,0)按工号匹配姓名
正则提取=REGEXEXTRACT(A2,"[0-9]{11}")从混合文本中提取11位手机号

VLOOKUP是出镜率最高的查找函数,最后一个参数0代表精确匹配,很多事故都是因为漏了这个0变成模糊匹配。REGEXEXTRACT在新版Excel和WPS里都有,用于按正则规则提取文本——我常用它从地址列里提取手机号或身份证号,比手工复制高效太多。

Excel函数的核心理念是“结果随数据自动更新”。你写好了公式,明天数据源更新,输出自动跟着变。这就是函数式思维在表格工具里的体现:把一次性手工操作变成可复用的计算规则。

4.2 MySQL开窗函数:分组计算的神器

MySQL从8.0版本开始支持窗口函数,也叫开窗函数(Window Function)。它在数据处理领域的重要性怎么强调都不过分,因为传统GROUP BY会把多行压成一行,而窗口函数可以在不减少行数的情况下,给每一行带上聚合计算的上下文。

看一个典型需求:统计每门课程的学生排名。

SELECT student_name, subject, score, ROW_NUMBER() OVER (PARTITION BY subject ORDER BY score DESC) AS subject_rank FROM exam_scores;

PARTITION BY subject把数据按科目分成多个“窗口”,ORDER BY score DESC在每个窗口内按分数排序,ROW_NUMBER()给每个窗口内的行编号。结果里每个学生都保留自己的行,只是多了一列排名,后续想筛出每科前三名,套一层子查询就行。

开窗口函数四个高频函数是:ROW_NUMBER()严格编号,遇到相同分数也分先后;RANK()相同分数并列但会跳号,比如两个并列第二,下一个就是第四;DENSE_RANK()相同分数并列但不跳号;还有配合OVER使用的SUM()/AVG()/MAX()聚合函数,可以做“累计求和”“移动平均”这类分析。我在数据报表里用得最多的就是SUM(amount) OVER (PARTITION BY user_id ORDER BY order_date),直接得到每个用户的累计消费曲线。

4.3 股票SLOPE函数与JSON查询函数:专业场景下的“小切口”

热词里有个“股票slope函数使用及效果图”。股票软件里的SLOPE和Excel里的SLOPE原理一样,计算一组数据的线性回归斜率。它的输入是一段时间序列(比如最近20天的收盘价),输出是拟合直线的方向系数——斜率大于0说明趋势向上,小于0说明趋势向下,斜率的绝对值反映出涨跌速度。

[\text{slope} = \frac{n\sum(xy) - \sum x \sum y}{n\sum(x^2) - (\sum x)^2}]

这个函数我建议当成“趋势判断辅助工具”而不是“买点信号”。原因是回归斜率只看数值走势,不看成交量、消息面,任何单一指标都有滞后性。想用它做策略,通常会和均线、MACD放在一起确认信号。

JSON查询函数则是在数据库里处理JSON字段时必用的。MySQL里常用JSON_EXTRACT配合JSON_UNQUOTE取值:

SELECT JSON_UNQUOTE(JSON_EXTRACT(user_info, '$.name')) AS name FROM users WHERE id = 1;

JSON_EXTRACT返回的是带引号的JSON字符串,JSON_UNQUOTE把引号去掉变成普通文本。处理嵌套对象和数组时,就用$.items[0].price这种路径语法。现在很多业务数据直接存JSON,用好这些内置查询函数,比把JSON字段查出来到代码里再解析省事得多。

5. 算法与工程场景中的“高级函数”都在解决什么问题

前面说的都是相对“通用”的函数,到了算法和专用工程领域,函数的概念会延伸成损失函数、核函数、位姿解算、信号相关性分析这些专业术语。它们本质上也是“输入-处理-输出”,只不过专精于某个目标。

5.1 损失函数:机器学习模型自省的那把尺子

损失函数(Loss Function)是机器学习里判断“模型当前做得有多差”的函数。训练模型的过程,本质上就是不断调整参数让损失函数值变小。热词里提到0-1损失函数和YOLO损失函数,正好代表了两个极端:0-1损失最直观,预测对了记0,错了记1,但它不连续、不可导,梯度下降用不了,所以实际训练里用得少;YOLO这种目标检测模型的损失函数,则是把多个子任务拼在一起的组合损失。

import torch import torch.nn.functional as F def yolo_simplified_loss(pred, target): # 边界框回归损失,常用MSE或CIoU box_loss = F.mse_loss(pred["box"], target["box"]) # 分类损失,多分类用交叉熵 cls_loss = F.cross_entropy(pred["cls"], target["cls"]) # 置信度损失,二分类用带logits的BCE obj_loss = F.binary_cross_entropy_with_logits( pred["obj"], target["obj"] ) return box_loss + 0.5 * cls_loss + 1.0 * obj_loss

这里不同子损失配不同权重,是因为检测框坐标的误差和分类误差的量级不一样,直接相加会导致某个任务主导训练。损失函数选得好不好,直接决定模型收敛速度和最终精度——这个属于机器学习的底层“工程直觉”,光看不练是学不会的。

5.2 核函数:SVM从低维到高维的“作弊器”

机器学习里的支持向量机(SVM)经常提到核函数。它的作用一句话:让原本在低维空间线性不可分的数据,映射到高维空间变得线性可分,而且不需要显式计算高维映射结果。

举个例子,二维平面里有一堆点,圆圈和叉号交错分布在某个半径范围内,你想画一条直线把它们分开是不可能的。但映射到三维空间后,一个平面就能把它们切开。核函数的巧妙之处在于,它直接算出“映射后向量的内积”,绕开了“先映射再点积”的计算开销,这就是核技巧(Kernel Trick)。

热词里“optdigits手写数字分类中SVM核函数与参数的影响研究”,指的是用SVM识别手写数字,比较不同核函数和参数的效果。实践结论我直接给:RBF高斯核通常是默认首选,因为它能处理非线性边界;线性核适合特征维度很高、样本量很大的场景;多项式核参数多、调起来麻烦,一般不用。RBF核有两个关键超参数——C和gamma:

参数偏低时偏高时
C欠拟合,分类面过于平滑过拟合,噪声被强记忆
gamma每个样本影响范围大,决策面平滑决策面紧贴训练样本,泛化变差

调参建议用GridSearchCV做交叉验证,别靠肉眼猜。一个小规律:C从0.1到100之间搜,gamma从0.001到1之间搜,用对数刻度取值。

5.3 图像与信号处理中的函数们:solvePnP、相位噪声与互相关函数

OpenCV里的solvePnP第一次看到会觉得陌生,但它在AR增强现实、机器人视觉、无人机定位里是核心函数。它的任务是:已知空间中一些3D点的坐标,以及它们对应的2D像素坐标,求解相机的旋转向量和平移向量,也就是“相机在什么位置、朝向哪里”。

import cv2 import numpy as np object_points = np.array([ [0, 0, 0], [0.1, 0, 0], [0.1, 0.1, 0], [0, 0.1, 0], ], dtype=np.float32) image_points = np.array([ [320, 240], [380, 245], [378, 295], [318, 292], ], dtype=np.float32) camera_matrix = np.array([ [800, 0, 320], [0, 800, 240], [0, 0, 1], ], dtype=np.float32) dist_coeffs = np.zeros((4, 1)) success, rvec, tvec = cv2.solvePnP( object_points, image_points, camera_matrix, dist_coeffs )

至少要给4组对应点才能解出唯一位姿。返回的rvec是旋转向量,想变成3x3旋转矩阵要用cv2.Rodrigues。实际项目里标定板上的角点坐标就是天然稳定的3D-2D对应点对,所以我经常用ArUco码标定板作为solvePnP的输入。

信号处理领域,热词里的“相位噪声”和“互相关函数”也是函数应用的经典案例。相位噪声是衡量信号源短期频率稳定度的指标,测量时常用互相关法:把同一信号源接到两套独立测量系统,分别采集,然后计算两路噪声的互相关函数,因为系统自身的随机噪声会在互相关中被抵消,只剩下信号源的真实相位噪声。互相关函数的数学形式就像“两个信号延迟滑动后计算乘积和”,峰值位置对应时延估计,这在雷达、通信同步里非常常用。

6. 函数开发中的常见报错与排查实录

这一部分全是实战现场。函数写得多了,各种离奇报错都会遇到,我把最近高频出现在社区里的几类问题集中复盘一遍,能帮你省不少排查时间。

6.1 命令行工具全局失效:并不是函数的问题

热词里好几条都是这个格式:“pnpm : 无法将‘pnpm’项识别为 cmdlet、函数、脚本文件或可运行程序的名称”。报错里提到了“函数”,其实它是PowerShell对命令类型的描述,包括别名、函数、脚本文件、可运行程序。看到这个,99%的情况是PATH环境变量里没配对应工具路径。

排查顺序很固定。以pnpm为例:

# 1. 确认node/npm本身安装正常 node -v npm -v # 2. 查出npm全局包的安装位置 npm config get prefix

如果prefix输出的是C:\Users\你的用户名\AppData\Roaming\npm,那就把%APPDATA%\npm加进PATH。临时生效:

$env:PATH += ";C:\Users\你的用户名\AppData\Roaming\npm"

永久生效:

[Environment]::SetEnvironmentVariable( "PATH", [Environment]::GetEnvironmentVariable("PATH", "User") + ";C:\Users\你的用户名\AppData\Roaming\npm", "User" )

git、make、claude命令行工具报同样错误,只是一个道理:要么没安装,要么装了但bin目录不在PATH里。所以排查时先用where.exe 工具名看看系统到底能不能找到,找不到再看PATH,别急着重装。

6.2 VSCode中函数和变量无法跳转的排查路径

“vscode c++所有的函数 变量 都没办法跳转”,这种场面我遇到过好几回,大多不是代码有问题,而是VSCode的IntelliSense没找到该找的东西。

先分清情况:如果整个项目几百个文件只有几个文件跳转失败,可能是文件本身的宏没触发正确的代码分支;如果所有文件全都跳不了,重点检查C/C++插件的配置。

我的排查路径:

  1. 确认装了微软官方的C/C++扩展。
  2. 打开命令面板,运行“C/C++: Edit Configurations (UI)”。
  3. 检查includePath是否包含系统头文件目录(Windows下一般是安装MSVC或MinGW对应的include目录)和项目的第三方库头文件目录。
  4. 如果是CMake项目,最好让VSCode读取compile_commands.json。在c_cpp_properties.json里加一行:
{ "name": "Linux", "compileCommands": "${workspaceFolder}/build/compile_commands.json" }

这样IntelliSense能拿到每个文件的真实编译参数,跳转基本就准了。 5. 如果配置都对还是失效,运行“C/C++: Reset Database”清掉索引缓存,重载窗口。

VSCode右下角出现“已暂停的索引”也要留意,后台在索引大项目时CPU占满,跳转会暂时失灵,等几秒就好,不用瞎折腾。

6.3 printf汉字乱码与LVGL函数重复定义的现场修复

Linux下通常没事,Windows下“printf函数中输入汉字乱码”特别常见。根源就一句话:源文件编码和控制台代码页不一致。源文件是UTF-8、控制台默认GBK时,UTF-8的中文字节被GBK逐个解码,就成了“铟╂╂”这种乱码。

我在Windows上做测试时给三种解决办法,按推荐顺序来:

  1. 编译选项加/utf-8(MSVC),让编译器认为源文件是UTF-8,同时字符集也按UTF-8处理。
  2. 运行前在控制台执行chcp 65001,把代码页切到UTF-8。
  3. 源文件保存为带BOM的UTF-8,MSVC会按UTF-8读取,不容易被本地化代码页干扰。

如果是要在日志里输出中文,我建议直接把日志系统换成宽字符接口或干脆编码成UTF-8写文件,别依赖控制台显示。

“lvgl 打印函数 重复定义”是嵌入式GUI开发里典型的链接错误。现象是编译时报multiple definition of 'log_message'。原因几乎都是同一个:函数定义放进了头文件,而头文件被多个.c文件包含,链接器看到多份同名函数定义就炸了。

正确做法分两种,我直接给你对比:

// 错误示例:头文件里直接定义函数 // debug.h void log_message(const char *msg) { printf("%s\n", msg); } // 正确做法1:声明和定义分离 // debug.h void log_message(const char *msg); // debug.c #include "debug.h" void log_message(const char *msg) { printf("%s\n", msg); } // 正确做法2:头文件里必须定义时,加static inline static inline void log_message(const char *msg) { printf("%s\n", msg); }

static inline让每个包含该头文件的编译单元都保留一份内部副本,链接时不产生冲突,代价是会增加一点代码体积。在LVGL这种资源紧张的嵌入式项目里,我还是倾向声明和定义分离,能看到清晰的函数接口列表,也方便后续做日志开关控制。

我个人带过不少新人,发现一个特别有意思的现象:同样是学函数,有人觉得难,有人觉得简单,差距往往不在智商,而在于有没有把函数当成“输入到输出的映射”去看。你理解了这一点,再去看Excel那些公式、SQL那些开窗函数、JS里的箭头函数,甚至YOLO的损失函数,都会有一种“原来都是一个套路”的豁然。所以如果只能留下一句话,我想说的是——不要被各种语言里函数的花样吓住,先把“输入是什么、输出是什么、这个函数到底在算什么”想清楚,你就已经赢了大半。剩下那些报错,一个一个查,都是成长的踏脚石。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/5 3:26:42

JQuick-Excel dateFormat 转换实战:日期值与 FORMAT 显示格式的边界

JQuick-Excel dateFormat 转换实战&#xff1a;日期值与 FORMAT 显示格式的边界 tags: #JQuickExcel #Java #Excel日期 #DSL 简介 日期字段是 Excel 导入导出中最容易产生误解的字段之一&#xff1a;用户看到的格式、单元格实际保存的值、下游接口要求的文本可能并不相同。JQui…

作者头像 李华
网站建设 2026/10/5 3:26:10

Sql Server随机查询一条记录:从NEWID到存储过程封装实践

开头最近在做一个题库系统的小功能&#xff0c;客户提了个很实在的需求&#xff1a;每次考试从题库里随机抽一道题&#xff0c;而且抽题逻辑要能复用——以后不仅抽题要用&#xff0c;抽奖品、抽幸运用户、做数据抽查可能都要用。说白了就是一个“Sql Server里随机查询一条表记…

作者头像 李华
网站建设 2026/10/5 3:25:31

Python独立双样本t检验与z检验:场景选择、代码实现与避坑指南

做Python数据分析的人&#xff0c;十有八九都遇到过这种需求&#xff1a;手里有两组数据&#xff0c;可能是A/B测试跑出来的两个版本&#xff0c;可能是两个门店同一时期的营业额&#xff0c;也可能是两种治疗方案下患者的指标变化&#xff0c;业务方丢给你一句话&#xff1a;“…

作者头像 李华
网站建设 2026/10/5 3:24:53

Flutter for OpenHarmony实战:分类管理功能开发全流程解析

开源生态里做跨端开发&#xff0c;最头疼的往往不是业务逻辑本身&#xff0c;而是平台适配那一层看不见的坑。Flutter for OpenHarmony这组合&#xff0c;我断断续续折腾了小半年&#xff0c;从工程跑不起来到最终在真机上稳定运行&#xff0c;中间踩过的坑比写业务代码的时间还…

作者头像 李华
网站建设 2026/10/5 3:24:20

Ubuntu 20.04 连 WiFi 全攻略:图形界面与命令行配置及驱动排查

简介&#xff1a;这份PDF资料面向刚安装Ubuntu 20.04却无法连接Wi-Fi、系统托盘缺少无线图标的用户&#xff0c;尤其适合Linux入门者与运维新手排查网卡驱动缺失或网络配置错误的问题。资源包共1个PDF文件&#xff0c;大小约27KB&#xff0c;内容以图文步骤形式整理&#xff0c…

作者头像 李华