【问题标题】:Implementing an alternative to scanf实现 scanf 的替代方案
【发布时间】:2018-07-13 20:29:43
【问题描述】:

因为几乎所有旨在从 stdin 获取数据的 C 函数都是坏的/有缺陷的:

  • gets这个话题越少越好

  • scanf 没有检查缓冲区溢出,'\n' 一直留在stdin,接下来搞砸了scanfs

  • scanf_s 几乎相同,但带有缓冲区溢出检查
  • fgets'\n' 附加到 string
  • gets_s没有之前的问题,但是对其他流没用

我决定编写自己的函数,至少可以用来读取来自stdin 的数字

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void *scano(char mode);

int main()
{

  // int *num = (int *) scano(sData, 'd');
   float *num = (float *)scano('f');
   printf("Half: %f", *(num)/2);



    return 0;
}

void *scano(char mode){

    char sData[20];
    fgets(sData, 20, stdin);
    *(sData + strlen(sData) - 1) = '\0'; //get rid of the '\n' before the '\0'
   switch(mode){
       case 'd':{
           int *dataI = (int *)malloc(sizeof(int));
           *dataI = atoi(sData);
           return dataI;
       }
       case 'f':{
           float *dataF = (float *)malloc(sizeof(float));
           *dataF = atof(sData);
           return dataF;
       }
       case 'D':{
           //double...
       }
   }

}

对于其他数据类型,功能显然是未完成的,但我先有几个问题:

  • 该函数的算法如何改进?
  • 我不需要在每个case 中添加free() 吗?我知道分配的内存 需要释放,但在使用列表时,free() 只是 用于删除Nodes,创建Nodes时,没有调用free()malloc() 之后。
  • 它完全安全吗?如果没有,如何保证安全?

【问题讨论】:

  • malloc 一个浮点数有什么意义?返回指向它的指针有什么意义?
  • 这个问题会在“代码审查”姐妹网站上得到更好的接受,但我有三个 cmets 给你:(1)使用strtolstrtod等,其中(不同于两者scanfato* 系列)实际上以理智的方式报告数字溢出; (2)你最初的fgets操作不正确,想想如果有人输入超过20个字符怎么办; (3) 您对malloc 的使用实际上是正确的!呼叫者有责任致电free。但最好有单独的scan_Iscan_Fscan_D 等直接返回值的函数。
  • @EugeneSh。我想分配一个浮点数没有意义,因为我可以声明一个。由于函数本身的类型 void *,我需要返回一个指向它的指针。我希望该函数能够处理多种数据类型。
  • @OlegPlachkov 如果你想返回一个指针,你必须使用malloc。返回指向已声明函数局部变量的指针是不正确的。
  • @zwol (2) 我不同意,fgets 只读取前 20 个字符,如果有人键入超过 20 个字符,则字符串将不超过 20 个符号(嗯,19 个有意义的符号) . (3) 你的意思是呼叫者有责任拨打免费电话是什么意思?你的意思是在我调用scano之后我必须在main中调用一个free?至于单独的 scan_I 函数等,其想法是让 1 个函数为它们完成所有工作,因此该函数为 void *。

标签: c scanf stdin


【解决方案1】:

以下是从输入源返回数值的几个函数的简单示例。这些示例期望使用几种类型的空白字符(空格、制表符、行尾、回车)中的一种来分隔一组数字字段。

这些是为了展示一种方法,肯定有改进的余地。

建议大家看看atoi vs atol vs strtol vs strtoul vs sscanf的问答讨论

fgetc() 函数用于从输入源一次拉出一个字符,并测试输入源的读取应该继续还是停止。通过使用fgetc() 函数,我们可以允许其他函数在使用scano_l()scan_d() 时继续从输入源读取数据。

我们还通过使用本地缓冲区并将实际值作为longdouble 返回,从而消除了对malloc() 和伴随的free() 和内存管理的需要。

例如,使用 C++ 主程序对这些进行简单测试(_tmain() 是因为我使用 Microsoft Visual Studio 2005 生成 Windows 控制台应用程序)如下。这可以通过编译然后尝试几种不同的数据输入场景来测试,其中输入一个整数,如1234,后跟一个或多个空白字符(空格、制表符、换行符),然后是一个浮点数,如如45.678 后跟至少一个空格字符,然后是一些文本字符。

// scan_no.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <stdlib.h>

extern "C" {
long  scano_l (FILE *pFile);

double  scano_d (FILE *pFile);
};

int _tmain(int argc, _TCHAR* argv[])
{
    // test by reading a few white space delimited numeric value and then a
    // text string just to demonstrate how it works.
    long l = scano_l (stdin);
    double d = scano_d (stdin);
    char testBuffer[256] = {0};

    fgets (testBuffer, 255, stdin);

    printf (" value is %ld\n", l);
    printf (" value is %lf\n", d);
    printf (" value is %s\n", testBuffer);
    return 0;
}

在我的例子中,在另一个源文件 csource.c 中的函数是:

#include <stdio.h>
#include <stdlib.h>

// define the states for our Finite State Machine. It be a simple one with
// straight transitions from one state to the next.
enum  StateFSM {StateBegin = 1, StateAccept = 2, StateEnd = 3};

static char *fetchValue (FILE *pFile, char *buffer, int nMaxLen)
{
    int    iBuffIndex = 0;
    enum StateFSM   iState = StateBegin;

    do {
        // until we reach an end state of our state machine or we reach end of file
        // on our input source, lets fetch characters from the input source and decide
        // what to do with the character until our end state is reached.
        // end state is either white space trailing the desired characters or end of file.
        int    iInput = fgetc (pFile);

        if (feof(pFile)) break;
        switch (iInput) {
            case ' ':
            case '\t':
            case '\n':
            case '\r':
                // eat up any leading whitespace
                if (iState != StateAccept) break;
                // we have found trailing white space so we are done.
                iState = StateEnd;
                break;
            default:
                if (iBuffIndex < nMaxLen) {
                    // as long as we are not at the max length lets get a character into
                    // the supplied buffer. if we are at max buffer length then we will
                    // just eat any remaining characters until we come to white space.
                    buffer[iBuffIndex++] = (iInput & 0x7f);
                }
                iState = StateAccept;
                break;
        }
    } while (! (iState == StateEnd));

    return buffer;    // be a good citizen and return the pointer provided to us. allows chaining.
}

long  scano_l (FILE *pFile)
{
    char   buffer[32] = {0};
    long   lValue = 0;
    char   *pTemp;

    lValue = strtol (fetchValue(pFile, buffer, 31), &pTemp, 10);  // max characters is 31 to ensure zero terminator.

    return lValue;
}

double  scano_d (FILE *pFile)
{
    char    buffer[32] = {0};
    double  dValue = 0.0;
    char    *pTemp;

    dValue = strtod (fetchValue(pFile, buffer, 31), &pTemp);  // max characters is 31 to ensure zero terminator.

    return dValue;
}

另外,方便的函数将是读取一串字符的函数。以下函数从输入中读取字符并将它们添加到字符缓冲区中,直到读取到结束字符或读取到最大字符数。

一个非空格的空白字符(制表符、换行符、回车符)被认为是文本结束指示符。空格字符现在被认为是添加到从输入构造的字符串中的有效文本字符。任何前导的非空格空白都将被丢弃,并且文本字符串被认为从第一个字符开始,该字符不是非空格空白字符。

char *  scano_asz(FILE *pFile, char *buffer, int nMaxLen)
{

    int    iBuffIndex = 0;
    enum StateFSM   iState = StateBegin;

    do {
        // until we reach an end state of our state machine or we reach end of file
        // on our input source, lets fetch characters from the input source and decide
        // what to do with the character until our end state is reached.
        // end state is either white space trailing the desired characters or end of file.
        int    iInput = fgetc(pFile);

        if (feof(pFile)) break;
        switch (iInput) {
        case '\t':
        case '\n':
        case '\r':
            // eat up any leading non-space whitespace. spaces embedded in the string are
            // considered part of the string. delimiters include tab, new line, return.
            if (iState != StateAccept) break;
            // we have found trailing non-space white space so we are done.
            iState = StateEnd;
            break;
        default:
            if (iBuffIndex < nMaxLen) {
                // as long as we are not at the max length lets get a character into
                // the supplied buffer. allowable characters include the space character
                // but not other white space characters such as tab, new line, return.
                buffer[iBuffIndex++] = (iInput & 0x7f);
                if (iBuffIndex >= nMaxLen) break;    // once we reach max size then we will break and exit.
            }
            iState = StateAccept;
            break;
        }
    } while (!(iState == StateEnd));

    if (iBuffIndex < nMaxLen) buffer[iBuffIndex] = 0;   // terminate the string if there is roome in the buffer.
    return buffer;
}

【讨论】:

    猜你喜欢
    • 2012-04-24
    • 1970-01-01
    • 2012-11-24
    • 1970-01-01
    • 1970-01-01
    • 2021-10-26
    • 2016-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多