您的位置:首页 > 编程语言 > C语言/C++

C语言如何编写可变参数函数(涉及到二级指针)

2016-03-13 13:36 561 查看
C语言中函数支持可变参数,常见的可变参数的函数有printf()函数,他的函数原型是_CRTIMP int __cdecl printf(const char *, ...);

可变函数的核心就在,...这里。

既然printf()函数是可变函数,这里就简单模拟printf()函数的功能写个myprintf()函数

#include <iostream>
using namespace std;
//************************************
// Method: myprintf
// FullName: myprintf
// Access: public
// Returns: int
// Qualifier:
// Parameter: const char * fmt
// Parameter: ...
//************************************
int myprintf(const char *fmt,...)
{
int i = 0; //记录传入数组fmt的下表
// int index = 0; //输出字符串下标
int arg = -1; //参数个数
// char putfmt[1024]; //输出字符串
char *s; //临时存储参数
int ii=0;
bool status = true; //正确标志

// memset(putfmt,0,1024);
int length = strlen(fmt)+1;
for(i=0;i<length;i++)
{

if(fmt[i]=='%')
{
i++;
int flag = 0;
int *ppp;
switch(fmt[i])
{
case 's':
if(-1 == arg)
arg = 0;

flag = 1;
ppp=(int *)&fmt+(1+arg);
s = (char *)*ppp;
ii = 0;
while(s[ii])
cout<<s[ii++];
// putfmt[index++]=s[ii++];

arg++;
break;
case 'd':
flag = 1;
ppp=(int *)&fmt+(1+arg);
cout<<*ppp;
arg++;
break;
default:
break;
}
if(0 == flag)
{
status = false;
break;
}
}
else
cout<<fmt[i];
// putfmt[index++]=fmt[i];

}
return arg;

}
void main(void)
{
myprintf("hello %s","oba没有马!");
}



运行结果(图1):



    图1

大致的原理:

第一个形参起重要作用,用来判断后面有几个参数,其中可以用%d来识别后面有个int类型变量,%s识别后面有个字符串变量,以此类推。取的第一个参数后,可通过指针偏移来获取可变参数的地址,继而进行后续的操作。

以本文章中的例子为例,可通过调试的形式来更形象的解释(图2)。



        
图2

其中第一个固定参数的地址为0x0043201c,通过查看内存地址可以看到对应的内容。而第二个参数可变参数紧跟在第一个参数对应的内存地址的后面(内存中并非显示正确的字是由于中文为两个字节,oba有三个字节,后面三个字的编码被错开放了,因此显示不一样的字)。那么,要如何正确的在内存中识别中相应的参数呢?

就上面的代码来说,函数中传递了两个参数,都是字符串类型,而字符串类型在传递给函数的过程中传递的是首地址,也可以理解为指针。既然是指针,而且同为一个函数的参数,那在指针的地址(二级指针)上会不会有所关联?

通过&fmt查看其地址(图3)



        
图3

由图3可知,&fmt的地址为0x0019feec,在内存中追踪该地址,可看到在内存中,0x0019feec-0x0019feef对应的值为1C 20 43 00,由于在INTEL CPU中,数据的存储形式为小端模式(低地址存地位,不懂可以看我的另一篇博客大端模式,小端模式的区别),因此&fmt里面的值为0x43201C,该值就是fmt的地址。而&fmt后面跟的四个字节,跟&fmt的值有点相像,取其值0x432028查看对应的地址(图4):



    图4

看到了没有!?这个地址里面的存的值就是第二个参数的的内容!

理一下思路:在调用自定义可变参数函数myprintf("hello %s","oba没有马!");时,总共传递了两个参数,一个固定参数"hello %s",另一个传递了可变参数"oba没有马",这两个参数(一级指针)的内容是非连续的(图1,中间有4个字节的00隔开,后面再讨论),但是它们的地址(二级指针)是连续的(图3)。因此可以通过二级指针的偏移来定义可变参数。

在函数myprintf()中,第一个固定参数fmt的类型为char *,取其地址后为0x0019feec,需要偏移4个字节到下一个地址。偏移到0x0019fef0时,此时为二级指针,需进行降级操作,用取值符*降成一级指针,此时指向地址0x00432028,再取里面的值就是要的可变参数的值了。

前面研究的为一个可变参数,如果更多个呢?那在内存中的形式又是怎么样的?下面用三个可变参数作为测试:

修改后的main函数

void main(void)
{
myprintf("hello %s %s %s","oba没有马!","5","you");
}
调试看其内存(图5)



图5

由图5可以看出,参数所占的内存大小以4字节对齐,不足4个字节的补全。参数跟参数之间有4个空白字节作为隔断(图5中的红色框部分)。再看它们的地址(二级指针,图6)



图6

图6中,红框部分为四个参数的二级指针!有兴趣的读者可将四个参数对应的指针代入图5中进行验证。

二级指针:

比如说一个字符串指针,char *arr=“hello",其中"hello"存在一个内存空间中,占5个字节,而指针只能存四个字节,因此要用一个指针表示5个地址的字符串,只能通过地址的形式表示,arr就是一级指针,指向"hello"的内存地址的首地址。现在问题来了,形参表中如何才能做到又能保存字符串,又能保存Int,float等其他类型的变量呢?

答案就是用二级指针(图7)。



把main函数中的代码改成:

void main(void)
{
myprintf("hello %s %d %s","world",5,"you");
}调试时结果如图7所示。

注:本文章的重点在于可变参数函数的研究,不在于printf()的研究,因此printf()只是简单的实现!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息