我刚刚制作了这个程序,我想知道如何改进它。它只是接收一个单词并向后打印。也可以接受数字序列,但不能接受空格(这就是我使用scanf的原因)。

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

int main(void)
{
    const unsigned short MAX = 30;
    char array[MAX];
    unsigned short length;

    scanf("%s", array);

    length = strlen(array) - 1;
    char array2[length];

    for (unsigned short i = length, n = 0; i >= 0, n <= length; i--, n++)
    {
        array2[n] = array[i];
        printf("%c", array2[n]);
    }

    return 0;
}


评论

“ char array2 [length];”您已经在代码中间声明了哪个值,并且使用了直到运行时才知道的值?建议您在顶部声明尺寸为MAX的产品。 (如果您不希望让scanf分配缓冲区的开销-我假设您想对输入中的多个单词执行逆转操作?)

实际上,不是多个单词。只有一个。

但是编写可重用的代码通常是一个好主意。说(因为您说自己是新手),这是编程课上的作业#1。分配#2反转文件中的每个单词的几率是多少?如果您已将反向代码作为一个函数编写,那么您差不多完成了。

for(无符号短i =长度,n = 0; i> = 0,n <=长度; i--,n ++)这不是C.它是C ++。更改为:unsigned short i; for(i =长度,n = 0; i> = 0,n <=长度; i--,n ++)

@尼克你错了。就是C。您说的是C89和C99之间的区别。

#1 楼

缓冲区溢出

char array[MAX];
scanf("%s", array);


如果用户输入超过array个字符,这可能会使MAX-1中的缓冲区溢出。可以通过多种方法解决此问题:



询问scanf以分配适当大小的缓冲区(如果您具有中等近期的glibc):

char *array = NULL;
scanf("%ms", &array);


您需要稍后释放分配的缓冲区:

free(array);


要使用free,您需要先#include <stdlib.h>


使用fgets()并告知其缓冲区大小:

fgets(array, sizeof(array), stdin);


这将读取整行内容,而不仅仅是一个单词。您可能需要删除自己的末尾也可能包含换行符:

length = strlen(array) - 1;
if (array[length] == '\n')
    array[length--] = '
char *array = NULL;
size_t array_size;
size_t length = getline(&array, &array_size, stdin);
// ... remove potential newline ...
// ... do stuff with array ...
free(array);
';



getline()的工作方式类似,但还会为您;方便地,它还返回读取的字符数(可能与array_size中存储的已分配缓冲区的长度不同):

length = strlen(array);
if (length > 0) {
    for (size_t i = length - 1, n = 0; n < length; i--, n++) {
        // ...
    }
 }


getline需要glibc的GNU扩展(#define _GNU_SOURCE)或POSIX.1-2008(#define _POSIX_C_SOURCE 200809L)。


size_t使用合适的数据类型



length,因为这是返回类型strlen,并保证保留所有可能的字符串长度。您需要注意以下特殊情况,以避免整数下溢(任何unsigned类型也会出现)。
i相同,因为它取决于length

角套


如果strlen(array) == 0怎么办?然后,strlen(array) - 1具有巨大的正值,因为它使strlen的无符号返回类型下溢并回绕到该类型的最大可能值。
以类似的方式,i >= 0始终为true,因为无符号类型不能假定值小于0。

您将必须找到一种方法来处理这些极端情况。在那种情况下,由于空字符串本身就是空字符串的返回,该怎么办呢?

我故意遗漏了i >= 0,因为它与n < length是多余的。由于C中的,运算符意味着仅其右侧负责整个(子)表达式的值,因此它也不起作用。您可能想要&&,但正如我所说,这两个条件在这里几乎差不多。

不必要的代码和(可能)更好的算法

您不需要array2如果您最终逐一打印字符。您可以直接从array打印它们:

printf("%c", array[i]);


或者,您可以先还原字符串,然后再打印整个字符串:

for (unsigned short i = length, n = 0; i >= 0, n <= length; i--, n++)
    array2[n] = array[i];

fwrite(array2, 1, length, stdout);


或者您可以在没有附加缓冲区/数组的情况下就地还原字符串:
for (unsigned short i = length, n = 0; i > n; i--, n++)
{
    char c = array[n];
    array[n] = array[i];
    array[i] = c;
}

puts(array);


puts打印整个字符串并附加换行符。按照约定,* nix程序始终终止其行,包括最后一行(除非它们对原始字节进行操作)。

更好的字符输出功能

printf非常适合使用需要格式化的输出,但它的权重也很高。如果仅打印单个字符,则可以使用:

putchar(array[i]);


错误检查

您应该检查所有以下函数的返回值:如果它影响程序的结果,则可能会失败。例如:如果发生的I / O错误会破坏程序的其余部分,则scanffgetsgetline可能都返回错误条件。您应该检查错误并做出相应的反应(即关闭程序):

if (scanf("%ms", &array) <= 0)
    // ...

if (fgets(...) == NULL)
    // ...

if (getline(...) == -1)
    // ...


如果发生错误,建议您打印一条错误消息并自己返回错误条件(如果从main()完成则终止该程序):

 if (...) {
     perror("Input error");
     return 1;
 }


除0以外的其他值会按照约定将错误通知给调用方。

以类似的方式,您可以检查printffwriteputc上的错误并对它们作出反应。

您可以在其手册页中了解功能的错误情况。在大多数* nices上,您都可以使用man <FUNCTION_NAME>轻松访问它们。

使用表达式变量名称




array是字符串的缓冲区:string_buffer或较短的strbuf

in是恢复循环的左右索引:leftright

最终程序

可能的最终值程序可能如下所示:

#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <stdio.h>

int main(void)
{
    char *strbuf = NULL;
    size_t strbuf_size;

    ssize_t length = getline(&strbuf, &strbuf_size, stdin);
    if (length == -1) {
        perror("Input error");
        return 1;
    }


    if (length != 0) {
      if (strbuf[length-1] == '\n')
            strbuf[--length] = 'q4312078q';

        if (length != 0) {
            for (size_t right = (size_t) length - 1, left = 0; left < right; right--, left++) {
                char c = strbuf[left];
                strbuf[left] = strbuf[right];
                strbuf[right] = c;
            }

            if (puts(strbuf) == EOF) {
                perror("Output error");
                return 1;
            }
        }
    }

    free(strbuf);

    return 0;
}


评论


\ $ \ begingroup \ $
您还可以将scanf()与固定大小的缓冲区一起使用,但要限制字符串的长度。
\ $ \ endgroup \ $
– 200_success
16年4月24日在19:24

\ $ \ begingroup \ $
@ 200_success:我也看过了,对于初学者来说似乎有点笨拙。太多的预处理器骇客……我相信它也仅限于INT_MAX个字符。
\ $ \ endgroup \ $
–大卫·福斯特(David Foerster)
16-4-24在19:29



\ $ \ begingroup \ $
putc需要两个参数,而不是一个。而且我认为这不是标准功能。也许您在考虑putchar?
\ $ \ endgroup \ $
– Spikatrix
16年4月25日在5:11

\ $ \ begingroup \ $
@CoolGuy:你是对的。我的意思是putchar。仅供参考,它们都是C89和C99的一部分。
\ $ \ endgroup \ $
–大卫·福斯特(David Foerster)
16年4月25日在17:17

#2 楼

很好,但我有几点注意事项:


void放入main()的参数自变量方面的工作很不错
将主算法提取到自己的函数中,与main()分开。如果将来将来扩展,这有助于将程序划分为更多可维护的部分。
字符串的长度可以短至30,这取决于您使用程序的目的。我建议您在输入字符串后测试字符串的长度,然后根据该长度创建一个指针,以使该变量更多。这使最终实现更加动态。
uint16_tstdint.h优先选择unsigned short。我只是更喜欢使用size_t,即使我怀疑您会超越short类型的范围。 size_t还是strlen()的返回类型。

您不必在0的末尾返回main(),就像您不会费心将return;放在void -returning函数的末尾一样。 C标准知道它的使用频率,并且让您不必打扰。


C99和C11§5.1.2.2(3)

...到达终止}函数的main()返回0
值。




最终程序:

关于一些人想要指出的内容的快速说明:C语言的一部分standard定义了字符串处理函数参数,除非另有说明,否则必须具有有效值。我在这里尝试模仿,因此排除了NULL检查。这也会略微提高执行速度。

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


char* strrev(const char *str)
{
    size_t len = strlen(str);
    char *rev = malloc(len + 1);

    for(size_t i = 0; i < len; ++i)
    {
        rev[i] = str[(len-1) - i];
    }

    // need to NULL terminate the string
    rev[len] = 'q4312078q';
    return rev;
}

int main(void)
{
    // outputs "blimey nice function"
    puts(strrev("noitcnuf ecin yemilb"));
}


评论


\ $ \ begingroup \ $
我只想问一个问题:为什么必须对字符串进行空终止?
\ $ \ endgroup \ $
–卢西奥·卡多佐(LúcioCardoso)
16-4-24在19:32



\ $ \ begingroup \ $
@LúcioCardoso如果不这样做,可能会导致一些讨厌的错误,未定义的行为将会发生。例如,如果我将一个不带终止符的字符串传递给strlen(),它将继续搜索内存,直到它a)找到了所述终止符(它不会);或b)击中导致某种内存保护故障的地址。想象一下,如果将相同的字符串传递给strcpy()会发生什么。它会导致各种死亡和破坏!
\ $ \ endgroup \ $
–syb0rg
16-4-24在19:40



\ $ \ begingroup \ $
@LúcioCardoso:C中的许多字符串处理功能都假定字符串以空字符终止。如果在预期字符串的末尾不存在任何字符,它将对字符串后面的内存中的“垃圾”进行处理,直到遇到空字符(或引发内存访问冲突)为止。这可能会提供无效的结果,并可能揭示或覆盖位于字符串缓冲区旁边的内存中的其他重要值。当然,某些函数(或其变体)允许您通过参数而不是缓冲区末尾的空字符来指定字符串长度。
\ $ \ endgroup \ $
–大卫·福斯特(David Foerster)
16-4-24在19:44



\ $ \ begingroup \ $
我建议重命名您的功能。有一个称为strrev的库函数(非标准),在某些实现中可用。就像他使用名为strdup的函数时那样,这可能会导致令人讨厌的错误,甚至产生无声的未定义行为。
\ $ \ endgroup \ $
– Spikatrix
16年4月25日在5:05

\ $ \ begingroup \ $
另外,检查malloc的返回值并释放malloc的内存
\ $ \ endgroup \ $
– Spikatrix
16-4-25在5:06



#3 楼

您的代码无法在C89中编译。如果要提高代码的可移植性并希望其在所有C标准中均可编译,请




不要使用非标准函数
不使用VLAs
不在代码中间声明变量

检查此代码:

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

#define xstr(s) str(s)
#define str(s) #s

#define MAX 30

int main(void)
{
    char array[MAX + 1];
    size_t length;

    if(scanf("%" xstr(MAX) "s", array) != 1)
    {
        fputs("scanf failed! Exiting...", stderr);
        exit(EXIT_FAILURE);
    }

    for (length = strlen(array); length-- ;)
    {
        putchar(array[length]);
    }

    return EXIT_SUCCESS;
}


改进:


在C89,C99和C11中编译时没有任何错误或警告。
添加了对scanf的错误检查以及一个长度修饰符,以防止缓冲区溢出。
已删除不必要的变量。
降低了代码复杂度。
使用适当的数据类型(size_t)。
使用putchar而不是printf可以提高性能。

想知道那些xstr和那些奇怪的#define是什么,请阅读Stringification。

评论


\ $ \ begingroup \ $
如果我没有收到C99的警告消息,那就太好了。
\ $ \ endgroup \ $
–pacmaninbw
16-4-25在11:22

\ $ \ begingroup \ $
对不起,没有得到您。什么警告
\ $ \ endgroup \ $
– Spikatrix
16-4-25的11:32

\ $ \ begingroup \ $
对不起,我不是指您的回答,我是在评论说,昨天我编译原始问题时,在for循环上收到警告消息。
\ $ \ endgroup \ $
–pacmaninbw
16-4-25在11:35



#4 楼

这是字符串反向的基本程序。您可以在数组中进行就地交换,而不必定义一个新的数组(该数组又会占用一些内存)。

代码看起来像这样:

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

    int main(void)
    {
        const unsigned short MAX = 30;
        char array[MAX];
        unsigned short length, i, j;

        scanf("%s", array);
        length = strlen(array) - 1;

        for (i = 0, j = length; i < j; i++, j--)
        {
            char temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        } 

        printf("%s\n", array);

        return 0;
    }


#5 楼

您应该简化以下循环以使其更具可读性:

    for (unsigned short i = length, n = 0; i >= 0, n <= length/2; i--, n++)
    {
        array2[n] = array[i];
        printf("%c", array2[n]);
    }


无需在for子句中全部推送:

    unsigned short i = length;
    for (unsigned short n = 0; n <= length; n++)
    {
        array2[n] = array[i];
        printf("%c", array2[n]);
        i--;
    }


以下内容需要较少的变量,并且更加清晰:

    for (unsigned short n = 0; n <= length; n++)
    {
        array2[n] = array[length-n];
        printf("%c", array2[n]);
    }


您不需要另一个数组即可将单词取反。您只需要一个附加变量:

    for (unsigned short n = 0; n <= length/2; n++)
    {
        temp = array[n];
        array[n] = array[length-n];
        array[length-n] = temp;
        printf("%c", temp);
    }


评论


\ $ \ begingroup \ $
上一个示例中的循环将不起作用,因为它会将每对字符交换两次,这将仅输出原始字符串。 (打印的文本将被反转,但是不会存储在字符串中。)您应该将循环转到length / 2,以便每次交换仅发生一次。 (对于2个数组,您确实需要循环中的完整长度,但不需要1。)
\ $ \ endgroup \ $
–达雷尔·霍夫曼(Darrel Hoffman)
16年4月25日在14:16

\ $ \ begingroup \ $
@DarrelHoffman:是的,我更改了循环。谢谢。
\ $ \ endgroup \ $
– miracle173
16-4-27的16:27