C语言 va_end 宏

Posted 猿说编程

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C语言 va_end 宏相关的知识,希望对你有一定的参考价值。

目录

零基础 C/C++ 学习路线推荐 : C/C++ 学习目录 >> C 语言基础入门

一.前言

printf 函数的使用,我们并不陌生,首先我们来看看下面关于 printf 函数的几种调用方式:

printf("hello world");
printf("%s","hello world");
printf("%s %s","hello world","C语言教程-猿说编程");
printf("%d",3);
printf("%d %d",3,6);
printf("%d %d %d ",1,2,3);

那么 printf 函数的原理到底是什么?

由于在 C 语言中没有函数重载,解决不定数目函数参数问题变得比较麻烦,即使采用 C++,如果参数个数不能确定,也很难采用函数重载。对这种情况,提出了指针参数来解决问题。

C 编译器通常提供了一系列处理这种情况的宏,以屏蔽不同的硬件平台造成的差异,增加程序的可移植性。这些宏包括 va_startva_argva_end 等,一般三者需要配套使用。

#include <stdarg.h>

#define va_start __crt_va_start
#define va_arg   __crt_va_arg
#define va_end   __crt_va_end

二.va_end 简介

va_end 函数声明如下:

#include <stdarg.h>//必须包含头文件

/*
*   清空参数列表, 并置参数指针arg_ptr无效.
*/
va_end(arg_ptr)

三.va_end 使用

va_startva_end 一般需要配套使用,示例如下:

/******************************************************************************************/
//@Author:猿说编程
//@Blog(个人博客地址): www.codersrc.com
//@File:C语言教程 - C语言 va_end 宏
//@Time:2021/07/10 08:00
//@Motto:不积跬步无以至千里,不积小流无以成江海,程序人生的精彩需要坚持不懈地积累!
/******************************************************************************************/

#include "stdafx.h"
#include <stdio.h>
#include <stdarg.h>

void myPtintf(char *format, ...)
{
	va_list args;

	va_start(args, format);
	vprintf(format, args);
	va_end(args);
}

int main()
{
	myPtintf("hello world\\n");
	myPtintf("%s\\n", "hello world");
	myPtintf("%s %s\\n", "hello world", "C语言教程-猿说编程");
	myPtintf("%d\\n", 3);
	myPtintf("%d %d\\n", 3, 6);
	myPtintf("%d %d %d \\n", 1, 2, 3);

	printf("");

	return(0);
}


输出:
hello world
hello world
hello world C语言教程-猿说编程
3
3 6
1 2 3
*/

注意:在自定义 printf 函数myPtintf 中,我们使用的是 vprintf 函数而非 printf 函数,注意 vprintf 函数和 printf 函数区别;

四.猜你喜欢

  1. C 语言 数组下标越界和内存溢出区别
  2. C 语言 使用指针遍历数组
  3. C 语言 指针和数组区别
  4. C 语言 指针数组和数组指针区别
  5. C 语言 野指针
  6. C 语言 函数值传递和址传递
  7. C 语言 函数不定长参数
  8. C 语言 函数指针
  9. C 语言 指针函数
  10. C 语言 回调函数 callback
  11. C 语言 #pragma once
  12. C 语言 #include <> 与 #include “” 区别
  13. C 语言 const 修饰函数参数
  14. C 语言 const 和 define 区别
  15. C 语言 #运算符
  16. C 语言 ##运算符
  17. C 语言 __VA_ARGS__
  18. C 语言 ##__VA_ARGS__
  19. C 语言 函数不定长参数 ##__VA_ARGS__经典案例
  20. C 语言 va_start 宏
  21. C 语言 va_end 宏

未经允许不得转载:猿说编程 » C 语言 va_end 宏

本文由博客 - 猿说编程 猿说编程 发布!

C语言函数参数中的三个点(三点 “...”)是干什么用的?(可变参数)<stdarg.h>va_start 宏va_arg 宏va_end 宏

参考文章:C 可变参数

参考文章2:C语言中的可变参数函数

文章目录

C 可变参数

有时,您可能会碰到这样的情况,您希望函数带有可变数量的参数,而不是预定义数量的参数。C 语言为这种情况提供了一个解决方案,它允许您定义一个函数,能根据具体的需求接受可变数量的参数。下面的实例演示了这种函数的定义。

int func(int, ... ) 

   .
   .
   .

 
int main()

   func(2, 2, 3);
   func(3, 2, 3, 4);

请注意,函数 func() 最后一个参数写成省略号,即三个点号(…),省略号之前的那个参数是 int,代表了要传递的可变参数的总数。为了使用这个功能,您需要使用 stdarg.h 头文件,该文件提供了实现可变参数功能的函数和宏。具体步骤如下:

  • 定义一个函数,最后一个参数为省略号,省略号前面可以设置自定义参数。
  • 在函数定义中创建一个 va_list 类型变量,该类型是在 stdarg.h 头文件中定义的。
  • 使用 int 参数和 va_start 宏来初始化 va_list 变量为一个参数列表。宏 va_start 是在 stdarg.h 头文件中定义的。
  • 使用 va_arg 宏和 va_list 变量来访问参数列表中的每个项。
  • 使用宏 va_end 来清理赋予 va_list 变量的内存。

现在让我们按照上面的步骤,来编写一个带有可变数量参数的函数,并返回它们的平均值:

实例

#include <stdio.h>
#include <stdarg.h>
 
double average(int num,...)

 
    va_list valist;
    double sum = 0.0;
    int i;
 
    /* 为 num 个参数初始化 valist */
    va_start(valist, num);
 
    /* 访问所有赋给 valist 的参数 */
    for (i = 0; i < num; i++)
    
       sum += va_arg(valist, int);
    
    /* 清理为 valist 保留的内存 */
    va_end(valist);
 
    return sum/num;

 
int main()

   printf("Average of 2, 3, 4, 5 = %f\\n", average(4, 2,3,4,5));
   printf("Average of 5, 10, 15 = %f\\n", average(3, 5,10,15));

当上面的代码被编译和执行时,它会产生下列结果。应该指出的是,函数 average() 被调用两次,每次第一个参数都是表示被传的可变参数的总数。省略号被用来传递可变数量的参数。

Average of 2, 3, 4, 5 = 3.500000
Average of 5, 10, 15 = 10.000000

原理

可变参数函数

在C语言中,有这样的一类函数:函数的参数个数是不确定的,动态变化的。比如我们经常用到的 prinf, sprinf 等等,这与我们平时定义的函数有些不同,它们便是可变参数函数,我们也可以自定义这类函数,这篇文章讲解C语言中的可变参数函数相关的内容。

原理与分析

C语言的参数列表是从右往左被压入堆栈的(函数右边的参数在栈底,左边的在栈顶),假设现在堆栈中有关参数的情况如下:

栈顶-不可变参数1-不可变参数2-....-不可变参数n-可变参数1-可变参数2-......可变参数n-栈低

现在假设我们知道了"可变参数n"的类型,我们还需要知道什么就能得到这个参数?

这个参数的地址!

那如何得到这个地址?

你必须知道前一个的地址和类型!

那怎么知道前一个的类型和地址?……

一直到最前面那个已经知道了类型和地址的不可变参数n是不是就搞定了?!

实现方案

下面看一下ANSI标准的实现。只讲相关的三个宏,用这三个宏就实现了上面的过程。

这三个宏是:

va_start( va_list arg_ptr, prev_param )
va_arg( va_list arg_ptr, type )
va_end( va_list arg_ptr )

既然明白了前面所述过程,就容易理解库里面那些宏或者函数的用途了。

1、 va_start 宏

这个函数用于确定第一个不可变参数的位置。它是如何做到的?就是通过最后一个不可变参数 n 实现的,因此它里面有两个参数,一个是不可变参数 n ,一个是可变参数 1 的地址。因为不可变参数 n 的地址和类型都能够得到,因此只要将这个指针(将它的地址赋给一个相同类型的指针)加 1 就能得到可变参数 1 的指针,通过这个宏, va_list 的指针就指向第一个可变参数。

2、 va_arg 宏

这个函数的作用就是获得当前指向的参数的值。但当前我们只是得到了可变参数 1 的地址,它的类型怎么确定?只能通过前面的不可变参数来传达这个信息,像 printf 里面的格式化字符串,或者你可以认为可变参数列表的参数类型和第几个不可变参数的相同。这种信息的传递是由程序员来设计的。

3、 va_end 宏

因为在 va_start 的实现中可能会有对参数列表的动态内存分配,需要调用 va_end 宏来释放。如果忘记了,很可能会“内存泄露”。

应用举例

举例1:提前已知所有参数类型的简单情况

/*程序功能:这里实验可变参数的函数,以及可变参数的宏的特性.
*可变参数函数void my_sum(int count, ...);
*这个函数的功能是计算多个整数的和。
*其中count是将要求和的整数的数目。
*其它的参数是可变的,其中第一个参数是char*的参数,用于提示。
*后面的参数分别是待求和的整数,一共count个。
*/

#include <stdarg.h>
#include <stdio.h>

void my_sum(int count, ...);

int main(int argc, char *argv[])

        int count = 5;
        printf("compute sum of %d numbers.\\n",count);
        my_sum(count,"the sum of numbers is:", 1, 2, 3, 4, 5);
        return 0;


void my_sum(int count, ...)

        //将要用来存放需要的某个可变参数的指针的信息
        va_list ap;
        char *prompt;
        int sum = 0;

        //开始的初始化,其中ap含有指向可变参数的指针的信息,count是当前函数中最后一个非可变的参数(这样才能定位).
        va_start(ap, count);

        //获取并返回下一个可变参数的值,第一个参数是ap不用说了,第二个参数是要获取的参数的类型。
        //根据文档,如果类型指定错误了,或者没有下一个可变参数了,那么返回的结果是随机的。
        prompt = va_arg(ap, char*);
        printf("%s\\n", prompt);

        int i;
        for(i = 0; i < count; ++i)
        
                sum += va_arg(ap, int);
        

        //使用完可变参数之后要用这个来释放资源
        va_end(ap);
        printf("%d\\n",sum);


运行结果:

compute sum of 5 numbers.
the sum of numbers is:
15

举例2:通过固定参数,来动态确定可变参数类型的复杂情况

#pragma warning(disable : 4996)
#include <stdarg.h>
#include <stdio.h>
void foo(char* fmt, ...)

    va_list ap;
    int d;
    char c;
    char* s;

    va_start(ap, fmt);
    while (*fmt) 
    
        switch (*fmt++)
        
            case 's':
                s = va_arg(ap, char*);
                printf("string %s\\n", s);
                break;
            case 'd':
                d = va_arg(ap, int);
                printf("int %d\\n", d);
                break;
            case 'c':
                c = (char)va_arg(ap, int);
                printf("char %c\\n", c);
                break;
        
    
    va_end(ap);


int main()

    foo("csds", 'b', "of", 50, "you");  //c代表char,s代表string,d代表int;用不同switch+case去解析后面的参数
    return 0;

VS抽风,静态检查报红,忽略它直接编译:

运行结果:

char b
string of
int 50
string you

以上是关于C语言 va_end 宏的主要内容,如果未能解决你的问题,请参考以下文章

C语言函数参数中的三个点(三点 “...”)是干什么用的?(可变参数)<stdarg.h>va_start 宏va_arg 宏va_end 宏

C语言中可变参数的函数(三个点,“...”)

C语言宏定义实现可变参数

C语言怎么实现可变参数?

va_start和va_end的使用及原理

C语言中如何实现可变参函数