繁体   English   中英

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

[英]K&R - Recursive descent parser - strcat

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

没有它,它似乎确实可以正常工作。

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

    }
}

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

虽然这条线

out[0] = '\0';

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

根据C99的初始化规则

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

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

int i = 0;

在执行以下操作之前:

i += 1;

这样垃圾值就不会增加

因此,仅在数组0索引中使用'\\ 0'表示数组完全为空,并且strcat函数从0索引开始追加值,覆盖了数组其他索引中的垃圾值。

如果程序在不重置数组的情况下工作,则表明您的IDE工具正在为您执行此操作,但是重置它是一种好习惯。

简而言之:在这种特殊情况下,这不是严格必要的,但在许多其他看起来可疑的情况下,确实如此,因此大多数人都将其视为“好风格”。 那么为什么有必要呢?

没有“空”内存之类的东西。 没有“长度”这样的东西。 除非您明确跟踪它,否则请定义自己的。

内存只是字节,是从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实际上所做的就是保留4个字节的空间,即'f''o''o'0表示结束。 当您在C中写入""时,它的作用是为单个字节0保留空间。 这样您就可以知道该字符串为空。

那么,在启动时将什么放入内存中会充满什么内存? 好吧,在大多数情况下,这只是垃圾。 上一次使用该内存中的内存是多少(毕竟,您的RAM有限,因此,当您退出计算机上的一个应用程序时,其内存可以重新用于随后启动的下一个应用程序)。 这些将是随机数,通常不在通用字符范围内。

所以,如果你想strcat看到out一个空字符串,你需要给它始于此的内存块0值字符。 如果您只保留原样的内存,则其中可能会有一些随机字符。 您的缓冲区可能含有“jbhasugaudq7e1723876123798dbkda 0 skno§§^^%$# - 9H 0 HWDZmwus 0的/ usr / local / bin目录”或什么是内存之前。 如果你现在追加一些文本,它会认为东西之前,首先0 (这只是随意在这个地方)是一个有效的字符串,并将其追加到这一点 如果您将0放在开头,它只会知道该字符串应该为空。

那么,为什么我说这“不是绝对必要的”呢? 好吧,因为在您的情况下, out是全局变量,而全局变量是特殊的,因为它们在您的应用程序启动时自动清零(或在声明它们时分配了您为其分配的任何值)。

但是,这仅适用于全局变量(常规全局变量和static全局变量)。 因此,许多程序员习惯于始终初始化其字节块。 这样,如果以后有人决定将全局变量更改为局部变量,或者将代码复制并粘贴到另一个位置以与局部变量一起使用,则不必担心忘记添加此语句。

这特别有用,因为随机存储器通常包含0字符。 因此,根据您先前使用的程序,您可能不会注意到您忘记了最初的0因为恰好那里已有一个。 而且只有稍后,当您的一个用户运行此应用程序时,他们才在字符串的开头得到垃圾。

这是否使事情澄清了一点?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM