如何在C语言中比较字符串的结尾?

57

我想确保我的字符串以“.foo”结尾。我正在使用C语言,这是我不太熟悉的语言。我找到了下面的最佳方法来实现它。有没有C语言专家能够验证我是否优雅而明智地完成了这个任务?

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

1
25个答案,只有4或5个没有问题。 - chqrlie
25个回答

1
#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

0

我经常检查glib字符串函数,它们有各种有用的部分。一个后缀检查函数已经存在。

gchar * str;

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

我对C语言还比较新,如果有不足之处请见谅...但是在我看来,这似乎是一个很好的守卫条件!


0

或者...

#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”语句解决了“needle”比“haystack”长的问题。否则感谢您的更正,我犯了一个错误。 - user14773854

0

可能吧...

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上不存在。 - lumpidu
使用相同的方法更简单:return foo && strlen(foo) == strlen(tail); - chqrlie

0
如果点的后面总是有一些东西,我们可以沉迷于一些指针算术:
int EndsWithFoo (char *str)
{
   int iRetVal = 0;
   char * pchDot = strrchr (str, '.');

   if (pchDot)
   {
      if (strcmp (pchDot+1, "foo") == 0)
      {
         iRetVal = 1;
      }
   }
   return iRetVal;
}

当然,您可能想添加一些strlen来检查点后面是否有内容 :-)

NB-我没有运行此代码进行检查,但它看起来还不错。


如果扩展名包含多个“.”,例如“.tar.gz”,则此解决方案无法正常工作。 - chqrlie

0

我想使用我的版本:

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;
}

你应该包含 <string.h> 并使用 size_t 而不是 uint - chqrlie

0

我的看法是:

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") - chqrlie

0

我只是写这个因为有人说了什么关于“最优化”的话。

#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;
    }
}

除非目标处理器几乎没有寄存器,否则不会从内存中加载任何字节超过一次。

循环中的char/byte复制应该在任何32位或更大的字目标处理器上被编译器转换为单个逻辑移位,但我编写它的方式是为了C代码不必意识到大小端。

编译器将sfx(后缀)转换为整数常量,并且等式后缀测试是单个32位整数相等性测试。

每个新字节都必须测试是否为0。虽然有一些位操作方法可以在一个字中测试0作为一个字节,但它们不能保护我们允许访问的内存之外的读取(假设str指向一个正确终止的字符字符串)。


你应该将 str 定义为 const char *。另外请注意,如果 char 的位数超过8位,则上述代码无法正常工作 :) - chqrlie
@chqrlie:将参数类型更改为const,但这取决于8位字符。 - nategoose
现在我正在考虑在AVR,8051或其他8位处理器上实现这个功能(可能还带有一些16位指令)。这些权衡变得更加困难,但我可能会在其中采取前缀或后缀的方法。我也在思考如何优化对于具有比8位宽的内存总线架构的负载(因为我之前曾考虑过这个问题)。 - nategoose
由于您依赖于8位字节,因此您应该将sfx.s定义为uint8_t s[4]。返回类型应为int_fast8_t。您还可以简化循环的返回,如下所示:return (cur.u == sfx.u); - chqrlie
@chqrlie:循环返回值的修改除非是在完全未优化的情况下,否则不会改变任何东西,并且有助于在调试时进行步进。将其更改为(cur.u ^ sfx.u)可能会产生更快和/或更小的代码,但偶尔人们确实会编写依赖于1作为TRUE的C代码,而在更改为int_fast8_t之后它就不再适合了。如果8!= sizeof(char),那么事情就会变得更加复杂。在这种情况下,我可能会添加编译陷阱以捕捉char大小问题。 - nategoose

0

个人而言,我会自然地选择 plinth 的答案,它简单、优雅,不会重新发明轮子 - 这正是应该的。

然而,由于这是一个非常基本的问题,我在这里缺少的是一个尽可能简洁的解决方案,因此我想添加一个只使用指针增量/减量和比较的解决方案,不使用任何其他函数:

#define SUFFIX_MATCH           0
#define SUFFIX_NO_MATCH        1
#define SUFFIX_INVALID_STR    -1
#define SUFFIX_INVALID_SUFFIX -2
#define SUFFIX_STR_TOO_SHORT   2

int str_endswith(char *str, char *suffix) {
/* find out, if a string ends with another string

  If str ends with suffix, return zero (to mimick how strcmp() from string.h
  works), otherwise return SUFFIX_NO_MATCH; return SUFFIX_INVALID_STR or
  SUFFIX_INVALID_SUFFIX if the respective parameter is NULL or an empty
  string, return SUFFIX_STR_TOO_SHORT if the suffix is longer than str.

  This function is case-sensitive and does not care about localisation etc.
*/
  if (str == NULL || *str == 0) return(SUFFIX_INVALID_STR);
  if (suffix == NULL || *suffix == 0) return(SUFFIX_INVALID_SUFFIX);

  char *p, *q;   // pointer, query-pointer (just arbitrary names)

  // set p to the last non-zero char of str
  p = str;
  while (*p++);
  --p;

  // move p forward as many characters as suffix has
  q = suffix;
  while (*q++ && p-- != str);
  if(p < str) {
    // if suffix is longer than str, there can be no success
    return(SUFFIX_STR_TOO_SHORT);
  }

  // compare what p points to with suffix, char by char
  q = suffix;
  while (*p == *q && *p && *q) {    // mind the \0
    ++p;
    ++q;
  }

  // if strings are equal, both pointers now point to \0
  if (*p == 0 && *q == 0) return(SUFFIX_MATCH);   // success!

  return(SUFFIX_NO_MATCH);   // or no success otherwise
}

这是一个编辑,我很惭愧地承认从我的一个项目中拿走了原始代码,将其简化了太多,并没有彻底测试它;这个版本应该是非常严密的……如果我想要为更复杂的事情编写一个函数,这应该是一个很好的起点,这些事情不能轻易地通过标准库调用来覆盖。希望这可以帮助到你!

不,我没有使用“const char”,因为我在可变字符串上使用这样的函数,而这个函数返回零成功时,就像(在代码中提到的)strcmp()一样,这给了你额外的好处,如果需要的话,能够检查为什么没有匹配。


“没有‘const char’是因为我在可变字符串上使用这样的函数”……这不是问题:可变字符串可以作为const char *参数传递,反之则会违反约束。 - chqrlie
如果后缀比字符串更长,则代码具有未定义的行为:while (*q++ && p-- != str); 将使 p 指向 str 之前,然后 while (*p++ == *q++ && *p && *q); 将解引用此无效指针并调用未定义的行为。此外,str_endswith("ab"+1, "ab") 将返回 1 而不是预期的 0 - chqrlie
此实现无法处理空字符串:如果 strsuffix 是空字符串,则其行为未定义,因为它会引用空终止符后面的字节。 - chqrlie
令我失望的是,我发现原始函数即使str或suffix是一个字符的字符串,它总是返回成功。我羞愧地承认我搞砸了最后一个while语句。然而,@chqrlie关于在成功时返回1...好吧,如果您使用它,请根据您的喜好调整该函数,重要的是,这个东西现在在所有可能的情况下都按预期工作。 - Zacharias
我恐怕不同意:为什么空字符串应该被认为是此函数的无效输入?每个字符串都匹配一个空后缀,而空字符串只匹配一个空后缀。 - chqrlie
显示剩余5条评论

-1
int strends(char* str, char* end){
    return strcmp(str + strlen(str) - strlen(end), end) == 0;
}

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

1
假设 end 比 str 短,可能应该编写防御性代码并进行检查。 - djna

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接