【问题标题】:Storing and retrieving combination of characters and integers in an array在数组中存储和检索字符和整数的组合
【发布时间】:2015-01-12 05:58:45
【问题描述】:

我想创建一个数组,我可以在其中存储运行时提供的输入,例如- + 2 * 3 4 / 12 6(此处共有 9 个元素)。假设没有一个输入实例在数组中需要超过 50 个索引。我正在考虑为此目的使用一个整数数组,但我没有得到任何可以在scanf() 中使用的格式说明符来获取输入(在 return/Enter keypress 处终止),以便我可以在程序中区分后者是否特别index 是 char 或 int,它的值是多少。

如果我使用%c 甚至getchar() 函数,我将面临处理多于一位的整数的麻烦。 如果我使用 %d 字符,例如 * + - / 则不会被存储。以此类推。

因此,如果可行,请提出一些可行的方法。

【问题讨论】:

  • 您在一个问题中提出了 2 个不同的问题:如何解析输入字符串并将字符与数字分开(文本中的第一个问题)以及如何将其存储在数组中(标题中的第二个问题)。而且您没有提供足够的上下文来正确回答第一个问题。 究竟您对输入的规范是什么?只有 4 个运算符和十进制整数?任何多字符函数(inv 用于 1/x 或 sqrt)?任何括号(())?任何十进制浮点数(1.25)?或者它真的是一个反向抛光 4 操作整数计算器?您是否接受多个空格、制表符、...?
  • 实际上我想实现类似前缀表达式评估器的东西。在其中我不想使用括号,我只想使用数组索引来区分操作数。就像在我的示例中一样,12 应该存储在倒数第二个索引中,而 6 应该存储在数组的最后一个索引中。现在我只是在考虑整数,但是如果您也可以建议一种浮点数的方法,那就太好了。并且是的,我只使用 5 个运算符。 +- * / 和 %

标签: c arrays


【解决方案1】:

当我(很久以前......)学习编程时,我的老师说“在明确你想通过正确的分析实现什么之前不要开始编码”。如果我理解正确,您正在构建一个计算器,只有五个运算符 (+-/*%) 遵循以下语法:

expr : number
     | operator expr expr

with the following lexical tokens :

operator: single character among +-*/%
number: consecutive sequence of decimal digits ([0-9]*)
not printing characters (space, tab, \r, \n) are used as delimiters or otherwise ignored

any other character causes an error.

好的:这是您当前的规范,如果您以后想使用十进制数字,您只需更改数字定义以允许一个可选的小数点。

这样写,lex和yacc用起来倒是很方便,但是这么简单的语法,肯定是大材小用了。

即使我们将空格定义为分隔符,也不可能使用scanf 来获取令牌,因为它会默默地吃掉+ 符号:+12 与 12 相同。

因此,您必须使用返回标记的getc 构建一个简单的词法分析器,然后构建一个递归计算表达式的解析器。无需在数组中存储任何内容:

typedef struct _token {
    enum {OPERATOR, INT, END, ERROR } type;
    union {
        int ival;
        char op;
    } value;
} TOKEN;

TOKEN getToken(FILE *fdin) {
    static const char valid_op[] = "+-*/%";
    static const char spaces[] = " \t\r\n";
    int c;
    TOKEN tok;
    int val = 0;
    int isval = 0;
    while ((c = getc(fdin)) != EOF) {
        if ((c >= '0') && (c <= '9')) {
            val = 10 * val + (c - '0');
            isval = 1;
        }
        else if (isval != 0) {
            tok.type = INT;
            tok.value.ival = val;
            ungetc(c, fdin);
            return tok;
        }
        else if (strchr(valid_op, c)) {
            tok.type = OPERATOR;
            tok.value.op = c;
            return tok;
        }
        else if (! strchr(spaces, c)) {
            tok.type = ERROR;
            return tok;
        }
    }
    tok.type = END;
    return tok;
}

int parse(FILE *fdin, int *typ) {
    int i, j;
    *typ = INT;
    for(;;) {
        TOKEN tok = getToken(fdin);
        if (tok.type == INT) {
            return tok.value.ival;
        }
        else if (tok.type == OPERATOR) {
            i = parse(fdin, typ);
            if (*typ != INT) {
                *typ = ERROR;
                return 0;
            }
            j = parse(fdin, typ);
            if (*typ != INT) {
                *typ = ERROR;
                return 0;
            }
            switch(tok.value.op) {
                case '+': return i+j;
                case '-': return i-j;
                case '*': return i*j;
                case '/': return i/j;
                case '%': return i * j / 100;
            }
        }
        else {
            *typ = tok.type;
            return 0;
        }
    }
}

【讨论】:

    【解决方案2】:

    您需要一种可以保存各种数据类型的数据类型:运算符和整数,甚至可能是浮点数或名称(变量或函数)。

    C 中的一个常见方法是使用union,它可以在同一个空间中保存多个类型。您一次只能使用其中一种类型,因此您需要一种方法来指示哪些类型处于活动状态,这可以通过enum 完成。然后将enumunion 包裹在struct 中,让它们整齐地排列在一起。

    以下是 auch 数据类型的示例实现。它不做任何操作,它只解析一个字符串并打印标记。

    与您的示例一样,所有标记都必须用空格分隔,以便strtok 可以找到它们。如果您想将5/2 识别为三个标记,您可以构建一个词法分析器,正如 Serge Ballesta 在他非常系统的回答中所建议的那样。下面的实现不能识别负数,例如-1。错误处理也很基础。

    此代码仍可作为解决方案的起点:

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <ctype.h>
    
    enum Type {                     /* enumeration of possible types */
        Operator,     
        Integer, 
        Float, 
        Name,
        Illegal
    };
    
    struct Token {
        enum Type type;             /* token type */
        union {                     /* mutually exclusive data fields */
            long long int l;        /* ... for Integer */
            double x;               /* ... for Float */
            char name[20];          /* ... for Name and Operator */
        } data;
    };
    
    struct Token illegal(const char *str)
    {
        struct Token tk = {Illegal};
    
        snprintf(tk.data.name, 20, "%s", str);
        return tk;
    }
    
    struct Token parse(const char *str)
    {
        struct Token tk = {Illegal};
    
        if (strchr("+-*/%", *str)) {
            if (str[1]) return illegal("Overlong operator");
            tk.type = Operator;
            strcpy(tk.data.name, str);
            return tk;
        }
    
        if (isdigit(*str)) {
            double x;
            long long l;
            char *end; 
    
            l = strtoll(str, &end, 0);
            if (end != str && *end == '\0') {
                tk.type = Integer;
                tk.data.l = l;      
                return tk;
            }
    
            x = strtod(str, &end);
            if (end != str && *end == '\0') {
                tk.type = Float;
                tk.data.x = x;     
                return tk;
            }
    
            return illegal("Illegal number");
        }
    
        if (isalpha(*str)) {
            const char *p = str;
    
            while (*p) {
                if (!isalnum(*p++)) return illegal("Illegal name");
            }
            tk.type = Name;
            snprintf(tk.data.name, 20, "%s", str);        
            return tk;
        }
    
        return illegal("Illegal character");
    }
    
    int split(struct Token tk[], int max, char *str)
    {
        int n = 0;
        char *p;
    
        p = strtok(str, " \t\n");
        while (p) {
            struct Token curr = parse(p);
    
            if (curr.type == Illegal) {
                fprintf(stderr, "Parse error: %s.\n", curr.data.name);
                return -1;
            }
    
            if (n < max) tk[n] = curr;
            n++;
    
            p = strtok(NULL, " \t\n");
        }
    
        return n;
    }
    
    void print(struct Token tk)
    {
        switch (tk.type) {
        case Operator:  printf("operator %c\n", tk.data.name[0]);
                        break;
        case Integer:   printf("integer %lld\n", tk.data.l);
                        break;
        case Float:     printf("float %g\n", tk.data.x);
                        break;
        case Name:      printf("name \"%s\"\n", tk.data.name);
                        break;
        default:        printf("illegal token\n");
        }
    }
    
    int main()
    {
        char line[] = "- + 2 * alpha beta / 12.0 6";
        struct Token tk[20];
        int i, n;
    
        n = split(tk, 20, line);
        for (i = 0; i < n; i++) {
            print(tk[i]);
        }
    
        return 0;
    }
    

    【讨论】:

      【解决方案3】:

      使用

      1. fgets() 读到行尾。
      2. 解析行并使用strtok() 将行拆分为标记,空格作为分隔符。
      3. 检查每个标记是字符还是数字。有多种方法可以检查令牌是否为数字。使用strtol() 将标记转换为整数,这将帮助您找到0character 之间的区别

      【讨论】:

      • 你能用一段代码演示一下吗?如何在标准输入和 fgets 的上下文中使用 fgets() 将指向 char 数组的指针作为其输入参数如何处理它。
      • @DeepankarSingh 你能告诉我你想在pasring之后对输入的每个toekn做什么吗?
      • 我想根据字符(运算符)和整数(操作数)进行算术计算。
      猜你喜欢
      • 1970-01-01
      • 2017-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多