【问题标题】:How to compare ends of strings in C?如何比较C中字符串的结尾?
【发布时间】:2009-04-13 17:51:16
【问题描述】:

我想确保我的字符串以“.foo”结尾。我正在使用 C,一种我并不完全熟悉的语言。我发现最好的方法如下。任何 C 大师都想确保我优雅而明智地做到这一点?

int EndsWithFoo(char *str)
{
    if(strlen(str) >= strlen(".foo"))
    {
        if(!strcmp(str + strlen(str) - strlen(".foo"), ".foo"))
        {
            return 1;
        }
    }
    return 0;
}

【问题讨论】:

  • 25 个答案,只有 4 或 5 个没有问题。

标签: c string


【解决方案1】:

每个字符串不要多次调用 strlen。

int EndsWith(const char *str, const char *suffix)
{
    if (!str || !suffix)
        return 0;
    size_t lenstr = strlen(str);
    size_t lensuffix = strlen(suffix);
    if (lensuffix >  lenstr)
        return 0;
    return strncmp(str + lenstr - lensuffix, suffix, lensuffix) == 0;
}

int EndsWithFoo(const char *str) { return EndsWith(str, ".foo"); }

编辑:为迂腐添加了 NULL 检查。对于极端学究,如果 str 和 suffix 都为 NULL,是否应该返回非零值。

【讨论】:

  • 在这种情况下,您可以使用 strcmp() 而不是 strncmp()(甚至是 memcmp()),因为我们知道此时两个字符串中还剩下多少个字符,虽然速度差异几乎不会引起注意。
  • 任何对 strlen 的调用都会在您打开优化后立即从程序集中消失,因此这可能是过早优化的一种情况(尽管 C 字符串令人讨厌,足以让人想到这些问题)跨度>
  • @Johannes:对于编译时已知字符串文字以外的字符串,这怎么可能?当然你可以内联 strlen 代码,但在某种程度上你仍然需要找到字符串的长度。对于 const 字符串文字,编译器知道它有多长,但一般情况下并非如此。想法?
  • 当 !str && !suffix 时,返回 1 不是更有意义吗?
  • @MattJ:有点晚了......,但我认为 adam 的意思是编译器将删除所有调用,但每个字符串的第一个调用除外,因为它们没有更改。你当然是正确的,不是所有都可以被优化掉。
【解决方案2】:
int EndsWithFoo( char *string )
{
  string = strrchr(string, '.');

  if( string != NULL )
    return( strcmp(string, ".foo") );

  return( -1 );
}

如果以“.foo”结尾,则返回 0。

【讨论】:

  • 谦卑的鞠躬你很善良 :-)
  • 返回值应该取反,因为零为假,非零为真,函数名表示布尔返回。
  • @dreamlax -> 是的,这是真的
  • @Squirrel strcmp 检查两个字符串中的空终止符。如果一个字符串比另一个短,它将返回非零值。它不会过度阅读。
  • 使用变量“string”对我来说不是一个好主意,因为它可能与 c++ 中的 std::string 发生冲突。不确定编译器的性能如何,但我也建议使用 const char *。所以它将是 int EndsWithFoo(const char *s1 ) { const char * s2 = strrchr(s1, '.'); ....现在一切都与 s2 进一步。
【解决方案3】:

我现在无法使用编译器,有人可以告诉我这是否可行吗?

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

int EndsWithFoo(const char* s);

int
main(void)
{
  printf("%d\n", EndsWithFoo("whatever.foo"));

  return 0;
}

int EndsWithFoo(const char* s)
{
  int ret = 0;

  if (s != NULL)
  {
    size_t size = strlen(s);

    if (size >= 4 &&
        s[size-4] == '.' &&
        s[size-3] == 'f' &&
        s[size-2] == 'o' &&
        s[size-1] == 'o')
    {
      ret = 1;
    }
  }

  return ret;
}

无论如何,请务必将参数限定为const,它告诉所有人(包括编译器)您不打算修改字符串。

【讨论】:

  • +1 最优化。当 'foo' 不变时,我更喜欢这样的版本!
  • 提示:如果您有互联网连接,您可以在 codepad.org 获得 C 编译器
  • 墨菲定律说“.foo”会在最不合时宜的时刻发生变化。
  • 当您阅读 .foo 部分两次时,它并不是“最优化的”——一次用于 strlen,一次用于比较。不过很好也很实用。 +1
  • 我很确定带有循环和 const char * 的标准版本无论如何都会通过 -O3 优化到这样的东西。
【解决方案4】:

如果您可以更改函数的签名,请尝试将其更改为

int EndsWith(char const * str, char const * suffix, int lenstr, int lensuf);

这将产生更安全、更可重用和更高效的代码:

  1. 添加的 const 限定符将确保您不会错误地更改输入字符串。这个函数是一个谓词,所以我认为它永远不会有副作用。
  2. 要比较的后缀作为参数传入,因此您可以保存此函数以供以后与其他后缀一起使用。
  3. 如果您已经知道字符串的长度,此签名将使您有机会传递字符串的长度。我们称之为dynamic programming

我们可以这样定义函数:

int EndsWith(char const * str, char const * suffix, int lenstr, int lensuf)
{
    if( ! str && ! suffix ) return 1;
    if( ! str || ! suffix ) return 0;
    if( lenstr < 0 ) lenstr = strlen(str);
    if( lensuf < 0 ) lensuf = strlen(suffix);
    return strcmp(str + lenstr - lensuf, suffix) == 0;
}

对于额外参数的明显反驳是,它们意味着代码中的噪音更多,或者代码表达力更低。

【讨论】:

  • 如果lenstr &lt; lensuf,无论是作为参数值还是使用strlen() 计算,您的代码都有未定义的行为。另一个相反的论点是int 的正数范围小于size_t
【解决方案5】:

strlen(".foo")s 不是必需的。如果你真的想让它更灵活,你可以使用sizeof ".foo" - 1——一个编译时间常数。

此外,空字符串检查会很好。

【讨论】:

  • 如果我错了,请纠正我,但不是 sizeof(".foo") 5,而是 strlen(".foo") 4?我认为 strlen 更容易阅读,因为我在这里处理字符串长度。并且编译器应该将它优化为一个常量......函数的其余部分看起来如何?
  • 具体来说,没有必要,因为我们已经知道“.foo”有多长了。
  • “.foo”不是 const char * 吗?即使算作一个数组,它也有五个字符,因为在数组形式中它确实有终止符 '\0'。
  • @JoeF:对。我的错。但是除了空检查之外,您的功能对我来说看起来不错。
  • strlen 用于字符串文字通常会被优化掉。
【解决方案6】:

测试代码,包括测试:

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

int ends_with_foo(const char *str)
{
    char *dot = strrchr(str, '.');

    if (NULL == dot) return 0;
    return strcmp(dot, ".foo") == 0;
}

int main (int argc, const char * argv[]) 
{
    char *test[] = { "something", "anotherthing.foo" };
    int i;

    for (i = 0; i < sizeof(test) / sizeof(char *); i++) {
        printf("'%s' ends %sin '.foo'\n",
               test[i],
               ends_with_foo(test[i]) ? "" : "not ");
    }
    return 0;
}

【讨论】:

  • 快!虽然我不费心比较 strcmp() 结果 - 直接返回它。
  • 您假设没有其他“。”在输入字符串中。
  • 没什么区别。如果有两个相同的字符串(例如长度必须相同),strcmp() 只会返回 0。如果字符串长度不同,比较将提前完成。
  • @Blank Xavier:如果问题中的版本以“.foo”结尾,则返回 1。仅返回 strcmp() 的结果将在成功时返回 0。
  • @Naveen: strrchr() 返回最后一次出现的搜索字符。
【解决方案7】:

这是您可以在此处找到的最有效的(对于计算机而言)答案。

int endsWith(const char *string,const char *tail)
{

    const char *s1;

    const char *s2;

    if (!*tail)
        return 1;
    if (!*string)
        return 0;
    for (s1 = string; *s1; ++s1);
    for (s2 = tail; *s2; ++s2);
    if (s1 - string < s2 - tail)
        return 0;
    for (--s1, --s2; *s1 == *s2 && s2 >= tail; --s1, --s2);
    if (s2 < tail)
        return 1;
    else
        return 0;
}

【讨论】:

  • 如果s2tail 开始之前递减,恐怕s2 &gt;= tail 会调用未定义的行为,if (s2 &lt; tail) 也是如此。你应该写while (*--s1 == *--s2) { if (s2 == tail) return 1; } return 0;
  • @chqrlie 在您的评论中思考,我只是更改了比较的顺序:s2 &gt;= tail &amp;&amp; *s1 == *s2; 谢谢!
  • @DanielDeLeón:恐怕您的修复无效:如果匹配,您将使用具有未定义行为的 s2 = tail - 1 取消引用 *s2。此外,如果s2tail 开始之前被递减,即使s2 &lt; tail 也有未定义的行为。为什么不使用我的建议:while (*--s1 == *--s2) { if (s2 == tail) return 1; } return 0;
  • 谢谢@chqrlie!你谨慎行事,是 C 的最佳策略。我非常感谢你对细节的关注。现在我用了你的建议,效果很好!
  • @DanielDeLeón:实际上我的评论是指问题中的当前代码。您修复无效,因为在 tail 开头之后递减 s2 具有未定义的行为。无论如何,s2 &gt;= tail 在这种情况下毫无意义。这是一个病态的例子:在 MS/DOS 和/或 16 位 Windows 上,使用大模型或远指针,如果 tail 具有 0x0000 的偏移量,tail - 1 将具有 0xFFFF 的偏移量和相同段,导致tail - 1 &gt;= tail 评估为真。
【解决方案8】:

这是一个通用的解决方案,它使用 memcmp() 返回与 Python 的 str.endswith() 相同的值。不检查 str / suffix 是否为 NULL 是有意的,其他 libc str 函数也不检查 NULL:

int ends_with(const char *str, const char *suffix) {
  size_t str_len = strlen(str);
  size_t suffix_len = strlen(suffix);

  return (str_len >= suffix_len) &&
         (!memcmp(str + str_len - suffix_len, suffix, suffix_len));
}

测试 C:

printf("%i\n", ends_with("", ""));
printf("%i\n", ends_with("", "foo"));
printf("%i\n", ends_with("foo", ""));
printf("%i\n", ends_with("foo", "foo"));
printf("%i\n", ends_with("foo", "foobar"));
printf("%i\n", ends_with("foo", "barfoo"));
printf("%i\n", ends_with("foobar", "foo"));
printf("%i\n", ends_with("barfoo", "foo"));
printf("%i\n", ends_with("foobarfoo", "foo"));

结果 C:

1
0
1
1
0
0
0
1
1

测试 Python:

print("".endswith(""))
print("".endswith("foo"))
print("foo".endswith(""))
print("foo".endswith("foo"))
print("foo".endswith("foobar"))
print("foo".endswith("barfoo"))
print("foobar".endswith("foo"))
print("barfoo".endswith("foo"))
print("foobarfoo".endswith("foo"))

结果 Python:

True
False
True
True
False
False
False
True
True

【讨论】:

    【解决方案9】:
    #include <assert.h>
    #include <string.h>
    
    int string_has_suffix(const char *str, const char *suf)
    {
        assert(str && suf);
    
        const char *a = str + strlen(str);
        const char *b = suf + strlen(suf);
    
        while (a != str && b != suf) {
            if (*--a != *--b) break;
        }
        return b == suf && *a == *b;
    }
    
    // Test Unit
    int main (int argc, char *argv[])
    {
        assert( string_has_suffix("", ""));
        assert(!string_has_suffix("", "a"));
        assert( string_has_suffix("a", ""));
        assert( string_has_suffix("a", "a"));
        assert(!string_has_suffix("a", "b"));
        assert(!string_has_suffix("a", "ba"));
        assert( string_has_suffix("abc", "abc"));
        assert(!string_has_suffix("abc", "eeabc"));
        assert(!string_has_suffix("abc", "xbc"));
        assert(!string_has_suffix("abc", "axc"));
        assert(!string_has_suffix("abcdef", "abcxef"));
        assert(!string_has_suffix("abcdef", "abxxef"));
        assert( string_has_suffix("b.a", ""));
        assert( string_has_suffix("b.a", "a"));
        assert( string_has_suffix("b.a", ".a"));
        assert( string_has_suffix("b.a", "b.a"));
        assert(!string_has_suffix("b.a", "x"));
        assert( string_has_suffix("abc.foo.bar", ""));
        assert( string_has_suffix("abc.foo.bar", "r"));
        assert( string_has_suffix("abc.foo.bar", "ar"));
        assert( string_has_suffix("abc.foo.bar", "bar"));
        assert(!string_has_suffix("abc.foo.bar", "xar"));
        assert( string_has_suffix("abc.foo.bar", ".bar"));
        assert( string_has_suffix("abc.foo.bar", "foo.bar"));
        assert(!string_has_suffix("abc.foo.bar", "xoo.bar"));
        assert(!string_has_suffix("abc.foo.bar", "foo.ba"));
        assert( string_has_suffix("abc.foo.bar", ".foo.bar"));
        assert( string_has_suffix("abc.foo.bar", "c.foo.bar"));
        assert( string_has_suffix("abc.foo.bar", "abc.foo.bar"));
        assert(!string_has_suffix("abc.foo.bar", "xabc.foo.bar"));
        assert(!string_has_suffix("abc.foo.bar", "ac.foo.bar"));
        assert( string_has_suffix("abc.foo.foo", ".foo"));
        assert( string_has_suffix("abc.foo.foo", ".foo.foo"));
        assert( string_has_suffix("abcdefgh", ""));
        assert(!string_has_suffix("abcdefgh", " "));
        assert( string_has_suffix("abcdefgh", "h"));
        assert( string_has_suffix("abcdefgh", "gh"));
        assert( string_has_suffix("abcdefgh", "fgh"));
        assert(!string_has_suffix("abcdefgh", "agh"));
        assert( string_has_suffix("abcdefgh", "abcdefgh"));
    
        return 0;
    }
    
    // $ gcc -Wall string_has_suffix.c && ./a.out
    

    【讨论】:

      【解决方案10】:

      抱歉,我参加聚会有点晚了。 你不能用一些简单的指针数学来做点什么吗?

      char* str = "hello.foo"; //this would be string given
      
      int x = 4; //.foo has 4 characters
      
      int n = strlen(str)- x; //where x is equal to suffix length
      
      char* test = &str[n]; //do some pointer math to find the last characters
      
      if(strcmp(test, ".foo") == 0){
          //do some stuff
      }// end if
      

      字符指针通过指向其数组中的第一个字符来工作。因此,当您这样做时,您将测试的第一个字符设置为“。”在“.foo”中(如果它包含的话)。这也是你不需要为它分配内存的原因,因为它只是指向已经存在的字符数组。

      【讨论】:

        【解决方案11】:

        任何 C 大师都想确保我优雅而明智地做到这一点?

        只要参数是一个有效的以空字符结尾的字符串,您的解决方案就正确工作。这是最重要的,在这方面,您这样做明智。作为答案发布的更复杂的解决方案不符合此目标。

        编译器将内联strlen(".foo"),并且应该能够确定strlen(str) 的两个实例返回相同的值,因此生成一个调用(clang and gcc do)

        然而,恕我直言,计算一次长度并使用 memcmp() 而不是 strcmp() 会更优雅,因为 strcmp() 需要更多的工作并且没有内联。您还应该将str 定义为const char * 以实现const 的正确性并防止在使用常量字符串或字符串文字调用您的函数时出现警告。

        测试特定的".foo" 后缀是一个更普遍问题的特例:测试一个字符串是否是另一个字符串的后缀。

        这是一个简单有效的解决方案:

        #include <string.h>
        
        int strEndsWith(const char *s, const char *suff) {
            size_t slen = strlen(s);
            size_t sufflen = strlen(suff);
        
            return slen >= sufflen && !memcmp(s + slen - sufflen, suff, sufflen);
        }
        
        int strEndsWithFoo(const char *s) {
            return strEndsWith(s, ".foo");
        }
        

        代码非常简单和通用,但现代编译器将非常有效地内联strEndsWithFoo。正如可以在GodBolt's compiler explorer 上验证的那样,clang 12.0.0 在编译时计算".foo" 的长度并将memcmp() 内联为单个cmp 指令,仅生成12 个x86_64 指令:

        strEndsWithFoo:                            # @strEndsWithFoo
                pushq   %rbx
                movq    %rdi, %rbx
                callq   strlen
                movq    %rax, %rcx
                xorl    %eax, %eax
                cmpq    $4, %rcx
                jb      .LBB1_2
                xorl    %eax, %eax
                cmpl    $1869571630, -4(%rbx,%rcx)      # imm = 0x6F6F662E
                sete    %al
        .LBB1_2:
                popq    %rbx
                retq
        

        gcc 11.2 生成非常相似的代码,也是 12 条指令:

        strEndsWithFoo:
                pushq   %rbx
                movq    %rdi, %rbx
                call    strlen
                xorl    %r8d, %r8d
                cmpq    $3, %rax
                jbe     .L7
                xorl    %r8d, %r8d
                cmpl    $1869571630, -4(%rbx,%rax)
                sete    %r8b
        .L7:
                movl    %r8d, %eax
                popq    %rbx
                ret
        

        英特尔的 ICC 编译器会生成一组冗长而复杂的 SIMD 指令,即使在英特尔处理器上也更难以遵循并且效率可能更低。性能在很大程度上取决于strlen() 库函数的效率,因此基准测试应包括字符串长度的各种分布。

        对于最有效的解决方案怎么办?没有绝对的答案,但简单性并不排除效率,简单直接的代码更容易验证。当它结合了简单、正确和高效时,就达到了优雅

        引用Brian Kernighan:

        • 控制复杂性是计算机编程的本质。
          软件工具 (1976), p. 319(与 P. J. Plauger)。

        • 每个人都知道,调试的难度是编写程序的两倍。因此,如果您在编写它时尽可能地聪明,您将如何调试它?
          “编程风格的要素”,第 2 版,第 2 章。

        【讨论】:

          【解决方案12】:

          如果总有一些超出点的东西,我们可以沉迷于一些指针算术:

          int EndsWithFoo (char *str)
          {
             int iRetVal = 0;
             char * pchDot = strrchr (str, '.');
          
             if (pchDot)
             {
                if (strcmp (pchDot+1, "foo") == 0)
                {
                   iRetVal = 1;
                }
             }
             return iRetVal;
          }
          

          当然,您可能想在此处添加一点 strlen 以检查 是否 点之外的东西 :-)

          注意 - 我没有运行它来检查它,但对我来说它看起来不错。

          【讨论】:

          • 如果扩展名包含多个.,如".tar.gz",则此方案无效。
          【解决方案13】:

          我想使用我的版本:

          bool endsWith(const char *filename, const char *ext) {
              const uint len = strlen(filename);
              const uint extLen = strlen(ext);
              if (len < extLen) {
                  return false;
              }
              for (uint index  = 1; index <= extLen; index++) {
                  if (filename[len - index] != ext[extLen - index]) {
                      return false;
                  }
              }
              return true;
          }
          

          【讨论】:

          • 您应该包含&lt;string.h&gt; 并使用size_t 而不是uint
          【解决方案14】:

          我总是检查 glib 字符串函数,它们有各种有用的位。已存在后缀检查功能。

          gchar * str;
          
          if (!g_str_has_suffix(str)) {
              return FALSE;
          }
          

          我是 C 的新手,所以如果这不是 100%,我很抱歉……但对我来说这似乎是一个可靠的保护条款!

          【讨论】:

            【解决方案15】:

            我对此的看法:

            int string_has_suffix(const char* string, const char* suffix) {
                if (string && suffix) {
                    if (strlen(string) >= strlen(suffix)) {
                        const char* testLoc;
                        testLoc = strrchr(string, suffix[0]);
                        if (testLoc) {
                            return (strcmp(suffix, testLoc) == 0);
                        }
                    }
                }
                return 0;
            }
            

            【讨论】:

            • 如果suffix 中的第一个字符重复,这将不起作用。例如:string_has_suffix("file.tar.gz", ".tar.gz")
            【解决方案16】:

            在 POSIX 系统上,您可以使用 glob 模式匹配字符串的结尾

            #include <fnmatch.h>
            
            if (fnmatch("*.foo", my_string, 0))
              /* match */
            

            【讨论】:

              【解决方案17】:

              也许……

              bool endswith (const char *str, const char *tail)
              {
                const char *foo = strrstr (str, tail);
                if (foo)
                {
                   const int strlength = strlen (str);
                   const int taillength = strlen (tail);
                   return foo == (str + strlength - taillength);
                }
                return false;
              }
              
              endswith (str, ".foo");
              

              顺便说一句,除了重复的strlen 调用之外,原始问题中的解决方案看起来不错。

              【讨论】:

              • strrstr() 是非标准的,不存在,至少在 glibc 2.15 上
              • 用同样的方法更简单:return foo &amp;&amp; strlen(foo) == strlen(tail);
              【解决方案18】:

              我写这个只是因为有人说“最优化”。

              #include <stdint.h>
              
              int_fast8_f EndsWithFoo(const char *str) {
                  char c;
                  union {
                      uint32_t u;
                      char s[4];
                  } sfx = { .s = { '.','f','o','o'} },
                    cur = { .u = 0 };
                  c = *str;
                  if (0 == c) { return 0; }
                  cur.s[0] = c;
                  c = *++str;
                  if (0 == c) { return 0; }
                  cur.s[1] = c;
                  c = *++str;
                  if (0 == c) { return 0; }
                  cur.s[2] = c;
                  c = *++str;
                  if (0 == c) { return 0; }
                  cur.s[3] = c;
                  while (1) {
                      c = *++str;
                      if (0 == c) {
                              if (cur.u == sfx.u)
                              {
                                      return 1;
                              } else {
                                      return 0;
                              }
                      }
                      cur.s[0] = cur.s[1];
                      cur.s[1] = cur.s[2];
                      cur.s[2] = cur.s[3];
                      cur.s[3] = c;
                  }
              }
              

              从内存中加载的字节数不会超过一次(除非您的目标处理器几乎没有任何寄存器)。 循环中的字符/字节副本应由编译器在任何 32 位或更大的字目标处理器上转换为单个逻辑移位,但我按照我的方式对其进行编码,这样 C 代码就不必知道字节序. sfx(后缀)被编译器转为整数常量,相等后缀测试是单个32位整数相等测试。 每个新字节都必须测试为 0。虽然有一些小技巧可以将 0 测试为一个字中的一个字节,但它们不能防止读取过去我们应该可以访问的内存(假设str 指向一个正确终止的字符串)。

              【讨论】:

              • 您应该将str 定义为const char *。另请注意,如果char 超过 8 位,则上述代码不起作用:)
              • @chqrlie:将参数类型更改为 const,但这确实依赖于 8 位字符。
              • 现在我正在考虑在 AVR、8051 或其他 8 位处理器(可能带有一些 16 位指令)上执行此操作。那里的权衡变得更难权衡,但我可能会在这些上做一个后缀方法的前缀。我还在思考内存总线宽度超过 8 位的架构的负载优化(再次因为我记得之前思考过)。
              • 由于您依赖 8 位字节,您应该将 sfx.s 定义为 uint8_t s[4]。返回类型应为int_fast8_t。您还可以将循环返回简化为return (cur.u == sfx.u);
              • @chqrlie:修改从循环返回不会改变任何东西,除非在完全未优化的情况下,它有助于调试时逐步完成。将其更改为 (cur.u ^ sfx.u) 可能会产生更快和/或更小的代码,但有时人们确实编写 C 确实依赖 1 为 TRUE,并且在更改为 int_fast8_t 之后它无论如何都不适合了。如果 8!=sizeof(char) 那么事情会变得更复杂。在这种情况下,我可能会添加一个编译陷阱来捕获字符大小问题。
              【解决方案19】:

              或者……

              #include <stdbool.h>
              #include <stdio.h>
              #include <string.h>
              
              bool strendscmp(const char* haystack, const char* needle) {
                  size_t len_str = strlen(haystack);
                  size_t len_ending = strlen(needle);
                  return len_str >= len_ending && strcmp(&haystack[(len_str - len_ending)], needle) == 0;
              }
              
              //SOME TESTS
              int main(int argc, char** argv) {
                  printf("%s\n", strendscmp("abc", "bc") ? "true" : "false"); //true
                  printf("%s\n", strendscmp("abc", "d") ? "true" : "false"); //false
                  printf("%s\n", strendscmp("abc", "") ? "true" : "false"); //true
                  printf("%s\n", strendscmp("sumo", "omo") ? "true" : "false"); //false
                  printf("%s\n", strendscmp("babbbba", "bbaabaab") ? "true" : "false"); //false
                  printf("%s\n", strendscmp("dadaab", "bdadaab") ? "true" : "false"); //false
              }
              

              【讨论】:

              • @chqrlie 最后的printf 声明说明了needlehaystack 长的问题。否则感谢您的更正,我犯了一个错误。
              【解决方案20】:

              你也可以这样概括:

              int endsWith(const char* text, const char* extn)
              {
                  int result = 1;
                  int len = strlen(text);
                  int exprLen = strlen(extn);
                  int index = len-exprLen;
                  int count = 0;
              
                  if(len > exprLen)
                  {
                      for( ; count  < exprLen; ++count)
                      {
                          if(text[index + count] != extn[count])
                          {
                              result = 0;
                              break;
                          }
              
                      }
                  }
                  else
                  {
                      result = 0;
                  }
                  return result;
              }
              

              【讨论】:

              • 如果textextn 具有完全相同的内容,您的解决方案将返回0,这似乎不正确。索引变量也使用size_t
              【解决方案21】:
              int strends(char* str, char* end){
                  return strcmp(str + strlen(str) - strlen(end), end) == 0;
              }
              

              我发现这是实现结果的最简单方法。

              【讨论】:

              • 假设 end 比 str 短,可能应该进行防御性编码并检查。
              【解决方案22】:

              使用一个 strlen(needle)、strstr() 并测试 '\0' 的通用解决方案:

              #include <stdio.h>
              #include <string.h>
              #include <stdbool.h>
              
              bool endsWith(const char* haystack, const char* needle)
              {
                  bool rv = false;
                  if (haystack && needle)
                  {
                      size_t needle_size = strlen(needle);
                      if (needle_size == 0) return false;
                      const char* act = haystack;
                      while (NULL != (act = strstr(act, needle)))
                      {   
                          if (*(act + needle_size) == '\0')
                          {   
                              rv = true;
                              break;
                          }
                          act += 1;
                      }
                  }
              
                  return rv;
              }
              
              int main (int argc, char * argv[])
              {
                  char *a = "file1.gz";
                  char *b = "1.gz";
                  char *c = NULL;
                  char *d = "1.gzabc";
                  char *e = "1.gzabc1.gz";
                  char *f = "";
                  char *g = "rbrbr";
                  char *h = "rbr";
              
                  printf("endsWith:\n");
                  printf("'%s' '%s' = %d\n",a,b,endsWith(a,b));
                  printf("'%s' NULL = %d\n",a,endsWith(a,c));
                  printf("'%s' '%s' = %d\n",d,b,endsWith(d,b));
                  printf("'%s' '%s' = %d\n",e,b,endsWith(e,b));
                  printf("'%s' '%s' = %d\n",e,f,endsWith(e,f));
                  printf("'%s' '%s' = %d\n",g,h,endsWith(g,h));
              
                  return 0;
              }
              

              【讨论】:

              • 这个解决方案对needle = "" 有一个无限循环。此外,它无法匹配字符串"rbrbr" 的后缀"rbr"。为了解决这个问题,act += needle_size; 应该改为act += 1;,这样在病理情况下效率会更差。
              • @chqrlie:我添加了更改,感谢 cmets
              • needle为空字符串时,应返回true
              • 逻辑上这可能是正确的,因为\0,即空字符串是每个 C 字符串的结尾,但是你能给我一个直觉,传递一个空字符串是故意的,而不是结果有错误吗?
              【解决方案23】:

              我会这样做:

              /**
                * Return 0 if the string haystack ends with the string needle
                * 
                * @param haystack the string to be analyzed
                * @param needle the suffix string
                * @return 0 if the string haystack ends with the string needle, 1 if not
              */
              int strbcmp(const char *haystack, const char *needle) {
                  int length;
                  if (haystack && needle && strlen(haystack) >= (length = strlen(needle)) && strlen(strstr(haystack, needle)) == length) return 0;
                 return 1;
              }
              

              测试程序是:

              #include <stdio.h>
              #include <string.h>
              
              int strbcmp(const char *haystack, const char *needle) {
                  int length;
                  if (haystack && needle && strlen(haystack) >= (length = strlen(needle)) && strlen(strstr(haystack,needle)) == length) return 0;
                  return 1;
              }
              
              int main (int argc, char * argv[]){
                  char *a = "file1.gz";
                  char *b = "1.gz";
                  char *c = NULL;
                  char *d = "1.gzabc";
              
                  printf("%s %s = %d\n",a,b,strbcmp(a,b));
                  printf("%s %s = %d\n",a,c,strbcmp(a,c));
                  printf("%s %s = %d\n",d,b,strbcmp(d,b));
              
                  return 0;
              }
              

              【讨论】:

              • 甚至不编译
              • 抱歉源代码中的拼写错误。现在好了。
              • 以下测试不适用于您的实现:char *e = "1.gzabc1.gz"; printf("%s %s = %d\n",e,b,strbcmp(e,b));
              • 如果haystack 字符串中出现多次,则此代码无法匹配尾随needle
              【解决方案24】:

              我建议最好的方法是反转字符串,然后比较前 n 个字符。

              那里有许多字符串反转函数的示例(甚至 Joel 都将其引用为标准面试问题),因此只需实现其中一个,然后逐步通过反转字符串进行比较。

              编辑以回应反对票。好的,是的,这种方法确实需要额外的 CPU 或内存来实现,但提问者没有指出任何此类限制,他明确要求提供一个优雅的解决方案。反转字符串然后从前面进行比较比寻找字符串的末端并向后工作要优雅得多。并且对于下一个程序员来说也更容易掌握和维护。

              【讨论】:

              • 是的,这会起作用,但它要么是为副本分配内存,要么是双重反转来消除损坏。
              • 那又怎样?没有迹象表明他在 CPU 或内存受限的情况下运行,并且从前面反转字符串以进行比较比寻找 end-n 位置并从那里破解逻辑更容易维护。
              • 你必须找到字符串的结尾才能知道它有多长,这样你就知道如何反转它!不管你怎么做,问题在于字符串的结尾,所以你必须以一种或另一种方式找到它。
              • 当然可以,但是如果您先反转字符串问题的结尾,则会有效地将其抽象为反转函数,这就是为什么它是一个更优雅、可维护和易于理解的解决方案
              • 鉴于没有标准的 C89 或 C99 函数来反转字符串,您必须实现和测试这个函数以及最后检查 .foo 的函数。为这个问题实施任何提供的解决方案会比你的更简单、更容易并且花费更少的时间
              猜你喜欢
              • 2020-08-25
              • 1970-01-01
              • 1970-01-01
              • 2013-05-06
              • 1970-01-01
              • 2021-06-11
              • 1970-01-01
              • 2016-03-06
              • 1970-01-01
              相关资源
              最近更新 更多