首页 > 代码库 > [2012山东ACM省赛] Fruit Ninja II (三重积分,椭球体积)

[2012山东ACM省赛] Fruit Ninja II (三重积分,椭球体积)

              大概去年夏天的时候,在《C和指针》里面一个练习题要求实现一个很简单的不包含打印浮点数功能的printf函数。但是很好奇,于是一直纠结下去,结果就是知道了printf的实现,自己也写了一个简单的。或许是夏天的原因吧,那时候暑假没回去,凌晨四点兴奋到不能睡觉。那时候刚开始写blog。没想整理一下,只是简单的把最重要的实现"工具"贴了一个blog在

http://blog.csdn.net/cinmyheart/article/details/9804189


那时候第一次看linux的内核代码,熬过来的感觉总是很舒坦的。。。现在可能还是有很多看不懂,但是不会去畏惧那些长段长段的宏定义函数了。。。各种高上大的skill啊。。。。


言归正传,实现printf。

 


其实printf和getchar()类似,它们都是一个”外壳“,真正实现功能的不是它本身,而是通过调用别的函数。


getchar() is equivalent to getc(stdin).


printf有一家子print函数

 
       printf, fprintf, sprintf, snprintf, vprintf, vfprintf, vsprintf, vsnprintf - formatted output conversion
它们的声明在不同的header file里面

       #include <stdio.h>

       int printf(const char *format, ...);
       int fprintf(FILE *stream, const char *format, ...);
       int sprintf(char *str, const char *format, ...);
       int snprintf(char *str, size_t size, const char *format, ...);

       #include <stdarg.h>

       int vprintf(const char *format, va_list ap);
       int vfprintf(FILE *stream, const char *format, va_list ap);
       int vsprintf(char *str, const char *format, va_list ap);
       int vsnprintf(char *str, size_t size, const char *format, va_list ap);

       snprintf(), vsnprintf():
           这两个函数是C99新加的,编译的时候 注意 -std=c99

实现之前还是“复习”一下printf比较好,就当是铺垫


有意思的是printf的declaration。

int printf(const char *format, ...);

返回值是int,第一个参数是const字符串指针,第二个参数是个...


先看看返回值int有哪些情况


Return value
    Upon successful return, these functions return the number of characters printed (excluding the  null  byte  used  to  end  output  to strings).


嗯哼。。。返回的是成功打印的字符的个数,这里不包括NULL

demo:

#include<stdio.h>

int main()
{
        int counter = 0;

        counter = printf("hello world! %d\n",10);

        printf("counter is %d\n",counter);

        return 0;
}

jasonleaster@ubuntu:~$ ./a.out
hello world! 10
counter is 16



接着,第一个参数是一个指针,指向const字符串


Format of the format string
       The format string is a character string, beginning and ending in its initial shift state, if any.  The format string is  composed  of
       zero or more directives: ordinary characters (not %), which are copied unchanged to the output stream; and conversion specifications,
       each of which results in fetching zero or more subsequent arguments.  Each conversion specification is introduced by the character %,
       and  ends  with a conversion specifier.  In between there may be (in this order) zero or more flags, an optional minimum field width,
       an optional precision and an optional length modifier.


很少人会用下面这种用法

printf("%*d",10,5);


我第一次遇到的时候,可以说是“惊愕”,究竟会打印什么东西。折腾了好久,最后搞定了。总结在这里

http://blog.csdn.net/cinmyheart/article/details/10116359


Format of the format string


       The format string is a character string, beginning and ending in its initial shift state, if any.  The format string is  composed  of zero or more directives: ordinary characters (not %), which are copied unchanged to the output stream; and conversion specifications, each of which results in fetching zero or more subsequent arguments.  Each conversion specification is introduced by the character %, and  ends  with a conversion specifier.  In between there may be (in this order) zero or more flags, an optional minimum field width, an optional precision and an optional length modifier.


       The arguments must correspond properly (after type promotion) with the conversion specifier.  By default, the arguments are  used  in the  order  given, where each ‘*‘ and each conversion specifier asks for the next argument (and it is an error if insufficiently many arguments are given).  One can also specify explicitly which argument is taken, at each place where an argument is required, by writing  "%m$"  instead  of  ‘%‘  and  "*m$" instead of ‘*‘, where the decimal integer m denotes the position in the argument list of the
       desired argument, indexed starting from 1.  Thus,

           printf("%*d", width, num);

       and

           printf("%2$*1$d", width, num);

       are equivalent.  The second style allows repeated references to the same argument.  The C99 standard does not include the style using ‘$‘,  which comes from the Single UNIX Specification.  If the style using ‘$‘ is used, it must be used throughout for all conversions taking an argument and all width and precision arguments, but it may be mixed with "%%" formats which do  not  consume  an  argument.
       There  may  be  no  gaps in the numbers of arguments specified using ‘$‘; for example, if arguments 1 and 3 are specified, argument 2
       must also be specified somewhere in the format string.

第三个参数  ...


嗯,这家伙有点屌,叫做变长参数。把这个搞定,C总会有点长进的


           这个stdarg.h 我在现在的GCC和现在的linux 3.0版本的内核里面找了好久,都木有,估计是封装到被的地方了。。。。

__builtin_va_start(v,l) 线索就死在这个地方。。。之后就找不到__builtin_va_start的定义了


还是看早起内核的实现吧

0.12内核里面的stdarg.h

#ifndef _STDARG_H
#define _STDARG_H

typedef char *va_list;

/* Amount of space required in an argument list for an arg of type TYPE.
   TYPE may alternatively be an expression whose type is used.  */

#define __va_rounded_size(TYPE)    (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

#ifndef __sparc__
#define va_start(AP, LASTARG) 						 (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#else
#define va_start(AP, LASTARG) 						 (__builtin_saveregs (),						  AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))
#endif

void va_end (va_list);		/* Defined in gnulib */
#define va_end(AP)

#define va_arg(AP, TYPE)						 (AP += __va_rounded_size (TYPE),					  *((TYPE *) (AP - __va_rounded_size (TYPE))))

#endif /* _STDARG_H */


va_list 是一个指向字符串的指针


分析上面的宏定义

#define __va_rounded_size(TYPE)    (((sizeof (TYPE) + sizeof (int) - 1) / sizeof (int)) * sizeof (int))

这个用来得到TYPE元素类型的字节大小,若不足4字节(例如short 和char),那么认为这个元素的大小为4字节,简单的说就是检测元素的大小,不足4字节的当作4字节看待。。。

 

#define va_start(AP, LASTARG) 						 (AP = ((char *) &(LASTARG) + __va_rounded_size (LASTARG)))

AP一般都是va_list,LASTARG则是指向参数变长函数的格式化字符串的指针.

va_start的作用就很明显了。取得变长参数列表的第一个参数的地址。


va_end 则是把指针va_list 置0 (通过google知道的,这个va_end真没找到定义,代码里面就一句#define 我无能为力啊。。。)


不过知道用va_start 和va_end 就OK啦



下面先来个变长参数的demo

/*****************************************************************************
code writer : EOF
code date : 2014.04.26
e-mail:jasonleaster@gmail.com
code purpose:
        just a demo for varible parameter function.

        usage: va_sum(a number,anohter number...,0);
               va_sum(1,2,3,4,5,0); return 15

******************************************************************************/

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

int va_sum(int* a,...);

int main()
{
        int number = 1;

        int foo = 0;

        foo = va_sum(&number,2,3,4,5,0);

        return 0;
}


int va_sum(int* a,...)
{
        int counter = 0;
        int element = 0;

        va_list arg;

        va_start(arg,a);

        while((element = va_arg(arg,int)) != 0)
        {
                counter += element;
        }

        va_end(arg);

        return counter;
}

写这个demo的时候,稍微纠结了一下,还是留了个小陷阱,提示一下,最后foo的值不是15

这里要特别注意变长参数函数的第一个值,必须是个指针,就是利用这个指针和栈的FILO的性质来找到后面的元素的,这个是参数变长函数最根本的原理,利用了参数传递保存在栈里面,而且是连续的!


va_arg的作用很明显

#define va_arg(AP, TYPE)						 (AP += __va_rounded_size (TYPE),					  *((TYPE *) (AP - __va_rounded_size (TYPE))))

        把移动AP指针并取4字节内容读取出来。。。。我的语言表达只能这样了。。。T-T,看官自行领悟了。。。


        好吧,是时候贴出来我自己写的一个闹着玩的printf了,仅仅是好玩,实现一下很基本的功能而已,viewer有兴趣的话可以去看vsprintf.c, 看源码是个很爽的过程(如果看懂的话)。

/********************************************************************
code writer : EOF
code date : 2014.04.26
e-mail : jasonleaster@gmail.com

copyright@jasoneleaster

code purpose:
        I love open source and I would like to help another people
to learn programming. This is just a demo for how to implement 
a "printf". It‘s not perfect just like a little duck. But it‘s a 
representation of "printf".If there is something wrong with my code
please touch me by e-mail. Thank you.

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

int my_printf(const char* string,...);

int main()
{
        my_printf("hello world!\n");

        return 0;
}

int my_printf(const char* string,...)
{

        char buffer[BUFSIZ];//Be tolerant with my_printf, never try to jump ovet the band of the buffer -- buffer overflow

        int temp = 0;
        va_list arg;

        char* p_string = NULL;
        char* p_buffer = buffer;
        char* p_temp   = NULL;

        int counter = 0;
        int number  = 0;
        int foo     = 0;

        va_start(arg,string);

        for(counter = 0,p_string = string;*(p_string) != ‘\0‘;)
        {
                switch(*p_string)
                {
                        case ‘%‘:
                                p_string++;

                                switch(*p_string)
                                {
                                        case ‘d‘:

                                                temp = va_arg(arg,int);

                                                foo = temp;

                                                while(foo)
                                                {
                                                        number++;
                                                        counter++;
                                                        foo /= 10;
                                                }

                                                foo = temp;

                                                while(number)
                                                {
                                                        *(p_buffer+number-1) = (foo%10);
                                                        foo /= 10;
                                                        number--;
                                                }

                                                p_buffer += number;
                                        break;

                                        case ‘c‘:
                                                temp = va_arg(arg,int);
                                                *(p_buffer++) = temp;
                                        break;

                                        case ‘s‘:
                                                p_temp = va_arg(arg,char*);

                                                while(p_temp != NULL)
                                                {
                                                        *(p_buffer++) = *(p_temp++);
                                                        counter++;
                                                }
                                        break;

                                        default:
                                                break;

                                }
                        break;

                        default:
                                *(p_buffer++) = *(p_string++);
                                counter++;
                }
        }

        va_end(arg);

        p_buffer = NULL;

        puts(buffer);

        return counter;
}


真的很幼稚,建议去看vsprintf。很精彩








[2012山东ACM省赛] Fruit Ninja II (三重积分,椭球体积)