본문 바로가기
c언어

c언어 문자와 문자열에 대하여 알아보기

by 개발자 L 2022. 12. 20.
반응형

c언어 문자와 문자열에 대하여 알아보기

네 안녕하세요, 이번 포스팅에서는 문자와 문자열에 대하여 알아보는 시간을 가져보도록 하겠습니다.

우리가 프로그램 소스를 작성을 할 때 숫자나 수식, 명령어 등만 쓰는 것이 아니죠?

바로 문자를 씁니다.

그 이유는 개발자가 어떤 소스를 작성을 하였는지 주석을 달아 설명을 해야 하는데,

그 과정에서 필연적으로 글을 쓰게 되고요,

그리고 소스 자체에서 문자를 다뤄야 하는 경우도 생깁니다.

그래서 문자와 문자열은 프로그래밍 언어에서 정말 필수적인 요소이죠.

그럼 지금부터 이 문자에 대하여 좀 더 자세히 알아보도록 합시다.

그럼 함께 보시죠.

 

1. 문자와 문자열

기본적으로 우리가 흔히 말하는 문자는 하나의 글자를 말하고, 문자열은 이 글자가 여러 개 모여있는 문자들의 모임입니다.

c언어에서는 문자와 문자열을 구분을 해주며, 문자의 경우는 작은 따옴표를 써서 'A'와 같이 쓰고,

문자열의 경우는 큰 따옴표를 써서 "ABC"와 같이 씁니다.

그리고 여담으로 문자와 문자열을 크게 구별을 하지 않는 언어도 있습니다.

 

1  - 1. 문자와 문자열의 저장 위치

문자는 char 자료형을 써서 저장을 하는데, 문자열은 어떤 식으로 저장을 할까요?

c언어에서는 문자열에 대한 저장 자료형을 딱히 정의를 해두지 않았습니다.

그래서 문자열 역시 문자의 그룹이기 때문에 문자를 저장 및 선언할 때 쓰는 자료형인 char을 써서 저장을 합니다.

 

2. NULL 문자

우리가 문자열을 저장을 할 때 꼭 있어야 하는 문자가 있습니다.

그건 바로 NULL 문자입니다.

NULL문자는 srand, time 함수를 이용하여 난수를 발생 시킬 때도 쓰는 문자입니다.

NULL 문자는 아스키 코드의 값이 0인 문자인데, 말 그대로 '빈 값'을 뜻합니다.

그러면 여기서 질문을 하나 할 수 있겠죠?

빈 값인데 굳이 쓰지 않아도 되지 않느냐며 말이죠.

하지만 이걸 쓰지 않으면 여러가지 에러 사항들이 생깁니다.

NULL 문자의 기능은 크게 두 가지 입니다.

  • 쓰레기 값 유입 방지
  • 문자열의 끝 표시

이렇습니다.

그래서 난수 등을 발생시킬 때 NULL값을 집어넣는 이유는 빈 값이라는 데이터가 존재하지 않으면

어떤 값이 들어가도 상관이 없다는 뜻이라서 정말 쓸데없는 쓰레기 값이 들어가서 코드를 망치는 일도 생길 수 있고,

문자열에서는 문자열의 끝을 알리지 않는다면 컴파일러에서 인식을 제대로 못해서 쓰레기 값을 가져온다던지,

아니면 문자열이 중간에 끊기는 현상도 생길 수 있습니다.

그런 것을 방지하기 위해 씁니다.

그래서 NULL 문자는 쉽게 말해서 하나의 약속과 같은 것이라고 보시면 됩니다.

실제로 현업에서도 초기값 설정을 할 때 확실한 값이 정해지지 않은 상황에서는

NULL 값을 이용하여 정의를 합니다.

그만큼 많이 쓰이고, 중요한 문자입니다.

그럼 이를 이해하기 위해서 간단한 코드를 하나 작성을 해보도록 하겠습니다.

#include <stdio.h>

int main()

{
    int i = 0;
    char str[4];

    str[0] = 'a';
    str[1] = 'b';
    str[2] = 'c';
    str[3] = '\0'; // NULL 문자와 같은 뜻임.

    while(str[i] != '\0')
    {
        printf("%c", str[i]);
        
        i++;
    }

    printf("\n");

    return 0;
}

이렇게 작성을 해봤는데, 여기서 신기한 문자가 하나 있죠?
바로 '\0'이라 쓰인 것이 보일 텐데,

이건 NULL 값을 표현을 한 것입니다.

NULL값 표현을 c언어에서는 이렇게 합니다.

그리고 문자열을 표현하기 위해서 배열을 이용했고요,

abc라는 문자열을 표현하기 위해서는 NULL 값 까지 4개를 표현해야 하기 때문에 배열 안의 숫자는 4가 되었습니다.

그럼 결과가 어떻게 나오는지 한 번 보도록 하겠습니다.

abc

결과도 잘 나온 것을 확인할 수 있습니다.

반응형

 

3. 문자열의 초기화

그리고 문자열 역시 하나의 배열이기 때문에 초기화하는 과정이 필요합니다.

문자열을 초기화하는 방버 역시 배열을 초기화 하는 방법이랑 거의 비슷합니다.

그럼 어떤 식으로 하는지 바로 보여드리겠습니다.

char str[4] = {'a', 'b', 'c', '\0'}; //문자열을 초기화 하는 가장 전형적인 방법
char str[4] = "abc";
/*문자열 상수를 이용하여 초기화 하는 방법
(배열의 크기가 문자열의 상수의 크기보다 커야만 하며,
조건 충족 시 컴파일러가 자동으로 NULL 문자를 문지열 끝에 할당)*/
char str[4] = "abcdef";
/*문자열 배열의 크기가 충분치 않을 시, 컴파일러는 경고 메시지를 보내며,
일부 문자는 출력이 되지 않을 수 있음.*/
char str[6] = "abc";
/*문자열 배열의 크기가 문자열 상수보다 클 경우,
나머지 빈 공간은 모두 NULL문자 할당*/
// 배열 초기화 시 배열의 크기가 커서 공간이 남을 경우 그 빈 자리를 0으로 초기화 하는 것과 같은 원리임.
char str[4] = ""; // 문자열 배열의 크기 모두를 NULL 문자로 초기화 하여 모든 공간에 NULL 문자 할당
char str[] = 'abc';
/*문자열 상수의 크기만큼 문자열 배열의 크기 할당
(이 경우는 NULL 문자 포함 4가 되며,
문자의 개수를 일일이 셀 필요가 없어 편리해 정말 많이 쓰는 방법임.*/

원리는 각각 주석 글로 같이 적어뒀으니, 참고하여 보시면 됩니다.

 

4. 문자열의 출력

문자열을 출력할 때 쓰는 방법은 반복문을 써서 문자열을 출력하는 것인데,

그 방법이 엄청 고전적인 것이라 알고리즘 공부를 할 때 도움이 많이 되지만,

효율이 정말 많이 떨어집니다.

그래서 그냥 가장 간단한 방법으로 printf() 함수를 써서 출력을 합니다.

이런 식으로 말이죠.

printf("%s", str); // 문자열 형식 지정자 : %s
printf(str); // 문자열 형식 지젖자가 없어도 문자열 출력 가능

이렇게 사용을 할 수 있으며, 문자열의 경우, 형식 지정자가 없어도 문자열이 출력이 되는데,

되도록이면 다른 자료형과 혼동이 되지 않도록 형식 지정자를 써서 오류를 방지하는 것이 좋습니다.

그럼 이어서 간단한 코드를 이용해 같이 살펴보도록 합시다.

#include <stdio.h>

int main()

{
    char str1[6] = "Seoul";
    char str2[3] = {'i', 's', '\0'};
    char str3[] = "the capital city of Korea";

    printf("%s %s %s\n", str1, str2, str3);

    return 0;
}

이렇게 한 번 작성을 해봤습니다.

그럼 결과를 바로 보도록 하겠습니다.

Seoul is the capital city of Korea

변수를 선언한 순서대로 잘 나온 것을 볼 수 있습니다.

그러면 이번에는 문자열을 복사하는 방법에 대한 코드를 짜 보도록 하겠습니다.

문자열을 복사를 할 때는 src, dst라는 변수를 써서 작업을 할 거고,

각각은 source, destination의 약자이며, 보통 복사나 매개 인자 전달을 할 때 주로 쓰는 변수명입니다.

물론 변수명은 개발자 재량이지만, 그래도 어느 정도 정형화되어있는 것을 쓰면 크게 주석 글을 달지 않아도

다들 한 번에 알아볼 수 있기 때문에 좀 더 편하겠죠?

그리고 문자열을 복사를 하는 것은 라이브러리 함수를 이용해서 하는 것도 가능하지만,

이번에는 문자열이 복사가 되는 원리를 직접 보여드리고 싶어서

문자열에 있는 각각의 문자들을 복사하는 것을 보여드리겠습니다.

그럼 바로 작성을 해보도록 하겠습니다.

#include <stdio.h>

int main()

{
    char src[] = "The worst things to eat before you sleep";
    char dst[100];

    int i;

    printf("원본 문자열 : %s\n", src);

    for(i = 0; src[i] != '\0'; i++)
    {
        dst[i] = src[i];
    }

    dst[i] = '\0';

    printf("복사된 문자열 : %s\n", dst);

    return 0;
}

이렇게 작성을 해봤습니다.

원본 문자열 src를 복사할 목적지 dst에 복사를 합니다.

그럼 결과를 보여드리도록 하겠습니다.

원본 문자열 : The worst things to eat before you sleep
복사된 문자열 : The worst things to eat before you sleep

문자열이 제대로 복사된 것을 볼 수가 있습니다.

그럼 이번에는 문자열의 길이를 구해보도록 하겠습니다.

문자열의 길이를 구할 때에는 문자열의 길이가 확실히 정해져 있는 것이 아니기 때문에

while문을 사용하여 작성을 하도록 하겠습니다

그럼 바로 보여드리도록 하겠습니다.

#include <stdio.h>

int main()

{
    char str[30] = "C language is easy";

    int i = 0;

    while(str[i] != '\0')
    {
        i++;
    }

    printf("문자열 \"%s\" 의 길이는 %d 입니다.\n", str, i);

    return 0;
}

이렇게 한 번 작성을 해봤습니다.

그럼 결과를 바로 보도록 하겠습니다.

문자열 "C language is easy" 의 길이는 18 입니다.

결과도 문제없이 잘 나온 것을 볼 수가 있습니다.

 

5. 문자열의 변경

문자열도 결국 하나의 배열로써 저장이 되기 때문에 배열이 변경이 가능하듯이, 똑같이 변경이 가능합니다.

문자열을 바꾸는 방법은 이러한 방법들이 있습니다.

  • 문자열을 쪼개서 직접 변경하는 방법
  • 라이브러리 함수를 써서 변경하는 방법

먼저 문자를 직접 바꾸는 방법입니다.

char str[30] = "Hello";
str[0] = 'W';
str[1] = 'o';
str[2] = 'r';
str[3] = 'l';
str[4] = 'd';
str[5] = '\0';

이 방법은 가장 확실한 방법이기는 하지만, 너무 번거롭습니다.

그리고 줄 수 역시 너무 많이 잡아먹어서 일률도 떨어집니다.

그래서 위 방법보다는 후자의 방법을 더 많이 씁니다.

char str[10] = "Hello";
strcpy(str, "World");

여기에 쓰인 strcpy() 함수가 바로 문자열을 변경하는 함수입니다.

여기서 strcpy를 쓰지 않고 변경을 하려고 하면 오류가 나니 주의를 하셔야 합니다.

 

6. 문자열 상수와 포인터

문자열 상수는 우리가 이전에 선언했던 "abc"와 같은 것들입니다.

이들은 어찌 됐건 상수이기 때문에 변경은 불가합니다.

그리고 저장되는 위치는 텍스트 세그먼트라는 특수한 위치에 저장이 됩니다.

그리고 앞서 말했듯이 읽기만 가능하기 때문에 우리는 이를 불러오기 위해서는 포인터를 써야만 합니다.

이런 식으로 말이죠.

char *p = "HelloWorld";

이렇게 하게 되면 포인터 변수가 생성이 되었음을 알 수 있습니다.

그리고 포인터는 해당 변수의 주소 값을 복사하여 가져 가는 매개 변수이기 때문에

데이터 세그먼트에 저장이 됩니다.

다시 말하면 포인터 변수 역시 이 데이터 세그먼트에 선언 및 저장이 되는 것이고,

문자열 상수인 "HelloWorld"는 포인터 변수 p에 주소가 저장이 되는 것이죠.

그리고 여기서 주의를 해야 할 사항이 한 가지 있다면,

문자열 상수는 어찌 됐건 읽기만 가능하고 쓰기는 불가한 변경 불가한 요소이기 때문에

포인터를 이용하여 문자열 상수를 변경을 시도하게 되면 컴파일러 상에서는 오류가 뜨지 않겠지만,

이것은 메모리를 건드는 일이기 때문에 운영체제 자체에서 오류가 발생하여 실행을 중지하게 됩니다.

예를 들면 이런 경우이죠.

char *p = "HelloWorld";
strcpy(p, "Farewell"); // 메모리에 저장이 된 상수를 건드린 것이기 때문에 실행 오류가 남.

이런 경우입니다.

하지만 이렇게 쓰는 것은 됩니다.

char *p = "HelloWorld";
p = "Farewell";

이게 되는 이유는 포인터 변수 p는 데이터 세그먼트에 있기 때문에 언제든지 값을 변경할 수 있기 때문입니다.

그래서 다른 문자열 상수의 주소를 포인터 변수 p에 저장을 할 수 있습니다.

아니면 이런 경우도 가능합니다.

char p[] = "HelloWorld";
strcpy(p, "Farewell"); // p는 배열이므로 가능함.

이 경우에는 p가 배열이기 때문에 값을 변경할 수 있습니다.

그리고 우리가 이전 포스팅에서 배열과 포인터의 관계에 대하여 배울 때,

배열에 있는 인덱스가 곧 포인터들이고, 포인터가 곧 배열 내에 있는 인덱스라고 그랬습니다.

그렇기 때문에 배열 역시 포인터와 똑같은 기능을 하기 때문에 가능한 것입니다.

그럼 이들을 간단한 예제를 통하여 한 번 확인을 해봅시다.

#include <stdio.h>

int main()

{
    char *p = "HelloWorld!";

    printf("%s\n", p);

    p = "Welcome to C World!";

    printf("%s\n", p);

    p = "Farewell";
    printf("%s\n", p);

    // p[0] = 'a'; // 오류 발생

    return 0;
}

이렇게 한 번 해봤습니다.

그럼 결과를 한 번 보도록 하겠습니다.

HelloWorld!
Welcome to C World!
Farewell

이렇게 결과도 잘 나오는 것을 볼 수가 있습니다.

그리고 필요에 따라서 문자열 상수를 많이 저장을 해야 하는 경우가 생길 겁니다.

예를 들어서 메뉴판을 만들기 위해서 음식 이름을 저장한다고 합시다.

그럴 경우애는 정말 많은 문자열을 필요로 하죠?

그럴 때는 이런 식으로 쓸 수 있습니다.

#include <stdio.h>

int main()

{
    char *pmenu[10] = {"pizza", "chicken", "peo", "bunzza", "steak", "pasta"};

    for(int i = 0; i < 6; i++)
    {
        printf("%s\n", pmenu[i]);
    }

    return 0;
}

이런 식으로 배열과 반복문 등을 써서 나타낼 수 있습니다.

그럼 결과는 어떻게 나왔는지 한 번 보도록 하겠습니다.

pizza
chicken
peo
bunzza
steak
pasta

결과도 잘 나온 것을 확인할 수 있습니다.

 

여기까지 문자와 문자열에 대하여 알아보았는데요,

다음 포스팅에서는 문자 입출력 라이브러리에 대하여 알아보도록 하겠습니다.

긴 글 읽어주신 독자분들께 진심으로 감사드립니다~

반응형

댓글