四、(标准输出函数)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;
/*前导码不可为空字符,否则生成的数据的最前面为空字符,将无法打印数据
*以下对lead、width的初始化归0必须放在这个位置,若在for(; *fmt != '\0'; fmt++)之前,
*将会对第二次printf()内第二次连续输出时造成lead和width错误,程序崩溃
*/
lead = ' ';
width = 0;
//判断前导码lead,只有两种' '或'0';
if(*fmt == '0')
{
lead = '0';
fmt++;
}
else if(*fmt == ' ')
{
lead = ' ';
fmt++;
}
//判断数据宽度
while(*fmt >= '0' && *fmt <= '9')
{
width = width*10 + (*fmt - '0');
fmt++;
}
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 见本系列【一】