存储和检索数组中的字符和整数的组合

时间:2015-01-12 05:58:46

标签: c arrays

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

如果我使用%c或甚至getchar()函数,我将面临处理整数多个数字的麻烦。 如果我使用%d字符,如* + - /未被存储。等等。

如果可行,请提供一些方法。

3 个答案:

答案 0 :(得分: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;
        }
    }
}

答案 1 :(得分:1)

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

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

答案 2 :(得分:0)

使用

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