【C语言】可变参数列表

news/2024/7/21 21:20:18 标签: c语言, c++, 算法

本篇博客让我们来认识一下C语言学习过程中往往被忽略的可变参数列表

所谓可变参数,就是一个不限定参数数量的函数,我们可以往里面传入任意个数的参数,以达成某些目的。

关联:C++11可变模板参数;本文首发于 慕雪的寒舍

1.函数

#include <stdarg.h>

void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);

1.1 va_start

void va_start(va_list ap, last_arg);
  • ap: 这是一个 va_list 类型的对象,它用来存储通过 va_arg 获取额外参数时所必需的信息
  • 这个函数的作用是初始化 ap 变量,它与 va_argva_end 函数一起使用。
  • last_arg 是最后一个传递给函数的已知的固定参数,即省略号之前的参数

要想设置一个带可变参数的函数,函数声明是下面这样的

void test(int a,int b, ...);

这里出现的省略号就是可变参数的特征,而变量b就是va_start函数需要的last_arg

1.2 va_arg

type va_arg(va_list ap, type);

这个函数的作用是来提取可变参数列表中的参数。注意,每次提取的参数是直接返回的,并没有放到变量ap中。

每次对va_arg的调用都会修改ap,以便下次调用时,返回下一个参数;推断参数的时候需要指定type,如果当前参数类型和type不统一,就会发生不可预知的错误(man手册里面说的)

If ap is passed to a function that uses va_arg(ap,type) then the value of ap is undefined after the return of that function.

如果ap被传递给va_arg(ap,type),则在该函数返回后,ap的值未定义。

1.3 va_end

void va_end(va_list ap);

每一个va_start都需要有一个配套的va_end,其用于清空ap

可以把他俩的关系理解为malloc/free,记得加上就行

1.4 va_copy

这个函数的作用是将可变参数列表从第二个参数src拷贝到第一个参数dest

void va_copy(va_list dest,va_list src);

其也能够初始化dest。调用了va_copy后,无须调用va_start初始化dest,但va_end还是需要的。

2.简单示例

2.1 打印多个参数

#include<stdarg.h>
#include<stdio.h>
int print(int num_args,...)
{
    va_list ap;
    va_start(ap,num_args);//初始化可变参数
    for(int i=0;i<num_args;i++)
    {
        printf("%d ",va_arg(ap,int));
    }
    printf("\n");
    va_end(ap);//结束对ap的使用
}

int main()
{
    print(5,1,2,3,4,5,6,7,8,9);
    return 0;
}

运行该函数,会打印如下结果

$ ./test
1 2 3 4 5 

这就表明了,...省略号之前的参数,和va_arg返回可变参数其实是没有关系的。

int print(int num_args,...)
{
    va_list ap;
    va_start(ap,num_args);//初始化可变参数
    for(int i=0;i<8;i++)
    {
        printf("%d ",va_arg(ap,int));
    }
    printf("\n");
    va_end(ap);//结束对ap的使用
}

int main()
{
    print(5,1,2,3,4,5,6,7,8,9,10);
    return 0;
}

即便在最后都没有使用num_args,也不会影响结果的正确性。va_start需要这个参数,其实是用来标识可变参数的起点。

$ ./test
1 2 3 4 5 6 7 8 

2.2 多参数求和

#include<stdarg.h>
#include<stdio.h>
// 采用可变参数,第一个参数用于标识参数数量
int sum(int num_args, ...)
{
   int val = 0;
   va_list ap;
   int i;

   va_start(ap, num_args);
   for(i = 0; i < num_args; i++)
   {
      val += va_arg(ap, int);
   }
   va_end(ap);
 
   return val;
}

void test1()
{
    printf("10、20 和 30 的和 = %d\n",  sum(3, 10, 20, 30) );
    printf("4、20、25 和 30 的和 = %d\n",  sum(4, 4, 20, 25, 30) );
}

运行如下

$ ./test
10、20 和 30 的和 = 60
4、20、25 和 30 的和 = 79

3.利用可变参数实现log类

现在有了可变参数,我们就可以接用这个参数来进行日志的打印了

#pragma once

#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>

#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3

const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};

// 采用可变参数列表
void logging(int level, const char *format, ...)
{
    assert(level >= DEBUG || level <= FATAL);

    char *name = getenv("USER");// 获取环境变量中的用户(执行命令的用户)

    char logInfo[1024];
    // 获取可变参数列表
    va_list ap; // ap -> char*
    va_start(ap, format);

    vsnprintf(logInfo, sizeof(logInfo)-1, format, ap);

    va_end(ap); // ap = NULL

    // 根据日志等级选择打印到stderr/stdout
    FILE *out = (level == FATAL) ? stderr:stdout;
    // 格式化打印到文件中
    fprintf(out, "%s | %u | %s | %s\n", \
        log_level[level], \
        (unsigned int)time(nullptr),\
        name == nullptr ? "unknow":name,\
        logInfo);
}

3.1 vsnprint

作用:使用vsnprintf()用于向一个字符串缓冲区打印格式化字符串,且可以限定打印的格式化字符串的最大长度。

此函数需要C99或者C++11及以上版本才能支持。

int vsnprintf(char* sbuf, size_t n, const char* format, va_list arg);
  • 第一个参数:目标缓冲区(字符数组)
  • 第二个参数,限定最多打印到缓冲区的字符数量为n-1个(留位置给\0
  • 第三个参数,打印的格式(如%d:%s
  • 第四个参数,可变参数arg,需要用va_start初始化

返回:成功打印到sbuf中的字符的个数,不包括末尾追加的\0。如果格式化解析失败,则返回负数。

用这个函数,就能把我们的来源字符串给输入到缓冲区char logInfo[1024];

3.2 fprintf

使用fprintf,将printf的输出打印到指定文件中;用法和printf是一样的

int fprintf(FILE *stream, const char *format, ...);

这样是为了区分stderr/stdout。同时添加上执行命令的用户信息,以及当前的时间戳

    fprintf(out, "%s | %u | %s | %s\n", \
        log_level[level], \
        (unsigned int)time(nullptr),\
        name == nullptr ? "unknow":name,\
        logInfo);

3.3 运行结果

int main()
{
    logging(DEBUG, "socket create success: %d", 114514);
    logging(FATAL, "socket:%s:%d", strerror(errno), 11234);
    return 0;
}
$ ./test
DEBUG | 1675322313 | muxue | socket create success: 114514
FATAL | 1675322313 | muxue | socket:Success:11234

The end

对于可变参数的简单介绍就到这里!基本的使用能看懂久OK啦!


http://www.niftyadmin.cn/n/61805.html

相关文章

数据库(第一天)

文档信息 文档类别正式文档文档编号数据库基础课 1.2-001版本1.2-001文档名称数据库基础课编写负责人/编写时间梁昭东/2023 年 1 月 30 日审核负责人/审核时间年 月 日批准人/批准时间年 月 日 变更记录 日期版本号变更内容修订者2023.01.30v1.2版根据实际情况增删了部分内容…

vue3 Proxy响应式原理分析(面试题)

在开始正文前&#xff0c;先理一下vue2 Object.defineProperty 和 vue 3 Proxy 区别&#xff1a; Object.defineProperty&#xff1a;数据劫持 Proxy&#xff1a;数据代理 注意&#xff1a; 响应式原理和双向数据绑定原理是两回事&#xff0c;一般面试官会先问响应式原理再问双…

SQL注入Getshell的奇思妙想(下)

前言 由于笔者发现大量的hr面试官都喜欢从SQL注入开始询问&#xff0c;所以留心了一下关于SQL注入的问题的频率。结果非常amazing啊&#xff01;不出意外的是–os- shell名列榜首。 但是啊由于我们不能被面试官所引导&#xff0c;这样太被动啦&#xff01;我们应该引导hr问我们…

linux的三权分立设计思路和用户创建(安全管理员、系统管理员和审计管理员)

目录 一、三权分立设计思路 1、什么是三权 2、三员及权限的理解 3、三员之三权 4、权限划分 5、“三员”职责 6、“三员”配置要求 二、linux三权分立的用户创建 1、系统管理员 2、安全管理员 3、审计管理员 一、三权分立设计思路 1、什么是三权 三权指的是配置、…

【C++设计模式】学习笔记(5):Decorator 装饰模式

目录 简介动机(Motivation)模式定义结构(Structure)要点总结笔记结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有错误的地方,欢迎您指出~ ଘ(੭ˊᵕˋ)੭ 昵称:海轰 标签:程序猿|C++选手|学生 简介:因C语言结识编程,随后转入计算机专业,获得过国家奖学金…

Prometheus 告警规则

Prometheus 告警规则 Prometheus官方内置的第三方报警通知包括&#xff1a;邮件、 即时通讯软件&#xff08;如Slack、Hipchat&#xff09;、移动应用消息推送(如Pushover)和自动化运维工具&#xff08;例如&#xff1a;Pagerduty、Opsgenie、Victorops&#xff09; Promethe…

[动手写操作系统]-01-开机运行系统

文章目录 **概念和目标**概念目标理论源码概念和目标 概念 assembler: 汇编程序BIOS: BIOS(Basic Input Output System,基本输入输出系统)是个可编程的微型操作系统,用于管理计算机中的软硬件,它控制着系统的启动,系统是如何连接外部设备,怎样响应,调整相应操作,都是…

自启动管理 - Win10

自启动管理 - Win10前言关闭开机自启方案1&#xff1a;在软件中设置方案2&#xff1a;在任务管理器设置方案3&#xff08;不推荐&#xff09;&#xff1a;通过注册表管理方案4&#xff1a;通过第三方工具管理工具1&#xff1a;360安全卫士工具2&#xff1a;Autoruns工具3&#…