C (6) - 문자와 문자열

문자

 

프로그램에서 문자를 사용할 때는 항상 양쪽에 작은따옴표를 붙입니다. 컴파일러는 a를 변수명으로 해석하는 반면, 'a'의 경우 문자 상수로 해석합니다. 그러나 이런 문자 표현법은 컴파일 후에는 약속된 정수 값인 아스키 코드 값으로 바뀌기 때문에 더 이상 의미가 없습니다.

 

예를 들어 문자 상수 'a'는 정수 값 97로 바뀝니다. 결국 문자는 메모리에 저장되는 방식이 정수와 같습니다. 따라서 int형 변수에 저장하고 정수처럼 연산할 수 있습니다. 즉, 문자 'a'를 %d로 출력하면 97을 출력하고, 반대로 정수 97을 %c로 출력하면 아스키 코드 값이 97인 문자를 출력합니다. 결론적으로 문자 상수 'a'와 정수 97은 같은 데이터며, 변환 문자열의 종류에 따라 출력 형태가 결정됩니다.

 

아스키 코드란?

아스키 코드는 128개의 문자를 0~127의 숫자 중에서 각각 어떤 값으로 표현할지 정의한 것으로 다음과 같은 특징을 갖습니다.

  • 알파벳과 숫자는 각각 연속된 아스키 코드 값을 갖는다.
  • 소문자가 대문자보다 아스키 코드 값이 크다.
  • 제어 문자는 백슬래시와 함께 표시하며 출력될 때 그 기능을 수행한다.

숫자(0~9)는 48~57, 대문자(A~Z)는 65~90, 소문자(a~z)는 97~122를 아스키 코드 값으로 가집니다.

 

 

문자 상수는 int 자료형과 동일하게 4바이트의 크기를 갖지만 아스키 코드 값이 0~127의 범위에 있으므로 2진수로 바꾸면 왼쪽 3바이트는 모두 0이 되고 오른쪽 1바이트만 의미를 갖습니다. 따라서 문자는 1바이트 크기의 char형 변수에 저장해 사용할 수 있습니다.

 

scanf 함수

 

scanf 함수로 문자를 입력할 때는 %c 변환 문자를 사용합니다. %c는 알파벳이나 숫자 모양의 문자등 형태가 있는 문자를 입력하지만 공백(space)이나 탭(tabl), 개행 문자(enter)와 같은 제어 문자도 입력하므로 주의해야 합니다.

#include <stdio.h>

int main(void)
{
    char ch1, ch2;

    scanf("%c%c", &ch1, &ch2);

    printf("[%c%c]", ch1, ch2);

    return 0;		
}

위와 같은 코드를 실행하고 다음 3가지 입력값에 대한 결과를 살펴보겠습니다.

  1. a, b, enter
  2. a, space, enter
  3. a, enter

1번

2번

3번

 

그런데 만약 위 코드에서 %c를 %d로 수정하고 실행해본다면 다른 결과가 나오게 됩니다. space는 숫자가 될 수 없기 때문에 공백은 모두 무시됩니다.

 

Space Bar, Tab, Enter 를 눌렀을 때 입력되는 문자를 묶어 화이트 스페이스(white space)라고 부릅니다. 화이트 스페이스는 %d, %lf, %s와 같은 변환 문자로 숫자나 문자열을 입력할 때는 데이터를 구분하는 용도로 쓰이며, 그 자체가 데이터로 입력되지는 않습니다.

 

getchar, putchar 함수

 

scanf 함수는 문자뿐 아니라 숫자도 입력하는 기능이 포함되어 있으므로 문자만 입력하는 함수에 비해 크기가 큽니다. printf 함수도 마찬가지입니다. 따라서 문자만 입출력하는 경우 문자 전용 함수를 쓰는 것이 효율적입니다.

int getchar(void);
int puchar(int);
#include <stdio.h>

int main(void) {
    int ch1, ch2;

    ch1 = getchar();
    ch2 = getchar(); 

    printf("[%c%c]", ch1, ch2);

    return 0;
};

위 코드를 실행한 뒤 scanf와 동일한 방식으로 입력하면, 같은 결과를 얻을 수 있습니다.

 

getchar 함수는 매개변수가 없으므로 괄호만 사용해 호출합니다. 호출된 함수는 키보드로 입력한 문자의 아스키 코드 값을 반환하므로 반환값은 int형 변수로 받습니다. 반환값은 필요에 따라 char형 변수나 배열에 옮겨 문자나 문자열로 사용합니다.

getchar 함수는 아스키 코드 값을 반환하지만, Ctrl + Z 와 같은 EOF를 나타내는 -1을 처리하기 위해 int 자료형을 사용합니다.

 

putchar 함수는 문자 상수나 문자의 아스키 코드 값을 인수로 주면 해당 문자를 화면에 출력합니다. 그리고 출력한 문자를 다시 반환하며, 출력 과정에서 에러가 발생하면 -1을 반환합니다.

 

입력 함수와 버퍼

 

버퍼는 프로그램에서 직접 할당하는 것이 아니라, 프로그램을 실행하는 중에 운영체제가 자동으로 할당하는 메모리의 저장 공간입니다. 키보드로 입력하는 데이터는 일단 버퍼에 저장된 후 scanf 함수에 의해 변수에 입력됩니다.

 

버퍼는 데이터를 보관하는 역할을 하므로 최초 입력할 때 필요한 데이터를 한꺼번에 저장해 놓으면 scanf 함수는 호출 즉시 버퍼에서 데이터를 가져올 수 있습니다.

 

입출력 함수가 버퍼를 사용하면 두 가지 좋은 점이 있습니다.

  1. 데이터를 안정적으로 입력받을 수 있습니다.
  2. 입력장치와 독립적으로 사용할 수 있습니다.

 

1. scanf 함수

#include <stdio.h>

int main(void)
{
    char ch;
    int i;

    for (i = 0; i < 3; i++)
    {
        scanf("%c", &ch);
        printf("%c", ch);
    }

    return 0;
}

위 예제는 문자를 입력하고 화면에 출력하는 과정을 세 번 반복합니다.

그러나 tiger를 입력하면 실행결과는 한 번의 키보드 입력으로 끝납니다. 최초 scanf 함수가 호출될 때 키보드로 문자열을 입력하면 일단 버퍼에 저장한 후 첫 번째 문자만 변수에 저장합니다. 두 번째 scanf 함수 호출부터는 버퍼에 남아 있는 문자열에서 차례로 다음 문자를 가져오므로 결국 새로운 키보드 입력이 필요 없습니다. 물론 scanf 함수가 버퍼에 저장된 데이터를 모두 가져온다면 키보드에서 추가로 데이터를 입력해야 합니다.

 

데이터의 입력 방식과 관련해 기억해야 할 내용이 있습니다. 입력 데이터는 Enter를 누르는 순간 버퍼에 저장되며 개행 문자도 함께 저장됩니다. 따라서 개행 문자 또한 하나의 입력 데이터로 사용이 가능합니다. 이 경우에는 입력을 종료하는 별도의 신호가 필요한데, 이때 scanf의 반환값을 사용합니다.

 

scanf 함수는 키보드로 Ctrl+Z(리눅스 시스템에서는 Ctrl+D)를 누르면 -1을 반환합니다. 이를 활용해서 scanf 함수가 -1을 반환하기 전까지 반복 입력을 하면 개행 문자를 포함한 모든 문자를 데이터로 사용할 수 있습니다.

#incldue <stdio.h>

int main(void)
{
    int res;
    char ch;

    while (1)
    {
        res = scanf("%c", &ch);
        if (res == -1) break;
        printf("%d ", ch);
    }

    return 0;
}

scanf 는 기본적으로 입력한 값의 개수를 반환합니다. 즉, 10번 행에서 문자를 제대로 입력한 경우 1을 반환합니다. 

 

실행결과의 입력과 출력은 Enter를 기준으로 반복됩니다. 입력한 데이터는 Enter를 누르는 순간 버퍼로 저장되고 반복문이 수행되면서 버퍼의 문자를 하나씩 가져다 아스키 코드 값을 출력합니다. 그리고 버퍼의 데이터를 모두 처리하면 다시 키보드로부터 새로운 데이터를 입력받기 위해 대기 상태가 됩니다. 마지막으로 Crtl+Z를 누르면 scanf 함수는 -1을 반환하고 if문의 조건식이 참이 되어 반복을 종료합니다.

그리고 키보드로 숫자를 입력하는 경우에도 일단 문자열의 형태로 버퍼에 저장됩니다. 그 후에 문자열이 실제 연산이 가능한 값으로 변환되어 변수에 저장됩니다. 예를 들어 20을 입력하는 경우 문자 '2' 와 '0'이 각각 아스키 코드 값으로 코드화되어 버퍼에 저장되고, 그 후에 변환 문자에 의해 연산이 가능한 숫자로 변환됩니다. 결국 %d 변환 문자는 코드화된 문자열을 숫자로 변환하는 방법을 scanf 함수에 알려주는 역할을 합니다.

scanf 함수의 반환값과 비교할 때 -1 대신에 EOF를 쓸 수 있습니다. 1번째 행의 stdio.h 헤더 파일에는 소스 코드에 있는 EOF라는 이름을 -1로 바꾸는 전처리 지시자가 있습니다. 따라서 (res == -1) 대신에 (res == EOF) 라고 작성하는 것도 가능합니다.

 

2. getchar 함수

getchar 함수도 버퍼를 사용하는 문자 입력 함수입니다. getchar 함수를 반복 사용하면 한 줄의 문자열을 char 배열에 입력할 수 있습니다. 

#include <stdio.h>

void my_gets(char *str, int size);

int main(void)
{
    char str[7];

    my_gets(str, sizeof(str));
    printf("입력한 문자열 : %s\n", str);

    return 0;
}

void my_gets(char *str, int size)
{
    int ch;
    int i = 0;

    ch = getchar();
    while ((ch != '\n') && (i < size -1))
    {
        str[i] = ch;
        i++;
        ch = getchar();
    }
    str[i] = "\0";
}

위 예제에서 getchar 함수를 사용해 키보드로 입력한 한 줄의 문자열을 char 배열로 저장합니다. 또한 입력된 문자열 끝에는 널 문자를 저장하도록 하였습니다.

getchar 함수는 scanf 함수와 달리 변환 지정자를 사용하지 않기 때문에, 모든 입력을 문자 단위로만 처리합니다. 따라서 20을 입력하면, 정수 20이 아닌 문자 '2'와 '0'으로 인식됩니다.

 

동작 원리 정리

앞서 살펴본 scanf 와 getchar 함수의 동작 원리를 정리하면 다음과 같습니다.

  1. 버퍼(표준 입력 버퍼: stdin)에 문자가 남아 있으면 즉시 읽어서 반환합니다.
  2. 버퍼가 비어 있다면 사용자가 입력을 완료(Enter)할 때 까지 기다립니다.

 

입력 버퍼 지우기

 

scanf와 getchar 함수는 같은 버퍼를 사용하며 입력 데이터를 공유합니다. 따라서 앞서 실행한 입력 함수가 버퍼에 남겨 둔 데이터를 그 이후에 수행되는 함수가 잘못 가져갈 가능성이 있습니다. 따라서 버퍼에 남아 있는 불필요한 데이터는 미리 제거하는 것이 좋습니다.

#include <stdio.h>

int main(void)
{
    int num, grade;

    printf("학번 입력 : ");
    scanf("%d", &num);
    getchar();

    printf("학점 입력 : ");
    grade = getchar();

    printf("학번 : %d, 학점 : %c", num, grade);

    return 0;
}

8행에서 scanf 함수는 일단 버퍼로부터 입력을 시도하지만, 처음에는 버퍼가 비어 있으므로 키보드로부터 입력받기 위해 대기합니다. 여기서 20을 입력하고 Enter를 누르면 20과 개행 문자(\n)가 함께 버퍼에 저장됩니다. 그 후 문자 '2', '0' 은 정수로 변환되어 변수 num에 저장되고 버퍼에는 개행 문자만 남습니다. 이 버퍼에 남은 개행 문자가 이후의 입력에 영향을 줄 수 있으므로 9행과 같이 getchar 함수를 추가로 호출해 버퍼에 남아 있던 개행 문자를 제거해야 합니다.

 

만약 9행에서 버퍼의 개행 문자를 제거하지 않으면, 아래 그림처럼 버퍼에 있는 개행 문자를 12행에서 호출되는 getchar 함수가 가져가 변수 grade에 저장되게 됩니다.

문자 입출력 함수에는 fgetc와 fputc도 있습니다. 이들을 호출할 때 추가적인 인수가 필요한데 fgetc는 stdin을 주고, fputc는 stdout을 줍니다. 별도로 인수를 주는 것 외에는 getchar, putchar 함수와 기능은 같습니다.

 

 

문자열

 

문자열 상수

 

printf("%s", "apple");

지금까지는 위 수식을 만났을 때 자연스럽게 apple이 출력된다고 생각했습니다. 만약 "apple"이 각 문자 'a', 'p' 등을 가진 배열이라면 당연히 배열명을 사용할 것이고 실제로는 배열의 시작 위치를 가지고 출력하게 됩니다. 배열이 아닌 문자열을 직접 출력하는 경우도 마찬가지입니다. 컴파일 과정에서 문자열은 첫 번째 문자의 주소로 둔갑하고 그 이후에는 배열을 출력하는 것과 같은 과정을 거칩니다.

 

다음 예제를 통해 직접 확인해보겠습니다.

#include <stdio.h>

int main(void)
{
    printf("시작 주소1: %p\n", "apple");
    printf("시작 주소2: %p\n", "apple");
    printf("두 번째 문자 주소: %p\n", "apple" + 1);

    return 0;
}
시작 주소1: 0x1004604f8
시작 주소2: 0x1004604f8
두 번째 문자 주소: 0x1004604f9

 

문자열의 크기는 일정하지 않기 때문에 컴파일러는 문자열 상수를 독특한 방법으로 처리합니다. 컴파일 과정에서 문자열을 char 배열 형태로 따로 보관하고 문자열 상수가 있던 곳에는 배열의 위치 값을 사용합니다. 

 

이처럼 문자열 리터럴은 컴파일 과정에서 char* 타입의 주소로 변환되기 때문에, 포인터 연산이 가능합니다.
다음처럼 덧셈을 통해 다음 문자의 주소를 구할 수 있고, 간접 참조 연산이나 배열명처럼 사용하는 것도 가능합니다.

  • *"apple"
  • *("apple" + 1)
  • "apple"[2]

 

또한 위의 실행 결과에서 특이한 점을 하나 발견할 수 있습니다.

 

"apple"이라는 문자열을 변수에 저장하지 않았음에도 불구하고, 5행과 6행에서 출력된 주소값이 같은 이유는 C에서 문자열 리터럴이 컴파일 시 읽기 전용 데이터 영역에 저장되기 때문입니다. 동일한 문자열 리터럴은 중복 저장되지 않고, 하나의 메모리 공간을 공유하게 됩니다.

 

이러한 특성 때문에  *"apple" = 't';  와 같이 주소로 접근해서 문자열을 바꿔서는 안됩니다. 연산 자체는 문제가 없으므로 정상적으로 컴파일되지만, 읽기 전용 메모리의 값을 변경하는 명령을 제한하기 때문에 실행할 때 운영체제에 의해서 강제 종료될 가능성이 있습니다.

 

char 포인터

 

문자열은 곧 주소라고 했습니다. 따라서 문자열도 char 포인터에 대입해 사용할 수 있습니다. 문자열을 char 포인터에 대입하면 문자열에 이름을 붙여 사용할 수 있고 다른 문자열로 쉽게 바꿀 수 있습니다.

#include <stdio.h>

int main(void) 
{
    char *dessert = "apple";

    printf("과일1 : %s\n", dessert);
    dessert = "banana";
    printf("과일2 : %s\n", dessert);

    return 0;
}

위 코드에서 7행의 실행 방식을 살펴보겠습니다.

 

dessert가 가리키는 곳에는 'a' 가 저장되어 있습니다. dessert를 1씩 증가시키면서 다음 문자의 주소를 구한 후 간접 참조 연산을 수행해 널 문자가 나올 때까지 문자를 출력하면 결국 문자열 전체를 출력할 수 있습니다.

그렇다면 7행의 수식은 다음과 같이 표현할 수 있습니다.

while (*dessert != \0)
{
    putchar(*dessert);
    dessert++;
}

 

그리고 8행의 수식에서는 "apple" 문자열 리터럴을 수정하는 것이 아닌 "banana"를 새로운 char 배열에 넣고 포인터가 그 첫 번째 문자를 가리키도록 변경한 것입니다.

 

문자열 입력 함수

 

문자열 상수의 경우 값을 바꿀 수 없으므로 바꿀 수 있는 문자열을 원한다면 char 배열을 사용해야 합니다. scanf, gets, gfets 함수를 사용하여 배열에 문자열을 입력하는 방법을 알아보겠습니다.

1. scanf

scanf 함수는 %s를 사용해 공백이 없는 연속된 문자를 입력받습니다.

#include <stdio.h>

int main(void)
{
    char str[80];

    printf("입력: ");
    scanf("%s", str);
    printf("첫 번째 단어 : %s\n", str);
    scanf("%s", str);
    printf("두 번째 단어 : %s\n", str);

    return 0;
}

scanf 함수는 버퍼를 사용하므로 키보드로 입력한 문자열은 Enter를 누를 때 버퍼에 저장됩니다. 그 후에 scanf 함수는 버퍼에서 문자열을 가져와 배열에 저장하는데 중간에 공백 문자, 탭 문자, 개행 문자가 있으면 그 이전까지만 저장합니다.

 

위 코드를 실행하고 apple jam 이라고 입력한 경우를 예로 살펴보겠습니다.

8행에서 최초 입력 받은 문자열(apple jam)에서 공백 문자 이전까지만 저장하므로 9행에서는 apple 만 출력됩니다.

 

이후 10행에서 scanf 함수를 또 호출했지만, 버퍼에 문자열이 남아 있으므로 키보드로부터 문자열을 새로 입력받지 않고 버퍼에 남아 있는 문자열을 가져와 배열에 저장합니다. 기존에 배열에 저장된 apple 을 jam 으로 덮어쓰며 널 문자를 추가합니다. 그리고 11행에서는 jam이 출력됩니다. 추가로 버퍼에는 개행 문자(\n)가 여전히 남아있습니다.

 

2. gets

앞서 scanf 함수에서는 공백 이전까지만 저장하기 때문에 공백을 포함한 문자열을 입력할 수 없었습니다. 이 경우에는 gets 함수를 사용하면 가능합니다.

#include <stdio.h>

int main(void)
{
    char str[80];

    printf("입력 : ");
    gets(str);
    printf("문자열 : %s", str);

    return 0;
}

scanf 함수와 동일하게 apple jam을 입력해보겠습니다.

gets 함수는 키보드로 Enter를 누를 때까지 입력한 한 줄을 char 배열에 저장합니다. 버퍼를 사용하므로 키보드로 입력한 데이터는 일단 버퍼에 저장된 후에 gets 함수가 가져갑니다. 이때 중간에 있는 공백이나 탭 문자도 모두 포함하여 가져갑니다. 그리고 버퍼의 개행 문자(\n)는 배열에 저장될 때 널 문자로 바뀌어 저장됩니다.

 

scanf 함수와 달리 개행 문자도 가져오기 때문에 문자열을 입력하지 않고 Enter만 눌러도 입력을 끝냅니다. 이 경우에는 배열의 첫 번째 요소에 널 문자만 저장합니다.

 

3. fgets 

앞서 살펴본 scanf 함수와 gets 함수는 입력되는 문자열의 크기가 배열 크기를 넘어설 위험성이 있습니다. 이렇게 문자열의 크기가 배열보다 커지면 할당되지 않은 메모리 공간을 침범해 실행 중인 프로그램에 에러가 발생할 수 있습니다. 안전하게 문자열을 입력하려면 최대 배열의 크기까지만 문자열을 입력하는 fgets 함수를 사용하는 것이 좋습니다.

#include <stdio.h>

int main(void)
{
    char str[80];

    printf("입력 : ");
    fgets(str, sizeof(str), stdin);
    printf("문자열 : %s\n", str);

    return 0;
}

fgets 함수는 문자열을 저장할 배열명 외에 배열의 크기와 표준 입력 버퍼를 뜻하는 stdin과 함께 사용됩니다. 이렇게 배열의 크기를 알려주므로 배열의 크기를 넘는 문자열을 입력해도 '배열의 크기 - 1개' 의 문자만을 저장합니다.

fgets 함수는 버퍼에 있는 개행 문자도 배열에 저장하고 널 문자를 붙여 문자열을 완성합니다.

 

만약 개행 문자는 제거하고 저장하고 싶다면 다음처럼 개행 문자 위치에 널 문자를 넣어주면 됩니다.

#include <string.h>

str[strlen(str) - 1] = '\0')

 

표준 입력 함수의 버퍼 공유 문제

 

scanf 함수나 getchar 함수 같은 표준 입력 함수는 입력 버퍼를 공유합니다. 마찬가지로 gets와 fgets 함수도 같은 버퍼를 공유하므로 문자열 입력 과정에 문제가 생길 수 있습니다. 앞서 입력한 함수가 버퍼에 개행 문자를 남겨 놓는 경우 이어서 호출되는 함수가 버퍼에서 개행 문자만 가져오고 입력을 끝내는 문제가 생기기 때문입니다.

#include <stdio.h>

int main(void)
{
    int age;
    char name[20];

    printf("나이: ");
    scanf("%d", &age);

    printf("이름 : ");
    gets(name);
    printf("나이 : %d, 이름 : %s\n", age, name);

    return 0;
}

이 문제는 입력 함수들이 버퍼를 공유해서 생기므로 필요한 경우 버퍼의 내용을 지워야 합니다. 개행 문자를 제거하기 위해서는 개행 문자를 읽어 들이는 문자 입력 함수를 호출하면 됩니다.

getchar();

scanf("%*c");

fgets(stdin);

이름을 입력 받기 전에 위 3가지 함수 중 하나를 추가하면 개행 문자를 읽어들여 제거할 수 있습니다.

 

문자열 출력 함수

 

화면에 문자열만을 출력할 때는 전용 출력 함수인 puts와 fputs를 사용합니다. 두 함수 모두 정상 출력된 경우 0을 반환하고, 출력에 실패하면 -1(EOF)을 반환합니다.

#include <stdio.h>

int main(void)
{
    char str[80] = "apple juice";
    char *ps = "banana";

    puts(str);
    fputs(ps, stdout);
    puts("milk");

    return 0;		
}

puts와 fputs 함수는 문자열의 시작 위치부터 널 문자가 나올 때까지 모든 문자를 출력합니다. 따라서 char 배열의 배열명이나 포인터, 문자열 상수 모두 인수로 줄 수 있습니다.

 

차이점은 puts 함수는 문자열을 출력한 뒤 자동으로 줄바꿈을 추가하지만, fputs 함수는 줄바꿈 없이 문자열만 출력합니다.

 

문자열 연산 함수

 

char 배열은 문자열을 저장하는 변수의 역할을 하며 이를 문자열로 쉽게 초기화할 수 있습니다. 그러나 다른 문자열로 바꾸려면 문자를 옮기는 번거로운 일을 수행해야 합니다. 예를 들어 문자열 "strawberry"가 저장된 배열을 "apple"로 바꾸는 과정을 살펴보겠습니다.

char str[80] = "strawberry";
str[0] = 'a';
str[1] = 'p';
str[2] = 'p';
str[3] = 'l';
str[4] = 'e';
str[5] = '\0';

위와 같이 문자를 하나씩 대입해야 하며, 마지막에 널 문자도 저장해야 합니다.

 

1. strcpy

이때 strcpy 함수를 사용하면 문자열을 한 번에 대입할 수 있습니다. strcpy 함수를 문자열 연산 함수라고 하며 사용하기 위해서는 string.h 헤더 파일을 include 해야 합니다. strcpy는 string copy의 약어입니다.

char str1[80] = "strawberry";
char str2[80] = "apple";

strcpy(str1, str2);
strcpy(str1, "banana");

strcpy 함수는 복사받을 곳의 배열명을 첫 번째 인수로 주고 복사할 문자열을 두 번째 인수로 줍니다. 문자열을 복사하는 방식은 문자열의 첫 번째 문자부터 널 문자가 나올 때까지 문자를 하나씩 배열에 옮겨 저장합니다.

 

strcpy 함수를 사용할 때는 다음 2가지를 기억해야 합니다.

  1. 첫 번째 인수로는 char 배열이나 배열명을 저장한 포인터만 사용할 수 있습니다.
  2. 두 번째 인수로는 문자열의 시작 위치를 알 수 있다면 어떤 것이든 사용할 수 있습니다.

 

2. strncpy

strncpy 함수는 문자열을 복사할 때 문자의 수를 지정할 수 있습니다.

char str[80] = "strawberry";

strncpy(str, "apple-pie", 5);
str[5] = "\0";

 

 

strncpy 함수는 strcpy 함수와 달리 자동으로 널 문자를 저장하지 않기 때문에 별도로 저장해줘야 합니다.

 

3. strcat, strncat

strcpy 함수는 초기화된 문자열을 지우고 새로운 문자열로 바꿀 때 사용합니다. 반면, 배열에 있는 문자열 뒤에 이어 붙일 때는 strcat 또는 strncat 함수를 사용합니다. 

char str[80] = "straw";

strcat(str, "berry");
strncat(str, "piece", 3);

strcat 함수는 먼저 붙여 넣을 배열에서 널 문자의 위치를 찾고 그 위치부터 붙여 넣을 문자열을 복사합니다. 그리고 붙여 넣기가 끝나면 널 문자를 저장해 마무리합니다.

 

strcat 함수를 사용할 때는 다음 2가지를 주의해야 합니다.

  1. strcat 함수는 메모리를 침범할 수 있으니 배열의 크기가 충분히 커야 합니다.
  2. strcat 함수를 사용할 때는 배열을 초기화해야 합니다.

strcat 함수로 붙여 넣기 전에 먼저 널 문자의 위치를 찾으므로 배열을 초기화하지 않으면 쓰레기 값의 중간부터 넣을 가능성이 큽니다.

초기화는 특정 문자열로 하거나, 널 문자로 합니다.

 

4. strlen

문자열을 저장하는 char 배열은 다양한 길이의 문자열을 저장할 수 있도록 충분히 크게 선언해서 사용합니다. 따라서 배열에 저장된 문자열의 길이는 배열의 크기와 다를 수 있습니다. 배열에 저장된 쿤자열의 실제 길이를 알고 싶을 때 strlen 함수를 사용합니다.

char str[80] = "strawberry";

strlen(str);

 

5. strcmp, strncmp

문자열을 비교할 때는 strcmp와 strncmp 함수를 사용합니다.

strcmp 함수는 두 문자열의 사전 순서를 판단해 그 결과를 반환합니다. 사전 순서는 사전에 단어가 수록되는 알파벳 순서를 말하며, 함수의 사용법과 반환값은 다음과 같습니다.

// str1이 str2보다 사전에 나중에 나오면 1 반환
// str1이 str2보다 사전에 먼저 나오면 -1 반환
// str1과 str2가 같은 문자열이면 0 반환
strcmp(str1, str2);

strcmp 함수는 내부적으로 두 문자열에서 각각 첫 번째 문자부터 아스키 코드 값을 비교하여 대소 비교를 진행합니다. 아스키 코드 값은 대문자보다 소문자가 더 크고, 알파벳순으로 커집니다.