首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >K&R -递归下降解析器- strcat

K&R -递归下降解析器- strcat
EN

Stack Overflow用户
提问于 2016-11-02 19:30:54
回答 3查看 357关注 0票数 1

out[0] = '\0';函数上使用main()的原因是什么?

如果没有它,它似乎确实起作用。

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAXTOKEN 100
enum { NAME, PARENS, BRACKETS };

int tokentype;
char token[MAXTOKEN]; /*last token string */
char name[MAXTOKEN]; /*identifier name */
char datatype[MAXTOKEN]; /*data type = char, int, etc. */
char out[1000];

void dcl(void);
void dirdcl(void);
int gettoken(void);

/*
Grammar:

    dcl:            optional * direct-dcl
    direct-dcl:     name
                    (dcl)
                    direct-dcl()
                    direct-dcl[optional size]
*/

int main() /* convert declaration to words */
{
    while (gettoken() != EOF) {    /* 1st token on line */

        /* 1. gettoken() gets the datatype from the token  */
        strcpy(datatype, token);

        /* 2. Init out to end of the line? */
        /* out[0] = '\0'; */

         /* parse rest of line */
        dcl();

        if (tokentype != '\n')
            printf("syntax error\n");

        printf("%s: %s %s\n", name, out, datatype);
    }

    return 0;
}

int gettoken(void) /* return next token */
{
    int c, getch(void);
    void ungetch(int);
    char *p = token;

    /* Skip blank spaces and tabs */
    while ((c = getch()) == ' ' || c == '\t')
        ;

    if (c == '(') {

        if ((c = getch()) == ')') {

            strcpy(token, "()");
            return tokentype = PARENS;

        } else {
            ungetch(c);
            return tokentype = '(';
        }

    } else if (c == '[') {

        for (*p++ = c; (*p++ = getch()) != ']'; )
            ;

        *p = '\0';
        return tokentype = BRACKETS;

    } else if (isalpha(c)) {

        /* Reads the next character of input */
        for (*p++ = c; isalnum(c = getch()); ) {
            *p++ = c;
        }

        *p = '\0';
        ungetch(c); /* Get back the space, tab */

        return tokentype = NAME;

    } else
        return tokentype = c;
}

/* dcl: parse a declarator */
void dcl(void)
{
    int ns;

    for (ns = 0; gettoken() == '*'; ) /* count *'s */
        ns++;

    dirdcl();

    while (ns-- > 0)
        strcat(out, " pointer to");
}

/* dirdcl: parse a direct declarator */
void dirdcl(void)
{
    int type;

    if (tokentype == '(') {

        dcl();

        if (tokentype != ')')
            printf("error: missing )\n");

    }
    else if (tokentype == NAME) /* variable name */ {
        strcpy(name, token);
        printf("token: %s\n", token);
    }
    else
        printf("error: expected name or (dcl)\n");

    while ((type = gettoken()) == PARENS || type == BRACKETS) {

        if (type == PARENS)
            strcat(out, " function returning");
        else {
            strcat(out, " array");
            strcat(out, token);
            strcat(out, " of");
        }

    }
}
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2016-11-02 19:37:07

您需要out[0]为零才能使strcat工作。

而这条线

代码语言:javascript
复制
out[0] = '\0';

在引入静态初始化规则之前是必需的,因此不再需要它,因为静态数组(如out[] )被初始化为所有零。

initialization rules of C99称,

  • ..。
  • 如果它具有算术类型,则初始化为(正或无符号)零。
  • 如果是聚合,则根据这些规则初始化每个成员(递归)。
票数 2
EN

Stack Overflow用户

发布于 2016-11-02 19:41:14

它正在将char数组(也称为字符串)重置为空数组。(删除垃圾值)就像我们使用的那样:

代码语言:javascript
复制
int i = 0;

在做以下事情之前:

代码语言:javascript
复制
i += 1;

这样垃圾价值就不会增加

因此,只有数组的0索引中的'\0‘表示数组是完全空的,strcat函数从0索引开始追加值,在数组的其他索引中写入垃圾值。

如果程序在没有重置数组的情况下工作,那么它就意味着IDE工具正在为您做这件事,但是重新设置它是很好的做法。

票数 1
EN

Stack Overflow用户

发布于 2016-11-02 20:40:10

简而言之:在这种特殊情况下,这并不是绝对必要的,但在其他看起来很相似的情况下,它确实是必要的,所以大多数人都把它当作“好风格”。那为什么有这个必要呢?

没有所谓的“空”记忆。没有“长度”这样的东西。除非您显式地跟踪它,或者定义您自己的。

内存只是字节,是0到255之间的数字。由于0与255一样有效,因此无法判断是否使用了字节。如果你需要更大的数字,你可以“加”几个字节,但是最终,所有的东西都是由字节构成的。文本被简单地映射到一个数字。二十年前,人们决定哪个数字代表哪个字符。所以如果你看到一个32值的字节,它可能是32。也可以是计算机字母表中的第32个字母(这是空格字符)。

当您收到一个字符串,而您不知道要处理多少文本时,通常所做的是保留一个大字节块。这就是上面char out[1000];所做的。但是你怎么知道文本的结尾呢?您已经使用过的1000个字节中有多少?

好吧,在过去,有些人只会声明另一个变量,比如int length;,并跟踪到目前为止他们使用了多少字节。C的设计者走了一条不同的路线。他们决定挑选一个非常罕见的字符,并以此作为标记。他们选择了值为0的字符(这不是字符'0‘)。字符'0‘实际上是计算机字母表的第48个字母)。

因此,您可以从一开始就查看字符串中的所有字节,如果一个字符> 0,您就知道它是使用的。如果到达0字符,则知道这是字符串的结尾。这两种方法都有各种好处。int只使用4个字节,另外一个0-字符仅为1。另一方面,如果使用int,字符串也可以包含0-字符,这只是另一个字符,没有人关心。

每当您用C编写"foo"时,C实际上所做的是为'f''o''o'0预留4个字节的空间以指示结束。当您用C编写""时,它所做的是为单个字节( 0 )预留空间。这样你就可以知道字符串是空的。

那么,在启动之前,内存中装满了什么呢?嗯,在大多数情况下,这只是垃圾。不管上次使用内存是什么(毕竟,你的内存有限,所以当你退出计算机上的一个应用程序时,它的内存就可以在你启动的下一个应用程序中重新使用)。这些都是随机数,往往超出了普通字符的范围。

因此,如果希望strcatout视为空字符串,则需要给它一个以0值字符开头的内存块。如果你只留下这样的记忆,里面可能会有一些随机字符。您的缓冲区可能包含"jbhasugaudq7e1723876123798dbkda0skno§§^^%$#-9H0HWDZmwus0/usr/local/bin“或以前在该内存中的任何内容。如果您现在附加了一些文本,它会认为第一个0之前的内容(在这里只是随机的)是一个有效的字符串,并将其附加到该字符串中。只有当您将0放在开头时,它才会知道这个字符串应该是空的。

那为何我说这是“绝对没有必要”呢?因为在您的示例中,out是一个全局变量,而全局变量是特殊的,因为它们在应用程序启动时自动被清除为0(或者在声明它们时分配给它们的任何值)。

但是,这只适用于全局变量(常规全局变量和static全局变量)。很多程序员习惯于总是初始化他们的字节块。这样,如果某个人后来决定将全局变量更改为局部变量,或者将代码复制并粘贴到另一个位置以便与局部变量一起使用,那么他们不必担心忘记添加这个语句。

这特别有用,因为随机内存通常包含0字符。因此,根据您以前使用的程序,您可能不会注意到您忘记了初始的0,因为其中碰巧已经有一个了。直到稍后,当您的一个用户运行这个应用程序时,他们才会在字符串开始时收到垃圾。

这能澄清一些事情吗?

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/40388241

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档