44.1 문자열 안에서 문자로 검색하기
#include <stdio.h> #include <string.h> int main() { char s1[30] = "A Garden Diary"; char *ptr = strchr(s1, 'a'); while (ptr != NULL) { printf("%s\n", ptr); ptr = strchr(ptr + 1, 'a'); //의문이 있는 지점입니다. } return 0; }
개념 자체에 대한 질문이 있습니다.
UNIT39(문자열 사용하기)에서 배우기로는, 문자열 포인터(char*s1)에 일단 문자열(char*s1="Hello";)이 할당되면 다른 문자열("s1="Word";(불가능))은 할당될 수 없는 것으로 알고 있습니다.
그런데 44단원에서는 문자열 포인터(char*ptr)에 값을 할당을 하고 나서
char *ptr = strchr(s1, 'a');바로 그 문자열 포인터에 다른 값을 할당할 수 있는 이유가 이해되지 않습니다!
while (ptr != NULL) { printf("%s\n", ptr); ptr = strchr(ptr + 1, 'a'); //의문이 생기는 부분입니다. }
고민한 끝의 제 생각으로는, 이미 선언된 배열에 새로운 배열의 값을 할당할 수 없지만,
UNIT42(문자열을 복사하고 붙이기)의 strcpy를 통해 그것이 가능하게 된 것처럼, 그냥 받아들여야 하는 건가 싶습니다.
질문: 이미 값이 할당된 문자열 포인터인 ptr에 또 다른 값이 재 할당 될 수 있는 이유가 궁금합니다
char* s1="Hello";
여기서 "Hello"는 컴파일 과정에 자동으로 포함되는 값입니다. 리터럴 문자열이죠. 이는 읽기 전용입니다.
char *ptr = strchr(s1, 'a');여기서의 ptr은 해당 위치는 가리키는 용도로 사용했을 뿐입니다. 읽기 전용이 아닙니다.
char* s1 = "Hello";는 컴파일하면서 읽기 전용으로 할당되어 있는 것입니다. 이러한 대입 연산자는 선언과 할당을 동시에 할 때만 허용됩니다.
"Hello"가 읽기 전용이라는 뜻입니다. s1은 포인터이고, 포인터가 가리키는 대상 자체는 바꿀 수 있습니다. 다만, 다시는 "Hello"를 가리킬 방법이 없겠지요.
char* s1 = "Hello";
char* s2 = "World";
s1 = s2;
이렇게 하고 s1을 출력하면 "World"가 출력됩니다.
"Hello"와 "World"는 읽기 전용입니다. 따라서 이 값을 바꾸는 건 불가능합니다.
s1[0] = 'C';
이렇게 바꾸는 건 안 됩니다. "Hello"는 읽기 전용이니까 "Cello"로 바꿀 수 없습니다.
하지만, s1이 가리키는 대상 "Hello"인데, s1이 "World"를 가리키게 할 수는 있습니다.
s1 = s2;
이렇게 하면 되겠지요.
그러나 이제 s1 -> "Hello"를 가리키던 관계는 잃어버렸습니다. 다시 "Hello" 읽기 전용 문자열이 있는 메모리 위치를 가리킬 방법은 없어졌습니다.
char* s1 = "Hello";
char* s2 = "World";
s1 = s2;
s1 = "Hellooooooooo";
이렇게 해도 가능합니다. "Hellooooooooo"도 코드에 작성한 값이므로 리터럴 문자열입니다. 리터럴은 컴파일하면 코드에 값이 들어가 있습니다.
파일에서 데이터를 읽거나 사용자가 입력한 값이 아니니까요.
출력하면 "Hellooooooooo"가 잘 출력됩니다.
포인터인 s1 자체는 읽기 전용이 아닙니다. s1이 가리키는 주솟값은 얼마든지 바뀔 수 있습니다. 리터럴 문자열이 읽기 전용입니다.
그렇다고 해서 이게 C 언어의 이상한 부분인 건 아닙니다. 정확하게 배우는 겁니다. 하드웨어와 가까워서 그렇죠.
자바나 파이썬 같은 언어는 이런 게 없어 보이죠.
>>> id(s1)
64147168
>>> s1[0] = 'C'
Traceback (most recent call last):
File "<pyshell#2>", line 1, in <module>
s1[0] = 'C'
TypeError: 'str' object does not support item assignment
>>> s1
'World'
>>> id(s1)
64351136
>>>
자바에서도 간단히 자바 문법을 테스트할 수 있는 jshell이 있습니다.
jshell> String s1 = "Hello"
s1 ==> "Hello"
문자열 s1에 "Hello"를 할당했습니다.
jshell> s1.hashCode()
$2 ==> 69609650
파이썬은 id() 함수였고, 자바에서는 hashCode() 메서드입니다.
C 언어라면 s1의 포인터 주소를 printf에서 %p로 출력한 거라 생각하면 됩니다.
jshell> s1[0] = 'C'
| Error:
| array required, but java.lang.String found
| s1[0] = 'C'
| ^---^
원소 하나 바꾸기는 안 됩니다.
jshell> s1[0] = "C"
| Error:
| array required, but java.lang.String found
| s1[0] = "C"
| ^---^
원소 하나를 문자열로 바꾸는 것도 안 됩니다.
jshell> s1 = "World"
s1 ==> "World"
변수 s1에 "World"는 됩니다.
jshell> s1.hashCode()
$4 ==> 83766130
hashCode()를 찍어보면 값이 바뀌었습니다. s1이 가리키는 메모리의 대상이 바뀐 것이죠. 문자열은 메모리 어딘가에 반드시 있어야 하고, 그 메모리 위치를 가리킨다고 보면 됩니다.
컴퓨터는 메모리를 주솟값으로 접근하기 때문에 어떤 프로그래밍 언어를 쓰든 주솟값으로 접근합니다. 그걸 포인터라고 하든, id라고 하든, hashCode라고 하든 말이죠. 이 값이 바뀌었다면 같은 대상을 가리키는 게 아니라는 것은 알 수 있죠.
개념만 정확하게 알면 됩니다. C 언어라서 어렵고, 파이썬이라서 쉽고, 그런 게 아닙니다.