32. 문자열
1. 키워드
- 문자열
2. 문자열 사용하기
- C에서는 문자 자료형인
char
는 있지만 문자열을 저장하는 자료형은 없다. - 그래서 다음과 같이
char
에 문자열을 저장하면 컴파일은 되지만 실행은 안 된다.
#include <stdio.h>
int main()
{
char s1 = "Hello"; // "Hello"는 문자열, 문자열은 ""로 둘러쌈
printf("%s", s1); // 실행 에러
return 0;
}
3. 문자와 문자열 포인터 알아보기
- 문자열은
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
에 저장된 상태를 나타낸다.
- 문자열은
char *s1
처럼 포인터를 이용해서 저장한다. - 포인터는 메모리 주소를 저장하므로 메모리 주소를 보고 문자열이 저장된 위치로 이동하게 된다.
문자열의 메모리 주소 출력
printf
로 문자열 대신 문자열이 저장된 메모리 주소를 출력하고 싶다면 서식 지정자로%p
를 사용하면 된다.
- 문자는 변수
c1
안에 그대로 저장되지만 문자열 리터럴이 변수s1
안에 저장되지 않고, 문자열이 있는 곳의 메모리 주소만 저장된다. - 단, 이 문자열 리터럴이 있는 메모리 주소는 읽기 전용이므로 다른 문자열을 덮어쓸 수는 없다.
- 또한, 문자열 리터럴이 저장되는 위치는 컴파일러가 알아서 결정하므로 신경쓰지 않아도 된다.
// ↓ 변수 안에 'a'가 그대로 저장됨
char c1 = 'a';
// ↓ 메모리에 저장되어 있음(읽기 전용)
char *s1 = "Hello";
// ↑ 문자열 리터럴이 있는 곳의 메모리 주소만 저장
- 문자열을 사용할 때는
char
타입 포인터에""
(큰따옴표)로 묶은 문자열만 할당하면 된다. - 그리고
printf
로 문자열을 출력할 때는 서식 지정자로%s
를 사용하면 된다.
- 여기서 중요한 점은 C의 문자열은 마지막에 항상 널 문자(
NULL
)가 붙는다는 점이다. NULL
은0
으로도 표현할 수 있으며 문자열의 끝을 나타낸다.- 그래서
printf
는 문자열을 출력할 때 문자열을 계속 출력하다가NULL
에서 출력을 끝낸다.
문자열의 앞과 뒤는 어디인가요?
- 보통 문자열이 시작되는 쪽을 앞, 문자열이 끝나는 쪽을 뒤라고 부른다.
- 하지만 실제 메모리 주소를 다루는 포인터 연산에서는 32비트를 기준으로 낮은 주소(
0x00000000
)가 뒤쪽(Backward), 높은 주소(0xFFFFFFFF
)가 앞쪽(Forward)이다. - 그래서 뒤쪽으로 가는 것을 역방향, 앞쪽으로 가는 것을 순방향이라고 한다.
- 여기서 문자열은 메모리의 낮은 주소부터 시작해서 높은 주소까지 저장되는데, 이 상태로는 문자열이 시작되는 부분이 뒤, 끝나는 부분이 앞이 되어버린다.
- 보통 사람은 문자열이 시작되는 부분을 앞으로 인식하므로 메모리 주소로 처리하는 방법과는 반대로 이해할 수밖에 없다.
- 그래서 메모리 주소의 앞 뒤와 구분하기 위해 문자열은 왼쪽(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
이 들어있다. NULL
은printf
로 출력해도 화면에 표시되지 않는다.
- 문자열 포인터에서 인덱스로 문자를 할당할 때 어떻게 되는지 확인해 보자.
#include <stdio.h>
int main()
{
char *s1 = "Hello"; // 포인터에 문자열 "Hello"의 주소 저장
// "Hello"가 있는 메모리 주소는 읽기 전용
s1[0] = 'A'; // 문자열 포인터의 인덱스 0에 문자 A를 할당
// 실행 에러
printf("%c\n", s1[0]);
return 0;
}
- 문자열 리터럴이 있는 메모리 주소는 읽기 전용이기 때문에 에러가 발생한다.
- 따라서 문자열 포인터는 인덱스로 접근하여 문자를 할당하면 안 된다.
5. 배열 형태로 문자열 선언하기
- 문자열은 문자(
char
) 배열에 저장할 수도 있다.
#include <stdio.h>
int main()
{
char s1[10] = "Hello"; // 크기가 10인 char 타입 배열을 선언하고 문자열 할당
printf("%s\n", s1); // %s로 문자열 출력
return 0;
}
// Hello
char s1[10] = "Hello";
와 같이 크기가10
인char
타입 배열(문자 배열)을 선언한 뒤 문자열을 할당했다.- 그리고
printf
에서 서식 지정자%s
로 배열을 출력해 보면 배열에 저장된 문자열이 출력된다.
- 문자열 리터럴을 포인터에 할당하는 방식과는 달리 문자열을 배열에 저장하는 방식은 배열 요소 하나 하나에 문자가 저장된다.
- 즉, 이렇게 배열 안에 일렬로 나열된 문자가 모여 문자열을 이루게 되는 것이다.
- 물론 배열이기 때문에 인덱스는
0
부터 시작한다. - 그리고 여기서도 문자열의 맨 뒤에
NULL
이 들어간다.
- 배열에 문자열과
NULL
을 저장한 뒤 남는 요소들은 딱히 신경쓸 필요는 없다. - 보통 남는 공간에는 모두
NULL
이 들어간다.
- 배열로 문자열을 사용할 때 한 가지 주의할 점은 배열을 선언한 즉시 문자열로 초기화해야 한다는 점이다.
- 배열을 미리 선언해놓고 문자열을 나중에 할당할 수는 없다.
#include <stdio.h>
int main()
{
char s1[10]; // 크기가 10인 char 타입 배열 선언
s1 = "Hello"; // 이미 선언된 배열에 문자열을 할당하면 컴파일 에러 발생
printf("%s\n", s1); // %s로 문자열 출력
return 0;
}
- 이미 선언된 배열에는 문자열을 할당할 수 없다.
- 정 할당하고 싶다면 다음과 같이 배열의 요소에 문자를 하나 하나 집어넣으면 된다.
- 배열을 선언할 때는 배열의 크기를 할당할 문자열보다 크게 지정해야 한다.
char s1[10] = "Hello"; // 크기가 10인 배열. "Hello"는 5글자이므로 올바른 방법
char s2[3] = "Hello"; // 크기가 3인 배열. "Hello"는 5글자이므로 잘못된 방법
- 문자열의 문자 개수보다 배열의 크기가 작다면 컴파일은 되지만, 문자열을 출력했을 때 제대로 출력되지 않는다.
- 문자열을 저장할 때 배열의 최소 크기는 저장할 문자열 크기에
NULL
하나를 더해야 한다.
- 다음과 같이 문자 배열을 선언하면서 문자열을 바로 할당할 때는 배열의 크기를 생략할 수 있다.
#include <stdio.h>
int main()
{
char s1[] = "Hello"; // 문자열을 할당할 때 배열의 크기를 생략하는 방법
printf("%s\n", s1); // %s로 문자열 출력
return 0;
}
// Hello
- 배열의 크기는 할당되는 문자열의 문자 개수에 맞춰 자동으로 지정된다.
- 여기서는
"Hello"
이므로5
에NULL
하나를 더해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"
가 되었다.- 배열 형태의 문자열은 인덱스로 접근하여 내용을 변경할 수 있다.