수색…


소개

C에서 문자열은 내장 유형이 아닙니다. C- 문자열은 '\0' 의해 널 문자로 끝나는 1 차원 문자 배열을 갖는 규칙입니다.

즉, 내용이 "abc" C 문자열은 'a' , 'b' , 'c''\0' 4 개의 문자를 갖게됩니다.

문자열 예제에 대한 기본 참조하십시오.

통사론

  • char str1 [] = "안녕하세요, 세상!"; / * 수정 가능 * /
  • char str2 [14] = "안녕하세요, 세상!"; / * 수정 가능 * /
  • char * str3 = "Hello, world!"; / * 변경 불가능 * /

길이 계산 : strlen ()

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

int main(int argc, char **argv) 
{
    /* Exit if no second argument is found. */
    if (argc != 2) 
    {
        puts("Argument missing.");
        return EXIT_FAILURE;
    }

    size_t len = strlen(argv[1]);
    printf("The length of the second argument is %zu.\n", len);

    return EXIT_SUCCESS;
}

이 프로그램은 두 번째 입력 인수의 길이를 계산하고 그 결과를 len 저장합니다. 그런 다음 해당 길이를 터미널에 인쇄합니다. 예를 들어 program_name "Hello, world!" 매개 변수 program_name "Hello, world!" , 프로그램이 출력 The length of the second argument is 13. Hello, world! 문자열이기 때문 Hello, world! 13 자입니다.

strlen 은 문자열의 시작부터 종료 NUL 문자 '\0' 까지의 모든 바이트 를 계산합니다. 따라서이 문자열은 문자열이 NUL로 끝나는 것이 보장 될 때만 사용할 수 있습니다.

또한 문자열에 유니 코드 문자가 포함되어 있으면 strlen 은 문자열에 몇 개의 문자가 포함되어 있는지 알려주지 않습니다 (일부 문자는 여러 바이트가 될 수 있기 때문에). 이 경우 문자 ( 코드 단위)를 직접 계산해야합니다. 다음 예제의 출력을 고려하십시오.

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

int main(void) 
{
    char asciiString[50] = "Hello world!";
    char utf8String[50] = "Γειά σου Κόσμε!"; /* "Hello World!" in Greek */

    printf("asciiString has %zu bytes in the array\n", sizeof(asciiString));
    printf("utf8String has %zu bytes in the array\n", sizeof(utf8String));
    printf("\"%s\" is %zu bytes\n", asciiString, strlen(asciiString));
    printf("\"%s\" is %zu bytes\n", utf8String, strlen(utf8String));
}

산출:

asciiString has 50 bytes in the array
utf8String has 50 bytes in the array
"Hello world!" is 12 bytes
"Γειά σου Κόσμε!" is 27 bytes

복사 및 연결 : strcpy (), strcat ()

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

int main(void)
{
  /* Always ensure that your string is large enough to contain the characters
   * and a terminating NUL character ('\0')!
   */
  char mystring[10];

  /* Copy "foo" into `mystring`, until a NUL character is encountered. */
  strcpy(mystring, "foo");
  printf("%s\n", mystring);

  /* At this point, we used 4 chars of `mystring`, the 3 characters of "foo",
   * and the NUL terminating byte.
   */

  /* Append "bar" to `mystring`. */
  strcat(mystring, "bar");
  printf("%s\n", mystring);

  /* We now use 7 characters of `mystring`: "foo" requires 3, "bar" requires 3
   * and there is a terminating NUL character ('\0') at the end.
   */

  /* Copy "bar" into `mystring`, overwriting the former contents. */
  strcpy(mystring, "bar");
  printf("%s\n", mystring);

  return 0;
}

출력 :

foo
foobar
bar

기존 문자열에 추가하거나 기존 문자열에서 복사하는 경우 NUL 종료인지 확인하십시오!

문자열 리터럴 (예 : "foo" )은 항상 컴파일러에서 NUL 종료됩니다.

비교 : strcmp (), strncmp (), strcasecmp (), strncasecmp ()

strcase* - 함수는 표준 C가 아니라 POSIX 확장입니다.

strcmp 함수는 사전 식으로 두 개의 null로 끝나는 문자 배열을 비교합니다. 함수는, 최초의 인수가 2 번째의 사전 순서로 출현하고있는 경우는 부의 값, 동등의 경우는 0, 3 번째의 인수는 사전 순서로 2 번째의 인수가 출현하는 경우는 부의 값을 돌려줍니다.

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

void compare(char const *lhs, char const *rhs)
{
    int result = strcmp(lhs, rhs); // compute comparison once
    if (result < 0) {
        printf("%s comes before %s\n", lhs, rhs);
    } else if (result == 0) {
        printf("%s equals %s\n", lhs, rhs);
    } else { // last case: result > 0
        printf("%s comes after %s\n", lhs, rhs);
    }
}

int main(void)
{
    compare("BBB", "BBB");
    compare("BBB", "CCCCC");
    compare("BBB", "AAAAAA");
    return 0;
}

출력 :

BBB equals BBB
BBB comes before CCCCC
BBB comes after AAAAAA

strcmp 와 마찬가지로 strcasecmp 함수는 각 문자를 소문자로 변환 한 후 사전 식으로 인수를 비교합니다.

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

void compare(char const *lhs, char const *rhs)
{
    int result = strcasecmp(lhs, rhs); // compute case-insensitive comparison once
    if (result < 0) {
        printf("%s comes before %s\n", lhs, rhs);
    } else if (result == 0) {
        printf("%s equals %s\n", lhs, rhs);
    } else { // last case: result > 0
        printf("%s comes after %s\n", lhs, rhs);
    }
}

int main(void)
{
    compare("BBB", "bBB");
    compare("BBB", "ccCCC");
    compare("BBB", "aaaaaa");
    return 0;
}

출력 :

BBB equals bBB
BBB comes before ccCCC
BBB comes after aaaaaa

strncmpstrncasecmp 는 최대 n 문자를 비교합니다.

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

void compare(char const *lhs, char const *rhs, int n)
{
    int result = strncmp(lhs, rhs, n); // compute comparison once
    if (result < 0) {
        printf("%s comes before %s\n", lhs, rhs);
    } else if (result == 0) {
        printf("%s equals %s\n", lhs, rhs);
    } else { // last case: result > 0
        printf("%s comes after %s\n", lhs, rhs);
    }
}

int main(void)
{
    compare("BBB", "Bb", 1);
    compare("BBB", "Bb", 2);
    compare("BBB", "Bb", 3);
    return 0;
}

출력 :

BBB equals Bb
BBB comes before Bb
BBB comes before Bb

토큰 화 : strtok (), strtok_r () 및 strtok_s ()

strtok 함수는 문자열을 작은 문자열 또는 토큰으로 구분 기호 세트를 사용하여 구분합니다.

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

int main(void)
{
    int toknum = 0;
    char src[] = "Hello,, world!";
    const char delimiters[] = ", !";
    char *token = strtok(src, delimiters);
    while (token != NULL)
    {
        printf("%d: [%s]\n", ++toknum, token);
        token = strtok(NULL, delimiters);
    }
    /* source is now "Hello\0, world\0\0" */
}

산출:

1: [Hello]
2: [world]

구분 기호 문자열에는 하나 이상의 구분 기호가 포함될 수 있으며 strtok 호출 할 때마다 다른 구분 기호 문자열을 사용할 수 있습니다.

동일한 소스 문자열을 토큰 화하는 작업을 계속하기 위해 strtok 을 호출하면 소스 문자열을 다시 전달해서는 안되며 대신 첫 번째 인수로 NULL 을 전달 NULL . 동일한 소스 문자열 전달되면 첫 번째 토큰이 다시 토큰 화됩니다. 즉, 동일한 분리 문자를 사용하면 strtok 은 첫 번째 토큰을 다시 반환합니다.

strtok 은 토큰에 새로운 메모리를 할당하지 않으므로 소스 문자열을 수정합니다 . 즉, 위의 예에서 문자열 srcstrtok 에 대한 호출에서 반환 된 포인터가 참조하는 토큰을 생성하기 위해 조작됩니다. 즉, 소스 문자열을 const 지정할 수 없으므로 (문자열 리터럴이 될 수 없습니다). 또한 구분 바이트의 ID가 손실된다는 것을 의미합니다 (예 : ","및 "!"는 소스 문자열에서 효과적으로 삭제되고 어떤 구분 문자가 일치하는지 알 수 없음).

소스 문자열의 여러 연속 구분 기호는 하나로 처리됩니다. 예제에서 두 번째 쉼표는 무시됩니다.

strtok 은 파싱하는 동안 정적 버퍼를 사용하기 때문에 thread로부터 안전하지도 않고 re-entrant가 아닙니다. 이 함수가 호출하는 경우 있음을 의미 strtok , 그것을 사용하는 동안은 전화 기능 없음 strtok 사용할 수도 있습니다 strtok , 그리고 그것을 사용하여 자신을 어떤 함수가 호출 할 수 없습니다 strtok .

strtok 이 재진입 할 ​​수 없다는 사실로 인해 발생하는 문제를 보여주는 예는 다음과 같습니다.

char src[] = "1.2,3.5,4.2";
char *first = strtok(src, ","); 

do 
{
    char *part;
    /* Nested calls to strtok do not work as desired */
    printf("[%s]\n", first);
    part = strtok(first, ".");
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok(NULL, ".");
    }
} while ((first = strtok(NULL, ",")) != NULL);

산출:

[1.2]
 [1]
 [2]

예상되는 작업은 outer do while 루프가 각 10 진수 문자열 ( "1.2" , "3.5" , "4.2" )로 구성된 세 개의 토큰을 생성해야한다는 것입니다. 각 토큰은 내부 루프에 대한 strtok 호출이 별도의 ( "1" , "2" , "3" , "5" , "4" , "2" ).

그러나 strtok 은 재진입이 아니기 때문에 발생하지 않습니다. 대신 첫 번째 strtok 이 "1.2 \ 0"토큰을 올바르게 만들고 내부 루프가 토큰 "1""2" 올바르게 만듭니다. 그러나 외부 루프의 strtok 은 내부 루프가 사용하는 문자열의 끝에 있으며 즉시 NULL을 반환합니다. src 배열의 두 번째 및 세 번째 하위 문자열은 전혀 분석되지 않습니다.

C11

표준 C 라이브러리는 thread-safe 또는 re-entrant 버전을 포함하지 않지만 POSIX의 strtok_r 과 같은 일부 버전은 포함합니다. MSVC에서 strtok 과 동등한 strtok_s 는 스레드로부터 안전합니다.

C11

C11에는 strtok_s 라는 스레드 안전성과 재진입 성 버전을 제공하는 선택적 부분 인 Annex K가 있습니다. __STDC_LIB_EXT1__ 을 사용하여 기능을 테스트 할 수 있습니다. 이 선택적 부분은 널리 지원되지 않습니다.

strtok_s 함수는 POSIX strtok_r 함수와 달리 토큰 화 된 문자열 외부에 저장하는 것을 방지하고 런타임 제약 조건을 검사합니다. 하지만 제대로 작성된 프로그램에서는 strtok_sstrtok_r 이 똑같이 작동합니다.

예제와 함께 strtok_s 를 사용하면 strtok_s 과 같이 정확한 응답을 얻을 수 있습니다.

/* you have to announce that you want to use Annex K */ 
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>

#ifndef __STDC_LIB_EXT1__
# error "we need strtok_s from Annex K"
#endif

char src[] = "1.2,3.5,4.2";  
char *next = NULL;
char *first = strtok_s(src, ",", &next);

do 
{
    char *part;
    char *posn;

    printf("[%s]\n", first);
    part = strtok_s(first, ".", &posn);
    while (part != NULL)
    {
        printf(" [%s]\n", part);
        part = strtok_s(NULL, ".", &posn);
    }
} 
while ((first = strtok_s(NULL, ",", &next)) != NULL);

출력은 다음과 같습니다.

[1.2]
 [1]
 [2]
[3.5]
 [3]
 [5]
[4.2]
 [4]
 [2]

특정 문자의 처음 / 마지막 항목 찾기 : strchr (), strrchr ()

strchrstrrchr 함수는 문자열에서 NUL 종료 문자 배열에있는 문자를 찾습니다. strchr 은 첫 번째 항목에 대한 포인터를 반환하고 strrchr 은 마지막 항목에 대한 포인터를 반환합니다.

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

int main(void)
{
    char toSearchFor = 'A';

    /* Exit if no second argument is found. */
    if (argc != 2)
    {
        printf("Argument missing.\n");
        return EXIT_FAILURE;
    }

    {
        char *firstOcc = strchr(argv[1], toSearchFor);
        if (firstOcc != NULL) 
        {
            printf("First position of %c in %s is %td.\n", 
              toSearchFor, argv[1], firstOcc-argv[1]); /* A pointer difference's result 
                                     is a signed integer and uses the length modifier 't'. */
        }
        else
        {
            printf("%c is not in %s.\n", toSearchFor, argv[1]);
        }
    }

    {
        char *lastOcc = strrchr(argv[1], toSearchFor);
        if (lastOcc != NULL)
        {
            printf("Last position of %c in %s is %td.\n",
              toSearchFor, argv[1], lastOcc-argv[1]);
        }
    }

    return EXIT_SUCCESS;
}

출력 ( pos 라는 실행 파일을 생성 한 후) :

$ ./pos AAAAAAA
First position of A in AAAAAAA is 0.
Last position of A in AAAAAAA is 6.
$ ./pos BAbbbbbAccccAAAAzzz
First position of A in BAbbbbbAccccAAAAzzz is 1.
Last position of A in BAbbbbbAccccAAAAzzz is 15.
$  ./pos qwerty             
A is not in qwerty.

strrchr 한 가지 일반적인 용도는 경로에서 파일 이름을 추출하는 것입니다. 예를 들어 C:\Users\eak\myfile.txt 에서 myfile.txt 를 추출하려면 다음과 같이하십시오.

char *getFileName(const char *path)
{
    char *pend;

    if ((pend = strrchr(path, '\')) != NULL)
        return pend + 1;

    return NULL;
}

문자열에서 문자 반복하기

문자열의 길이를 알면 for 루프를 사용하여 문자를 반복 할 수 있습니다.

char * string = "hello world"; /* This 11 chars long, excluding the 0-terminator. */
size_t i = 0;
for (; i < 11; i++) {
    printf("%c\n", string[i]);    /* Print each character of the string. */
}

또는 문자열이 무엇인지 모르는 경우 표준 함수 인 strlen() 을 사용하여 문자열의 길이를 구할 수 있습니다.

size_t length = strlen(string);
size_t i = 0; 
for (; i < length; i++) {
    printf("%c\n", string[i]);    /* Print each character of the string. */
}

마지막으로, C의 문자열이 null로 종료된다는 사실을 활용할 수 있습니다 (앞의 예에서 strlen() 에 전달할 때 이미 strlen() .-)). 크기에 관계없이 배열을 반복 할 수 있고 널 문자에 도달하면 반복을 중지 할 수 있습니다.

size_t i = 0;
while (string[i] != '\0') {       /* Stop looping when we reach the null-character. */
    printf("%c\n", string[i]);    /* Print each character of the string. */
    i++;
}

문자열에 대한 기본 소개

C에서 문자열 은 널 문자 ( '\ 0')로 끝나는 일련의 문자입니다.

문자열 리터럴을 사용하여 문자열을 만들 수 있습니다. 문자열 리터럴 은 큰 따옴표로 묶인 문자 시퀀스입니다. 예를 들어, "hello world" 문자열 리터럴을 가져옵니다. 문자열 리터럴은 자동으로 null로 종료됩니다.

여러 가지 방법을 사용하여 문자열을 만들 수 있습니다. 예를 들어, char * 선언하고 문자열의 첫 번째 문자를 가리 키도록 초기화 할 수 있습니다.

char * string = "hello world";

char * 를 위와 같이 문자열 상수로 초기화 할 때 문자열 자체는 일반적으로 읽기 전용 데이터로 할당됩니다. string 은 배열의 첫 번째 요소에 대한 포인터이며 'h' 문자입니다.

문자열 리터럴은 읽기 전용 메모리에 할당되므로 수정할 수 없습니다 1 . 그것을 수정하려고 시도하면 정의되지 않은 동작 이 발생하므로 const 를 추가하여 다음과 같은 컴파일 타임 오류를 얻는 것이 좋습니다

char const * string = "hello world";

유사한 효과 2를가 집니다.

char const string_arr[] = "hello world";

수정 가능한 문자열을 만들려면 문자 배열을 선언하고 다음과 같이 문자열 리터럴을 사용하여 내용을 초기화 할 수 있습니다.

char modifiable_string[] = "hello world";

이것은 다음과 같습니다.

char modifiable_string[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0'};

두 번째 버전에서는 중괄호로 묶인 이니셜 라이저를 사용하므로 문자 배열에 '\0' 문자가 명시 적으로 포함되어 있지 않으면 문자열이 자동으로 null로 끝나지 않습니다.


1 수정할 수없는 것은 문자열 리터럴의 문자를 수정할 수 없다는 것을 의미하지만 포인터 string 을 수정할 수 있음을 기억하십시오 (다른 곳을 가리킬 수 있거나 증가 또는 감소 할 수 있음).

2 두 문자열 모두 비슷한 의미를 가지므로 두 문자열의 문자를 수정할 수 없습니다. stringchar 대한 포인터이며 수정 가능한 l 값 이므로 배열 string_arr 이 수정 불가능한 l 값인 동안 증가하거나 다른 위치를 가리킬 수 있으므로 수정할 수는 없습니다.

문자열 배열 만들기

문자열의 배열은 몇 가지를 의미 할 수 있습니다.

  1. 요소가 char * s 인 배열
  2. 요소가 char 의 배열 인 배열

다음과 같이 문자 포인터 배열을 만들 수 있습니다.

char * string_array[] = {
    "foo",
    "bar",
    "baz"
};

기억하십시오 : 문자열 리터럴을 char * 에 할당하면 문자열 자체가 읽기 전용 메모리에 할당됩니다. 그러나 배열 string_array 는 읽기 / 쓰기 메모리에 할당됩니다. 즉, 배열의 포인터를 수정할 수는 있지만 포인터가 가리키는 문자열은 수정할 수 없습니다.

C에서 main argv (프로그램이 실행될 때 전달 된 명령 행 인수의 배열)에 대한 매개 변수는 char * : char * argv[] 의 배열입니다.

문자 배열 배열을 만들 수도 있습니다. 문자열은 문자 배열이므로 문자열 배열은 요소가 문자 배열 인 배열입니다.

char modifiable_string_array_literals[][4] = {
    "foo",
    "bar",
    "baz"
};

이것은 다음과 같습니다.

char modifiable_string_array[][4] = {
    {'f', 'o', 'o', '\0'},
    {'b', 'a', 'r', '\0'},
    {'b', 'a', 'z', '\0'}
};

배열의 두 번째 차원 크기로 4 를 지정합니다. null 배열 캐릭터를 포함 할 필요가 있기 때문에 우리 배열의 각 문자열은 실제로 4 바이트입니다.

strstr

/* finds the next instance of needle in haystack 
   zbpos: the zero-based position to begin searching from
   haystack: the string to search in
   needle: the string that must be found
   returns the next match of `needle` in `haystack`, or -1 if not found
*/
int findnext(int zbpos, const char *haystack, const char *needle)
{
    char *p; 

    if (((p = strstr(haystack + zbpos, needle)) != NULL)
        return p - haystack;

    return -1;
}

strstrhaystack (첫 번째) 인수에서 needle 가리키는 문자열을 검색합니다. 발견되면 strstr 은 발생 주소를 반환합니다. needle 찾을 수 없으면 NULL을 반환합니다. 우리는 zbpos 사용하여 같은 바늘을 반복해서 찾아 내지 못합니다. 첫 번째 인스턴스를 건너 뛰기 위해 zbpos 의 오프셋을 추가합니다. 다음 찾기 대화 상자를 구현하기 위해 메모장 클론이 findnext 이와 같이 호출 할 수 있습니다.

/*
    Called when the user clicks "Find Next"
    doc: The text of the document to search
    findwhat: The string to find
*/
void onfindnext(const char *doc, const char *findwhat)
{
    static int i;

    if ((i = findnext(i, doc, findwhat)) != -1)
        /* select the text starting from i and ending at i + strlen(findwhat) */
    else
        /* display a message box saying "end of search" */
}

문자열 리터럴

문자열 리터럴은 null로 끝나는 정적 지속 기간char 배열을 나타냅니다. 저장 기간이 정적이므로 문자열 리터럴 또는 동일한 기본 배열에 대한 포인터는 자동 배열에 대한 포인터로는 사용할 수없는 여러 가지 방법으로 안전하게 사용할 수 있습니다. 예를 들어 함수에서 문자열 리터럴을 반환하면 잘 정의 된 동작이 나타납니다.

const char *get_hello() {
    return "Hello, World!";  /* safe */
}

역사적인 이유로 인해 문자열 리터럴에 해당하는 배열의 요소는 공식적으로 const 가 아닙니다. 그럼에도 불구하고 수정하려는 시도는 정의되지 않은 동작 입니다. 일반적으로 문자열 리터럴에 해당하는 배열을 수정하려고하는 프로그램이 충돌하거나 그렇지 않으면 오작동합니다.

char *foo = "hello";
foo[0] = 'y';  /* Undefined behavior - BAD! */

포인터가 문자열 리터럴을 가리킬 때 - 또는 때로는 할 수있는 곳 - 실수로 그러한 정의되지 않은 동작을 피하기 위해 포인터의 지시 대상 const 를 선언하는 것이 좋습니다.

const char *foo = "hello";
/* GOOD: can't modify the string pointed to by foo */

반면에 문자열 리터럴의 기본 배열을 가리키는 포인터는 본질적으로 특수하지 않습니다. 그 값은 다른 것을 가리 키도록 자유롭게 수정할 수 있습니다 :

char *foo = "hello";
foo = "World!"; /* OK - we're just changing what foo points to */

또한 char 배열의 초기화 프로그램은 문자열 리터럴과 동일한 형식을 가질 수 있지만 이러한 초기화 프로그램을 사용하면 초기화 된 배열에 문자열 리터럴의 특성을 부여하지 않습니다. 이니셜 라이저는 단순히 배열의 길이와 초기 내용을 지정합니다. 특히 명시 적으로 선언하지 않으면 요소를 수정할 수 있습니다. const :

char foo[] = "hello";
foo[0] = 'y';  /* OK! */

문자열을 0으로 만드는 것

memset 을 호출하여 문자열 (또는 다른 메모리 블록)을 0으로 만들 수 있습니다.

여기서 str 은 0이되는 문자열이고 n 은 문자열의 바이트 수입니다.

#include <stdlib.h> /* For EXIT_SUCCESS */
#include <stdio.h>
#include <string.h>


int main(void)
{
  char str[42] = "fortytwo";
  size_t n = sizeof str; /* Take the size not the length. */

  printf("'%s'\n", str);

  memset(str, '\0', n);

  printf("'%s'\n", str);

  return EXIT_SUCCESS;
}

인쇄물:

'fortytwo'
''

다른 예시:

#include <stdlib.h> /* For EXIT_SUCCESS */
#include <stdio.h>
#include <string.h>


#define FORTY_STR "forty"
#define TWO_STR "two"

int main(void)
{
  char str[42] = FORTY_STR TWO_STR;
  size_t n = sizeof str; /* Take the size not the length. */
  char * point_to_two = strstr(str, TWO_STR);

  printf("'%s'\n", str);

  memset(point_to_two, '\0', n);

  printf("'%s'\n", str);

  memset(str, '\0', n);

  printf("'%s'\n", str);

  return EXIT_SUCCESS;
}

인쇄물:

'fortytwo'
'forty'
''

strspn 및 strcspn

문자열이 주어진 경우 strspn 은 특정 문자 목록만으로 구성된 초기 하위 문자열 (길이)의 길이를 계산합니다. strcspn 은 나열된 문자열을 제외한 모든 문자로 구성된 초기 하위 문자열의 길이를 계산한다는 점을 제외 strcspn 비슷합니다.

/*
  Provided a string of "tokens" delimited by "separators", print the tokens along
  with the token separators that get skipped.
*/
#include <stdio.h>
#include <string.h>

int main(void)
{
    const char sepchars[] = ",.;!?";
    char foo[] = ";ball call,.fall gall hall!?.,";
    char *s;
    int n;

    for (s = foo; *s != 0; /*empty*/) {
        /* Get the number of token separator characters. */
        n = (int)strspn(s, sepchars);

        if (n > 0)
            printf("skipping separators: << %.*s >> (length=%d)\n", n, s, n);

        /* Actually skip the separators now. */
        s += n;

        /* Get the number of token (non-separator) characters. */
        n = (int)strcspn(s, sepchars);

        if (n > 0)
            printf("token found: << %.*s >> (length=%d)\n", n, s, n);

        /* Skip the token now. */
        s += n;
    }

    printf("== token list exhausted ==\n");

    return 0;
}

넓은 문자열을 사용하는 유사한 함수는 wcsspnwcscspn . 그들은 같은 방식으로 사용됩니다.

문자열 복사

포인터 할당은 문자열을 복사하지 않습니다.

= 연산자를 사용하여 정수를 복사 할 수 있지만 = 연산자를 사용하여 C에서 문자열을 복사 할 수는 없습니다. C의 문자열은 null 문자로 끝나는 문자 배열로 표시되므로 = 연산자를 사용하면 주소 만 저장됩니다 ( 포인터).

#include <stdio.h>

int main(void) {
    int a = 10, b;
    char c[] = "abc", *d;

    b = a; /* Integer is copied */
    a = 20; /* Modifying a leaves b unchanged - b is a 'deep copy' of a */
    printf("%d %d\n", a, b); /* "20 10" will be printed */

    d = c; 
    /* Only copies the address of the string - 
    there is still only one string stored in memory */
    
    c[1] = 'x';
    /* Modifies the original string - d[1] = 'x' will do exactly the same thing */

    printf("%s %s\n", c, d); /* "axc axc" will be printed */

    return 0;
}

우리가 사용하기 때문에 위의 예는 컴파일 char *d 보다 char d[3] . 후자를 사용하면 컴파일러 오류가 발생합니다. C에서 배열에 할당 할 수 없습니다.

#include <stdio.h>

int main(void) {
    char a[] = "abc";
    char b[8];

    b = a; /* compile error */
    printf("%s\n", b);

    return 0;
}

표준 함수를 사용하여 문자열 복사하기

strcpy()

문자열을 실제로 복사하기 위해서, strcpy() 함수는 string.h 에서 사용 가능하다. 복사하기 전에 목적지에 충분한 공간을 할당해야합니다.

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

int main(void) {
    char a[] = "abc";
    char b[8];

    strcpy(b, a); /* think "b special equals a" */
    printf("%s\n", b); /* "abc" will be printed */

    return 0;
}
C99

snprintf()

버퍼 오버런을 피하기 위해 snprintf() 사용할 수 있습니다. 템플릿 문자열을 구문 분석해야하기 때문에 성능상 가장 좋은 솔루션은 아니지만 표준 라이브러리에서 즉시 사용할 수있는 문자열을 복사하기위한 유일한 버퍼 제한 안전 함수이며 추가 단계없이 사용할 수 있습니다.

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

int main(void) {
    char a[] = "012345678901234567890";
    char b[8];

#if 0
    strcpy(b, a); /* causes buffer overrun (undefined behavior), so do not execute this here! */
#endif

    snprintf(b, sizeof(b), "%s", a); /* does not cause buffer overrun */
    printf("%s\n", b); /* "0123456" will be printed */

    return 0;
}

strncat()

strncat() ( strcat() 의 버퍼 오버플로 검사 버전 strncat() 을 사용하는 것이 더 좋은 성능을 가진 두 번째 옵션입니다. 복사 할 최대 바이트 수를 알려주는 세 번째 인수가 필요합니다.

char dest[32];

dest[0] = '\0';
strncat(dest, source, sizeof(dest) - 1);
    /* copies up to the first (sizeof(dest) - 1) elements of source into dest,
    then puts a \0 on the end of dest */

이 공식은 sizeof(dest) - 1 ; 이것은 strncat() 항상 null 바이트 (양호)를 추가하기 때문에 중요하지만 문자열의 크기 (혼동 및 버퍼 덮어 쓰기의 원인)에는 포함되지 않습니다.

또한 빈 문자열이 아닌 문자열을 연결하는 대안은 훨씬 더 복잡합니다. 중히 여기다:

char dst[24] = "Clownfish: ";
char src[] = "Marvin and Nemo";
size_t len = strlen(dst);

strncat(dst, src, sizeof(dst) - len - 1);
printf("%zu: [%s]\n", strlen(dst), dst);

출력은 다음과 같습니다.

23: [Clownfish: Marvin and N]

길이로 지정된 크기 대상 배열의 크기이지만 단말 널 바이트를 카운트하지 안에 남은 공간의 양이 아니라고하지만, 참고. 이로 인해 큰 덮어 쓰기 문제가 발생할 수 있습니다. 또한 약간 낭비입니다. 길이 인수를 올바르게 지정하려면 대상에있는 데이터의 길이를 알고 있으므로 strncat() 않도록 저장하면서 기존 내용의 끝에 null 바이트의 주소를 지정할 수 있습니다.

    strcpy(dst, "Clownfish: ");
    assert(len < sizeof(dst) - 1);
    strncat(dst + len, src, sizeof(dst) - len - 1);
    printf("%zu: [%s]\n", strlen(dst), dst);

이것은 이전과 같은 출력을 생성하지만 strncat() 은 복사를 시작하기 전에 dst 의 기존 내용을 스 캔할 필요가 없습니다.

strncpy()

마지막 옵션은 strncpy() 함수입니다. 당신이 먼저 와야한다고 생각할 지 모르지만, 그것은 두 가지 주요 문제가있는 오히려기만적인 기능입니다 :

  1. strncpy() 를 통한 복사가 버퍼 한계에 도달하면 종료 null 문자는 기록되지 않습니다.
  2. strncpy() 항상 대상을 완전히 채 웁니다. 필요한 경우 null 바이트로 채 웁니다.

(그러한 기발한 구현은 역사적이며 처음에는 UNIX 파일 이름을 처리하기위한 것이 었습니다 )

이를 사용하는 유일한 올바른 방법은 수동으로 널 종료를 보장하는 것입니다.

strncpy(b, a, sizeof(b)); /* the third parameter is destination buffer size */
b[sizeof(b)/sizeof(*b) - 1] = '\0'; /* terminate the string */
printf("%s\n", b); /* "0123456" will be printed */

그렇더라도 커다란 버퍼를 가지고 있다면, 널 패딩을 추가하기 때문에 strncpy() 를 사용하는 것이 매우 비효율적이게됩니다.

문자열을 숫자로 변환 : atoi (), atof () (위험한, 사용하지 마십시오)

경고 : atoi , atol , atollatof 함수는 본질적으로 안전하지 않습니다. 결과의 값을 나타낼 수없는 경우 동작은 정의되지 않습니다. (7.20.1p1)

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

int main(int argc, char** argv)
{
    int val;
    if (argc < 2)
    {
        printf("Usage: %s <integer>\n", argv[0]);
        return 0;
    }

    val = atoi(argv[1]);

    printf("String value = %s, Int value = %d\n", argv[1], val);

    return 0;
}

변환 할 문자열이 범위 내에있는 유효한 10 진 정수이면 함수가 작동합니다.

$ ./atoi 100
String value = 100, Int value = 100
$ ./atoi 200
String value = 200, Int value = 200

숫자로 시작하고 뒤에 다른 문자가 오는 문자열의 경우 초기 숫자 만 구문 분석됩니다.

$ ./atoi 0x200
0
$ ./atoi 0123x300
123

다른 모든 경우에는 동작이 정의되지 않습니다.

$ ./atoi hello
Formatting the hard disk...

위의 모호함과 정의되지 않은 동작으로 인해 atoi 계열의 함수를 사용해서는 안됩니다.

  • long int 로 변환하려면 atol() 대신 strtol() 사용하십시오.
  • double 로 변환하려면 atof() 대신 strtod() 사용하십시오.
C99
  • long long int 로 변환하려면 atoll() 대신 strtoll() 사용하십시오.

문자열 형식의 데이터 읽기 / 쓰기

형식이 지정된 데이터를 문자열에 씁니다.

int sprintf ( char * str, const char * format, ... );

sprintf 함수를 사용하여 float 데이터를 문자열에 씁니다.

#include <stdio.h>
int main ()
{
  char buffer [50];
  double PI = 3.1415926;
  sprintf (buffer, "PI = %.7f", PI);
  printf ("%s\n",buffer);
  return 0;
}

문자열에서 형식이 지정된 데이터를 읽습니다.

int sscanf ( const char * s, const char * format, ...);

sscanf 함수를 사용하여 형식이 지정된 데이터를 구문 분석하십시오.

#include <stdio.h>
int main ()
{
  char sentence []="date : 06-06-2012";
  char str [50];
  int year;
  int month;
  int day;
  sscanf (sentence,"%s : %2d-%2d-%4d", str, &day, &month, &year);
  printf ("%s -> %02d-%02d-%4d\n",str, day, month, year);
  return 0;
}

안전하게 문자열을 Number로 변환 : strtoX 함수

C99

C99 이후 C 라이브러리에는 문자열을 숫자로 해석하는 안전 변환 함수 집합이 있습니다. 이들의 이름은 strtoX 형식 strtoX , 여기서 X 는 변환의 대상 유형을 결정하기 위해 l , ul , d 등 중 하나입니다.

double strtod(char const* p, char** endptr);
long double strtold(char const* p, char** endptr);

그들은 변환이 과다 또는 언더 플로우가 있는지 확인합니다.

double ret = strtod(argv[1], 0); /* attempt conversion */

/* check the conversion result. */
if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE) 
    return;  /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE) 
    return; /* numeric underflow in in string */

/* At this point we know that everything went fine so ret may be used */

실제로 문자열에 숫자가 전혀없는 경우 strtod 의이 사용법은 0.0 반환합니다.

만족스럽지 않으면 추가 매개 변수 endptr 사용할 수 있습니다. 문자열에서 발견 된 숫자의 끝을 가리키는 포인터에 대한 포인터입니다. 위와 같이 0 또는 NULL 로 설정하면 무시됩니다.

endptr 매개 변수는 성공적인 변환이 있었는지 여부를 나타내며, 그렇다면 숫자가 끝난 위치를 나타냅니다.

char *check = 0;
double ret = strtod(argv[1], &check); /* attempt conversion */

/* check the conversion result. */
if (argv[1] == check) 
    return; /* No number was detected in string */
else if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE) 
    return; /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE) 
    return; /* numeric underflow in in string */

/* At this point we know that everything went fine so ret may be used */

더 넓은 정수 타입으로 변환하는 유사한 함수가 있습니다 :

long strtol(char const* p, char** endptr, int nbase);
long long strtoll(char const* p, char** endptr, int nbase);
unsigned long strtoul(char const* p, char** endptr, int nbase);
unsigned long long strtoull(char const* p, char** endptr, int nbase);

이 함수에는 번호가 쓰여지는 번호를 nbase 하는 세 번째 매개 변수 인 nbase 가 있습니다.

long a = strtol("101",   0, 2 ); /* a = 5L */
long b = strtol("101",   0, 8 ); /* b = 65L */
long c = strtol("101",   0, 10); /* c = 101L */
long d = strtol("101",   0, 16); /* d = 257L */
long e = strtol("101",   0, 0 ); /* e = 101L */
long f = strtol("0101",  0, 0 ); /* f = 65L */
long g = strtol("0x101", 0, 0 ); /* g = 257L */

nbase 대한 특수 값 0 은 문자열이 C 프로그램에서 숫자 리터럴이 해석되는 것과 같은 방식으로 해석된다는 것을 의미합니다. 접두사 0x 는 16 진수 표현에 해당하고, 그렇지 않으면 선행 0 은 8 진수이며 다른 모든 숫자는 10 진수로 표시됩니다.

따라서 명령 줄 인수를 숫자로 해석하는 가장 실용적인 방법은

int main(int argc, char* argv[] {
    if (argc < 1)
        return EXIT_FAILURE; /* No number given. */

    /* use strtoull because size_t may be wide */
    size_t mySize = strtoull(argv[1], 0, 0);

    /* then check conversion results. */

     ...

    return EXIT_SUCCESS;
}

즉, 프로그램을 8 진수, 10 진수 또는 16 진수로 매개 변수와 함께 호출 할 수 있습니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow