我正在尝试优化一个问题,必须以相同的速度优化使其更具可读性。我的问题在于:
允许的功能: write.c ,仅此而已。
编写一个程序,该程序使用两个字符串并显示而不是双精度的 出现在任一字符串中的字符。
显示将按照字符在命令行中出现的顺序进行,并且 后面将带有\ n。
如您所见,在将其与GCC一起编译后,主要是将您的两个参数字符串(argv[1]
和argv[2]
)带入函数(void remove_dup(char *str, char *str2)
)中。
str1 = "hello"
和str2 = "laoblc"
。使用写函数,预期的输出结果将为“ heloabc”。
但是,GCC抱怨是因为我有一个数组下标,我的临时字符数组用字符串索引中的零填充。为了不再引起编译器的抱怨,我不得不将字符串索引转换为int形式,以将ASCII值保存在我的临时数组中。这将是我们的检查器,它将根据字符的值确定字符串中是否存在重复项。重新编译它,但是这次使用警告标志:gcc -Wextra -Werror -Wall remove_dup.c
。这是我得到的错误:
remove_dup:11错误:数组下标的类型为'char'[-Werror,-Wchar-subscripts]
if (temp[str[i]] == 0) ^~~~~~~
remove_dup:13错误:数组下标的类型为'char'[-Werror,-Wchar-subscripts]
temp[str[i]] = 1; ^~~~~~~
remove_dup:21错误:数组下标的类型为'char'[-Werror,-Wchar-subscripts]
if (temp[str2[i]] == 0) ^~~~~~~~
remove_dup.c:23错误:数组下标的类型为'char'[-Werror,-Wchar-subscripts]
temp[str2[i]] = 1; ^~~~~~~~
现在我真正的问题是,如何在不使用任何类型转换的情况下获得相同的时间效率BUT?该程序以O(m + n)
的身份运行,其中m
是我们的第一个字符串,n
是我们的第二个字符串。
这是代码:
void remove_dup(char *str, char *str2)
{
int temp[10000] = {0};
int i;
i = 0;
while (str[i])
{
if (temp[(int)str[i]] == 0)
{
temp[(int)str[i]] = 1;
write(1, &str[i], 1);
}
i++;
}
i = 0;
while (str2[i])
{
if (temp[(int)str2[i]] == 0)
{
temp[(int)str2[i]] = 1;
write(1, &str2[i], 1);
}
i++;
}
}
int main(int argc, char *argv[])
{
if (argc == 3)
remove_dup(argv[1], argv[2]);
write(1, "\n", 1);
return (0);
}
我希望我所解释的逻辑结构足够清楚。我可能有语法错误,请忍受:)。
答案 0 :(得分:1)
在此处进行铸造不会造成性能损失。
但是,根据经验,通常最好是尽可能避免显式强制转换。您可以通过以下方式来做到这一点:
temp[(int)str[i]]
收件人:
temp[+str[i]]
这将通过常规的算术转换起作用。
但是,您的代码还有另一个问题。您可能会问:为什么gcc会麻烦发出这样的 烦人 警告消息?
一个答案是,他们只是喜欢烦人。更好的猜测是,在大多数平台上,char
是signed
-请参见Is char signed or unsigned by default?-因此,如果您的字符串碰巧具有大于127(即小于零)的ASCII字符,您将遇到一个错误。
解决此问题的一种方法是替换:
temp[(int)str[i]]
具有:
temp[str[i] + 128]
(并将int temp[10000] = {0}
更改为int temp[256 + 128] = {0}
)。无论char
的默认符号如何,它都将起作用。
答案 1 :(得分:0)
现在我真正的问题是,如何在不使用任何类型转换的情况下获得相同的时间效率BUT?
我不认为使用C进行转换会带来运行时间损失。无论如何,C语言中的所有内容都是一个数字。我相信这只是告诉编译器,是的,您知道使用的类型错误,并且认为还可以。
请注意,char
可以签名。负数可能会潜入其中。
该程序运行为O(m * n),其中m是我们的第一个字符串,n是我们的第二个字符串。
否,它以O(n)运行。如果您要为另一个字符串的每个字符遍历一个字符串,则为O(m * n)。
for( int i = 0; i < strlen(str1); i++ ) {
for( int j = 0; j < strlen(str2); j++ ) {
...
}
}
但是您要在两个独立的循环中一个接一个地循环遍历每个字符串。这是O(m + n),即O(n)。
继续改进。首先,temp
只需要保持char
的{{1}}范围。让我们给它一个描述其作用的变量名256
。
最后,不需要存储完整的整数。通常我们会使用bool
from stdbool.h
,但是我们可以使用chars_seen
来定义自己的东西,而signed char
可能会这样做。我们肯定将其包装在stdbool.h
中,因此我们将使用提供的系统(如果有的话),它将比使用布尔类型更了解。
#ifndef bool
通过消除#ifndef bool
typedef signed char bool;
#endif
bool chars_seen[256] = {0};
而不是直接增加指针,可能会获得更高的性能。不仅性能更高,而且使许多字符串和数组操作更简单。
i
请注意,我要强制转换为size_t
,而不是for( ; *str != '\0'; str++ ) {
if( !chars_seen[(size_t)*str] ) {
chars_seen[(size_t)*str] = 1;
write(1, str, 1);
}
}
,因为这是索引的正确类型。
您是否可以通过使用后增量来消除麻烦,这是否取决于您的编译器。
int
最后,为了避免重复您的代码并将其扩展为可与任意数量的字符串一起使用,我们可以编写一个函数,该函数接受看到的字符集并显示一个字符串。尽管有疑问,但我们会向编译器提供内联它的提示。
if( !chars_seen[(size_t)*str]++ ) {
write(1, str, 1);
}
然后inline void display_chars_no_dups( const char *str, bool chars_seen[]) {
for( ; *str != '\0'; str++ ) {
if( !chars_seen[(size_t)*str]++ ) {
write(1, str, 1);
}
}
}
分配可见字符数组,并根据需要多次调用该函数。
main