💡 자료구조

문자열

ji-hyun 2021. 6. 16. 20:00
char str[6];
str[0] = 'h';
str[1] = 'e';
str[2] = 'l';
str[3] = 'l';
str[4] = 'o';
str[5] = '/0'

/* C언어는 문자열을 생성하는 편리한 방법을 제공 - 2가지 */
char str[] = "hello"; //혹은
char *str = "hello";

null character('\0')는 문자열의 끝을 표시하는 역할을 한다. 즉, 배열의 크기가 문자열의 길이보다 적어도 1만큼 길어야 한다.

 

 

하지만 C언어는 배열의 각 칸마다 문자 하나씩 저장되는 방법말고도 문자열을 생성하는 편리한 방법을 제공해주는데 바로, 겹따옴표(" ")를 사용하는 방법이다.

 

char str[] = "hello";   ------①
char *str = "hello";    ------②

 

배열의 이름(str)은 배열의 시작 주소를 가리키는 포인터 변수다. 따라서 문자열을 생성할 때 위의 두번째 방법도 가능하다. 6byte의 배열을 생성하는 이 두 방법은 결과적으론 동일하지만 완전히 동일하다고 볼 순 없다. 그 이유가 뭘까?

 

 

② 방법은 이렇게 정의된 문자열은 수정이 불가능하다는 점에서 위의 두 방법과 다르다.

왜냐하면 배열의 이름(str)은 배열의 시작 주소를 가리키는 포인터 변수이고 그 값을 변경할 수 없다는 특성 때문이다. 이것을 string literal이라고 부른다. (literal 의 뜻은 보통 수정할 수 없다는 의미를 가진다)

 

 

+ ) 부가적으로 ② 의 방법은 str이 전혀 다른 배열을 가리킬 수도 있지만, 1의 방법은 그렇게는 하지 못하고 배열 안의 값은 바꿀 수 있다.

 

 

 

 

 


<string.h> 라이브러리 함수

string.h 라이브러리는 문자열을 다루는 다양한 함수를 제공한다.

strcpy - 문자열 복사 (copy)

strlen - 문자열의 길이 (length)

strcat - 문자열 합치기 (concat)

strcmp - 문자열 비교 (compare)

 

 


 

<문자열들의 저장>

 

여러 개의 단어들을 포인터를 이용하여 위의 그림과 같이 저장해보자.

 

words 배열의 각각의 칸은 char 타입이며, 단어의 주소들을 저장해야 하므로 포인터 타입이 필요하다. 즉, 각 칸은 단어의 첫번째 주소가 저장되는 형태이다.

 

따라서 변수 선언은 char *words[100] 으로 해야 한다.

 

 

 

 

 

 

잘못된 예시(하지만 학습하면 좋을 코드)

 

#include <stdio.h>
#define BUFFER_SIZE 100    // 사이즈가 100을 넘어가지 않음을 정의

int main() {
	char *words[100];
	int n = 0;  // number of strings
	char buffer[BUFFER_SIZE];

	while (n<4 && scanf("%s", buffer) != EOF) {   // 배열의 이름은 실제 배열의 시작 주소를 가지고 있는 포인터변수라고 했다.
		words[n] = buffer;   // 여기를 생각해보기
		n++;
	}

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

 

코드를 이렇게 작성한다고 할 때, 위의 그림처럼 단어들을 저장할 수 있을까?

아니다.

실제로 프로그램을 실행해보면 결과는 이렇게 나온다.

 

 

실행결과

 

 

마지막에 저장한 문자열만 계속 출력된다. 이 이유는 words[ n ] = buffer ; 에서 생각해봐야 하는데, 먼저 buffer 또한  배열의 이름이므로, 배열의 시작 주소를 저장하는 포인터 변수이다.

 

따라서 buffer 에 문자열을 입력할 때, words[ n ] = buffer ; 에서 buffer의 시작 주소가 words 의 각 칸마다 넘어가는 것이다. 그래서 최종 마지막으로 입력한 문자열에 의해서 모두 똑같은 결과를 출력하게 되는 것이다.

 

 

 

 

 

#include <stdio.h>
#define BUFFER_SIZE 100    

int main() {
	char *words[100];
	int n = 0;  
	char buffer[BUFFER_SIZE];

	while (n<4 && scanf("%s", buffer) != EOF) { 
		words[n] = strcpy(words[n], buffer);  // strcpy(str2, str1)
		n++;
	}

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

 

strcpy 라는 문자열 복사 함수를 사용하는 것을 생각해볼 수 있는데,

strcpy( str2, str1) 은 str1의 문자열을 str2의 문자열에 복사 하는 함수로 바로 위에 작성한 코드에 대해서 적용해볼 때, 이 또한 오류가 난다. 왜냐하면 buffer 는 문자열이 맞지만 words[ n ] 은 문자열이라기보단 포인터 변수(문자 배열의 주소를 저장할 포인터 변수)일 뿐 문자 배열이라고 볼 수 없기 때문이다. 그래서 우린 다음과 같은 방법을 생각해 볼 수 있다.

 

 

 

 

 

 

 

< solution >

 

#include <stdio.h>
#define BUFFER_SIZE 100    

int main() {
	char *words[100];
	int n = 0;  
	char buffer[BUFFER_SIZE];

	while (n<4 && scanf("%s", buffer) != EOF) { 
		words[n] = strdup(buffer);
		n++;
	}

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

 

strdup 함수는 매개변수로 받은 문자열을 하나의 복제본을 만들어서 그 복제본의 주소를 리턴해주는 역할을 하는 함수이다.

 

 

 

 

 

 

 

< strdup 의 실행 원리에 대해 자세히 알아보기 >

strdup 가 실행되는 동작은 다음과 같다.

 

strlen(s) + 1 을 해주는 까닭은 문자열 끝을 알리는 '\0' 까지 포함하기 위해서이고 (여기서 또 말하지만, 문자열의 길이는 배열의 길이보다 1만큼 작다)

동적할당 함수를 이용해서 이를 메모리에 직접 할당을 해준다.

 

 

strcpy 함수를 이용해서 s에 저장되어 있는 문자열을 배열 p 로 복사해준다. 그 다음 배열 p의 주소를 리턴해준다.

 

 

 

 

 

( 생각 정리 ) 

여전히 배열, 포인터 변수 간의 상관관계를 파악하는게 좀 헷갈리는 것 같다.

일단 앞에서 배운 것과 종합하여 이해하기로는 strdup 내부의 동적할당 함수를 통해서 s의 배열의 길이만큼의 공간을 직접 할당하였기 때문에 배열의 공간이 생겨났고 그래서 strcpy가 사용이 가능하며 마지막으론 포인터 변수 p를 리턴해줌으로써 시작주소를 넘긴 것이 아닐까 하고 생각해본다. 

 

 

 

'💡 자료구조' 카테고리의 다른 글

문자열 예제  (0) 2021.06.18
시간 복잡도와 빅-오 표기법  (0) 2021.06.17
동적할당 malloc 함수  (0) 2021.06.16
Binary Search vs Linear Search  (0) 2021.06.14
함수의 인자에 대해 알아보기(배열과 포인터)  (0) 2021.06.10