origin:https://www.e-learn.cn/content/qita/716071
首先我们先来了解一下基础知识。
printf格式字符如下所示,
格式字符 |
说明 |
d |
以带符号的十进制形式输出整数(整数不输出符号) |
u |
以无符号十进制形式输出整数 |
x |
以十六进制无符号形式输出整数(不输出前导符0x), 用x则输出十六进制数的a~f时以小写形式输出 |
c |
以字符形式输出,只输出一个字符 |
s |
输出字符串 |
在C语言中,不仅参数的类型可变,而且参数的个数也是可变的.也就是说,在形参表中可以不明确指定传递参数的个数和类型,以上所说的printf函数就是如此.这种函数称之为变参函数。可变长参数函数的参数数目和类型虽然是可变,但其设计原理与固定参数函数的设计原理是一致的,我们有办法告诉变参函数没有指定的参数的个数和类型。
printf的声明如下:
int printf(const char *format, ...);
format:固定参数
... :可变参数(变参)
在C语言中,变参函数的声明是放在atdarg.h标准库中的,当然可以直接包含进来使用它,但我们这里自己定义它(参照头文件的宏定义)。
typedef char * va_list; /* 当sizeof(n)=1/2/4时,_INTSIZEOF(n)=4 */ #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_arg(ap,t) (ap = ap + _INTSIZEOF(t), *(t *)(ap - _INTSIZEOF(t))) /* 下面这个话的意思是,先指向下一个变量的地址,然后再(减)回来,最后取它原来地址里面的值 */ #define va_arg(ap,t) (*(t *)(ap = ap + _INTSIZEOF(t), ap - _INTSIZEOF(t))) /*指针用完后指向0地址,防止野指针的出现*/ #define va_end(ap) ( ap = (va_list)0 )
这里需要注意一下#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) -1) & ~(sizeof(int) - 1) ),可能不是很好理解,下面补充一个知识点:由于在x86(32位机器)平台下,GCC编译器默认按4字节对齐,所以当sizeof(n)=1/2/4时,_INTSIZEOF(n)=4这句话的意思是当变量类型是char,unsigned int,int,那么不足4字节的都按照4字节补齐。
下面给出My_printf.c
#include "my_printf.h" //================================================================================================== 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_arg(ap,t) ( *(t *)( ap=ap + _INTSIZEOF(t), ap- _INTSIZEOF(t)) ) #define va_end(ap) ( ap = (va_list)0 ) //这里就不解释了,不难理解 //================================================================================================== unsigned char hex_tab[]={'0','1','2','3','4','5','6','7',\ '8','9','a','b','c','d','e','f'}; //输出各种进制下的字符 static int outc(int c) { __out_putchar(c); //这里的_out_putchar其实就是putchar,在.h中定义 return 0; } static int outs (const char *s) //输出字符串 { while (*s != '\0') __out_putchar(*s++); return 0; } static int out_num(long n, int base,char lead,int maxwidth) { unsigned long m=0; char buf[MAX_NUMBER_BYTES], *s = buf + sizeof(buf); // sizeof算结束符'\0' ,strlen不算 int count=0,i=0; //注意这里s指向buf的末端,至于为什么继续往下看 *--s = '\0'; //先--,在赋值结束符,因为sizeof算结束符在内的长度 if (n < 0){ m = -n; //如果是输出的是负数就取反 } else{ m = n; } do{ *--s = hex_tab[m%base]; count++; }while ((m /= base) != 0); //将要打印的数字从个位开始一位一位存储在数组buf中,如果上面不是指向buf末端, if (n < 0) *--s = '-'; //负数的话加负号 return outs(s); } /*reference : int vprintf(const char *format, va_list ap); */ static int my_vprintf(const char *fmt, va_list ap) { char lead=' '; int maxwidth=0; for(; *fmt != '\0'; fmt++) { if (*fmt != '%') { //顺序查找判断,遇到%就推出,否则继续循环输出 outc(*fmt); continue; } fmt++; if(*fmt == '0'){ //遇到‘0’说明前导码是0 lead = '0'; fmt++; } while(*fmt >= '0' && *fmt <= '9'){ //紧接着的数字是长度,算出指定长度 maxwidth *=10; maxwidth += (*fmt - '0'); fmt++; } switch (*fmt) { //判断格式输出 case 'd': out_num(va_arg(ap, int), 10,lead,maxwidth); break; case 'o': out_num(va_arg(ap, unsigned int), 8,lead,maxwidth); break; case 'u': out_num(va_arg(ap, unsigned int), 10,lead,maxwidth); break; case 'x': out_num(va_arg(ap, unsigned int), 16,lead,maxwidth); break; case 'c': outc(va_arg(ap, int )); break; case 's': outs(va_arg(ap, char *)); break; default: outc(*fmt); break; } } return 0; } //reference : int printf(const char *format, ...); int printf(const char *fmt, ...) { va_list ap; va_start(ap, fmt); my_vprintf(fmt, ap); va_end(ap); return 0; } int my_printf_test(void) { printf("My_printf test\n\r") ; printf("test char =%c,%c\n\r", 'A','a') ; printf("test decimal number =%d\n\r", 123456) ; printf("test decimal number =%d\n\r", -123456) ; printf("test hex number =0x%x\n\r", 0x55aa55aa) ; printf("test string =%s\n\r", "yoyoyo") ; printf("num=%08d\n\r", 12345); printf("num=%8d\n\r", 12345); printf("num=0x%08x\n\r", 0x12345); printf("num=0x%8x\n\r", 0x12345); printf("num=0x%02x\n\r", 0x1); printf("num=0x%2x\n\r", 0x1); printf("num=%05d\n\r", 0x1); printf("num=%5d\n\r", 0x1); return 0; }
实验结果如下: