天天看点

cstdarg中可变参数的实现1. 可变参数的使用方法(cstdarg的使用)2. 可变参数的原理(栈中的参数列表)3. 可变参数的实现(cstdarg的实现)?4. 可变参数实现(C语言版代码)5. 转载请注明出处

  • 可变参数的使用方法cstdarg的使用
  • 可变参数的原理栈中的参数列表
  • 可变参数的实现cstdarg的实现
  • 可变参数实现C语言版代码
  • 转载请注明出处

1. 可变参数的使用方法(cstdarg的使用)

在C++中,可变参数有两种类型:

1. 类型不变,参数个数可变

2. 参数个数可变,类型可变

今天着重讲述第1种:类型不变,参数个数可变。

先看一个例子:

#include <iostream>
#include <cstdarg>

void show(int length, ...) {
    va_list ap;//定于一个指针,为char*类型
    va_start(ap, length);//让指针指向length后面的参数,也就是我们待会要打印的参数列表的栈地址
    for (int i = ; i < length; i++)std::cout << va_arg(ap, int) << "  ";//va_arg用于取出栈中的每一个参数,同行前进一个参数的步长
    va_end(ap);//关闭该指针,置为NULL
}

int main() {
    show(, , , );
    std::cin.get();
    return ;
}
           

运行结果:

11 22 33

通过观察运行结果,会发现:

1. 传入的第一个参数是后面需要打印的参数的个数

2. 参数列表中使用了 … 来表示多个参数

3. 使用了多个宏

我们为了准确打印参数的个数而传入了参数的个数,其实我们还有另外一种做法:传入一个终止标识

第一种做法:直接手动指定ap,不使用va_start(因为va_start使ap指针指向传入参数的下一个位置)

#include <iostream>
#include <cstdarg>

void show();

void show(int arg1,...) {
    va_list ap = reinterpret_cast<char*>(&arg1);
    for (int i = va_arg(ap,int); i != -; i = va_arg(ap, int))std::cout << i << "  ";
    va_end(ap);
}

int main() {
    show(, , , -);
    std::cin.get();
    return ;
}
           

运行结果:

11 22 33

第二种做法:稍微修改一下循环

#include <iostream>
#include <cstdarg>

void show();

void show(int arg1, ...) {
    va_list ap;
    va_start(ap, arg1);
    //for (int i = va_arg(ap, int); i != -1; i = va_arg(ap, int))std::cout << i << "  ";
    for (int i = arg1; i != -; i = va_arg(ap, int))std::cout << i << "  ";
    va_end(ap);
}

int main() {
    show(, , , -);
    std::cin.get();
    return ;
}
           

由此,我们可以总结出cstdarg头文件当中可变参数宏的用法的一般步骤:

1. va_list ap; 创建一个指向栈中存储参数列表位置的char类型指针ap

2. va_start(ap,v); 初始化ap,让ap指向与v相邻的下一个参数

3. va_arg(ap,type); 传入ap,并且传入参数的类型type,一个一个地取出存储在栈中的参数。

4. va_end(ap); 操作结束,将ap置为NULL

2. 可变参数的原理(栈中的参数列表)

注意:有些栈可能是从低地址向高地址增长,有些可能不是,这要根据具体情来判断,下面仅仅是一种比较常见的栈

cstdarg中可变参数的实现1. 可变参数的使用方法(cstdarg的使用)2. 可变参数的原理(栈中的参数列表)3. 可变参数的实现(cstdarg的实现)?4. 可变参数实现(C语言版代码)5. 转载请注明出处

3. 可变参数的实现(cstdarg的实现)?

这是实现代码当中的一种:

#include <iostream>

//获取传入参数的地址
#define _ADDRESSOF(v) (&reinterpret_cast<const char&>(v))
//获取与int对齐的元素大小,比如 1字节=4字节 2字节=4字节 5字节=8字节
//因为某些系统的栈实现时遵循内存对齐的规则
#define _INTSIZEOF(v) ((sizeof(v)+sizeof(int)-1)&~(sizeof(int)-1))
//定义一个字符型指针,指向某个参数的起始地址
#define va_list char*
//当ap用完时,置空ap,防止出现野指针
#define va_end(ap) (ap=reinterpret_cast<va_list>(0))
//初始化指向栈中参数列表的指针ap,让他指向v的下一个参数的位置
#define va_start(ap,v) (ap=const_cast<va_list>(_ADDRESSOF(v))+_INTSIZEOF(v))
//通过一个指向栈的指针ap,一个一个的取出参数列表中的参数
#define va_arg(ap,t) (*reinterpret_cast<t*>((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))

//使用它
void show(int a,...){
    va_list argp;
    va_start(argp,a);
    for(int i=;i<a;i++)std::cout<<va_arg(argp,int)<<std::endl;
    va_end(argp);
}

int main(){
    show(,,,);
    return ;
}
           

接下来我们将会一步一步地解析它实现的过程。

参考上文中提到的可变参数用法的一般过程:

由此,我们可以总结出cstdarg头文件当中可变参数宏的用法的一般步骤:

1. va_list ap; 创建一个指向栈中存储参数列表位置的char类型指针ap

2. va_start(ap,v); 初始化ap,让ap指向与v相邻的下一个参数

3. va_arg(ap,type); 传入ap,并且传入参数的类型type,一个一个地取出存储在栈中的参数。

4. va_end(ap); 操作结束,将ap置为NULL

我们可以总结出实现可变参数需要的操作分别有哪些:

  1. va_list 我们要创建一个ap,指向栈 -> 需要:创建ap的操作
  2. va_start 我们要初始化ap -> 需要:取地址操作并初始化ap
  3. 有些栈的实现遵循内存对齐原则 -> 需要:内存对齐操作
  4. va_arg 我们 -> 需要:一个一个地取出参数列表中的参数
  5. va_end 置空ap -> 需要:置空我们创建的指针ap

—- 步骤详解:—-

  • 创建一个ap
    //定义一个字符型指针,指向某个参数的起始地址
    
    #define va_list char*
               

  • 取地址操作
    //获取传入参数的地址,reinterpret_cast负责实现类型强转
    
    #define _ADDRESSOF(v) (&reinterpret_cast<const char&>(v))
               
为什么使用reinterpret_cast,因为该转换可以实现两种完全不相关的类型的之间的转换(重新阐释比特位),比如:传入int类型,但是需要让ap指向int数据的起始地址,此时需要将int类型转换为int的const引用,这样我们取得的地址将会是const类型的,可以保证栈中数据不被意外修改

  • 初始化ap操作
    //初始化指向栈中参数列表的指针ap,让他指向v的下一个参数的位置
    
    #define va_start(ap,v) (ap=const_cast<va_list>(_ADDRESSOF(v))+_INTSIZEOF(v))
               
const_cast去掉取出地址中的const属性,在赋值给ap(char*类型)之前,“+_INTSIZEOF(v)”操作就是将指针移动到v后面的一个元素的起始地址。

  • 内存对齐操作
    //获取与int对齐的元素大小,比如 1字节=4字节 2字节=4字节 5字节=8字节
    //因为某些系统的栈实现时遵循内存对齐的规则
    
    #define _INTSIZEOF(v) ((sizeof(v)+sizeof(int)-1)&~(sizeof(int)-1))
               
参考博文:CC++可变参数stdarg.h中的余数运用

  • 取出参数
    //通过一个指向栈的指针ap,一个一个的取出参数列表中的参数
    
    #define va_arg(ap,t) (*reinterpret_cast<t*>((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))
               

ap指向的就是栈中的某个元素的起始地址,此时我们只需将指针转换为t类型,最后用*取出该数据即可。

关于“(ap+=_INTSIZEOF(t)”是为了让ap移动到下一个参数的起始地址,返回之前“-_INTSIZEOF(t)”就是由于前面+=操作将指针移动到了下一个参数的内存地址,所以我们要将之减去后再取出。

  • 置空ap指针
    //当ap用完时,置空ap,防止出现野指针
    
    #define va_end(ap) (ap=reinterpret_cast<va_list>(0))
               
将NULL的赋值给ap

以上就是cstdarg的实现。

4. 可变参数实现(C语言版代码)

#include <stdio.h>

//获取传入参数的地址
#define _ADDRESSOF(v) ((const char*)&v)
//获取与int对齐的元素大小,比如 1字节=4字节 2字节=4字节 5字节=8字节
//因为某些系统的栈实现时遵循内存对齐的规则
#define _INTSIZEOF(v) ((sizeof(v)+sizeof(int)-1)&~(sizeof(int)-1))
//定义一个字符型指针,指向某个参数的起始地址
#define va_list char*
//当ap用完时,置空ap,防止出现野指针
#define va_end(ap) (ap=(va_list)0)
//初始化指向栈中参数列表的指针ap,让他指向v的下一个参数的位置
#define va_start(ap,v) (ap=(va_list)(_ADDRESSOF(v))+_INTSIZEOF(v))
//通过一个指向栈的指针ap,一个一个的取出参数列表中的参数
#define va_arg(ap,t) (*(t*)((ap+=_INTSIZEOF(t))-_INTSIZEOF(t)))

//使用它
void show(int a,...){
    va_list argp;
    va_start(argp,a);
    for(int i=;i<a;i++)printf("%d\n",va_arg(argp,int));
    va_end(argp);
}

int main(){
    show(,,,);
    return ;
}
           

5. 转载请注明出处

继续阅读