ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • C언어 문자열 (인프런)
    CS/C언어 2020. 1. 8. 21:56

    ※ 인프런 무료강좌 C로 배우는 자료구조(권오흠 교수님)를 보고 개인적인 복습을 위해 정리한 내용입니다.


     

    문자열

    C언어에서 문자열은 char 타입의 배열에 저장된다. 

     

    문자열의 끝에는 null charactor ('\0') 를 저장해 문자열의 끝을 표시해주어야 한다. 

    컴퓨터에게 문자열의 끝이 어디인지 알려주지 않으면 에러가 발생한다. 

    이렇게 null charactor를 문자열 끝에 저장해 문자열의 끝을 표시하는 것은 C언어 자체의 문법은 아니지만, 

    C언어의 많은 표준 라이브러리 함수들이 문자열을 다룰 때 문자열의 끝에 null charactor가 저장되어있다는 가정 하에서 동작한다.

    그래서 C언어를 사용할 때는 모든 문자열의 끝에 null charactor를 추가해 문자열의 끝을 표시하는 것이 바람직하다. 

     

    null charactor를 담기 위해 배열의 크기는 문자열의 길이보다 적어도 1만큼 길어야 한다. 

    (null charactor를 사용하는 대신 배열의 크기를 문자열의 길이에 딱 맞게 설정하면 되지 않을까 하는 생각을 해볼 수 있지만, 그렇게 하면 컴파일러에 따라 오류가 발생할 수 있다. )

     

     

    문자열 생성 방법

    1. 기본적인 생성 방법

    char str[6];
    
    str[0] = 'h';
    str[1] = 'e';
    str[2] = 'l';
    str[3] = 'l';
    str[4] = 'o';
    str[5] = '\0';

     

    2. " " 연산자를 이용한 생성

    char str[] = "hello"; //(1)
    
    char *str = "hello"; //(2)

     

    " " 연산자를 이용해 문자열을 생성하면, 

    C 컴파일러가 자동으로 문자의 갯수를 카운팅해서 문자 갯수 + 1(null charactor) 크기의 배열을 생성하고,

    문자를 하나씩 저장한 다음 마지막 원소로 null charactor를 저장한다. 

    그리고 배열의 첫번째 원소의 주소를  (1) str[] 변수 또는  (2) *str 변수에 담는다. (둘 다 포인터 변수이다.)

     

    (1)과 (2)의 생성 결과는 동일하지만

    (1)의 경우, 배열이름 포인터 변수인 str[] 변수는 값을 바꿀 수가 없다. (배열 이름 포인터 변수는 다른 포인터 변수와 달리 값을 바꿀 수가 없다. 원래의 문자열이 아닌 다른 문자열을 가리킬 수가 없다는 뜻이다.)

    그리고 (2)로 생성한 문자열의 경우, 배열이 아닌 string literal이므로 다른 생성 방법들과 달리 문자열 수정이 불가능하다. 

     

    ※ string.h 라이브러리

    문자열을 다루는 함수를 제공하는 라이브러리이다.

    strcpy : 문자열 복사 

    strlen : 문자열의 길이

    strcat : 문자열 합치기

    strcmp : 문자열 비교

     

    scanf 함수

    int a;
    
    scanf("%d", &a);

    scanf 함수를 사용할 때는 변수 a가 아닌, 변수 a의 주소(&a)로 입력받은 값을 넘겨준다.

    scanf 함수의 매개변수로는 자신이 읽은 데이터를 저장할 메모리의 주소를 넘겨주어야 한다. 

    (변수에 값을 할당하는 치환문은 (변수의 주소 = 값) 형태이므로 

    변수에 값을 할당할 때는 값을 변수의 '주소'로 넘겨주어야 하기 때문이다. )

     

    char s[10]; 
    
    scanf("%s", s); 

    그러나 문자열을 입력받아 넘겨주는 경우에는 & 연산자를 사용하지 않는다.

    문자열은 char 타입의 배열이고, 배열의 이름은 그 배열의 주소를 저장하고 있는 포인터 변수이기 때문에

    변수의 주소를 추출하는 & 연산자를 사용하지 않는 것이다. 

     

     

    문자열 예제 - 문자열 저장하기

    사용자로부터 여러 개의 단어를 입력받아 저장하는 예제이다. (자세한 내용은 강의 참고)

     

    여러 개의 단어(=여러 개의 문자열 배열)를 입력받고 각 배열들의 주소를 저장할 배열이 필요하므로

    char *words[100];

    로 선언해주면 된다. 

    입력되는 단어의 최대 개수는 100개 정도로 생각하고 배열의 크기를 설정했다. 

    배열의 타입이 char * 인 이유는,  char 타입 원소를 담고 있는 배열들의 주소를 저장할 것이기 때문이다.

     

    첫 번째 시도 (실패)

    #define BUFFER_SIZE 100
    
    int main() {
    	char *words[100];
        int n = 0; // number of strings. 현재까지 저장된 단어의 갯수
        char buffer[BUFFER_SIZE];
        
        //EOF : End of File. 파일이 끝나기 전까지 while문을 반복한다
        while (scanf("%s", buffer) != EOF) {
        	words[n] = buffer;
        	n++;
        }
        
        return 0;
    }

    이렇게 코드를 작성하면

    words 배열의 모든 원소들이 맨 마지막으로 입력했던 단어로 덮어씌워진 상태가 된다. 

    words[n] = buffer;

    이 부분 때문이다. 

    위 코드는 두 개의 포인터 변수 간의 치환문이다. (words[n]에는 배열의 주소가 저장되고, buffer는 배열의 이름이므로 마찬가지로 배열의 주소가 저장되는 포인터 변수.)

    이 치환문에 따라 buffer라는 포인터 변수가 가진 배열의 주소가 words[n]이라는 포인터 변수에 저장이 된다.

    buffer는 배열의 이름 포인터 변수이므로 값을 변경할 수가 없다. 즉, 다른 배열을 가리킬 수가 없다.

    그래서 words의 모든 원소들은 buffer라는 하나의 배열의 주소를 담게 된다. 

    words 배열의 각 칸마다 다른 배열들이 할당되는 것이 아니라, 모든 칸에 같은 buffer 배열이 할당되기 때문에

    scanf로 새로운 문자 배열(문자열)을 읽어올 때마다 원래 있던 문자열이 지워지고 새로운 문자열로 덮어씌워진다.

     

    이 문제를 해결하기 위해서는

    배열의 주소를 할당하는 것이 아니라, 배열에 담긴 값을 복사해서 전달해야 한다.

    문자열을 카피하려면 strcpy 함수나 strdup 함수를 이용하면 된다. (string.h 라이브러리 함수)

     

    strcpy와 strdup

    더보기

    strcpy(str2, str1)

    str1을 복사해 str2에 저장한다. 이 때 str1과 str2의 타입이 같아야 한다. (배열 <- 배열, 정수 <- 정수)

    예제에서 strcpy(words[n], buffer); 처럼 작성하면 buffer는 문자배열인 반면 words[n]은 문자배열의 주소를 담는 포인터 변수이기 때문에 타입이 달라 에러가 발생한다.

     

    strdup(buffer)

    매개변수로 하나의 문자열을 받아서 복제하고, 복제한 문자열의 주소를 반환한다.

    C표준 라이브러리 함수는 아니지만 대부분의 컴파일러가 지원하는 함수이다. (호환성 측면에서 약간 문제가 있을 수 있다)

     

    strdup 함수의 내부를 간단하게 나타낸 코드

    char * strdup(char *s) {
    	char *p;
    	p = (char *)malloc(strlen(s)+1);
    
    	if (p ! = NULL)
    		strcpy(p, s);
            
    	return p;
    }

    strlen 함수로 전달받은 문자열 s의 길이를 구하고,

    포인터 변수 p에 (문자열의 길이+1) 만큼의 크기를 가진 메모리를 동적으로 할당한다.

    strcpy 함수로 문자열을 복제해서 p가 가리키는 주소에 담는다. 

    p를 반환한다. 

     

    다음과 같이 strdup 함수를 이용하는 방식으로 코드를 수정하면 의도한 결과를 얻을 수 있다.

    #include <stdio.h>
    #define BUFFER_SIZE 100
    
    int main() {
    	int n = 0;
        char buffer[BUFFER_SIZE];
        
        while (scanf("%s", buffer) != EOF) {
        	words[n] = strdup(buffer);
            n++;
        }
    
    
    }

    strdup 함수는 buffer에 담긴 문자열을 동적으로 할당된 메모리에 복제하고, 문자열이 복제되어있는 메모리의 주소를 반환하여 

    words[n]에 담는다. 그러면 우리가 원하던 대로 words 배열의 각 원소 자리에 서로 다른 메모리에 할당되어있는 문자열들의 주소가 담긴다. 

    또한 strdup 함수는 내부적으로 문자열의 길이(+1)에 딱 맞는 크기의 메모리를 할당하기 때문에

    길이가 다른 여러 개의 단어들을 각 단어에 맞는 크기의 배열에 담을 수가 있다. 

     

     

    문자열을 파일로부터 입력받아 저장하기

    #include <stdio.h>
    
    void main() {
    	FILE * fp = fopen("input.txt", "r");
        char buffer[100];
        
        while (fscanf(fp, "%s", buffer) != EOF)
        	printf("%s ", buffer);
            
        fclose(fp);
    }

    문자열을 키보드에서 입력받지 않고 파일을 불러와 입력받고 저장하는 예제이다.

     

    파일 읽기

    FILE *fp = fopen("input.txt", "r") //읽기 모드로 파일 불러와 파일타입 포인터 변수에 저장
    
    fclose(fp); //파일 닫기

     

    파일 쓰기

    FILE *fp = fopen("output.txt", "w");//쓰기 모드로 파일 불러와 파일타입 포인터 변수에 저장
    
    fclose(fp); //파일 닫기

     

    댓글

Designed by Tistory.