手把手教你实现 C 语言的函数多参默认值 「上」

news2024/11/20 18:37:37

以下内容为本人的学习笔记,如需要转载,请声明原文链接 微信公众号「ENG八戒」https://mp.weixin.qq.com/s/nj0C9SbAuzUOPt_J1n8B_Q

自从上一篇关于 C 语言单个参数函数的默认值实现《C语言函数也可以给形参添加默认值?》发表以来,有很多的同学反馈想知道多参数函数的默认值又该如何实现,今天特地整理相关代码和实现思路说明如下。

由于 C 语言本身是不提供函数默认值功能和相关语法的,为了实现函数参数的默认值,归根到底是对原有函数参数的自动填充,填充的值就是默认值。

那么如何填充参数值,又不会影响程序性能呢?可以使用宏定义的灵活性在代码的编译预处理期做一些替换操作,本文和《C语言函数也可以给形参添加默认值?》文章的基本思路类似,但是实现上会繁琐一些,最终的目标是实现从单参数默认值向多参数默认值迭代升级。

设想一下总体思路:

由于带有默认值的函数在使用时,形式上输入的参数个数是不定的,所以需要计算函数实际输入参数的个数,然后针对未输入的参数填充默认值。所以这里提出两个问题,一个是如何计算函数实际输入参数的个数,另一个是如何对参数填充默认值?

接下来将对提出来的问题逐一破解。

计算函数实际输入参数的个数

我们先来定义一个带有 2 个输入参数的目标函数 _fun2():

void _fun2(int val1, int val2)
{
    printf("fun inputs val1:%d, val2:%d\n", val1, val2);
}

参考《C语言函数也可以给形参添加默认值?》文章的基本思路,定义一个变长参数宏 fun2() 代表目标函数 _fun2(),原来调用目标函数 _fun2() 的语句将变成直接调用宏 fun2(),而目标函数 _fun2() 间接被调用。

但是由于我们的思路里多了一个因素—函数实际输入参数的个数,所以变长参数宏 fun2() 和目标函数 _fun2() 之间还需要多一层转换,下面统称为转换函数 _funs(),函数实际输入参数(调用宏 fun2() 时输入的参数)的个数作为参数输入到转换函数 _funs(),目标函数 _fun2() 在转换函数 _funs() 内部被调用。

#define fun2(...)           _funs(ARGC(__VA_ARGS__), 123, 456, ##__VA_ARGS__)

void _funs(int real_param_num, ...)
{
    ...
    // _fun2(val1, val2);
}

宏定义 ARGC() 用于计算函数实际输入参数的个数,123 和 456 作为目标函数 _fun2() 的参数默认值。real_param_num 是转换函数 _funs() 的第一个参数,用于接收变长参数宏 fun2() 被调用时输入的参数个数。

另外,鉴于视乎实际需求,目标函数的参数也可以是 2 之外的其它数目,所以为了便于扩展转换函数 _funs() 的应用范围,需要输入目标函数的完整参数个数,继续迭代转换函数 _funs()。

#define fun2(...)           _funs(2, ARGC(__VA_ARGS__), 123, 456, ##__VA_ARGS__)

void _funs(int param_num_max, int real_param_num, ...)
{
    ...
    // _fun2(val1, val2);
}

用变长参数宏 fun2() 代表有 2 个输入参数的目标函数 _fun2(),param_num_max 指定目标函数的完整参数个数。

ARGC 如何实现?

为什么使用宏定义来计算函数实际输入参数的个数?

由于 C 运行过程中确认参数个数会消耗系统资源,并降低程序运行效率,所以尽量采用在编译预处理时计算的宏定义。

暂定参数个数上限是 2 个,用宏定义预处理对参数的展开来统计参数个数,涉及占位符的妙用(相信会令你开眼界):

#define __ARGS(X) (X)
#define __ARGC_N(_0,_1,N,...) N
#define __ARGC(...) __ARGS(__ARGC_N(__VA_ARGS__,2,1))
#define ARGC(...)       __ARGC(__VA_ARGS__)

其中 _0, _1,… 是预处理占位符,用于在宏定义展开参数时逐个匹配参数,_0 匹配第一个参数,_1 匹配第二个参数,以此类推,最终 N 用于匹配 __ARGC_N(__VA_ARGS__,2,1) 后边的逆序数字 2 或者 1,这个 N 就是最终计算所得的参数个数。

测试一下对参数个数的计算效果:

printf("ARGC() arg num=%d\n", ARGC());
printf("ARGC(1) arg num=%d\n", ARGC(1));
printf("ARGC(1, 2) arg num=%d\n", ARGC(1, 2));

针对上面的调用,为了更好理解求值过程,让我们尝试一下手动对宏调用 ARGC(1) 展开看看:

ARGC(1)
__ARGC(1)
__ARGS(__ARGC_N(1,2,1))
(__ARGC_N(1,2,1))       ---->  (__ARGC_N(1,2,N))
(1)                     ----> N = 1

ARGC(1) 应该返回 1。

完整测试结果输出:

ARGC() arg num=1
ARGC(1) arg num=1
ARGC(1, 2) arg num=2

为什么 ARGC() 输入 0 个参数时,计算结果不为 0,而输入其它数量的参数计算结果符合预期呢?计算过程还有待改进。

你可以自己手动展开一下 ARGC() 看看,当 ARGC() 输入 0 个参数时,__ARGC_N(__VA_ARGS__,2,1) 后边的逆序数字数量不足以匹配 N,此时的 N 就强制匹配最后一个数字 1 了。为了解决这种异常匹配,当 N == 1 时,可以追加对第一个输入参数是否为空的判断:

#define __ARGC_N(_0,_1,N,...) N==1?(#_0)[0]!=0:N

#_0 用于将第一个参数转换成字符串,会占用一点空间,但不影响程序运行效率。(#_0)[0] 提取字符串的第一个字节。当 N == 1 时,如果第一个参数是空字符,(#_0)[0]!=0 返回 false,否则返回 true。false 转换成 0,true 转换成 1。

最终测试结果输出:

ARGC() arg num=0
ARGC(1) arg num=1
ARGC(1, 2) arg num=2

如果参数上限是 3 个,又怎么修改呢?

#define __ARGS(X) (X)
#define __ARGC_N(_0,_1,_2,N,...) N==1?(#_0)[0]!=0:N
#define __ARGC(...) __ARGS(__ARGC_N(__VA_ARGS__,3,2,1))
#define ARGC(...)       __ARGC(__VA_ARGS__)

__ARGC_N(__VA_ARGS__,3,2,1) 后边的逆序最大数字 3 就决定了参数的上限个数为 3。当然修改参数上限,__ARGC_N 宏定义输入参数中的占位符 _0,_1,_2 个数需要对应参数上限的个数,占位符从 _0 开始。

如果输入的参数超过了上限呢?

printf("ARGC(1, 2, 3) arg num=%d\n", ARGC(1, 2, 3));
printf("ARGC(1, 2, 3, 6) arg num=%d\n", ARGC(1, 2, 3, 6));
printf("ARGC(1, 2, 3, 6, 9) arg num=%d\n", ARGC(1, 2, 3, 6, 9));

结果输出:

ARGC(1, 2, 3) arg num=3
ARGC(1, 2, 3, 6) arg num=6
ARGC(1, 2, 3, 6, 9) arg num=6

输出的参数个数就等于超出上限的第一个参数值,不过,有言在先,参数个数有上限,而且合理设计的函数参数不应该过多,不必过度计较了。


未完待续,关注我查看更多精彩内容


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1609676.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

构建Python中的分布式日志系统:ELK与Fluentd的结合

👽发现宝藏 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 在现代软件开发中,日志系统是至关重要的组成部分。它们不仅用于故障排查和性能监…

跟TED演讲学英文:How AI could empower any business by Andrew Ng

How AI could empower any business Link: https://www.ted.com/talks/andrew_ng_how_ai_could_empower_any_business Speaker: Andrew Ng Date: April 2022 文章目录 How AI could empower any businessIntroductionVocabularyTranscriptSummary后记 Introduction Expensiv…

MySQL与Redis缓存一致性的实现与挑战

缓存是提高应用性能的重要手段之一,而 MySQL 和 Redis 是两种常用的数据存储和缓存技术。在许多应用中,常常将 Redis 用作缓存层,以加速对数据的访问。然而,在使用 MySQL 和 Redis 组合时,保持缓存与数据库之间的一致性…

使用 Flask 和 Flask-Login 构建用户认证的 Web 应用程序

在本篇技术博客中,我们将学习如何使用 Flask 框架和 Flask-Login 扩展构建一个具有用户认证功能的简单 Web 应用程序。我们将从创建 Flask 应用实例开始,然后逐步添加用户认证功能。 1. 安装依赖库 首先,确保您已经安装了 Flask、Flask-PyM…

【题解】NC398 腐烂的苹果(多源BFS)

https://www.nowcoder.com/practice/54ab9865ce7a45968b126d6968a77f34?tpId196&tqId40529&ru/exam/oj 从每个腐烂的苹果开始使用广度优先遍历&#xff08;bfs&#xff09; class Solution {int n, m;int dx[4] {0, 0, 1, -1};int dy[4] {1, -1, 0, 0};vector<v…

[AI OpenAI-doc] 微调

学习如何为您的应用程序定制模型。 介绍 微调允许您通过提供以下内容&#xff0c;从 API 提供的模型中获得更多收益&#xff1a; 比提示更高质量的结果能够训练比提示中能容纳的更多示例由于提示更短而节省的标记更低的延迟请求 OpenAI 的文本生成模型已经在大量文本上进行…

ElasticSearch可视化工具:kibana + elasticsearch-head

kibana 下载 地址&#xff1a;https://www.elastic.co/cn/downloads/kibana 下载别的版本&#xff1a;https://www.elastic.co/cn/downloads/past-releases#kibana 将Kibana安装包解压缩 进入config目录&#xff0c;在kibana.yml中添加es服务器地址。&#xff08;如果之前没…

实在Agent:超自动化智能体的革命(附导引指南)

在自动化技术的浪潮中&#xff0c;实在智能推出了实在Agent&#xff08;智能体&#xff09;&#xff0c;一款基于大语言模型和屏幕语义理解技术的超自动化智能体。它通过自然对话交互&#xff0c;将复杂工作简化为一句话指令&#xff0c;自动规划并执行工作任务&#xff0c;极大…

链表 - OJ(超清晰思路+实现)

目录 题目一-移除链表元素&#xff08;来源&#xff09; 题目描述 思路实现 思路一 &#xff08;双指针&#xff09; 思路二&#xff08;虚拟头节点-哨兵位&#xff09; 题目二-反转链表&#xff08;来源&#xff09; 题目描述 思路实现 思路一&#xff08;双指针&#…

【Linux】帮助类命令

在Linux中&#xff0c;man用于查看系统手册页&#xff08;manual pages&#xff09;。它用于查阅关于特定命令、函数、工具或文件格式的详细信息。要使用man命令&#xff0c;只需在终端中输入man&#xff0c;后跟您要查看的命令或主题的名称。 例如&#xff0c;如果查看ls命令…

fastjson转换json时默认将属性第一个字母转小写

描述&#xff1a; 我新建了一个实体类&#xff0c;但是实体类的首字母是大写的&#xff0c;但是使用fastjson后打印的&#xff0c;Json字符串首字母却是小写的&#xff0c;这是fastjson的一个bug 实体类&#xff1a; Json字符串&#xff1a; 解决方法&#xff1a; 一、使…

securecrt 批量登录服务器介绍

一、前言 在有一些IT环境中&#xff0c;可能存在各种情况的服务器&#xff0c;因为各种原因不能统一部署类似ansible、saltstack等批量操控软件&#xff0c;当遇到需要对这些服务器进行某项信息的排查或调整配置时&#xff0c;你是否还是通过securecrt一台一台登录后进行操作&a…

java算法day59 | 单调栈part02 ● 503.下一个更大元素II ● 42. 接雨水

503.下一个更大元素II 思路&#xff1a; 相比于单纯寻找下一个最大元素&#xff0c;要遍历两边数组&#xff0c;注意i%nums.length。 class Solution {public int[] nextGreaterElements(int[] nums) {int[] resnew int[nums.length];for(int i0;i<res.length;i){res[i]-1;…

Microchip逆市扩张,接连收购2家公司

尽管年初传来降薪停工的消息&#xff0c;全球领先的半导体解决方案供应商Microchip并未因此停下扩张的脚步。相反&#xff0c;该公司在短短的一个月内&#xff0c;接连宣布收购两家公司&#xff0c;展现了其坚定的市场布局和前瞻的战略眼光。 4月11日&#xff0c;Microchip成功…

2024年开通最新版云开发cms步骤,开始开发微信小程序前的准备工作,认真看完奥!

小程序官方有改版了&#xff0c;搞得石头哥不得不紧急的再新出一版&#xff0c;教大家开通最新版的cms网页管理后台 一&#xff0c;技术选型和技术点 1&#xff0c;小程序前端 wxml css JavaScript MINA原生小程序框架 2&#xff0c;数据库 云开发 云数据库 云…

(四)相关性分析 学习简要笔记 #统计学 #CDA学习打卡

目录 一. 相关性分析简介 二. 相关性分析方法 1&#xff09;连续型变量vs连续型变量&#xff1a;Pearson/Spearman &#xff08;a&#xff09;Pearson &#xff08;b&#xff09;Spearman等级相关系数 2&#xff09;二分类变量&#xff08;自然&#xff09;vs连续型变量&…

C++奇迹之旅:深入理解赋值运算符重载

文章目录 &#x1f4dd;赋值运算符重载&#x1f320; 运算符重载&#x1f309;特性 &#x1f320; 赋值运算符重载&#x1f320;传值返回&#xff1a;&#x1f320;传引用赋值&#xff1a;&#x1f309;两种返回选择&#x1f309;赋值运算符只能重载成类的成员函数不能重载成全…

【YOLOv8改进[Backbone]】使用MobileNetV3助力YOLOv8网络结构轻量化并助力涨点

目录 一 MobileNetV3 1 面向块搜索的平台感知NAS和NetAdapt 2 反向残差和线性瓶颈 二 使用MobileNetV3助力YOLOv8 1 整体修改 ① 添加MobileNetV3.py文件 ② 修改ultralytics/nn/tasks.py文件 ③ 修改ultralytics/utils/torch_utils.py文件 2 配置文件 3 训练 其他 …

在瑞芯微RV1126 Linux系统上调试WiFi的详细指南

目录标题 1. **系统和环境准备**2. **检查WiFi设备状态**3. **启用和禁用WiFi接口**4. **扫描可用的WiFi网络**5. **连接到WiFi网络**6. **查看当前的WiFi连接状态**7. **断开和重新连接WiFi**8. **管理WiFi网络配置**9. **使用iw工具进行高级WiFi调试**10. **故障排除和日志获…

C#基础|Debug程序调试学习和技巧总结

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 在程序的开发过程中&#xff0c;可能绝大部分时间是用来调试程序&#xff0c; 当完成了某个功能的编程&#xff0c;都需要调试一下程序&#xff0c;看编程是否存在问题。 01 为什么需要程序调试 无论是电气工程师还…