【问题标题】:Removing a space, newline, and tabs with string trimming in C在 C 中使用字符串修剪删除空格、换行符和制表符
【发布时间】:2017-10-08 06:21:58
【问题描述】:

我试图从互联网上收集资源以了解它的工作原理和功能。基本上,每次读取字符串时,我都需要检查空格、换行符和制表符。所以我做了一个处理这种情况的函数:

#include <stdlib.h>
#include <stdio.h>

static int  isspace(char c)
{
    return (c == '\t' || c == '\n' || c == ' ');
}

然后,我使用下面的函数在另一个函数中实现它

char *my_strtrim(char const *string)
{
    char *i;
    char *s;
    int ready;

    i = s;
    s = (char *)string;
    ready = 0;
    while(*i)
    {
        ++i;
        if(isspace(*i))
        {
            if(!ready)
            {
                continue ;
            }
            ready = 0;
        }
        ready = 1;
        *(s++) = *i;
    }

    *s = 0;
    return ((char *)string);
}

对于我的主要内容,我只是做了一个随机测试用例,它负责处理空格、制表符和换行符:

int main()
{
    char str[] = "                      hello world\n !";
    printf("%s",my_strtrim(str));
}

由于s 没有NULL 的结果,所以在带有i = s 的my_strtrim 函数中存在错误的输出错误。错误说:

my_strtrim.c: error: variable 's' is uninitialized when used here [-Werror,-Wuninitialized]
        i = s;
            ^
my_strtrim.c: note: initialize the variable 's' to silence this warning
        char *s;
               ^
                = NULL

在我修复它所说的内容后(使s = NULL)我得到一个分段错误。这个问题变得令人困惑,因为它可以作为 for 循环正常工作,但不能作为 while 循环。我需要将这个问题作为一个while循环来解决。

解决方案 我的朋友给了我一个小技巧/经验法则,它使代码保持简单易懂。我有一个集群功能可以同时做很多事情;因此,我感到困惑。他指导我,告诉我让我把我的整个功能浓缩成小块。

步骤 0:初始化变量并声明

不言自明

第 1 步:获取指针字符串的位置

int     step1_getPosition(char const *string)
{
    int i;

    i = 0;
    while(my_iswhitespace(string[i]))
    {
            i++;
            continue;
    }
    return (i);
}

第 2 步:复制您的字符串

char    *step2_copyString(char const *string, int pos)
{
    char *tmp;
    int     i;

    i = 0;
    tmp = my_strnew(my_strlen(string));
    if(tmp == NULL)
        return (NULL);
    while (string[pos] != '\0')
        tmp[i++] = string[pos++];
    return (tmp);
}

第 3 步:删除空格

char    *step3_removeWhite(char *str)
{
    int i;

    i = my_strlen(str);
    while (str[i] == '\0' || my_iswhitespace(str[i]))
    {
        str[i] = '\0';
        i--;
    }
    return (str);
}

第 4 步:删除额外的 NULL-BYTES ('\0')

char    *step4_removeExtraNulls(char *str)
{
    char *newstring;

    newstring = my_strdup(str);
    if(newstring == NULL)
        return (NULL);
    free(str);
    return (newstring);
}

第 5 步:调用主函数并创建其他函数

char    *my_strtrim(char const *string)
{
    char    *trim;
    int     i;

    i = step1_getPosition(string);
    trim = step2_copyString(string, i);
    if (trim == NULL)
        return (NULL);
    step3_removeWhite(trim);
    trim = step4_removeExtraNulls(trim);
    if (trim == NULL)
        return (NULL);
    return (trim);
}

我得到的输出是: hello world ! 这是正确的

【问题讨论】:

  • 为什么不使用标准 isspace函数?
  • 顺便说一句,当您修改一个字符串 const 时,您有 undefined behavior。如果要修改string的内容,不要标记为const
  • 至于你的错误,想想你做事的顺序。当你做i = s时,s指向哪里?
  • 最后,我建议您多考虑一下自己在做什么。当您的函数返回 unmodified 变量 string 时,您认为会发生什么? string 指向哪里?
  • @Someprogrammerdude,不妨继续写下它作为答案,并将另一个未回答的问题移出书本......

标签: c string error-handling while-loop trim


【解决方案1】:

“修剪”前导空白字符的最简单方法是跳过它们,根本不修改字符串。

这依赖于这样一个事实,即 C 中的“字符串”可以表示为指向以空字符结尾的字符序列的指针。

以你的字符串为例

char str[] = "                      hello world\n !";

如果我们让数组str 衰减为指向其第一个元素的指针,则它指向第一个空间。如果我们有一个指向'h' 的指针呢?那将是一个同样有效的“字符串”。

要获取该指针,我们只需遍历字符串,只要当前字符是空格(当然不是终止符)。

把它付诸实践我们得到

char *my_strtrim(char const *string)
{
    for (/* empty */; *string && my_isspace(*string); ++string)
    {
        // Empty
    }

    return string;
}

在上述函数的循环之后,指针stringeither 指向终止符(如果字符串全是空格),或者指向字符串中的第一个非空格字符.

如果我们像这样使用它

printf("%s\n", my_strtrim(str));

然后打印出来

你好世界 !

[嵌入的换行符是因为你的字符串中有它。]

应该注意,这不会修剪 尾随 空格。为此,参数string 不能是指向常量 个字符的指针。

【讨论】:

    【解决方案2】:

    问题正在发生,因为当您分配 i = s 时,s 变量处于未定义状态。

    请考虑以下代码:

    char *my_strtrim(char const *string)
    {
        char *i;
        char *s;
        int ready;
    
        s = (char *)string;
        i = s;
        ready = 0;
        while(*i)
        {
            ++i;
            if(isspace(*i))
            {
                if(!ready)
                {
                    continue ;
                }
                ready = 0;
            }
            ready = 1;
            *(s++) = *i;
        }
    
        *s = 0;
        return ((char *)string);
    }
    

    【讨论】:

    • *s = '\0'*s = 0 100% 等效。 ASCII nul-character '\0' 具有十进制值 0。 (就像'A' 具有十进制值65 等...)
    【解决方案3】:

    Zeid,从 cmets 继续。效率目标之一应该是限制你在字符串上的传球次数(最好是一次)。您还应该考虑传递一个最终数组来保存修剪后的字符串,因为在许多情况下需要保留原始字符串,或者,正如某些程序员老兄正确指出的那样,您不能修改并将参数指定为 const 或一个驻留在只读内存中(例如字符串文字)。

    您可以在调用者中为此目的使用 VLA。把它放在一起并添加一个额外的参数来保存结果,'r' 下面,您可以执行以下操作。

    它只是删除所有前导空格,然后备份,删除所有尾随空格,并检查最后一个字符是否不是alnum 字符(因为大多数句子都以某种标点符号结尾)。然后,它检查标点符号和字符串中的最后一个 alnum 字符之间是否有任何额外的空格,方法是向前移动结束字符以覆盖找到的任何中间空格(这将消除你在@987654325 之间的额外'\n ' @和'!')

    #include <stdio.h>
    #include <ctype.h>
    
    /** remove leading and trailing whitespace, original is preserved.
     *  this funciton can be used with or without assigning return.
     *  any intervening whitespace between end punctuation and first
     *  alpha-num character is also removed.
     */
    char *strtrimws (char *r, const char *s)
    {
        char *p = r;                            /* pointer to result         */
        *r = 0;                                 /* initialize as empty str   */
        if (!s) return NULL;                    /* validaate source str      */
        if (!*s) return r;                      /* empty str - nothing to do */
        while (isspace (*s))  s++;              /* skip leading whitespace   */
        while (*s) *p++ = *s++;                 /* fill r with s to end      */
        *p = 0;                                 /* nul-terminate r           */
        while (p > r && isspace (*--p)) *p = 0; /* overwrite spaces from end */
        while (p > r && !isalnum (*--p)) {      /* continue until 1st alnum  */
            if (isspace (*p)) {                 /* if spaces found           */
                char *rp = p, *wp = p;          /* set read & write pointers */
                while (*rp++) *wp++ = *rp;      /* shuffle end chars forward */
                *wp = 0;                        /* nul-terminate at new end  */
            }
        }
        return r;
    }
    
    int main (void) {
    
        char str[] = "                      hello world\n !",
            result[sizeof str];
        printf ("%s\n", strtrimws (result, str));
        return 0;
    }
    

    使用/输出示例

    $ ./bin/trimws
    hello world!
    

    查看一下,如果您有任何其他问题,请告诉我。如果没有,祝你编码顺利。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-01-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-10
      • 2011-11-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多