使用指针在 C 中跳过空格

skipping spaces in C using pointers

有人要求我这样做:

Make a skip_spaces() function accepting a string s, that returns a reference to the first element in the array that is not a space character (if the string is only composed of spaces, the pointer will address the null terminator [=15=]). Then make a main program principal calling this function with a string read on stdin. From the given result, the program will then display the string from the first non-space char."

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

skip_spaces.c我有:

char *skip_spaces(char *s[]) {
  char *ref = '[=10=]';
  int i = 0;
  while (*s[i] != '[=10=]') {
    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;
}

现在,我还不确定程序是否按照我的意愿执行。

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

我有两个非常顽固的错误:

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

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

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

工作代码

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

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

可以这样调用:

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

说明

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

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

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

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

/* unnecessary */
while (**str != '[=12=]' && **str == ' ')

用户输入

如果您使用 scanf 扫描用户输入,前导白色 space 将自动删除,如 中所述。

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

如果您不希望 scanf 自动 trim 前导白色 space,您可以使用 fgets。如果您只想 trim 简单 spaces ' ' 使用函数 void rmspaces(char **str).

,这可能会有所帮助
char str[20];
/* prevent buffer overflow */
fgets(str, 20, stdin);

白色space 对比 space

白色spaces和spaces是有区别的。白色space 可以是制表符'\t' 或换行符'\n',而space 只能是' '.

这是检查字符是否为白色的方法space:

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

这是检查字符是否为 space:

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

这个声明

char *str[30];

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

char str[30];

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

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

跳过前导白色 spaces,因此它也没有意义,因为输入的字符串将不包含前导白色 spaces。相反,使用标准函数 fgets.

函数的参数skip_spaces声明为

char* s[]

上面提到的是不正确的。您需要向函数传递一个字符串。所以参数应该声明为

const char *s

注意限定词const。它说函数的用户字符串本身不会在函数内更改。

函数内skip_spaces这个声明

char* ref = '\0';

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

  *ref = *s[i];

调用未定义的行为。

另外,白色space的集合并不只有' '一个字符。例如,用户可以键入制表符 '\t'.

并输出消息

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

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

可以按照下面的演示程序所示声明和定义函数。

#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" ) ] = '[=17=]';
        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!"

C 已经提供了一个函数可以为您做到这一点。 strspn(const char *s, const char *accept) 函数将 return s 中的初始字符数由 accept 字符串中的字符组成。参见 man 3 strspn

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

您需要做的就是 return 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 '[=10=]' */
}

一个完整的例子是:

#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 '[=11=]' */
}

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

例子Use/Output

$ ./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 skin-the-cat 中总是有不止一种方法 :)

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