实现 scanf 的替代方案 [英] Implementing an alternative to scanf

查看:75
本文介绍了实现 scanf 的替代方案的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Since almost all C functions designed to get data from stdin are bad / flawed:

  • gets 说得越少越好

scanf 不检查缓冲区溢出和 '\n' 不断留在 stdin 中,搞砸了下一个 scanfs

scanf no checks for buffer overflow and '\n' constantly remaining in stdin, screwing up next scanfs

我决定编写自己的函数,至少可以用于从 stdin

I decided to write my own function that would at least be usable to read numbers from 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...
       }
   }

}

对于其他数据类型,该函数显然未完成,但我先有一些问题:

The function is obviously unfinished for other data types, but I have some questions first:

  • 如何改进函数的算法?
  • 难道我不需要在每个 casefree() 吗?我知道分配的内存需要释放,但是在处理列表时,free() 只是用于删除Nodes,创建Nodes时,没有调用free()malloc() 之后.
  • 它是完全安全的吗?如果没有,如何使其安全?
  • How can the algorithm of the function be improved?
  • Don't I need to free() in every case? I'm aware allocated memory needs to be freed, but when working with lists, free() was only used to delete Nodes, when creating Nodes, no free() was called after a malloc().
  • Is it fully safe? If not, how can it be made safe?

推荐答案

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

The following is a simple example of a couple of functions to return a numeric value from an input source. These examples expect that one of several types of white space characters (space, tab, end of line, return) are used to delimit a set of numeric fields.

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

These are to demonstrate an approach and there is definitely room for improvement.

建议你看看atoi vs atol vs strtol vs strtoul vs sscanf

fgetc() 函数用于从输入源中一次拉出一个字符,并测试输入源的读取是继续还是停止.通过使用 fgetc() 函数,当这些函数 scano_l()scan_d()被使用.

The fgetc() function is used to pull one character at a time from the input source and test whether the reading of the input source should continue or stop. By using the fgetc() function we can allow some other function to continue reading from the input source when these functions, scano_l() and scan_d() are used.

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

We also eliminate the need for the malloc() and the accompanying free() and memory management by using a local buffer and returning the actual value as a long or a double.

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

For instance a simple test of these using a C++ main would be (the _tmain() is because I am using Microsoft Visual Studio 2005 to generate a Windows console application) is below. This can be tested by compiling and then trying out several different scenarios of data entry in which an integer number such as 1234 is entered followed by one or more white space characters (space, tab, new line) then a floating point number such as 45.678 followed by at least one more white space character then some text characters.

// 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 中的函数是:

The functions, which in my case are in another source file, csource.c, are:

#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;
}

和额外的,方便的功能将是一个将读取一串字符的功能.以下函数从输入中读取字符并将它们添加到字符缓冲区,直到读取到结束字符或读取到最大字符数为止.

and additional, handy function would be one that will read in a string of characters. The following function reads characters from the input and adds them to a character buffer until either an ending character is read or the max number of characters is read.

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

A non-space white space characters (tab, new line, return) are considered to be end of text indicators. A space character is now considered to be a valid text character which is added to the string being constructed from the input. Any leading non-space white space is discarded and the text string is considered to begin at the first character which is something other than a non-space white space character.

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;
}

这篇关于实现 scanf 的替代方案的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆