programing

C의 문자열 끝을 어떻게 비교합니까?

goodcopy 2022. 8. 3. 21:40
반응형

C의 문자열 끝을 어떻게 비교합니까?

문자열이 ".foo"로 끝나는지 확인합니다.저는 제가 잘 모르는 언어인 C를 사용하고 있습니다.그것을 하는 가장 좋은 방법은 다음과 같다.제가 우아하고 현명하게 일을 하고 있는지 확인하고 싶은 C 전문가가 있나요?

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

문자열마다 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"); }

EDIT: 현학적인 NULL 체크가 추가되었습니다.초현학자의 경우 스트링과 접미사가 모두 NULL인 경우 0이 아닌 값으로 반환해야 하는지 여부를 논의합니다.

int EndsWithFoo( char *string )
{
  string = strrchr(string, '.');

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

  return( -1 );
}

.foo로 끝나는 경우 0을 반환합니다.

저는 지금 컴파일러에 접속할 수 없습니다만, 이것이 기능하는지를 가르쳐 주실 수 있겠습니까?

#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이 명령어는 모든 사용자(컴파일러 포함)에게 문자열을 변경할 생각이 없음을 나타냅니다.

다음은 memcmp()를 사용하여 Pythons 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

시험 파이선:

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

결과 파이선:

True
False
True
True
False
False
False
True
True

어떤 C전문가들 나는 우아하고 현명하게 이는 게 확실하게 만들어 볼까?

당신의 해결 올바르게 한 논쟁이 유효한 null로 종료되었으며 문자열입니다.이것은 가장고, 이와 관련하여 현명하게 이 일을 하고 있는 중요하다.로 답들은 이 목적을 충족하지 않는 보다 복잡한 해결책을 게재했다.

을 합니다.strlen(".foo") 두 인스턴스 모두 '어느 인스턴스든 상관없다'라고할 수 .strlen(str), 따라서(clang과 gcc니)은 단일 호출 같은 값을 반환합니다.

그러나 더 내 짧은 생각으로 한번 사용은 길이를 계산하기 위해 우아할 것이다.memcmp()strcmp()인라인을 하다, 「」도 할 필요가 있습니다.strconst char *constCorrectness고 상수 문자열 또는 문자열 오며 리터럴과 함수를 호출한 경고를 방지하다

의 「」의 ".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의 컴파일러 탐색기에서 확인할 수 있듯이, 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 명령어를 생성합니다.이 명령어는 인텔 프로세서에서도 훨씬 이해하기 어렵고 효율도 떨어질 수 있습니다.은 포먼 the the the the the the the the the 、 the 、 the the 、 the the 、 the the 、 the the the the 。strlen()따라서 벤치마크에는 문자열 길이의 다양한 분포가 포함되어야 합니다.

가장 효율적인 솔루션이라면 어떻게 할 것인가에 대한 절대적인 답은 없습니다.단, 심플하다고 해서 효율이 저하되는 것은 아닙니다.또, 심플하고 알기 쉬운 코드로 검증할 수도 있습니다.심플함, 정확성, 효율성을 겸비하면 우아함이 실현됩니다.

Brian Kernighan의 말을 인용:

  • 복잡성 조절은 컴퓨터 프로그래밍의 본질이다.
    소프트웨어 도구(1976), 페이지 319(P. J. Plauger 포함).

  • 디버깅이 프로그램 작성보다 두 배 더 어렵다는 것은 누구나 알고 있습니다.만약 당신이 그것을 쓸 때 최대한 똑똑하다면, 그것을 어떻게 디버깅할 것인가?
    "프로그래밍 스타일의 요소", 제2판, 제2장.

#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

이것은 여기서 찾을 수 있는 가장 효과적인 대답입니다.

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

기능의 시그니처를 변경할 수 있는 경우는, 다음과 같이 변경해 주세요.

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

이를 통해 보다 안전하고 재사용 가능하며 효율적인 코드가 생성됩니다.

  1. 추가된 한정자는 입력 문자열을 잘못 변경하지 않도록 합니다.이 함수는 술어이기 때문에 부작용이 있는 것은 결코 아니라고 생각합니다.
  2. 비교할 접미사가 매개 변수로 전달되므로 나중에 다른 접미사와 함께 사용할 수 있도록 이 함수를 저장할 수 있습니다.
  3. 이 시그니처는 문자열의 길이를 이미 알고 있는 경우 그 길이를 전달할 수 있는 기회를 제공합니다.우리는 이것을 동적 프로그래밍이라고 부릅니다.

다음과 같이 함수를 정의할 수 있습니다.

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

추가 파라미터에 대한 명백한 반론은 이들 파라미터가 코드 내의 노이즈가 더 많거나 표현력이 떨어지는 코드를 의미한다는 것입니다.

파티에 좀 늦어서 미안해요.간단한 포인터 수학으로 뭐라도 할 수 없을까?

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'의 '.'로 설정합니다(이 문자가 포함되어 있는 경우).또, 메모리 할당은 기존의 문자 배열을 가리킬 뿐이기 때문에, 메모리를 할당할 필요가 없습니다.

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

나는 이것이 그 결과를 얻는 가장 간단한 방법이라는 것을 알았다.

다음과 같이 일반화할 수도 있습니다.

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

strlen(니들), 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;
}

저는 이렇게 하고 싶어요.

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

strlen(".foo")는 필수가 아닙니다.유연성이 필요한 경우sizeof ".foo" - 1-- 컴파일 시간 상수.

또한 null 문자열 체크가 좋습니다.

테스트된 코드(테스트 포함):

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

만약 항상 점을 넘어서는 무언가가 있다면, 우리는 포인터 산술에 빠져들 수 있다.

int EndsWithFoo (char *str)
{
   int iRetVal = 0;
   char * pchDot = strrchr (str, '.');

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

물론 점 너머에 무엇이 있는지 확인하기 위해 약간의 stren을 추가하는 것이 좋습니다.-)

NB - 확인하려고 실행한 것은 아니지만, 문제 없습니다.

내 버전을 사용하고 싶다:

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

나는 항상 glib 문자열 함수를 체크한다. 그것들은 모든 종류의 유용한 비트를 가지고 있다.접미사 검사 기능이 이미 있습니다.

gchar * str;

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

C는 처음이라 100%가 아니라면 사과드립니다.하지만 내겐 확실한 경계 조항처럼 보여!

이에 대한 나의 견해:

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

POSIX 시스템에서는 글로벌 패턴을 사용하여 문자열의 끝을 일치시킬 수 있습니다.

#include <fnmatch.h>

if (fnmatch("*.foo", my_string, 0))
  /* match */

어쩌면...

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콜을 클릭합니다.

누가 '최적화'라고 해서 쓴 거예요

#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비트 이상의 워드 타깃 프로세서에서 컴파일러에 의해 루프의 char/byte 복사본이 단일 논리 시프트로 변환되어야 하는데, C 코드가 엔디안을 인식할 필요가 없도록 코드화했습니다.sfx(suffix)는 컴파일러에 의해 정수 정수로 변환되며 등가 서픽스 테스트는 단일 32비트 정수 등가 테스트입니다.새로운 바이트마다 0을 테스트해야 합니다.단어 내에서 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
}

가장 좋은 방법은 문자열을 반전시킨 다음 처음 n개의 문자를 비교하는 것입니다.

String Reverse 함수의 예는 얼마든지 있기 때문에(Joel도 표준 인터뷰 질문으로 인용) 그 중 하나를 구현하면 비교 대상 String과 반대로 실행할 수 있습니다.

다운 투표에 대한 응답으로 편집.네, 이 방법에서는 구현에 CPU 또는 메모리가 추가로 필요하지만 질문자는 이러한 제약조건을 제시하지 않고 우아한 솔루션을 명시적으로 요구했습니다.현을 거꾸로 해서 앞에서 비교하는 것이 현의 끝을 찾아 뒤쪽으로 작업하는 것보다 훨씬 우아하다.그리고 다음 프로그래머도 쉽게 파악하고 유지할 수 있습니다.

언급URL : https://stackoverflow.com/questions/744766/how-to-compare-ends-of-strings-in-c

반응형