【C语言】浅谈可变参数与printf函数

一.何谓可变参数

int printf( const char* format, ...);

这是使用过C语言的人所再熟悉不过的printf函数原型,它的参数中就有固定参数format和可变参数(用”…”表示).

而我们又可以用各种方式来调用printf,如:

printf( "%d ",value);
printf( "%s ",str);
printf( "the number is %d ,string is:%s ", value, str);

二.实现原理

C语言用宏来处理这些可变参数。

这些宏看起来很复杂,其实原理挺简单:就是根据参数入栈的特点从最靠近第一个可变参数的固定参数开始,依次获取每个可变参 数的地址.下面我们来分析这些宏.

在VC中的stdarg.h头文件中,针对不同平台有不同的宏定义,我们选取X86平台下的宏定义:

typedef char *va_list;
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )
#define va_start(ap,v)( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

可以用下图来表示:

在VC等绝大多数C编译器中,默认情况下,参数进栈的顺序是由右向左的,因此,参数进栈以后的内存模型如下图所示:最后一个固定参数的地址位于第一个可变参数之下,并且是连续存储的。

|——————————————————————————|

|最后一个可变参数 | -> 高内存地址处

|——————————————————————————|

...................

|——————————————————————————|

|第N个可变参数 | ->
va_arg(arg_ptr,int)后arg_ptr所指的地方,

| | 即第N个可变参数的地址。

|——————————————— |

………………………….

|——————————————————————————|

|第一个可变参数 | ->
va_start(arg_ptr,start)后arg_ptr所指的地方

| | 即第一个可变参数的地址

|——————————————— |

|———————————————————————— ——|

| |

|最后一个固定参数 | -> start的起始地址

|—————————————— —| .................

|—————————————————————————— |

| |

|——————————————— |-> 低内存地址处

三.printf研究

下面是一个简单的printf函数的实现,参考了中的156页的例子,读者可以结合书上的代码与本文参照。

#include <stdio.h>
#include <stdlib.h>
//一个简单的类似于printf的实现,//参数必须都是int 类型
void myprintf(char* fmt, ...){
	char* pArg=NULL; //等价于原来的va_list
	char c;
	pArg = (char*)&fmt; //注意不要写成p = fmt !!因为这里要对参数取址,而不是取值
	pArg += sizeof(fmt); //等价于原来的va_start
	do{
		c =*fmt;
		if (c != ‘%‘){
			putchar(c); //照原样输出字符
		}else{
			//按格式字符输出数据
			switch(*(++fmt)){
			case ‘d‘:
				printf( "%d",*((int*)pArg));
				break;
			case ‘x‘:
				printf( "%#x",*((int*)pArg));
				break;
			default:
				break;
			}
			pArg += sizeof(int); //等价于原来的va_arg
		}
		++fmt;
	}while (*fmt != ‘‘);
	pArg = NULL; //等价于va_end
	return;
}
int main(){
	int i = 1234;
	int j = 5678;
	myprintf( "the first test:i=%d
",i);
	myprintf( "the secend test:i=%d; %x;j=%d; ",i,0xabcd,j);
	system( "pause ");
	return 0;
}

输出:

the first test:i=1234
the secend test:i=1234; 0xabcd;j=5678;

四.应用

求最大值:

#include <stdarg.h>//不定数目参数需要的宏
#include <stdio.h>
int max(int n,int num, ...){
	int m = num;
	int i = 0;
	va_list x;//说明变量x
	va_start(x,num);//x被初始化为指向num后的第一个参数
	for(i=1; i<n; i++){
		//将变量x所指向的int类型的值赋给y,同时使x指向下一个参数
		int y = va_arg(x,int);
		if(y>m){
			m=y;
		}
	}
	va_end(x);//清除变量x
	return m;
}
int main(){
	int ret1 = max(3,5,56,55);
	int ret2 = max(6,0,4,32,45,533,6565);
	printf( "%d,%d ",ret1,ret2);
	return 0;
}

输出:

56,6565
    相关推荐

  1. 【C语言】模拟实现printf函数(可变参数)
  2. printf函数功能、原型、用法及实例
时间: 2024-11-21 06:01:36

【C语言】浅谈可变参数与printf函数的相关文章

黑马程序员_浅谈out参数、ref参数和可变参数

1.out参数 out关键字会导致参数通过引用来传递,通俗点说,就是往外传值的. out参数的作用:用于以内部变量为外部变量赋值的,out一般适用于某个方法不只是使用return返回单个值,而是需要有多个返回值的情况. out参数的使用需要注意以下几点:  1)out参数传递的变量在传递之前不需要对其进行初始化. 分析:在调用方法之前,对out参数传递的变量只需声明,可以赋值也可以不赋值,不过反正都是要在调用时被覆盖掉,所以大可不必赋值,因为赋值了虽然不会报错,但却根本也没有用处,没必要多此一举

C语言中的可变参数函数 三个点“…”printf( const char* format, ...)

第一篇 C语言编程中有时会遇到一些参数个数可变的函数,例如printf()函数,其函数原型为: int printf( const char* format, ...); 它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的(用三个点“…”做参数占位符),实际调用时可以有以下的形式: printf("%d",i); printf("%s",s); printf("the number is %d ,string is:%s",

c语言中对可变参数列表的简单理解

函数原型中一般情况下参数的数目是固定的,但是如果想在不同的时候接收不定数目的参数时该怎么办呢?c语言提供了可变参数列表来实现. 可变参数列表是通过宏来实现的,这些宏定义在stdarg.h的头文件中.头文件中声明了一个va_list类型和va_start.va_arg.va_end三个宏.我们使用可变参数列表的时候需要声明一个va_list类型的变量配合这三个宏使用. va_start(va_list变量名,省略号前面最后一个有名字的参数):在提取可变参数前必须调用这个宏实现初始化. va_arg

编写一个可变参数的C函数——头文件stdarg.h中宏va_start ,va_arg和va_end的应用

我们在C语言编程中会遇到一些参数个数可变的函数,例如printf()这个函数,它的定义是这样的:int printf( const char* format, ...);它除了有一个参数format固定以外,后面跟的参数的个数和类型是可变的,例如我们可以有以下不同的调用方法:printf("%d",i);printf("%s",s);printf("the number is %d ,string is:%s", i, s);究竟如何写可变参数的

函数模板,函数模板重载,可变参数模板,函数模板覆盖,通过引用交换数据

 1.函数模板初级,如果想使用模板,需要实例化,实例化的方式是加上<数据类型> #include <iostream> //函数模板可以对类型进行优化重载,根据类型会覆盖 //如果仍然要使用模板函数,需要实例化 template<class T> T add(T a, T b) { std::cout << "T add " << std::endl; return a + b; } int add(int a, int

【c语言】用可变参数列表实现一个简化的printf函数

//实现一个简化的printf函数. #include <stdarg.h> #include <string.h> void my_printf(const char *str,...) { va_list arg; //准备访问可变参数 va_start(arg,str); while(*str != '\0') { switch(*str) { case 'c': putchar( va_arg( arg,char ) ); break; case 's': puts( va

【C语言】用可变参数列表实现printf函数

//用可变参数列表实现printf函数 #include <stdio.h> #include <stdarg.h> void my_printf(const char *str,...) { va_list fun; va_start (fun,str); while(*str!=0) { if(*str=='c') { putchar(va_arg(fun,char )); } if(*str=='s') { puts(va_arg(fun,char *)); } str++;

【转载】c语言中的可变参数编程

在c语言中使用变长参数最常见的就是下面两个函数了: int printf(const char *format, ...); int scanf(const char *format, ...); 那他们是怎样实现支持变成参数的呢?在使用变长参数的函数(这里假设是func)实现部分其实用到了stdarg.h里面的多个宏来访问那些不确定的参数,它们分别是: void va_start(va_list ap, last); type va_arg(va_list ap, type); void va

C语言之 认识可变参数

printf()的原型是int printf(const char *fmt, ...);后面三个 . 表示C语言的变参. 那么什么是变参?它有什么功能和作用呢? 变参就是参数不确定,可以随意根据需要来改变的参数. 我们先举一个固定参数列表的函数: int swap(int a,int b) { int c; c=a; a=b; b=c; return a; } 这是随便写的一个函数,虽然由于里面全都是局部变量的原因所以并不能交换参数,但是这个函数的形式还是挺不错的. 再举例一个可变参数列表函数