#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;
}
#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错误会破坏程序的其余部分,则
scanf
,fgets
或getline
可能都返回错误条件。您应该检查错误并做出相应的反应(即关闭程序):if (scanf("%ms", &array) <= 0)
// ...
if (fgets(...) == NULL)
// ...
if (getline(...) == -1)
// ...
如果发生错误,建议您打印一条错误消息并自己返回错误条件(如果从
main()
完成则终止该程序): if (...) {
perror("Input error");
return 1;
}
除0以外的其他值会按照约定将错误通知给调用方。
以类似的方式,您可以检查
printf
,fwrite
和putc
上的错误并对它们作出反应。您可以在其手册页中了解功能的错误情况。在大多数* nices上,您都可以使用
man <FUNCTION_NAME>
轻松访问它们。使用表达式变量名称
array
是字符串的缓冲区:string_buffer
或较短的strbuf
。i
和n
是恢复循环的左右索引:left
和right
。最终程序
可能的最终值程序可能如下所示:
#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_t
到stdint.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
评论
“ 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之间的区别。