【问题标题】:skipping spaces in C using pointers使用指针跳过 C 中的空格
【发布时间】:2021-07-01 09:21:22
【问题描述】:

我被要求这样做:

创建一个skip_spaces() 函数接受字符串s,它返回对数组中第一个不是空格字符的元素的引用(如果字符串仅由空格组成,则指针将寻址空终止符\0)。 然后使用在stdin 上读取的字符串创建一个主程序主体调用此函数。 根据给定的结果,程序将显示来自第一个非空格 char 的字符串。"

我才开始使用指针,而且我显然不是 C 专家,所以我很迷茫。 这是我到目前为止得到的:

skip_spaces.c 我有:

char *skip_spaces(char *s[]) {
  char *ref = '\0';
  int i = 0;
  while (*s[i] != '\0') {
    if (*s[i] == ' '):
      i++;
    else {
      *ref = *s[i];
    }
  }
}

skip_spaces.h 我有:

char *skip_spaces(char *);

还有我的主程序:

#include "skip_spaces.h"
#include <stdio.h>

int main(void) {
    int input;
    char *str[30];
    char *spaceless;
    printf("input string : ");
    input = scanf("%s", str);
    if (input == 1) {
        int i = 0;
        spaceless = skip_spaces(str);
        printf("modified string : %s.", spaceless);
    return -1;
}

现在,我还不确定该程序是否能完成我想要它做的事情。

我的问题是,此时我什至无法对其进行测试:我已经尝试了很多东西,但我永远无法正确编译,每当我在某个地方解决问题时,我在其他地方遇到了另一个问题。几乎所有错误都来自我的主程序。

我有两个非常持久的错误:

  • error: format ‘%s’ expects argument of type ‘char *’, but argument 2 has type ‘char **’

这个错误指向我的 input = scanf 行,更准确地说是我的 str var

  • skip_spaces.h:1:8: note: expected ‘char *’ but argument is of type ‘char **’

我试过坐立不安,将* 放在这里,&amp; 放在那里,但要么我有这 2 个错误,要么我还有更多其他错误......

我什至在 Internet 上为这个确切的功能找到了几个工作代码(实际上大部分来自 SO),但是每当我尝试在我的代码中实现他们的工作解决方案时,我都会再次遇到这些错误。 100%肯定我的问题来自我对指针的理解。我希望有人能解释一下。

【问题讨论】:

  • 阅读Modern C。见this C reference。使用GCC 调用gcc -Wall -Wextra -g 编译您的C 代码,然后使用GDB 调试器来了解您的程序的行为。如果允许,请在您的个人笔记本电脑上安装 Debian
  • @BasileStarynkevitch 感谢您的建议!我总是忘记使用 GDB...我目前在使用 Linux shell 的 Windows 10 上
  • 为什么禁止在笔记本电脑上安装Debian?至少从现有开源软件的源代码中获得灵感,比如GNU bash?如果您在笔记本电脑上安装 Debian 或 Ubuntu,您会发生什么?随时给我发电子邮件basile@starynkevitch.net

标签: arrays c trim c-strings function-definition


【解决方案1】:

工作代码

这个简单的函数从字符串中删除前导空格:

/* remove leading spaces from string */
void rmspaces(char **str)
{
    while (**str == ' ')
        (*str)++;
}

可以这样调用:

char *str = "  hello";
rmspaces(&str);

说明

您的方法是创建一个没有前导空格的全新字符串,但是将指针传递给指向字符串第一个字符的指针更简单。然后您可以使用(*str)++; 将指针*str 移动到下一个字符,而该字符是一个空格。

这样做的好处是每次调用函数时都不必分配新字符串,因为旧字符串可以重用

也不需要检查当前字符是否是终止符null character'\0',因为while循环中的条件会自动保证这一点。

/* sufficient */
while (**str == ' ')

/* unnecessary */
while (**str != '\0' && **str == ' ')

用户输入

如果您使用scanf 扫描用户输入,则会自动删除前导空格,如Vlad from Moscow's answer 中所述。

char str[20];
/*
 * prevent buffer overflow and
 * take null character into account 
 */
scanf("%19s"); 

如果您不希望scanf 自动修剪前导空格,您可以使用fgets。如果您只想使用函数void rmspaces(char **str) 修剪简单的空格' ',这可能会有所帮助。

char str[20];
/* prevent buffer overflow */
fgets(str, 20, stdin);

空白与空格

空格和空格是有区别的。空格可以是制表符'\t' 或换行符'\n',而空格只能是' '

这是检查字符是否为空格的方法:

/* `c` is an `unsigned char` */
if (isspace(c))

这是检查字符是否为空格的方法:

/* `c` is an `unsigned char` */
if (c == ' ')

【讨论】:

  • 非常感谢,感谢您对您的代码建议做出解释。然而......指针的指针?哎呀......现在我更困惑了哈哈
  • @Andy Sukowski-Bang 为什么您实际上复制并粘贴了我的部分答案?这不是一个好的行为。
  • @VladfromMoscow 我没有复制粘贴部分答案。在您在回答中提到它之后,我添加的唯一想法是 scanf 自动删除前导空格,但我在这一点上进行了扩展并添加了防止缓冲区溢出等的代码。但是我现在已经在我的帖子中引用了您的答案并且添加了一个链接。
  • @froggyalex 指向指针的指针只需要将*str的地址传递给rmspaces方法即可。虽然一开始可能看起来有点吓人,但在我看来,这是修剪前导空格的最优雅的方式。
  • rmspaces() 的替代(更简单?)接口是char *rmspaces(char *str)。那么你就不需要双指针符号:char *rmspaces(char *str) { while (*str == ' ') str++; return str; } 就可以了。这将在空终止符或任何其他非空白字符处停止。
【解决方案2】:

此声明

char *str[30];

没有意义。它声明了一个指针数组,而您需要声明一个包含字符串的字符数组。

char str[30];

此调用中使用的转换说明符

input = scanf("%s", str);

跳过前导空格,因此它也没有意义,因为输入的字符串将不包含前导空格。而是使用标准函数fgets

函数skip_spaces的参数声明如下

char* s[]

正如上面提到的那样是不正确的。您需要将字符串传递给函数。所以参数应该声明为

const char *s

注意限定符const。它告诉函数的用户,字符串本身不会在函数内改变。

在函数skip_spaces这个声明中

char* ref = '\0';

声明一个空指针。因此取消引用它

  *ref = *s[i];

调用未定义的行为。

此外,这组空白不仅包含一个字符' '。例如,用户可以键入制表符'\t'

以及输出消息

printf("modified string : %s.",spaceless);

令人困惑。源字符串未修改。该函数只返回一个指向第一个非空白字符的指针。字符串本身保持不变。

函数可以像下面的演示程序中所示那样声明和定义。

#include <stdio.h>
#include <ctype.h>
#include <string.h>

char * skip_spaces( const char *s  )
{
    while ( *s && isspace( ( unsigned char )*s ) ) ++s;
    
    return ( char * )s;
}

int main(void) 
{
    enum { N = 30 };
    char str[N];
    
    printf( "Input a string (no more than %d characters): ", N );
    
    if ( fgets( str, N, stdin ) )
    {
        str[ strcspn( str, "\n" ) ] = '\0';
        printf( "The left trimmed string is \"%s\"", skip_spaces( str ) );
    }

    return 0;
}

如果输入字符串" Hello World!",那么程序输出会是这样的

Input a string (no more than 30 characters):           Hello World!
The left trimmed string is "Hello World!"

【讨论】:

  • 感谢您的建议和解释。在使用它之前没有阅读 scanf 的文档,我 100% 内疚...
  • @froggyalex 你可以使用 scanf 但转换说明符应该是另一个,例如 if ( scanf( "%29[^\n]", str ) == 1 )
  • 我建议在您的答案中使用%29s%29[^\n] 作为scanf 格式说明符,以防止缓冲区溢出。
  • @VladfromMoscow 为什么防止缓冲区溢出无用?
  • @AndySukowski-Bang 我已经在我的评论中指出要使用例如 if ( scanf( "%29[^\n]", str ) == 1 ) 。为什么要重复我?!看来你能做的就是把别人写的东西重复一遍。
【解决方案3】:

C 已经提供了一个可以为你做这件事的函数。 strspn(const char *s, const char *accept) 函数将返回由accept 字符串中的字符组成的s 中的初始字符数。见man 3 strspn

如果您对accept 使用" \t\n"(对于spacetabnewline),则该函数返回字符串s 中前导空白字符的数量。如果s全是空格,则返回s中的字符数。

您需要做的就是返回s + strspn (s, " \t\n"),然后您就会得到答案,例如

const char *skip_spaces (const char *s)
{
    return s + strspn (s, " \t\n");     /* return pointer to 1st non-space or '\0' */
}

一个完整的例子是:

#include <stdio.h>
#include <string.h>

const char *skip_spaces (const char *s)
{
    return s + strspn (s, " \t\n");     /* return pointer to 1st non-space or '\0' */
}

int main (void) {
    
    const char *str[] = { "     w/leading space",
                          "w/o leading space",
                          "         \t  " };
    size_t n = sizeof str/sizeof *str;
    
    for (size_t i = 0; i < n; i++) {
        if (!*skip_spaces (str[i]))
            printf ("skip_spaces (str[%zu]): '%s' (all spaces)\n", 
                    i, skip_spaces (str[i]));
        else
            printf ("skip_spaces (str[%zu]): '%s'\n", i, skip_spaces (str[i]));
    }
}

使用/输出示例

$ ./bin/skip_spaces
skip_spaces (str[0]): 'w/leading space'
skip_spaces (str[1]): 'w/o leading space'
skip_spaces (str[2]): '' (all spaces)

在 C :)

给猫换皮的方法总是不止一种

另外,在访问手册页时,请注意伴随函数 strcspn (const char *s, const char *reject) 的作用正好相反,它返回 s 中的初始字符数,不包含 reject 中的任何字符。 (对于在由 fgets() 或 POSIX getline() 填充的缓冲区末尾修剪 '\n' 非常有用。

【讨论】:

    猜你喜欢
    • 2011-07-04
    • 2016-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-24
    相关资源
    最近更新 更多