首页 > 代码库 > 关于可变长数组的思考

关于可变长数组的思考

关于可变长数组的思考

  前述:

    第一条记:

    最近在海纳百川的地铁上,脑海中常想起魏源说过一句话"师夷长技以制夷". 联想最近中国和日本的态度,再联想起自己的人生经历,可以说我是看日本动漫,打日本游戏长大的,到现在看的工具书中绝大部分是老美和日本的.还有一个那就是看金庸和古龙所代表的武侠江湖梦.总结就是,在日本文化我学到了坚强,永不服输.老美的文化给了我探求科学的阶梯.中国的文化给我烙印上了骨气,我一直认为最后着是最重要的.再总结就是"用别人的长处征服别人,用自己的长处统治别人"

    第二条记:

    我的好朋友网视悠悠教给我与人处态度"宁可人负我,我绝不负人",我再联想我那‘老乡‘阿瞒说的一句话"宁教我负天下人,休教天下人负我".一合并,就是"宁教天下人负我,休教我负天下人".我觉得很霸气很对.

    第三条记:

    最后我想起了,雷峰同志笔记中记叙的一句话:"对待同志要像春天般的温暖,对待工作要像夏天一样火热,对待个人主义要像秋风扫落叶一样,对待敌人要像严冬一样残酷无情.".用他的思想扩展一下,我领悟的是"接受变化,敢于变化,必须变化".举个例子就是:对待日本,采用第一条记,别人=日本.对待中国,采用第二条记,天下人=中国.有点不冷静了,估计是我恨我自己,这么快就忘记了自己的锐气!

 

正文:

   最经看到下面一段程序(出自<<征服C指针>>,[日]前桥和弥,吴亚明 译):

#include <stdio.h>

#include <stdlib.h>

 

int main(void)

{

    char    buf[256];

    int     size;

    int     *variable_array;

    int     i;

 

    printf("Input array size>");

    fgets(buf,256,stdin);

    sscanf(buf,"%d",&size);

 

    variable_array=malloc(sizeof(int)*size);

 

    for(i=0;i<size;i++)

    {

        variable_array[i]=i;

    }

    for(i=0;i<size;i++)

    {

        printf("variable_array[%d]..%d\n",i,variable_array[i]);

    }

 

    return 0;

}

发现些这个程序的作者绝对是个另类的程序员,它发现了C中scanf()函数不安全,存在输入缓存问题. 

scanf()函数缓存问题描述如下:

while(scanf("%d",&hoge)!=1)

{

    fprintf(stderr,"Input error, please enter again!\n");

}

上面的代码只要输入出错一次,就进入了死循环.导致BUG就在于,输入的缓存问题.解决这个问题的办法就是,消除缓存.写上面的作者

显然也是考虑到了这个问题,采用了下面的方法

char buf[256];

int size;

fgets(buf,256,stdin);

sscanf(buf,"%d",&size);

读取一串数据到第一参数中,在数据少于256个ASII字符的情况下都没问题.再从buf字符串中格式化读取数据到size中.这种解决缓存的方法,很巧妙, 用缓存来读缓存,来消除缓存.但是对于严谨的程序而言这显然是不行的.当用户输入的数据超出了你所设置的缓存阀值,并注入了一段破坏的代码,怎么办.这个问题高桥和弥也说的很详细,先列举网络上的蠕虫病毒,最后又说256的缓存够了.他很严谨,但他仍然保留了这个错误,用小日本的话说,这是不可原谅的.

我的解决办法如下,设计原则是不相信用户(的输入).

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

 

int main(void)

{

    int size;/*数组大小*/

    int i;/*下标的指示*/

    int *variable_array;/*储存数据的数组*/

    int ch;/*清除缓存用的临时变量*/

    int hoge=0;/*记录用户输入变量是否和法*/

 

    do

    {

        printf("Input array size>");

        hoge=scanf("%d",&size);/*获取用户输入的变量*/

        if(hoge<=0||size<=0)

        {

            printf("You input is error!\n");

            printf("You need to enter again.\n");

            hoge=0;/*清空接收的值*/

        }

        while((ch=getchar())!=10&&ch!=EOF);/*这只适用于控制台和用t打开的文件*/

  }while(hoge<=0);

 

    /*下面是合法情况,声明空间*/

    variable_array=malloc(size*sizeof(int));

    /*为数组赋值*/

    for(i=0;i<size;i++)

    {

        *(variable_array+i)=i;

    }

 

    /*输出刚才赋的值*/

    printf("The following array data:\n");

    for(i=0;i<size;i++)

    {

     printf("variable_array[%d]...%d \n",i,variable_array[i]);

    }

 

    getch();

    return 0;

}

 

解决的方法就是利用

int ch;

while((ch=getchar())!=10&&ch!=EOF);

上面的代码消除输出缓存

再根据

if(hoge<=0||size<=0)

{

    printf("You input is error!\n");

    printf("You need to enter again.\n");

    hoge=0;/*清空接收的值*/

}

判断用户输入是否合法,不断的继续循环,利用getchar()吃掉多余的缓存,直到用户输入正确为止.

 

封装:

  上面方法虽然解决BUG,但是感觉代码耦合性太强,能不能分离封装一下呢.当然是可以的,下面先封装一个清空输入缓存的

方法

/*

 *描述:清除控制台输入流

 *函数声明:void clear_input_cache(void)

 *函数参数:void

 *返回值:void

 *使用例子:

 *        int hoge;

 *        scanf("%d",&hoge);

 *        clear_input_cache();

 */

void clear_input_cache(void)

{

    int ch;

    while((ch=getchar())!=10&&ch!=-1);

}

但是感觉scanf()函数能够获取数据,但是第一次输入错了,还有第二次输入的机会怎么办呢.继续封装scanf()

封装代码如下:

#include <stdarg.h>

/*

 *描述:安全的scanf,它会在用户输出错误的情况下,给予提示,让用户重新输入

 *       但是,还是存在scanf同样的BUG比如想得到%d数据,你输入了123ads也是可以的.

 *函数声明:void security_scanf(int variable_count,char *format,...)

 *函数参数:

 *        int variable_count;表示需要要接收多少个参数,这个参数是安全输入的关键.

 *        char *format;接收数据的格式

 *        ...:表示接收数据的变量,可变参数

 *返回值:void

 *使用例子:

 *         int hoge,piyo;

 *         security_scanf(2,"%d %d",&hoge,&piyo);

 */

void security_scanf(int variable_count,char *format,...)

{

    int scn_count=0;/*记录用户输入的变量*/

    va_list arg_list;

    for(;;)

    {

        va_start(arg_list,format);

        scn_count=scanf(format,arg_list);

        va_end(arg_list);

        /*如果接收成功就直接结束循环*/

        if(variable_count==scn_count)

            break;

        /*如果输入出错,先清空输入流,再重新开始,并给出用户提示*/

        clear_input_cache();

        printf("You input is error!\n");

        printf("You need to enter again.\n");

        printf("You input data>");

    }

}

利用上面封装方法,重新写一个程序,来解决scanf()输入缓存的问题.代码如下,说一些题外话,我没有‘赶尽杀绝‘,总是消灭输入

缓存.将scanf和clear_input_cache捆绑在一起,因为编程有的时候也需要输入缓存,例如可以输出缓存来分析程序出错的地方在哪.

而且scanf函数也能通过设置特殊的读取的格式来清除输入缓存.所以上面的方法只是一种参考,还有更多的方法.

自由在读者自己.自由也是C的一大乐趣吧.

下面是利用上面封装的方法写的程序:

#include <stdio.h>

#include <stdlib.h>

#include <conio.h>

#include <stdarg.h>

 

int main(void)

{

    int size;/*数组大小*/

    int i;/*下标的指示*/

    int *variable_array;/*储存数据的数组*/

 

    for(;;)

    {

        printf("Input array size>");

        security_scanf(1,"%d",&size);

        if(size>0)/*0这个值是否保留,值得讨论,在这里我就排除0了*/

            break;

        printf("Input array size is error!");

        printf("He can‘t less than and equal to zero!");

    }

 

    /*下面是合法情况,声明空间*/

    variable_array=malloc(size*sizeof(int));

    /*为数组赋值*/

    for(i=0;i<size;i++)

    {

        *(variable_array+i)=i;

    }

 

    /*输出刚才赋的值*/

    printf("The following array data:\n");

    for(i=0;i<size;i++)

    {

       printf("variable_array[%d]...%d \n",i,variable_array[i]);

    }

 

    getch();

    return 0;

}

其实观看上面封装好的代码,还是有点不爽的,代码还是比较多.这没真没办法了.其实关键是scanf()函数支持可变参数,

继续封装起来比较麻烦,否则如果参数固定,直接加一个void *指针接收函数指针来处里.那样封装起来就更爽了.当然我觉得

封装的越好,用起来就越爽,速度就慢下来了,效率就低了.

 

扩展:

下面展示一下scanf()函数源码,来自于Microsoft的C++,源自scanf.c文件中

/***

*scanf.c - read formatted data from stdin

*

*       Copyright (c) 1985-1997, Microsoft Corporation. All rights reserved.

*

*Purpose:

*       defines scanf() - reads formatted data from stdin

*

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

#include <cruntime.h>

#include <stdio.h>

#include <dbgint.h>

#include <stdarg.h>

#include <file2.h>

#include <internal.h>

#include <mtdll.h>

 

/***

*int scanf(format, ...) - read formatted data from stdin

*

*Purpose:

*       Reads formatted data from stdin into arguments.  _input does the real

*       work here.

*

*Entry:

*       char *format - format string

*       followed by list of pointers to storage for the data read.  The number

*       and type are controlled by the format string.

*

*Exit:

*       returns number of fields read and assigned

*

*Exceptions:

*

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

/*

 * stdin ‘SCAN‘, ‘F‘ormatted

 */

int __cdecl scanf (const char *format,...)

{

        int retval;

        va_list arglist;

        va_start(arglist, format);

        _ASSERTE(format != NULL);

        _lock_str2(0, stdin);

        retval = (_input(stdin,format,arglist));

        _unlock_str2(0, stdin);

        return(retval);

}