深入剖析printf函数(下):---形参列表和格式化输出是如何做到的?

来源:互联网 发布:客服淘宝退换货流程图 编辑:程序博客网 时间:2024/05/22 04:59

深入剖析printf函数(下)

---形参列表和格式化输出是如何实现的?


(作者:LL   出处:http://blog.csdn.net/tcpipstack , 欢迎转载,也请保留这段声明。谢谢!)


一、引言

在上一篇 [Linux内核小白]深入剖析printf函数(上):如何不借助第三方库在屏幕上输出"Hello World"?  里,我们已经实现了用汇编语言在屏幕上输出了“Hello World”, 迈出了万里长征的第一步,但是我们知道实际的printf的功能是十分强大的,它和scanf一样属于标准输入输出的一种格式化函数,我们一般是这样使用它的:

printf()的基本形式:printf("格式控制字符串",变量列表);


二、格式化输出


printf()函数是格式输出函数,请求printf()打印变量的指令取决与变量的类型.

例如,在打印整数是使用%d符号,在打印字符是用%c 符号.这些符号被称为转换说明.因为它们指定了如何不数据转换成可显示的形式.


下列列出的是ANSI C标准printf()提供的各种转换说明.

 
转换说明及作为结果的打印输出
%a                浮点数、十六进制数字和p-记数法(C99)
%A    浮点数、十六进制数字和p-记法(C99)
%c    一个字符 
%d    有符号十进制整数 
%e    浮点数、e-记数法
%E    浮点数、E-记数法
%f    浮点数、十进制记数法  
%g    根据数值不同自动选择%f或%e.
%G    根据数值不同自动选择%f或%e.
%i               有符号十进制数(与%d相同)
%o    无符号八进制整数
%p    指针    
%s    字符串
%u    无符号十进制整数
%x    使用十六进制数字0f的无符号十六进制整数 
%X    使用十六进制数字0f的无符号十六进制整数
%%    打印一个百分号


三、形参列表的读入

printf函数的参数列表是如下的形式:

int printf(const char *fmt, ...)
类似于上面参数列表中的token:...,介个是可变形参的一种写法。当传递参数的个数不确定时,就可以用这种方式来表示。

但是电脑比程序员更笨,函数体必须知道具体调用时参数的个数才能保证顺利执行,那么我们必须寻找一种方法来了解参数的个数。


让我们先回到代码中来:

[cpp] view plaincopy
  1. /************************************************************************************ 
  2. ** File: - Z:\code\c\LLprintf\print2.1\LLprintf.c 
  3. **   
  4. ** Copyright (C), Long.Luo, All Rights Reserved! 
  5. **  
  6. ** Description:  
  7. **      LLprintf.c 
  8. **  
  9. ** Version: 2.0 
  10. ** Date created: 23:56:33,24/01/2013 
  11. ** Author: Long.Luo 
  12. **  
  13. ** --------------------------- Revision History: -------------------------------- 
  14. **  <author>  <data>            <desc> 
  15. **  
  16. ************************************************************************************/  
  17.   
  18.    
  19. #include "LLprintf.h"  
  20.   
  21.   
  22. // Lprintf  
  23. int Lprintf(const char *fmt, ...)  
  24. {  
  25.     int i;  
  26.     char buf[256];  
  27.   
  28.     va_list arg = (va_list)((char*)(&fmt) + 4); /*4是参数fmt所占堆栈中的大小*/  
  29.     i = vsLprintf(buf, fmt, arg);  
  30.     buf[i] = 0;  
  31.     LLprint(buf, i);  
  32.   
  33.     return i;  
  34. }  

如上面代码中的 

  va_list arg = (va_list)((char*)(&fmt) + 4); 


而va_list的定义:
  typedef char *va_list

这说明它是一个字符指针


其中的: (char*)(&fmt) + 4)  表示的是...中的第一个参数。


大家肯定很迷惑,不急,再详细解释:


C语言中,参数压栈的方向是从右往左。也就是说,当调用printf函数的适合,先是最右边的参数入栈。


fmt是一个指针,这个指针指向第一个const参数(const char *fmt)中的第一个元素。
fmt也是个变量,它的位置,是在栈上分配的,它也有地址。
对于一个char *类型的变量,它入栈的是指针,而不是这个char *型变量。


换句话说:
你sizeof(p) (p是一个指针,假设p=&i,i为任何类型的变量都可以)
得到的都是一个固定的值。(我的计算机中都是得到的4)
当然,我还要补充的一点是:栈是从高地址向低地址方向增长的。


现在我想你该明白了:为什么说(char*)(&fmt) + 4) 表示的是...中的第一个参数的地址。


为毛我还是不明白啊????


不急,我给你更直观的解释:

[plain] view plaincopy
  1. /******************************************************************************************  
  2.                         可变参数函数调用原理(其中涉及的数字皆为举例)  
  3. ===========================================================================================  
  4.   
  5. i = 0x23;  
  6. j = 0x78;  
  7. char fmt[] = "%x%d";  
  8. printf(fmt, i, j);  
  9.   
  10.         push    j  
  11.         push    i  
  12.         push    fmt  
  13.         call    printf  
  14.         add     esp, 3 * 4  
  15.   
  16.   
  17.                 ┃        HIGH        ┃                        ┃        HIGH        ┃  
  18.                 ┃        ...         ┃                        ┃        ...         ┃  
  19.                 ┣━━━━━━━━━━┫                        ┣━━━━━━━━━━┫  
  20.                 ┃                    ┃                 0x32010┃        '\0'        ┃  
  21.                 ┣━━━━━━━━━━┫                        ┣━━━━━━━━━━┫  
  22.          0x3046C┃        0x78        ┃                 0x3200c┃         d          ┃  
  23.                 ┣━━━━━━━━━━┫                        ┣━━━━━━━━━━┫  
  24.    arg = 0x30468┃        0x23        ┃                 0x32008┃         %          ┃  
  25.                 ┣━━━━━━━━━━┫                        ┣━━━━━━━━━━┫  
  26.          0x30464┃      0x32000 ───╂────┐       0x32004┃         x          ┃  
  27.                 ┣━━━━━━━━━━┫        │              ┣━━━━━━━━━━┫  
  28.                 ┃                    ┃        └──→ 0x32000┃         %          ┃  
  29.                 ┣━━━━━━━━━━┫                        ┣━━━━━━━━━━┫  
  30.                 ┃        ...         ┃                        ┃        ...         ┃  
  31.                 ┃        LOW         ┃                        ┃        LOW         ┃  
  32.   
  33. 实际上,调用 vsprintf 的情形是这样的:  
  34.   
  35.         vsLprintf(buf, 0x32000, 0x30468);  
  36.   
  37. ******************************************************************************************/  

下面我们来看看下一句:

i = vsLprintf(buf, fmt, arg);

这句起什么作用呢?


让我们进入下一节:对参数进行格式化处理。


四、参数格式化输出

让我们来看看vsLprintf(buf, fmt, arg)是什么函数:
[cpp] view plaincopy
  1. int vsLprintf(char *buf, const char *fmt, va_list args)  
  2. {  
  3.     char *p;  
  4.     int m;  
  5.     char inner_buf[STR_DEFAULT_LEN];  
  6.     char cs;  
  7.     int align_nr;  
  8.       
  9.     va_list p_next_arg = args;  
  10.   
  11.     for (p=buf; *fmt; fmt++) {  
  12.         if (*fmt != '%') {  
  13.             *p++ = *fmt;  
  14.             continue;  
  15.         }  
  16.         else {      /* a format string begins */  
  17.             align_nr = 0;  
  18.         }  
  19.   
  20.         fmt++;  
  21.   
  22.         if (*fmt == '%') {  
  23.             *p++ = *fmt;  
  24.             continue;  
  25.         }  
  26.         else if (*fmt == '0') {  
  27.             cs = '0';  
  28.             fmt++;  
  29.         }  
  30.         else {  
  31.             cs = ' ';  
  32.         }  
  33.         while (((unsigned char)(*fmt) >= '0') && ((unsigned char)(*fmt) <= '9')) {  
  34.             align_nr *= 10;  
  35.             align_nr += *fmt - '0';  
  36.             fmt++;  
  37.         }  
  38.   
  39.         char * q = inner_buf;  
  40.         memset(q, 0, sizeof(inner_buf));  
  41.   
  42.         switch (*fmt) {  
  43.         case 'c':  
  44.             *q++ = *((char*)p_next_arg);  
  45.             p_next_arg += 4;  
  46.             break;  
  47.         case 'x':  
  48.             m = *((int*)p_next_arg);  
  49.             i2a(m, 16, &q);  
  50.             p_next_arg += 4;  
  51.             break;  
  52.         case 'd':  
  53.             m = *((int*)p_next_arg);  
  54.             if (m < 0) {  
  55.                 m = m * (-1);  
  56.                 *q++ = '-';  
  57.             }  
  58.             i2a(m, 10, &q);  
  59.             p_next_arg += 4;  
  60.             break;  
  61.         case 's':  
  62.             strcpy(q, (*((char**)p_next_arg)));  
  63.             q += strlen(*((char**)p_next_arg));  
  64.             p_next_arg += 4;  
  65.             break;  
  66.         default:  
  67.             break;  
  68.         }  
  69.   
  70.         int k;  
  71.         for (k = 0; k < ((align_nr > strlen(inner_buf)) ? (align_nr - strlen(inner_buf)) : 0); k++) {  
  72.             *p++ = cs;  
  73.         }  
  74.         q = inner_buf;  
  75.         while (*q) {  
  76.             *p++ = *q++;  
  77.         }  
  78.     }  
  79.   
  80.     *p = 0;  
  81.   
  82.     return (p - buf);  
  83. }  

这个函数起什么作用呢?

我们回想下printf起什么作用呢?
哦,printf接受一个格式化的命令,并把指定的匹配的参数格式化输出。
  
好的,我们再看看i = vsLprintf(buf, fmt, arg);
vsLprintf返回的是一个长度值,那这个值是什么呢?

会不会是打印出来的字符串的长度呢?

没错,返回的就是要打印出来的字符串的长度。


其实看看printf中后面的一句:LLprint(buf, i); 

介个是干啥的?

什么,你不知道,那赶紧看上一篇文章。

  

总结: vsLprintf的作用就是格式化。

它接受确定输出格式的格式字符串fmt。用格式字符串对个数变化的参数进行格式化,产生格式化输出


我们也可以看看一个串口的printf的实现:

[cpp] view plaincopy
  1. //*****************************************************************************  
  2. //  
  3. //! A simple UART based printf function supporting \%c, \%d, \%p, \%s, \%u,  
  4. //! \%x, and \%X.  
  5. //!  
  6. //! \param pcString is the format string.  
  7. //! \param ... are the optional arguments, which depend on the contents of the  
  8. //! format string.  
  9. //!  
  10. //! This function is very similar to the C library <tt>fprintf()</tt> function.  
  11. //! All of its output will be sent to the UART.  Only the following formatting  
  12. //! characters are supported:  
  13. //!  
  14. //! - \%c to print a character  
  15. //! - \%d to print a decimal value  
  16. //! - \%s to print a string  
  17. //! - \%u to print an unsigned decimal value  
  18. //! - \%x to print a hexadecimal value using lower case letters  
  19. //! - \%X to print a hexadecimal value using lower case letters (not upper case  
  20. //! letters as would typically be used)  
  21. //! - \%p to print a pointer as a hexadecimal value  
  22. //! - \%\% to print out a \% character  
  23. //!  
  24. //! For \%s, \%d, \%u, \%p, \%x, and \%X, an optional number may reside  
  25. //! between the \% and the format character, which specifies the minimum number  
  26. //! of characters to use for that value; if preceded by a 0 then the extra  
  27. //! characters will be filled with zeros instead of spaces.  For example,  
  28. //! ``\%8d'' will use eight characters to print the decimal value with spaces  
  29. //! added to reach eight; ``\%08d'' will use eight characters as well but will  
  30. //! add zeroes instead of spaces.  
  31. //!  
  32. //! The type of the arguments after \e pcString must match the requirements of  
  33. //! the format string.  For example, if an integer was passed where a string  
  34. //! was expected, an error of some kind will most likely occur.  
  35. //!  
  36. //! \return None.  
  37. //  
  38. //*****************************************************************************  
  39. void  
  40. UARTprintf(const char *pcString, ...)  
  41. {  
  42.     unsigned long ulIdx, ulValue, ulPos, ulCount, ulBase, ulNeg;  
  43.     char *pcStr, pcBuf[16], cFill;  
  44.     va_list vaArgP;  
  45.   
  46.     //  
  47.     // Check the arguments.  
  48.     //  
  49.     ASSERT(pcString != 0);  
  50.   
  51.     //  
  52.     // Start the varargs processing.  
  53.     //  
  54.     va_start(vaArgP, pcString);  
  55.   
  56.     //  
  57.     // Loop while there are more characters in the string.  
  58.     //  
  59.     while(*pcString)  
  60.     {  
  61.         //  
  62.         // Find the first non-% character, or the end of the string.  
  63.         //  
  64.         for(ulIdx = 0; (pcString[ulIdx] != '%') && (pcString[ulIdx] != '\0');  
  65.             ulIdx++)  
  66.         {  
  67.         }  
  68.   
  69.         //  
  70.         // Write this portion of the string.  
  71.         //  
  72.         UARTwrite(pcString, ulIdx);  
  73.   
  74.         //  
  75.         // Skip the portion of the string that was written.  
  76.         //  
  77.         pcString += ulIdx;  
  78.   
  79.         //  
  80.         // See if the next character is a %.  
  81.         //  
  82.         if(*pcString == '%')  
  83.         {  
  84.             //  
  85.             // Skip the %.  
  86.             //  
  87.             pcString++;  
  88.   
  89.             //  
  90.             // Set the digit count to zero, and the fill character to space  
  91.             // (i.e. to the defaults).  
  92.             //  
  93.             ulCount = 0;  
  94.             cFill = ' ';  
  95.   
  96.             //  
  97.             // It may be necessary to get back here to process more characters.  
  98.             // Goto's aren't pretty, but effective.  I feel extremely dirty for  
  99.             // using not one but two of the beasts.  
  100.             //  
  101. again:  
  102.   
  103.             //  
  104.             // Determine how to handle the next character.  
  105.             //  
  106.             switch(*pcString++)  
  107.             {  
  108.                 //  
  109.                 // Handle the digit characters.  
  110.                 //  
  111.                 case '0':  
  112.                 case '1':  
  113.                 case '2':  
  114.                 case '3':  
  115.                 case '4':  
  116.                 case '5':  
  117.                 case '6':  
  118.                 case '7':  
  119.                 case '8':  
  120.                 case '9':  
  121.                 {  
  122.                     //  
  123.                     // If this is a zero, and it is the first digit, then the  
  124.                     // fill character is a zero instead of a space.  
  125.                     //  
  126.                     if((pcString[-1] == '0') && (ulCount == 0))  
  127.                     {  
  128.                         cFill = '0';  
  129.                     }  
  130.   
  131.                     //  
  132.                     // Update the digit count.  
  133.                     //  
  134.                     ulCount *= 10;  
  135.                     ulCount += pcString[-1] - '0';  
  136.   
  137.                     //  
  138.                     // Get the next character.  
  139.                     //  
  140.                     goto again;  
  141.                 }  
  142.   
  143.                 //  
  144.                 // Handle the %c command.  
  145.                 //  
  146.                 case 'c':  
  147.                 {  
  148.                     //  
  149.                     // Get the value from the varargs.  
  150.                     //  
  151.                     ulValue = va_arg(vaArgP, unsigned long);  
  152.   
  153.                     //  
  154.                     // Print out the character.  
  155.                     //  
  156.                     UARTwrite((char *)&ulValue, 1);  
  157.   
  158.                     //  
  159.                     // This command has been handled.  
  160.                     //  
  161.                     break;  
  162.                 }  
  163.   
  164.                 //  
  165.                 // Handle the %d command.  
  166.                 //  
  167.                 case 'd':  
  168.                 {  
  169.                     //  
  170.                     // Get the value from the varargs.  
  171.                     //  
  172.                     ulValue = va_arg(vaArgP, unsigned long);  
  173.   
  174.                     //  
  175.                     // Reset the buffer position.  
  176.                     //  
  177.                     ulPos = 0;  
  178.   
  179.                     //  
  180.                     // If the value is negative, make it positive and indicate  
  181.                     // that a minus sign is needed.  
  182.                     //  
  183.                     if((long)ulValue < 0)  
  184.                     {  
  185.                         //  
  186.                         // Make the value positive.  
  187.                         //  
  188.                         ulValue = -(long)ulValue;  
  189.   
  190.                         //  
  191.                         // Indicate that the value is negative.  
  192.                         //  
  193.                         ulNeg = 1;  
  194.                     }  
  195.                     else  
  196.                     {  
  197.                         //  
  198.                         // Indicate that the value is positive so that a minus  
  199.                         // sign isn't inserted.  
  200.                         //  
  201.                         ulNeg = 0;  
  202.                     }  
  203.   
  204.                     //  
  205.                     // Set the base to 10.  
  206.                     //  
  207.                     ulBase = 10;  
  208.   
  209.                     //  
  210.                     // Convert the value to ASCII.  
  211.                     //  
  212.                     goto convert;  
  213.                 }  
  214.   
  215.                 //  
  216.                 // Handle the %s command.  
  217.                 //  
  218.                 case 's':  
  219.                 {  
  220.                     //  
  221.                     // Get the string pointer from the varargs.  
  222.                     //  
  223.                     pcStr = va_arg(vaArgP, char *);  
  224.   
  225.                     //  
  226.                     // Determine the length of the string.  
  227.                     //  
  228.                     for(ulIdx = 0; pcStr[ulIdx] != '\0'; ulIdx++)  
  229.                     {  
  230.                     }  
  231.   
  232.                     //  
  233.                     // Write the string.  
  234.                     //  
  235.                     UARTwrite(pcStr, ulIdx);  
  236.   
  237.                     //  
  238.                     // Write any required padding spaces  
  239.                     //  
  240.                     if(ulCount > ulIdx)  
  241.                     {  
  242.                         ulCount -= ulIdx;  
  243.                         while(ulCount--)  
  244.                         {  
  245.                             UARTwrite(" ", 1);  
  246.                         }  
  247.                     }  
  248.                     //  
  249.                     // This command has been handled.  
  250.                     //  
  251.                     break;  
  252.                 }  
  253.   
  254.                 //  
  255.                 // Handle the %u command.  
  256.                 //  
  257.                 case 'u':  
  258.                 {  
  259.                     //  
  260.                     // Get the value from the varargs.  
  261.                     //  
  262.                     ulValue = va_arg(vaArgP, unsigned long);  
  263.   
  264.                     //  
  265.                     // Reset the buffer position.  
  266.                     //  
  267.                     ulPos = 0;  
  268.   
  269.                     //  
  270.                     // Set the base to 10.  
  271.                     //  
  272.                     ulBase = 10;  
  273.   
  274.                     //  
  275.                     // Indicate that the value is positive so that a minus sign  
  276.                     // isn't inserted.  
  277.                     //  
  278.                     ulNeg = 0;  
  279.   
  280.                     //  
  281.                     // Convert the value to ASCII.  
  282.                     //  
  283.                     goto convert;  
  284.                 }  
  285.   
  286.                 //  
  287.                 // Handle the %x and %X commands.  Note that they are treated  
  288.                 // identically; i.e. %X will use lower case letters for a-f  
  289.                 // instead of the upper case letters is should use.  We also  
  290.                 // alias %p to %x.  
  291.                 //  
  292.                 case 'x':  
  293.                 case 'X':  
  294.                 case 'p':  
  295.                 {  
  296.                     //  
  297.                     // Get the value from the varargs.  
  298.                     //  
  299.                     ulValue = va_arg(vaArgP, unsigned long);  
  300.   
  301.                     //  
  302.                     // Reset the buffer position.  
  303.                     //  
  304.                     ulPos = 0;  
  305.   
  306.                     //  
  307.                     // Set the base to 16.  
  308.                     //  
  309.                     ulBase = 16;  
  310.   
  311.                     //  
  312.                     // Indicate that the value is positive so that a minus sign  
  313.                     // isn't inserted.  
  314.                     //  
  315.                     ulNeg = 0;  
  316.   
  317.                     //  
  318.                     // Determine the number of digits in the string version of  
  319.                     // the value.  
  320.                     //  
  321. convert:  
  322.                     for(ulIdx = 1;  
  323.                         (((ulIdx * ulBase) <= ulValue) &&  
  324.                          (((ulIdx * ulBase) / ulBase) == ulIdx));  
  325.                         ulIdx *= ulBase, ulCount--)  
  326.                     {  
  327.                     }  
  328.   
  329.                     //  
  330.                     // If the value is negative, reduce the count of padding  
  331.                     // characters needed.  
  332.                     //  
  333.                     if(ulNeg)  
  334.                     {  
  335.                         ulCount--;  
  336.                     }  
  337.   
  338.                     //  
  339.                     // If the value is negative and the value is padded with  
  340.                     // zeros, then place the minus sign before the padding.  
  341.                     //  
  342.                     if(ulNeg && (cFill == '0'))  
  343.                     {  
  344.                         //  
  345.                         // Place the minus sign in the output buffer.  
  346.                         //  
  347.                         pcBuf[ulPos++] = '-';  
  348.   
  349.                         //  
  350.                         // The minus sign has been placed, so turn off the  
  351.                         // negative flag.  
  352.                         //  
  353.                         ulNeg = 0;  
  354.                     }  
  355.   
  356.                     //  
  357.                     // Provide additional padding at the beginning of the  
  358.                     // string conversion if needed.  
  359.                     //  
  360.                     if((ulCount > 1) && (ulCount < 16))  
  361.                     {  
  362.                         for(ulCount--; ulCount; ulCount--)  
  363.                         {  
  364.                             pcBuf[ulPos++] = cFill;  
  365.                         }  
  366.                     }  
  367.   
  368.                     //  
  369.                     // If the value is negative, then place the minus sign  
  370.                     // before the number.  
  371.                     //  
  372.                     if(ulNeg)  
  373.                     {  
  374.                         //  
  375.                         // Place the minus sign in the output buffer.  
  376.                         //  
  377.                         pcBuf[ulPos++] = '-';  
  378.                     }  
  379.   
  380.                     //  
  381.                     // Convert the value into a string.  
  382.                     //  
  383.                     for(; ulIdx; ulIdx /= ulBase)  
  384.                     {  
  385.                         pcBuf[ulPos++] = g_pcHex[(ulValue / ulIdx) % ulBase];  
  386.                     }  
  387.   
  388.                     //  
  389.                     // Write the string.  
  390.                     //  
  391.                     UARTwrite(pcBuf, ulPos);  
  392.   
  393.                     //  
  394.                     // This command has been handled.  
  395.                     //  
  396.                     break;  
  397.                 }  
  398.   
  399.                 //  
  400.                 // Handle the %% command.  
  401.                 //  
  402.                 case '%':  
  403.                 {  
  404.                     //  
  405.                     // Simply write a single %.  
  406.                     //  
  407.                     UARTwrite(pcString - 1, 1);  
  408.   
  409.                     //  
  410.                     // This command has been handled.  
  411.                     //  
  412.                     break;  
  413.                 }  
  414.   
  415.                 //  
  416.                 // Handle all other commands.  
  417.                 //  
  418.                 default:  
  419.                 {  
  420.                     //  
  421.                     // Indicate an error.  
  422.                     //  
  423.                     UARTwrite("ERROR", 5);  
  424.   
  425.                     //  
  426.                     // This command has been handled.  
  427.                     //  
  428.                     break;  
  429.                 }  
  430.             }  
  431.         }  
  432.     }  
  433.   
  434.     //  
  435.     // End the varargs processing.  
  436.     //  
  437.     va_end(vaArgP);  
  438. }  

写的很精彩,是不是?



五、应用层的使用

通过上面的工作,我们已经实现了一个自己的printf函数: LLprintf

LLprintf的功能和我们标准库的printf一样强大,我们可以在上层如此使用LLprintf:

[cpp] view plaincopy
  1. /************************************************************************************ 
  2. ** File: - Z:\code\c\LLprintf\print2.1\app.c 
  3. **   
  4. ** Copyright (C), Long.Luo, All Rights Reserved! 
  5. **  
  6. ** Description:  
  7. **      app.c --- The Application Level. 
  8. **  
  9. ** Version: 2.1 
  10. ** Date created: 23:53:41,24/01/2013 
  11. ** Author: Long.Luo 
  12. **  
  13. ** --------------------------- Revision History: -------------------------------- 
  14. **  <author>  <data>            <desc> 
  15. **  
  16. ************************************************************************************/  
  17.   
  18. #include "LLprintf.h"  
  19.   
  20. int main(void)  
  21. {  
  22.     char *welcome = "     A Tiny Demo show the LLprintf   ";  
  23.     char *program_name = "LLprintf";  
  24.     char *program_author = "Long.Luo";  
  25.     char *date = "Jan. 24th, 2013";  
  26.       
  27.     float program_version = 2.1;  
  28.       
  29.     Lprintf("%s\n\n", welcome);  
  30.     Lprintf("\t\t%s, version %f \n\n", program_name, program_version);  
  31.     Lprintf("\tCreated by %s, %s.\n\n", program_author, date);  
  32.       
  33.     return 0;  
  34. }  

make一下,我们再来看看输出结果:



这样我们就了解了printf函数的前因后果,聪明的你,弄明白了吗?^_^

0 0