四、(标准输出函数)printf()函数的正确建立方法和调用方式——从字符串/字符指针(所指向的字符为字符串首字符)作为printf()函数的参数的正确传递方法和使用格式入手
难点:1)递归调用的函数较多,函数间的 传递 的 参数的关系容易判断出错
说明:1)字符串在函数间的传递正确方式为:
printf("This is www.100ask.org\n\r");
puts("wakaka!\n\r");
2)用到的函数原型:
int printf(const char * format, ...);
2、试数及论证过程如下:
【操作一:】 char * s = "Hello world!";
printf("char * s = %s\n\r", s);
解析:递归调用:函数my_printf_test()调用函数printf()函数,函数printf()调用my_printf(),
而函数my_printf调用函数puts(),函数puts()调用最底层函数putchar();
0、调用一级主函数【int main(void)】,主函数main()内,
执行:my_printf_test();
1、调用二级函数【int my_printf_test(void)】,函数my_printf_test()内,
执行:char * s = "Hello world!"; 此时,定义字符指针char * s,并使其指向字符串"Hello world!"的首字符'H';
执行:printf("char * s = %s\n\r", s); 把指针变量s的值作为实参传递给函数printf()的第一个可变参数;
2、调用三级函数【int printf(const char * format, ...)】,函数printf()内部,
执行:va_list p;va_start(p, format);
定义固定参数字符指针format指向字符串"char * s = %s\n\r"; 定义字符指针char *p并操作其指向第一个可变参数参数s;
执行:my_printf(format, p);
把字符指针format的值作为1号实参传递给函数my_printf(); 指针p的值作为2号实参传递给函数my_printf();
3、调用四级函数【static int my_printf(const char * fmt, va_list p)】函数my_printf()内部,
定义1号形参const char * fmt = (三级printf()函数)format = (二级my_printf_test()函数)"char * s = %s\n\r";
1)调用最底层函数putchar()打印字符串内的每一个字符,当遇到格式符时,根据switch()选择语句执行相应的分支语句;
2)但各分支选择语句的相同作用都是调用puts()函数打印"Hello world!",
即: puts("Hello world!");
puts(s);
puts(*p);其中,p为本级函数可变参数p;
定义2号形参char* p = (三级printf()函数指针)p = (二级my_printf_test()函数字符指针)s = "Hello world!";
4、调用五级函数【int puts(const char * s);】函数puts()调用情况:略
5、调用六级最底层函数putchar(),略
【操作二:】 printf("Test string = %s\n\r", "www.100ask.org");
解析:在调用三级函数【int printf(const char * format, ...);】时,执行函数printf()的结果依然是
定义固定参数字符指针format指向字符串"char * s = %s\n\r"; 定义字符指针char *p并操作其指向第一个可变参数参数s;
其余步骤,与【一】等同。
难点:1)递归调用的函数较多,函数间的 传递 的 参数的关系容易判断出错
说明:1)字符串在函数间的传递正确方式为:
printf("This is www.100ask.org\n\r");
puts("wakaka!\n\r");
2)用到的函数原型:
int printf(const char * format, ...);
int puts(const char * s);
结论:
1、函数printf()常用的调用方法有:
1)static int my_printf(const char * fmt, va_list p); 例如:printf("char * s = %s\n\r", s);
2)printf("Test string = %s\n\r", "www.100ask.org");
2、函数printf()递归调用函数puts()的最终正确调用方法为:
1) puts(va_arg(p, char *));
或 2) puts(*(char **)p);
p += _INTSIZEOF(char *);
1、函数的递归调用的逻辑图如下:
/*======================================================================*/ 探究正确的printf()函数使用方法,解析原因 使用情景: static int my_printf(const char * fmt, va_list p) { for(; *fmt; fmt++) { if(*fmt != '%') { putchar(*fmt); continue; } fmt++; //此时,字符指针fmt指向格式符d/o/x/u/c/s; switch(*fmt) { case 's': //递归调用函数puts(); 正确写法: 1) puts(va_arg(p, char *)); 或 2) puts(*(char **)p); p += _INTSIZEOF(char *); 错误写法: 1)puts(p); 2)puts((char *)p);时:→ Test string = p; 3)puts(*(char *)p); 4)puts(*p); //警告:通过“puts”的arg 1可以使指针从整数中脱离出来。 末尾添加:p += _INTSIZEOF(char *); } } return 0; } int printf(const char * format, ...) { va_list p; va_start(p, format); //指针p是可以指向【固定参数字符指针format和一众可变参数】的指针; my_printf(format, p); //此时指针p为(char *)类型,指向printf()函数的第一个可变参数; va_end(p); return 0; } int my_printf_test(void) { char * s = "Hello world!"; printf("char * s = %s\n\r", s); printf("Test string = %s\n\r", "www.100ask.org"); return 0; } //========================================================================
2、试数及论证过程如下:
【操作一:】 char * s = "Hello world!";
printf("char * s = %s\n\r", s);
解析:递归调用:函数my_printf_test()调用函数printf()函数,函数printf()调用my_printf(),
而函数my_printf调用函数puts(),函数puts()调用最底层函数putchar();
0、调用一级主函数【int main(void)】,主函数main()内,
执行:my_printf_test();
1、调用二级函数【int my_printf_test(void)】,函数my_printf_test()内,
执行:char * s = "Hello world!"; 此时,定义字符指针char * s,并使其指向字符串"Hello world!"的首字符'H';
执行:printf("char * s = %s\n\r", s); 把指针变量s的值作为实参传递给函数printf()的第一个可变参数;
2、调用三级函数【int printf(const char * format, ...)】,函数printf()内部,
执行:va_list p;va_start(p, format);
定义固定参数字符指针format指向字符串"char * s = %s\n\r"; 定义字符指针char *p并操作其指向第一个可变参数参数s;
执行:my_printf(format, p);
把字符指针format的值作为1号实参传递给函数my_printf(); 指针p的值作为2号实参传递给函数my_printf();
3、调用四级函数【static int my_printf(const char * fmt, va_list p)】函数my_printf()内部,
定义1号形参const char * fmt = (三级printf()函数)format = (二级my_printf_test()函数)"char * s = %s\n\r";
1)调用最底层函数putchar()打印字符串内的每一个字符,当遇到格式符时,根据switch()选择语句执行相应的分支语句;
2)但各分支选择语句的相同作用都是调用puts()函数打印"Hello world!",
即: puts("Hello world!");
puts(s);
puts(*p);其中,p为本级函数可变参数p;
定义2号形参char* p = (三级printf()函数指针)p = (二级my_printf_test()函数字符指针)s = "Hello world!";
4、调用五级函数【int puts(const char * s);】函数puts()调用情况:略
5、调用六级最底层函数putchar(),略
【操作二:】 printf("Test string = %s\n\r", "www.100ask.org");
解析:在调用三级函数【int printf(const char * format, ...);】时,执行函数printf()的结果依然是
定义固定参数字符指针format指向字符串"char * s = %s\n\r"; 定义字符指针char *p并操作其指向第一个可变参数参数s;
其余步骤,与【一】等同。
问题1:操作:printf("char * s = %s\n\r", s); 是否就是操作:printf("char * s = %s\n\r", "Hello world!\n\r"); 答:是的
3、测试程序如下:
/* 2018-05-31 File: my_printf.c 功能: 制作一个用于arm裸机平台的printf()函数,要求具备x86平台printf()函数的以下功能: 1)单纯的字符或字符串打印 2)可变参数----整型数据的格式打印: 例程 %#_ %d: i = 377; 377; %o: i = 571; 0571; %u: i = 377; 377; %x(小写):i = 179; 0x179; //%p: p = 0018FF44; //%#p p = 0X0018FF44; %c %s 3)可变参数----字符和字符串的打印 4)整型数据变量的格式打印 5)字符和字符串变量的打印 不考虑: 不考虑浮点数和结构体变量的输出 整型数据的格式不要前导码和最大宽度 */ #include "s3c2440_soc.h" #include "uart.h" //================================================================== #define _MAX_NUMBER_BYTE 64 //================================================================== 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) //================================================================== unsigned char hex_tab[] = "0123456789abcdef"; /*函数:整型数据打印函数 *方法及步骤:1)把原整型数据n,按照进制,转换成整形数据n *2)把数字m的位当作字符串倒着打印,打印顺序:符号位 → 最高位 → 次高位 → ··· → 十位 → 个位; */ static int out_num(long n, int base) //形参为:1)整型数据n 2)进制base { unsigned int m, unit; //m为n的绝对值,unit数字n的位数字 char buf[_MAX_NUMBER_BYTE]; char * p = buf + sizeof(buf); //数组buf[]的长度是否一定是按照四字节对齐?是否对齐,buf[]都代表整型数据的最大宽度,无影响 *--p = '\0'; if(n < 0) m = -n; else m = n; do { unit = m%base; //*和--运算符虽然是同一优先级,但是结合方向为从右到左 *--p = hex_tab[unit]; //把数字m一位一位塞进字符数组buf[]中 m = m/base; //putchar(hex_tab[unit]); //此语句会导致字符串倒着打印 }while(m); if(n < 0) *--p = '-'; return puts(p); } /*自制arm平台简易printf()函数主体部分*/ static int my_printf(const char * fmt, va_list p) //指针p是可以指向【固定参数字符指针format和一众可变参数】的指针 { for(; *fmt; fmt++) { if(*fmt != '%') { putchar(*fmt); continue; } fmt++; //此时,字符指针fmt指向格式符d/o/x/u/c/s; switch(*fmt) { case 'd': out_num(va_arg(p, int), 10); break; case 'o': out_num(va_arg(p, unsigned int), 8); break; case 'x': out_num(va_arg(p, unsigned int), 16); break; case 'u': out_num(va_arg(p, unsigned int), 10); break; case 'c': putchar(va_arg(p, int)); break; case 's': //puts(va_arg(p, char *)); puts(*(char **)p); //当puts(p);或puts((char *)p);时:→ Test string = p; //puts(*(char *)p); //警告:传递“puts”的arg1使指针从整数变为无类型=====// p += _INTSIZEOF(char *); break; default: putchar(*(fmt - 1)); putchar(*fmt); //如果不是以上格式符,判定此格式符和之前的'%'无效,作为普通字符打印 break; } } return 0; } /*自制arm平台简易printf()函数框架*/ int printf(const char * format, ...) { va_list p; va_start(p, format); //指针p是可以指向【固定参数字符指针format和一众可变参数】的指针 my_printf(format, p); va_end(p); return 0; } /*自制arm平台简易printf()函数的测试程序*/ int my_printf_test(void) { int i = 377, j = -528; char a = 'W'; char * s = "Hello world!\n\r"; int * p = &i; //1.纯数字、字符、字符串测试 /* printf("//======================================\n\r"); printf("This if www.100ask.org: my_printf_test\n\r"); printf("Test 正十进制→十进制 num = %d\n\r", 123456); printf("Test 正十进制→八进制 num = %o\n\r", 123456); printf("Test 正十进制→十六进制 num = %x\n\r", 123456); printf("Test 负十进制→十进制 num = %d\n\r", -123456); printf("Test 负十进制→无符号十进制 num = %u\n\r", -123456); printf("Test 负十进制→十六进制 num = %x\n\r", -123456); printf("Test 十六进制→十进制 num = %d\n\r", 0x55aa56ff); printf("Test 十六进制→十六进制 num = %x\n\r", 0x55aa56ff); printf("Test char = %c, %c\n\r", 'A', 'a'); printf("Test string = %s\n\r", "www.100ask.org"); */ //多组数据打印 printf("num1 = %d, num1 = %d\n\r", 0x12, 0x23); printf("//======================================\n\r"); //2、变量测试 printf("int i = %d\n\r", i); printf("int j = %d\n\r", j); printf("char a = %c\n\r", a); printf("char * s = %s\n\r", s); printf("int * p = %p\n\r", p); //多组变量打印 printf("i = %d, j = %d\n\r", i, j); printf("//======================================\n\r"); /**/ return 0; } /* 1、打印结果: Wakakaka //====================================== This if www.100ask.org: my_printf_test Test 正十进制→十进制 num = 123456 Test 正十进制→八进制 num = 361100 Test 正十进制→十六进制 num = 1e240 Test 负十进制→十进制 num = -123456 Test 负十进制→无符号十进制 num = -123456 Test 负十进制→十六进制 num = -1e240 Test 十六进制→十进制 num = 1437226751 Test 十六进制→十六进制 num = 55aa56ff Test char = A, a Test string = www.100ask.org num1 = 18, num1 = 35 //====================================== int i = 377 int j = -528 char a = W char * s = Hello world! int * p = %p i = 377, j = -528 //====================================== 2、打印结果: */
其他程序如:start.S led.c uart.c lib1funcs.S main.c uart.h my_printf.h s3c2440soc.h 见本系列【一】