IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 深入浅出c语言可变参数函数 -> 正文阅读

[C++知识库]深入浅出c语言可变参数函数

????????C语言可变参数函数,有很多介绍。本文只想从C语言和编译器角度,借助一点逻辑思考的方式,深入浅出理解一下可变参数函数的实现。

????????说到可变参数函数,最先想到的就是int printf(const char *format, ...)。刚开始看到可变参数函数感觉特别神奇,为什么这个函数就可以传入的参数就可以是任意个,类型也是任意的。对于固定参数的函数,形参,实参是一一对应的,调用函数的时候,参数一一传递进去,十分容易理解,但是对于可变参数呢,试想你在调用printf传入5个参数,那么printf在执行的时候,它要是想正确的执行你的调用,它必须有办法知道你输入的参数的个数和参数类型,这是关键中的关键。从表面上看printf没有办法知道啊,因为参数不是固定的,它是被调用方,它如何知道调用者输入了几个参数呢。让我们举例来分析:

printf("int?= %d, c?= %c\n, s = %s", 10?, 'A'?,"hello world!");

注意第一个输入参数,是一个字符串"int?= %d, c?= %c\n, s = %s",且称它为*fmt。作为一个C语言的使用者,光从这个字符串,我们很容易推导出,这个printf 有四个输入参数,第一个就是字符串,第二个是整型,第三个是字符型,第四个是字符串。是的,这是我们通过第一个字符串自己推导出来的。因为我们作为使用者知道%是个特殊字符,它和某些字符的结合,如%d,%s,%c,就能确定后面必然有对应的参数输入。好了,既然我们能推导出来,那么计算机自然也会推导的。printf只需要对第一个字符串*fmt进行解析,找出特殊符号组合,根据规则,就可以确定,函数后续输入参数的个数及其类型,这个并不难,就是分析字符串。与固定参数函数的区别在于,对于可变参数函数,想要得到参数,需要自己分析。

????????当然还有其他类型的可变参数函数,如int sum(int count, ...);这个函数可以计算任意个数据之和。可能有人会说,这个和printf不一样,它的第一个参数是个整数,如何确定参数个数和类型。注意了,这个函数的第一个参数,表示后续输入参数的个数,至于参数的类型,那是默认的int型。这样参数的类型和数据都知道了。

????????当然,可变参数还要有很多形式,但是有一点可以肯定,就是参数的数量和个数,要么通过函数的一个参数传递进去,要么就是针对该函数有一套约定的默认规则,函数根据传递的参数或者约定的规则,最终完成参数的解析。

? ? ? ? 说到这里,下一个问题来了,既然知道参数的个数和类型了,怎的得到参数的值呢?这个问题比较复杂,这里我不作展开。对于不同的cpu架构,不同的编译器,底层传参操作遵循的规则是不一样的。比如对于arm的apcs规则,参数数量较少,使用r0~r3来传递参数,参数太多,后续的会使用stack空间来传递参数。而对于x86架构,很明显是没有r0~r3的。幸运的是,一般编译器都帮我们编写好了用于定位参数,取出参数需要用的函数,它们一般定义在头文件<stdarg.h>中:

void?va_start(va_list?ap, last); //移动ap指向第一个参数
type?va_arg(va_list?ap, type);  //返回一个参数值,并将ap指向下一个参数
void?va_end(va_list?ap);        //重置ap
void?va_copy(va_list?dest,?va_list?src);

上述函数通常用宏来实现,例如标准ANSI形式下,这些宏的定义是:

typedef?char?*?va_list;?
//字符串指针
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
//移动ap指向第一个参数
#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
//返回一个参数值,并将ap指向下一个参数
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
//重置ap
#define va_end(ap) ( ap = (va_list)0 )

具体可以参考《C语言可变参数函数详解示例》的介绍。如果需要更深入了解底层的实现可参见《亲密接触C可变参数函数》。

这里我们以一个例子来说明这些函数的使用方法:

#include <stdio.h>
#include <stdarg.h>
 
void foo(char *fmt, ...)
{
    va_list ap;
    int d;
    char c, *s;
 
    va_start(ap, fmt);//移动ap指向第一个参数
    while (*fmt)
    {
        switch(*fmt++)
        {
        case 's': /* 字符串 */
            s = va_arg(ap, char *); //返回一个参数值,并将ap指向下一个参数
            printf("string %s/n", s);
            break;
        case 'd': /* 整数 */
            d = va_arg(ap, int); //返回一个参数值,并将ap指向下一个参数
            printf("int %d/n", d);
            break;
        case 'c' //字符
            c = va_arg(ap, char); //返回一个参数值,并将ap指向下一个参数
            printf("char %c/n", c);
        break;
        }
    }
    va_end(ap);
}

int main()
{
    foo("dcsd",15, 'A',"hello world", 18);
}

执行结果如下:

15
A
hello world
18

这个函数要实现一个可变参数的函数,第一个传入字符串,用于定义后续参数的个数和类型,例中“dcsd”,表示,后面有四个参数,分别是整型,字符型,字符串型,整型。

函数首先调用va_start();将ap指针指向后续第一个输入参数;然后foo函数开始了自己分析参数的过程:

  • 逐个扫描字符
  • 根据不同的字符,确定下一个参数的类型
  • 调用va_arg()获取参数的值,同时将ap指针指向下一个参数
  • 重复此过程,直至扫描完整个字符串

最后重置ap指针——va_end(ap)。

? ? ? ? 如果把函数的调用者比作领导,函数本身比作下属。那么,一般函数的领导,下命令就是逐条列出,思路清晰。而可变参数函数的领导,下的命令就是一锅大杂烩,需要下属自己去抽丝剥茧,逐条理清,或者是命令过于简单,需要下属自己根据日常规则去领悟。

? ? ? ? 总之,理解可变参数函数就两条:

1.如何确定输入参数的个数,以及对应的类型。这个需要函数自己根据规则去分析——自己动手,丰衣足食。

2.如何获取参数的值。必须的底层函数,编译器帮忙封装好了,能做的就是合理调用——按部就班,他山之石,可以攻玉。

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-10-01 16:38:20  更:2021-10-01 16:40:36 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 23:59:18-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码