如何检查一个字符串是否以C中的另一个字符串开头?


83

startsWith(str_a, str_b)标准C库中是否有类似内容?

它应该使用指向以空字节结尾的两个字符串的指针,并告诉我第一个字符串是否也完全出现在第二个字符串的开头。

例子:

"abc", "abcdef" -> true
"abcdef", "abc" -> false
"abd", "abdcef" -> true
"abc", "abc"    -> true

3
我认为您的第三个例子应该有一个真实的结果。
Michael Burr

Answers:


73

显然,对此没有标准的C函数。所以:

bool startsWith(const char *pre, const char *str)
{
    size_t lenpre = strlen(pre),
           lenstr = strlen(str);
    return lenstr < lenpre ? false : memcmp(pre, str, lenpre) == 0;
}

请注意,上面的内容很好理解,但是如果您是在紧密循环中使用或处理非常大的字符串,则它不能提供最佳性能,因为它会在前面扫描两个字符串的全长(strlen)。诸如wj32Christoph的解决方案可能会提供更好的性能(尽管有关向量化的评论超出了我对C的理解)。另请注意,Fred Foo避免使用strlenon的解决方案str(他是对的,如果您使用strncmp代替,则没有必要memcmp)。仅对于(非常)大的字符串或紧密循环中的重复使用很重要,但是当它很重要时,它很重要。


5
我应该提到,通常将字符串作为第一个参数,将前缀作为第二个参数。但是我还是把它们保持在上面,因为似乎是您的问题是如何构成的……顺序完全取决于您,但是我真的应该以另一种方式来完成它—大多数字符串函数将完整的字符串作为第一个参数,第二个子字符串。
TJ Crowder

1
这是一个优雅的解决方案,但确实存在一些性能问题。优化的实现永远不会查看每个字符串中的min(strlen(pre),strlen(str))个字符,也不会超出第一个不匹配项。如果字符串很长,但是早期的不匹配很常见,那么它将非常轻巧。但是,由于此实现将两个字符串的全长都放在最前面,因此即使字符串的第一个字符不同,它也会强制执行最坏的情况。这是否真的取决于实际情况,但这是一个潜在的问题。
汤姆·卡尔兹

1
@TomKarzes您可以memcmpstrncmp这里代替它,它的速度更快。没有UB,因为两个字符串都至少具有lenpre字节。strncmp检查两个字符串的每个字节是否为NUL,但strlen调用已保证没有任何调用。(但是,当它prestr它比实际的通用初始序列长时,它仍然具有您提到的性能损失。)
Jim Balter

1
@JimBalter-非常好!由于memcmp此处使用的不是上面的其他答案,因此我继续在答案中进行了更改。
TJ Crowder

1
PS这个(现在的)可能是最快的答案有些机器上有一些字符串,因为strlenmemcmp可以非常快的硬件指令来实现,而strlenS可以把串到缓存中,避免了双存储命中。在这样的机器上,strncmp可以像这样实现为2strlen和a memcmp,但是对于库编写者来说这样做是有风险的,因为对于带有短公共前缀的长字符串来说,这样做可能会花费更长的时间。这里的命中是明确的,并且strlens每次仅执行一次(Fred Foo的strlen+strncmp将执行3)。
吉姆·巴尔特

156

没有标准功能,但是您可以定义

bool prefix(const char *pre, const char *str)
{
    return strncmp(pre, str, strlen(pre)) == 0;
}

我们不必担心str会比pre因为C标准(7.21.4.4/2)而短:

strncmp函数n从所指向的数组到所s1指向的数组的字符比较不超过字符(不比较跟随空字符的字符)s2


12
为什么答案不行?显然,答案是肯定的,叫做strncmp
贾斯珀(Jasper)

6
^很明显为什么答案是否定的。它采用的算法strncmpstrlen不是“叫STRNCMP”。
吉姆·巴尔特

33

我可能会选择strncmp(),但只是为了好玩一个原始实现:

_Bool starts_with(const char *restrict string, const char *restrict prefix)
{
    while(*prefix)
    {
        if(*prefix++ != *string++)
            return 0;
    }

    return 1;
}

6
我最喜欢这个-没有理由扫描任何一个字符串的长度。
Michael Burr

1
我也许也可以使用strlen + strncmp,但是尽管它确实可以工作,但是关于模糊定义的所有争议都让我失望。所以我会用这个,谢谢。
山姆·沃特金斯2015年

4
strncmp除非您的编译器真正擅长矢量化,否则这可能会慢一些,因为glibc作家肯定是:-)
Ciro Santilli郝海东冠状病六四事件法轮功

3
如果前缀不匹配,则此版本应比strlen + strncmp版本要快,尤其是如果前几个字符已经存在差异时。
dpi

1
^该优化仅在内联函数时适用。
吉姆·巴尔特

5

我不是编写优雅代码的专家,但是...

int prefix(const char *pre, const char *str)
{
    char cp;
    char cs;

    if (!*pre)
        return 1;

    while ((cp = *pre++) && (cs = *str++))
    {
        if (cp != cs)
            return 0;
    }

    if (!cs)
        return 0;

    return 1;
}

5

使用strstr()功能。 Stra == strstr(stra, strb)


3
这样做似乎有些倒退-即使strb是否为前缀,即使从非常短的初始段中就应该清楚,您也将遍历整个stra。
StasM,2011年

1
过早的优化是万恶之源。我认为如果不是时间紧迫的代码或长字符串,这是最好的解决方案。
Frank Buss

1
@ilw这是著名计算机科学家的一句名言-用谷歌搜索。它经常被误用(如此处所示)...参见joshbarczak.com/blog/?p=580
Jim Balter,

2

优化(v.2。-已更正):

uint32 startsWith( const void* prefix_, const void* str_ ) {
    uint8 _cp, _cs;
    const uint8* _pr = (uint8*) prefix_;
    const uint8* _str = (uint8*) str_;
    while ( ( _cs = *_str++ ) & ( _cp = *_pr++ ) ) {
        if ( _cp != _cs ) return 0;
    }
    return !_cp;
}

2
否定投票:startsWith("\2", "\1")返回1,startsWith("\1", "\1")也返回1
thejh 2015年

该决策将不使用clang中的优化,因为不使用instrisincs。
socketpair 2015年

^内在函数在这里无济于事,尤其是当目标字符串比前缀长得多时。
吉姆·巴尔特

1

因为我运行了可接受的版本,并且str的长度很长,所以我不得不添加以下逻辑:

bool longEnough(const char *str, int min_length) {
    int length = 0;
    while (str[length] && length < min_length)
        length++;
    if (length == min_length)
        return true;
    return false;
}

bool startsWith(const char *pre, const char *str) {
    size_t lenpre = strlen(pre);
    return longEnough(str, lenpre) ? strncmp(str, pre, lenpre) == 0 : false;
}

1

或两种方法的组合:

_Bool starts_with(const char *restrict string, const char *restrict prefix)
{
    char * const restrict prefix_end = prefix + 13;
    while (1)
    {
        if ( 0 == *prefix  )
            return 1;   
        if ( *prefix++ != *string++)
            return 0;
        if ( prefix_end <= prefix  )
            return 0 == strncmp(prefix, string, strlen(prefix));
    }  
}

编辑:下面的代码做NOT工作,因为如果STRNCMP返回0,如果终止0或长度(BLOCK_SIZE)达到它是未知的。

另一个想法是按块比较。如果该块不相等,则将该块与原始函数进行比较:

_Bool starts_with_big(const char *restrict string, const char *restrict prefix)
{
    size_t block_size = 64;
    while (1)
    {
        if ( 0 != strncmp( string, prefix, block_size ) )
          return starts_with( string, prefix);
        string += block_size;
        prefix += block_size;
        if ( block_size < 4096 )
          block_size *= 2;
    }
}

常量13644096,还有的幂block_size都只是猜测。必须为使用的输入数据和硬件选择它。


这些是好主意。但是请注意,如果前缀短于12个字节(包括NUL在内为13个字节),则第一个在技术上是未定义的行为,因为语言标准未定义计算紧随其后的字节以外的字符串外部地址的结果。
吉姆·巴尔特

@JimBalter:您能添加参考吗?如果指针已取消引用并且在终止0之后,则未定义引用的指针值。但是,为什么地址本身未定义?这只是一个计算。
shpc

但是,存在一个一般错误:block_size增量必须在指针增量之后。现在已修复。
shpc
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.