Skip to content

32. 문자열


1. 키워드

  • 문자열


2. 문자열 사용하기

  • C에서는 문자 자료형인 char는 있지만 문자열을 저장하는 자료형은 없다.
  • 그래서 다음과 같이 char에 문자열을 저장하면 컴파일은 되지만 실행은 안 된다.


#include <stdio.h>

int main()
{
    char s1 = "Hello"; // "Hello"는 문자열, 문자열은 ""로 둘러쌈

    printf("%s", s1); // 실행 에러

    return 0;
}


3. 문자와 문자열 포인터 알아보기

  • 문자열은 char 타입 포인터 형식으로 사용한다.


char *변수이름 = "문자열";
#include <stdio.h>

int main()
{
    char c1 = 'a';      // 변수에 문자 'a' 저장
    char *s1 = "Hello"; // 포인터에 문자열 "Hello"의 주소 저장

    printf("%c\n", c1); // %c로 문자 출력
    printf("%s\n", s1); // %s로 문자열 출력

    return 0;
}

// a
// Hello


  • 문자(char)는 'a'처럼 글자가 하나만 있는 상태를 뜻하고 문자열(char *)은 "Hello"처럼 글자 여러 개가 계속 이어진 상태를 뜻한다.
  • 즉, 문자는 1바이트 크기의 char 타입에 저장할 수 있지만 문자열은 크기가 1바이트를 넘어서므로 char 타입에 저장할 수 없다.
  • 따라서 문자열은 변수에 직접 저장하지 않고 포인터를 이용해서 저장한다.


  • 다음은 문자가 변수 c1에 저장된 상태와 문자열이 변수 s1에 저장된 상태를 나타낸다.


001


  • 문자열은 char *s1처럼 포인터를 이용해서 저장한다.
  • 포인터는 메모리 주소를 저장하므로 메모리 주소를 보고 문자열이 저장된 위치로 이동하게 된다.


문자열의 메모리 주소 출력

  • printf로 문자열 대신 문자열이 저장된 메모리 주소를 출력하고 싶다면 서식 지정자로 %p를 사용하면 된다.


char *s1 = "Hello";

printf("%p\n", s1); // %p로 문자열이 저장된 메모리 주소 출력


  • 문자는 변수 c1 안에 그대로 저장되지만 문자열 리터럴이 변수 s1 안에 저장되지 않고, 문자열이 있는 곳의 메모리 주소만 저장된다.
  • 단, 이 문자열 리터럴이 있는 메모리 주소는 읽기 전용이므로 다른 문자열을 덮어쓸 수는 없다.
  • 또한, 문자열 리터럴이 저장되는 위치는 컴파일러가 알아서 결정하므로 신경쓰지 않아도 된다.


//   ↓ 변수 안에 'a'가 그대로 저장됨
char c1 = 'a';

//            ↓ 메모리에 저장되어 있음(읽기 전용)
char *s1 = "Hello";
//    ↑ 문자열 리터럴이 있는 곳의 메모리 주소만 저장


  • 문자열을 사용할 때는 char 타입 포인터에 ""(큰따옴표)로 묶은 문자열만 할당하면 된다.
  • 그리고 printf로 문자열을 출력할 때는 서식 지정자로 %s를 사용하면 된다.


printf("%s\n", s1); // %s로 문자열 출력


  • 여기서 중요한 점은 C의 문자열은 마지막에 항상 널 문자(NULL)가 붙는다는 점이다.
  • NULL0으로도 표현할 수 있으며 문자열의 끝을 나타낸다.
  • 그래서 printf는 문자열을 출력할 때 문자열을 계속 출력하다가 NULL에서 출력을 끝낸다.


002


문자열의 앞과 뒤는 어디인가요?

  • 보통 문자열이 시작되는 쪽을 앞, 문자열이 끝나는 쪽을 뒤라고 부른다.


               
"The Little Prince"


  • 하지만 실제 메모리 주소를 다루는 포인터 연산에서는 32비트를 기준으로 낮은 주소(0x00000000)가 뒤쪽(Backward), 높은 주소(0xFFFFFFFF)가 앞쪽(Forward)이다.
  • 그래서 뒤쪽으로 가는 것을 역방향, 앞쪽으로 가는 것을 순방향이라고 한다.


메모리
                  
역방향            순방향
0x00000000 ~ 0xFFFFFFFF


  • 여기서 문자열은 메모리의 낮은 주소부터 시작해서 높은 주소까지 저장되는데, 이 상태로는 문자열이 시작되는 부분이 뒤, 끝나는 부분이 앞이 되어버린다.


                   
                    
0x00000000 ~ 0xFFFFFFFF
"The Little Prince"


  • 보통 사람은 문자열이 시작되는 부분을 앞으로 인식하므로 메모리 주소로 처리하는 방법과는 반대로 이해할 수밖에 없다.
  • 그래서 메모리 주소의 앞 뒤와 구분하기 위해 문자열은 왼쪽(Left), 오른쪽(Right) 또는 처음(First), 마지막(Last)이라는 말을 자주 사용한다.


4. 문자열이 포인터에서 인덱스로 문자에 접근하기

  • 포인터도 [](대괄호)를 사용하여 인덱스로 접근할 수 있으니, 이번에는 문자열에서 문자를 하나씩 출력해 보자.


#include <stdio.h>

int main()
{
    char *s1 = "Hello"; // 포인터에 문자열 "Hello"의 주소 저장

    printf("%c\n", s1[1]); // 인덱스 1(두 번째)의 문자 출력
    printf("%c\n", s1[4]); // 인덱스 4(다섯 번째)의 문자 출력
    printf("%c\n", s1[5]); // 문자열 맨 뒤의 NULL(\0) 출력. NULL은 화면에 표시되지 않음

    return 0;
}

// e
// o
//


  • s1[1]처럼 문자열 포인터를 인덱스로 접근했고, printf에서 서식 지정자 %c로 문자를 출력했다.
  • 이렇게 문자열 포인터는 인덱스로 접근하면 char 타입과 같기 때문에 %c로 출력할 수 있다.
  • 문자열 포인터에서 문자열 맨 뒤의 문자를 가져와보면 NULL이 들어있다.
  • NULLprintf로 출력해도 화면에 표시되지 않는다.


  • 문자열 포인터에서 인덱스로 문자를 할당할 때 어떻게 되는지 확인해 보자.


#include <stdio.h>

int main()
{
    char *s1 = "Hello"; // 포인터에 문자열 "Hello"의 주소 저장
                        // "Hello"가 있는 메모리 주소는 읽기 전용

    s1[0] = 'A'; // 문자열 포인터의 인덱스 0에 문자 A를 할당
                 // 실행 에러

    printf("%c\n", s1[0]);

    return 0;
}


  • 문자열 리터럴이 있는 메모리 주소는 읽기 전용이기 때문에 에러가 발생한다.
  • 따라서 문자열 포인터는 인덱스로 접근하여 문자를 할당하면 안 된다.


5. 배열 형태로 문자열 선언하기

  • 문자열은 문자(char) 배열에 저장할 수도 있다.


char 배열이름[크기] = "문자열";
#include <stdio.h>

int main()
{
    char s1[10] = "Hello"; // 크기가 10인 char 타입 배열을 선언하고 문자열 할당

    printf("%s\n", s1); // %s로 문자열 출력

    return 0;
}

// Hello


  • char s1[10] = "Hello";와 같이 크기가 10char 타입 배열(문자 배열)을 선언한 뒤 문자열을 할당했다.
  • 그리고 printf에서 서식 지정자 %s로 배열을 출력해 보면 배열에 저장된 문자열이 출력된다.


  • 문자열 리터럴을 포인터에 할당하는 방식과는 달리 문자열을 배열에 저장하는 방식은 배열 요소 하나 하나에 문자가 저장된다.
  • 즉, 이렇게 배열 안에 일렬로 나열된 문자가 모여 문자열을 이루게 되는 것이다.
  • 물론 배열이기 때문에 인덱스는 0부터 시작한다.
  • 그리고 여기서도 문자열의 맨 뒤에 NULL이 들어간다.


003


  • 배열에 문자열과 NULL을 저장한 뒤 남는 요소들은 딱히 신경쓸 필요는 없다.
  • 보통 남는 공간에는 모두 NULL이 들어간다.


  • 배열로 문자열을 사용할 때 한 가지 주의할 점은 배열을 선언한 즉시 문자열로 초기화해야 한다는 점이다.
  • 배열을 미리 선언해놓고 문자열을 나중에 할당할 수는 없다.


#include <stdio.h>

int main()
{
    char s1[10]; // 크기가 10인 char 타입 배열 선언

    s1 = "Hello"; // 이미 선언된 배열에 문자열을 할당하면 컴파일 에러 발생

    printf("%s\n", s1); // %s로 문자열 출력

    return 0;
}


  • 이미 선언된 배열에는 문자열을 할당할 수 없다.
  • 정 할당하고 싶다면 다음과 같이 배열의 요소에 문자를 하나 하나 집어넣으면 된다.


s1[0] = 'H';
s1[1] = 'e';
s1[2] = 'l';
...


  • 배열을 선언할 때는 배열의 크기를 할당할 문자열보다 크게 지정해야 한다.


char s1[10] = "Hello"; // 크기가 10인 배열. "Hello"는 5글자이므로 올바른 방법
char s2[3] = "Hello";  // 크기가 3인 배열. "Hello"는 5글자이므로 잘못된 방법


  • 문자열의 문자 개수보다 배열의 크기가 작다면 컴파일은 되지만, 문자열을 출력했을 때 제대로 출력되지 않는다.


  • 문자열을 저장할 때 배열의 최소 크기는 저장할 문자열 크기에 NULL 하나를 더해야 한다.


char s1[6] = "Hello"; // 크기가 6인 배열. "Hello" 5글자에 NULL 하나를 더해 6개의 공간이 필요함


004


  • 다음과 같이 문자 배열을 선언하면서 문자열을 바로 할당할 때는 배열의 크기를 생략할 수 있다.


char 배열이름[] = "문자열";
#include <stdio.h>

int main()
{
    char s1[] = "Hello"; // 문자열을 할당할 때 배열의 크기를 생략하는 방법

    printf("%s\n", s1); // %s로 문자열 출력

    return 0;
}

// Hello


  • 배열의 크기는 할당되는 문자열의 문자 개수에 맞춰 자동으로 지정된다.
  • 여기서는 "Hello"이므로 5NULL 하나를 더해 6이 된다.


6. 배열 형태의 문자열에서 인덱스로 문자에 접근하기

  • 배열에 문자열을 저장한다면 당연히 인덱스로 접근할 수 있다.


#include <stdio.h>

int main()
{
    char s1[10] = "Hello"; // 크기가 10인 char 타입 배열을 선언하고 문자열 할당

    printf("%c\n", s1[1]); // 인덱스 1(두 번째)의 문자 출력
    printf("%c\n", s1[4]); // 인덱스 4(다섯 번째)의 문자 출력
    printf("%c\n", s1[5]); // 문자열 맨 뒤의 NULL(\0) 출력. NULL은 화면에 표시되지 않음

    return 0;
}

// e
// o
//


  • s1[1]처럼 배열을 인덱스로 접근했고, printf에서 서식 지정자 %c로 문자를 출력했다.
  • 이렇게 배열을 인덱스로 접근하면 char 타입과 같기 때문에 %c로 출력할 수 있다.
  • 마찬가지로 배열에 저장된 문자열도 맨 뒤에는 NULL이 저장되어 있다.


  • 배열 형태의 문자열은 인덱스로 접근하여 문자를 할당할 수 있다.


#include <stdio.h>

int main()
{
    char s1[10] = "Hello"; // 크기가 10인 char 타입 배열을 선언하고 문자열 할당
                           // 배열에 문자열이 복사됨

    s1[0] = 'A'; // 문자 배열의 인덱스 0에 문자 A를 할당

    printf("%s\n", s1); // 바뀐 문자열이 출력됨

    return 0;
}

// Aello


  • "Hello"가 들어있는 문자 배열 s1의 인덱스 0'A'를 할당하여 "Aello"가 되었다.
  • 배열 형태의 문자열은 인덱스로 접근하여 내용을 변경할 수 있다.

References