可变参数列表与printf的实现

先来两个认知:①c语言函数参数压栈的是从右往左开始的。

                         ②可变参函数压参数进栈时,每个参数栈4k个字节。(k=1,2,3,4,5,6...)

 

    有了上面的认知之后,剩下的问题就是按照我们自己的意愿去访问栈中的元素。当然了,凭我们自己90%不知道栈在哪里,

不知道如何指向栈顶。那么用下面的宏就可以帮我们找到栈了

        先定义一个va_list类型的变量 arg,他用于访问参数列表的未确定部分。

       ①va_start(arg,v) //v是整个参数里面的第一个参数。通过va_start来初始化arg变量,即这一步的目的是为了让指针arg跳过参数v,使得arg指向可变参(即...)的第一个参数。

      ②va_arg(arg,t)//t是可变参的类型。这步是在访问可变参。(有地址,有类型,当然可以访问到可变参了)

      ③va_end(arg)//这步是为了将arg指针置空。

 

一. 下面的代码是利用可变参来求未知个数整型的平均值。(先来个开胃小菜)

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

int average(int n, ...)
{
    va_list arg;
    int i = 0;
    int sum = 0;
    va_start(arg, n);
    for(i=0; i<n; i++)
    {
        sum += va_arg(arg, int);
    }
    return sum/n;
    va_end(arg);
}

int main()
{
    int a = 1;
    int b = 2;
    int c = 3;
    int avg1 = average(2, a, c);
    int avg2 = average(3, a, b, c);
    printf("avg1 = %d\n", avg1);
    printf("avg2 = %d\n", avg2);
    return 0;
}

     

         好了,下面是printf函数的代码。

       附:printf的实现大体跟上面的做法差不多,复杂就复杂在它的格式控制哦

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
#include<CoreWindow.h>
#include<cctype>

void f1(size_t i, char ch)
{
	int base = 10;
	static char arr[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
	if (!i)return;
	else
	{
		if (ch == 'o')
			base = 8;
		else if (ch == 'x' || ch == 'X' || ch == 'p')
			base = 16;
		f1(i / base, ch);
	}
	if (ch == 'p')
	{
		if (isdigit(arr[i % base]))
			putchar(arr[i % base]);
		else
			putchar(arr[i % base] - 32);
	}
	else
		putchar(arr[i % base]);
}


//%c %d %i %u %f %o %x/X %p %s %%  (%a/A %e/E  %g/G) %n
void print5(const char*format, ...)
{
	va_list arg;
	va_start(arg, format);
	while (*format)
	{
		if (*format == '%') // % +0m.n c   (% ch1 ch2 m.n c)
		{
			char ch1 = ' ', ch2 = ' ', ch3 = ' ', ch = ' ';
			int m = 0, n = 0;
			while (isdigit(*++format) || *format == '+' || *format == '-' || *format == '#' || (*format == '.' && isdigit(*(format + 1))))
			{//让数字,+,-,#,点号,进入循环。                                                //点号后面必须是数字
				switch (*format)
				{
				case'+':
				{
						   if (ch1 != '-')ch1 = '+';
						   break;
				}
				case'-':
				{
						   ch1 = '-';//有'-',则优先给ch1赋值为'-'。
						   break;
				}
				case'#'://只对o和x起作用 //对于o类,在输出时加前缀0;对于x类,在输出时加前缀0x。
				{
							ch3 = '#';
				}
				case'0'://ch1表示'+','-';ch2表示'0','空'
				{
							if (!isdigit(*(format - 1)))//如果*format=='0'时,前一个字符不是数字(不包括'0')
							{
								//ch1 = '-';
								ch2 = '0';
								break;
							}//'0','+'混合情况(输出+000xxxx);'0','-',('+')混合情况(输出+xxxx)。
				}
					//在处理m,n时,已经将ch1和ch2处理好了。
				case'1':
				case'2'://m表示域宽,即对应的输出项在输出设备所占字节数
				case'3':       //指定占字节数m 与 实际占字节数,输出项在输出设备所占字节数取二者最大值
				case'4'://n表示精度,主要用于说明输出的实型数的小数位数
				case'5':      //对于整型15:①%6.2d->    15;②%6.4d->  0015;③%6.8d->000015
				case'6':
				case'7':
				case'8':
				case'9':
				{
						   m = 10 * m + *format - '0';
						   break;
				}
				case'.':
				{
						   ch = *format;
						   while (isdigit(*++format))
							   n = 10 * n + *format - '0';
						   break;
				}
				}
				if (ch == '.')
					break;
			}
			switch (*format)
			{
			case 'c'://输出c时只和域宽m有关。
			{
						 char ch = va_arg(arg, char);
						 if (ch1 == '+'|| ch1 == ' ')
						 while (--m)putchar(ch2);//ch2可以是空,也可以是0.
						 putchar(ch);
						 if (ch1 == '-')
						 while (--m)putchar(' ');
						 break;
			}

			case 'i':
				;
			case 'd':
			{
						int i = va_arg(arg, int);
						if (i < 0)
						{
							putchar('-');
							i = -i;
						}
						int tmp = i, count = 0;
						while (tmp)++count, tmp /= 10;
						n = n > count ? n : count;//保证n是两者最大值
						if (ch1 == '+'|| ch1 == ' ')
						{
							while (--m - n>0)putchar(' ');//若m>n,这步的结果是让m = n;假如m<n,这步无效
							if (i>0 && ch1 != ' ')
								putchar('+');
							while (n - count > 0)--n, putchar('0');//假如n<count,这步无效
						}

						if (ch1 == '-')
						{
							int tmpn = n;;
							while (tmpn-- - count > 0)putchar('0');//假如n<count,这步无效
						}
						f1(i, *format);
						if (ch1 == '-')
						while (m - n>0)--m, putchar(' ');//假如m<n,这步无效
						break;
			}
			case 'u':
			{
						int u = va_arg(arg, unsigned int);
						size_t tmp = u, count = 0;
						while (tmp)++count, tmp /= 10;
						n = n > count ? n : count;//保证n是两者最大值
						if (ch1 == '+' || ch1 == ' ')
						{
							while (--m - n>0)putchar(' ');//若m>n,这步的结果是让m = n;假如m<n,这步无效
							if (u>0 && ch1 != ' ')
								putchar('+');
							while (n - count > 0)--n, putchar('0');//假如n<count,这步无效
						}

						if (ch1 == '-')
						{
							int tmpn = n;
							while (tmpn-- - count > 0)putchar('0');//假如n<count,这步无效
						}
						f1(u, *format);
						if (ch1 == '-')
						while (m - n>0)--m, putchar(' ');//假如m<n,这步无效
						break;
			}
			case 'f':
			{
						double f = va_arg(arg, double);
						if (0x80 & *((char*)(&f + 1) - 1))//判断该浮点数是否为负数
						{//这样写还不如写成:if(f<0)
							putchar('-');
							f = -f;
						}
						int tmp = (int)f,tmp2 = (int)f,count = 0;
						while (tmp)++count, tmp /= 10;
						count += n + 1;
						if (ch1 == '+' || ch1 == ' ')
						{
							if (f>0 && ch1 != ' ')
								putchar('+');
							while (--m - count>0)putchar(ch2);//假如m<count,这步无效
						}
						f1(tmp2, *format);
						putchar('.');

						if (n == 0)n = 6;
						while (n--)f*=10,tmp2 *= 10;
						tmp = f - tmp2;
						f1(tmp, *format);
						if (ch1 == ' ')
						{
							while (m-- - count > 0)putchar(' ');
						}
						break;
			}

			case 'o'://没测试ch2 == '0'的情况
			{
						int o = va_arg(arg, unsigned int);
						size_t tmp = o, count = 0;
						while (tmp)++count, tmp /= 8;
						n = n > count ? n : count;//保证n是两者最大值
						if (ch3 == '#')++n;
						if (ch1 == '+' || ch1 == ' ')
						{
							int tmpm = m;
							while (tmpm-- - n>0)putchar(' ');//假如m<n,这步无效
							if (m && m<n)--n;
							while (n - count > 0)--n, putchar('0');//假如n<count,这步无效
						}

						if (ch1 == '-')
						{
							int tmpn = n;
							while (tmpn-- - count > 0)putchar('0');//假如n<count,这步无效
						}
						f1(o, *format);
						if (m<n)--n;
						if (ch1 == '-')
						while (m - n>0)--m, putchar(' ');//假如m<n,这步无效
						break;
			}
			case'X':
				;
			case 'x'://没测试ch2 == '0'的情况
			{
						int x = va_arg(arg, unsigned);

						size_t tmp = x, count = 0;
						while (tmp)++count, tmp /= 16;
						n = n > count ? n : count;//保证n是两者最大值
						if (ch3 == '#')m -= 2;
						if (ch1 == '+' || ch1 == ' ')
						{
							int tmpm = m;
							while (tmpm-- - n>0)putchar(' ');//假如m<n,这步无效
							if (ch3 == '#')putchar('0'),putchar('x');
							while (n - count > 0)--n, putchar('0');//假如n<count,这步无效
						}

						if (ch1 == '-')
						{
							if (ch3 == '#')putchar('0'), putchar('x');
							int tmpn = n;
							while (tmpn-- - count > 0)putchar('0');//假如n<count,这步无效
						}
						f1(x, *format);
						if (ch1 == '-')
						{
							m -= 1;
							while (m - n>0)--m, putchar(' ');//假如m<n,这步无效
						}
						break;
			}
			case 'p'://没测试ch2 == '0'的情况
			{
						int p = va_arg(arg, unsigned int);
						size_t tmp = p, count = 0;
						while (tmp)++count, tmp /= 16;
						n = 8;
						if (ch1 == '+' || ch1 == ' ')
						{
							int tmpm = m;
							while (tmpm-- - n>0)putchar(' ');//假如m<n,这步无效
							if (ch3 == '#')putchar('0'), putchar('x');
							while (n - count > 0)--n, putchar('0');//假如n<count,这步无效
						}

						if (ch1 == '-')
						{
							if (ch3 == '#')putchar('0'), putchar('x');
							int tmpn = n;
							while (tmpn-- - count > 0)putchar('0');//假如n<count,这步无效
						}
						f1(p, *format);

						if (ch1 == '-')
						{
							m -= 2;
							while (m - n>0)--m, putchar(' ');//假如m<n,这步无效
						}
						break;
			}
			case 's'://没测试ch2 == '0'  和ch3 == '#'的情况
			{
						char *s = va_arg(arg, char*);
						n = (n && n<strlen(s)) ? n : strlen(s);
						if (ch1 == '+' || ch1 == ' ')
						{
							while (m-- - n > 0)putchar(' ');
						}
						for (size_t i = 0; i < n; ++i)
							putchar(*(s + i));
						if (ch1 == '-')
						{
							while (m-- - n > 0)putchar(' ');
						}
						break;
			}
			case '%':
			{
						putchar('%');
						break;
			}
			case'n'://可以将所输出字符串的长度值赋给一个变量。eg.int len;printf("abcdefg%n",&len);
				break;                                        //执行后变量被赋值为7.

			case'a'://浮点数、十六进制数字和p-(P-)计数法
			case'A':
				break;
			case'e'://浮点数指数输出
			case'E':
				break;
			case 'g'://自动选f格式或e格式中较短的一种输出,且浮点数不显示无意义的零
			case 'G':
				break;
			default:
				putchar(*format);
			}
		}
		else
		{
			putchar(*format);
		}
		format++;
	}
	va_end(arg);
}

//已完成:%c %d %i %u %f %o %x/X %p %s %%  未完成:(%a/A %e/E  %g/G) %n
int main()
{
	printf("%%%\n");
	print5("%%%\n");
	system("pause");
	return 0;
}

 

附录,一些不需要掌握的关于可变参的very very very重要的原理:

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值