在 C 中将单词从驼峰式转换为 snake_case

Converting words from camelCase to snake_case in C

我要编写的代码是,如果我输入 camelcase,它应该只打印出 camelcase,但是如果其中包含任何大写字母,例如,如果我输入 camelCase ], 它应该打印出 camel_case

下面是我正在处理的,但问题是,如果我输入 camelCase,它会打印出 camel_ase

谁能告诉我原因以及如何解决?

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

int main() {
    char ch;
    char input[100];
    int i = 0;

    while ((ch = getchar()) != EOF) {
        input[i] = ch;
        if (isupper(input[i])) {
            input[i] = '_';
            //input[i+1] = tolower(ch);
        } else {
            input[i] = ch;
        }
        printf("%c", input[i]);

        i++;
    }
}

你的代码有两个问题:

  • 你在if的每个分支中插入一个字符,而其中一个应该插入两个字符,并且
  • 你边走边打印字符,但第一个分支应该同时打印 _ch

您可以通过在插入 i++ 时递增 i 并在末尾打印整个单词来解决此问题:

int ch; // <<== Has to be int, not char
char input[100];
int i = 0;

while((ch = getchar())!= EOF && (i < sizeof(input)-1)) {
    if(isupper(ch)) {
        if (i != 0) {
            input[i++] = '_';
        }
        ch = tolower(ch);
    }
    input[i++] = ch;
}
input[i] = '[=10=]'; // Null-terminate the string
printf("%s\n", input);

Demo.

我不太清楚如何用 C 编写代码,但我认为您应该这样做。

if(isupper(input[i]))
{
    input[i] = tolower(ch);
    printf("_");

} else
{
    input[i] = ch;
}

首先查看您的代码并思考当有人输入超过 100 个字符的单词时会发生什么 -> 未定义的行为。如果你使用缓冲区作为输入,你总是必须添加检查,这样你就不会溢出这个缓冲区。

但是,既然直接打印字符,为什么还需要缓冲区呢?您展示的方法完全没有必要。试试这个:

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

int main()
{
    int ch;
    int firstChar = 1; // needed to also accept PascalCase
    while((ch = getchar())!= EOF)
    {
        if(isupper(ch))
        {
            if (!firstChar) putchar('_');
            putchar(tolower(ch));

        } else
        {
            putchar(ch);
        }
        firstChar = 0;
    }
}

旁注:我将 ch 的类型更改为 int。这是因为 getchar() returns 一个 intputchar()isupper()islower() 取一个 int 并且它们都使用一个值unsigned charEOF。由于允许对 char 进行签名,因此在带有签名 char 的平台上,您将获得使用负值 char 调用这些函数的未定义行为。我知道,这有点复杂。解决此问题的另一种方法是在调用将 unsigned char 的值作为 int.

的函数时始终将 char 转换为 unsigned char

当您使用缓冲区时,它现在没用了,您可能会感兴趣一种充分利用缓冲区的可能解决方案:在一个时间。这比为每个字符调用一个函数稍微高效一些。这是一个这样做的例子:

#include <stdio.h>

static size_t toSnakeCase(char *out, size_t outSize, const char *in)
{
    const char *inp = in;
    size_t n = 0;
    while (n < outSize - 1 && *inp)
    {
        if (*inp >= 'A' && *inp <= 'Z')
        {
            if (n > outSize - 3)
            {
                out[n++] = 0;
                return n;
            }
            out[n++] = '_';
            out[n++] = *inp + ('a' - 'A');
        }
        else
        {
            out[n++] = *inp;
        }
        ++inp;
    }
    out[n++] = 0;
    return n;
}

int main(void)
{
    char inbuf[512];
    char outbuf[1024]; // twice the lenght of the input is upper bound

    while (fgets(inbuf, 512, stdin))
    {
        toSnakeCase(outbuf, 1024, inbuf);
        fputs(outbuf, stdout);
    }
    return 0;
}

这个版本也避免了isupper()tolower(),但是牺牲了便携性。它 如果字符编码按顺序排列字母并且大写字母在小写字母之前,它才有效。对于 ASCII,这些假设成立。请注意,被视为(大写)字母的内容也可能取决于语言环境。上面的程序只适用于英语中的字母 A-Z。

您的代码中存在多个问题:

  • ch 定义为 char:如果 c 未定义为 int,则无法正确测试文件结尾。 getc() 可以 return 类型 unsigned char 的所有值加上特殊值 EOF,它是负数。将 ch 定义为 int.

  • 您将字节存储到数组 input 中并使用 isupper(input[i])isupper() 仅针对由 getc() 编辑的值 return 定义,而不针对 char 类型的潜在负值定义(如果此类型已在目标系统上签名)。使用 isupper(ch)isupper((unsigned char)input[i]).

  • 在将字节存储到 input[i] 之前,您没有检查 i 是否足够小,导致潜在的缓冲区溢出。请注意,对于您的问题,不必将字符存储到数组中。

  • 你应该在数组中插入'_'并且字符转换为小写。这是你的主要问题。

  • 是否要将 Main 转换为 _mainmain 或保留为 Main 是规范问题。

这是一个更简单的版本:

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

int main(void) {
    int c;

    while ((c = getchar()) != EOF) {
        if (isupper(c)) {
            putchar('_');
            putchar(tolower(c));
        } else {
            putchar(c);
        }
    }
    return 0;
}

要按照您显示的形式输出输入的字符,不需要使用数组。该程序可以如下所示

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

int main( void )
{
    int c;

    while ((c = getchar()) != EOF && c != '\n')
    {
        if (isupper(c))
        {
            putchar('_');
            c = tolower(c);
        }
        putchar(c);
    }

    putchar('\n');

    return 0;
}

如果您想使用字符数组,如果您希望该数组包含一个字符串,您应该保留它的一个元素作为终止零。

在这种情况下,程序看起来像

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

int main( void )
{
    char input[100];
    const size_t N = sizeof(input) / sizeof(*input);

    int c;
    size_t i = 0;

    while ( i + 1 < N && (c = getchar()) != EOF && c != '\n')
    {
        if (isupper(c))
        {
            input[i++] = '_';
            c = tolower(c);
        }
        if ( i + 1 != N ) input[i++] = c;
    }

    input[i] = '[=11=]';

    puts(input);

    return 0;
}