함수 C스타일 문자열 매개변수
목차
함수의 매개변수로 문자열을 전달하는 내용에 대해서 알아보겠습니다. C++ 에는 문자열을 다루는 클래스가 별도로 있습니다. C에서 다루던 문자열을 객체 지향으로 업그레이드 한 것 입니다.
C++의 학습에도 원초적인 C스타일 문자열의 특성을 들여다 보면 원리를 이해하는데 도움이 됩니다. 또 함수의 매개변수에 이것이 어떤 의미가 있는지, 또 배열과는 무슨 상관이 있는지도 확인해 보면 C++의 깊은 세계를 음미할 수 있을 것 입니다.
문자열이란?
문자열은 무엇일까요? 말 그대로 문자의 열입니다. 배열이 요소들의 열이라는 것과 공통점입니다. 열을 이루고 있습니다. 열이란 오와 열을 맞출 때 쓰는 말 처럼 어떤 기준을 가지고 줄을 서는 것 입니다. 간격을 맞추서 연속되게 줄을 서기 때문에 순서를 매길 수 있습니다. 프로그래밍 언어에서 순서가 있는 데이터를 일반적으로 씨퀀스(sequence)라고 합니다. 씨퀀스의 특징은 배열처럼 인덱스가 사용가능합니다. arr[0], arr[1], 이렇게 순서가 유지됩니다.
C스타일의 문자열은 배열의 한 종류 입니다. char 형 배열이라고도 할 수 있죠. 사실 CPU의 입장에서는 문자열이나 int 형이나 별 차이가 없겠지만 인간의 입장에서는 문자열이 중요합니다. C++만 아니라 거의 모든 프로그래밍 언어에서는 문자열을 다루는 방법들이 잘 발달해 있습니다. 문자열은 프로그램을 만드는 사람이나 그 프로그램을 사용하는 사용자 모두에게 핵심적입니다. CPU가 0과1로 프로그램을 처리할 때 사람은 문자열을 보고 정보를 해석하기 때문입니다.
지금은 객체 지향 언어가 발달해서 C스타일의 원초적인 문자열을 사용할 일은 거의 없어지고 있지만 학습적인 의미는 있습니다. 먼저 C스타일 문자열은 다음과 같습니다.
char myStr1[10]="Hello"; char *myStr2="World";
char 형 배열이나 포인터 방식으로 선언합니다. 이 두 방식의 차이점은 저 숫자 10에 있습니다. char 형 배열에 10개의 요소를 담는 것이니까 10바이트입니다. Hello 는 아스키코드로 5바이트니까 충분히 들어갑니다.그런데 “Hello my Friend” 처럼 글자가 10바이트를 초과하면 컴파일 오류가 납니다. 저장할 공간이 부족하기 때문입니다. 반면 myStr2 는 포인터이므로 상관이 없습니다. myStr2 에 저장하는 것은 “world” 라는 문자열이 저장된 첫번째 메모리 주소입니다.
또 한가지 알아야 할 것은 C스타일의 문자열의 끝에는 NULL 문자가 들어갑니다. Hello 라는 문자를 위한 공간이 5바이트가 필요할 것 같은데 그렇게 하면 컴파일 오류가 납니다. 1바이트는 NULL 문자로 확보를 해야 합니다. C언어의 printf 등 문자열을 다루는 함수는 문자열을 하나씩 처리하다가 배열의 요소에 NULL이 나오면 마지막 문자인 것을 알 수 있습니다.
NULL 값은 아스키 코드의 ‘\0’을 의미하며 실제 메모리에서 확인하면 0 입니다. 아스키 코드표 (ASCII CODE) “Hello” 같은 문자열 리터럴도 마찬가지로 끝에 NULL 값을 저장해서 문자열의 바이트보다 + 1로 계산할 수 있습니다.
함수의 매개변수 문자열
그럼 문자열을 함수의 매개변수로 사용해 보겠습니다. C++ 헤더가 <cstring> 이 C스타일이고 <string> 이 C++ 의 문자열 클래스가 들어 있는 라이브러리 입니다. 여기서는 cstring 을 include 합니다. strlen 같은 추억의 라이브러리 함수들이 들어있습니다.
아래의 예제는 포인터로 선언한 문자열과 배열로 선언한 문자열을 함수에 전달해서 몇 개인지 카운팅하여 출력합니다. 결과는 둘다 카운팅이 되는데 배열은 10바이트지만 문자가 저장된 바이트까지 읽고 종료합니다. 마지막 문자 다음의 널문자열 (‘\0’)에서 종료하도록 했기 때문입니다.
#include<iostream> #include<cstring> #define SIZE 5 using std::cout; using std::endl; using std::cin; void showCStringCount(const char *str); int main() { char * myCString="Hello"; cout << "strlen function : " << strlen(myCString) << endl; showCStringCount(myCString); char myStringArray[10]="World"; showCStringCount(myStringArray); return 0; } void showCStringCount(const char *str) { int count=0; cout << "index, char" << endl; cout << "-----------" << endl; while(*str) { cout << count << " , " << *str << endl; count++; str++; } cout << "string count : " << count << endl; } [실행결과] strlen function : 5 index, char ----------- 0 , H 1 , e 2 , l 3 , l 4 , o string count : 5 index, char ----------- 0 , W 1 , o 2 , r 3 , l 4 , d string count : 5
C스타일 문자열 리턴하기
다음은 C스타일 문자열을 리턴하는 방법입니다. 아래 코드는 두개의 문자열 포인터를 받아서 합친 후에 리턴하는 함수의 예제입니다. 예제를 위한 코드이므로 실용적이지는 않지만, 리턴값을 받는 방식에 포커스를 맞춰 봅니다. myConcat 함수는 char 형 포인터를 받게 되어 있고 const char *를 매개변수로 받습니다. 이것의 의미는 함수 내에서 매개변수의 원본에 접근하지 않겠다는 것입니다.또 함수의 최종 결과물은 포인터로 리턴하겠다는 것인데 함수 안에서 동적 메모리를 할당해서 리턴하는 형태입니다.
new 키워드로 메모리를 할당하는 과정이 있습니다. 함수의 호출이 종료되면 지역변수들은 사라지지만 포인터에 할당된 메모리는 유효합니다. 한편 이 메모리의 해제는 바깥에서 해줘야 합니다. new char[] 의 배열형식으로 할당 해줬으니 해제도 delete []ptrStr 로 배열을 해제합니다. 함수 내부에서 메모리를 할당하고 바깥에서 해제하는 방식은 썩 좋은 방식은 아니지만 중요한 것은 할당된 메모리의 주소를 리턴합니다.
#include<iostream> #include<cstring> #define SIZE 5 using std::cout; using std::endl; using std::cin; char * myConcat(const char * str1, const char * str2); int main() { char *myStr1="Hello My Friend!"; char *myStr2="We meet again!"; char *ptrStr; ptrStr=myConcat(myStr1, myStr2); // concatenated string cout << ptrStr << endl; delete []ptrStr; return 0; } char * myConcat(const char * str1, const char * str2) { int l1=strlen(str1); int l2=strlen(str2); int size=l1+l2; char * ptrStr=new char[size+1]; ptrStr[size]='\0'; int count=0; for (int i = 0; i < l1; i++) { ptrStr[count]=str1[i]; count++; } for (int i = 0; i < l2; i++) { ptrStr[count]=str2[i]; count++; } return ptrStr; } [실행] Hello My Friend!We meet again!
요약
C++에서 문자열은 string 클래스를 사용해서 훨씬 수월하게 다룰 수 있습니다. 그럼에도 c스타일의 문자열에 대해서 배우는 것은 원리적으로 깊은 이해를 하기 위해서 입니다. 현재는 응용 프로그램을 만드는데 사용하는 대부분의 언어들이 이런 고민을 할 필요가 없지요. 문자열 클래스는 (string class) 오늘날의 어떤 고수준(high level) 언어에서도 쉽게 볼 수 있습니다. 일반적인 문자열 클래스는 프로그래머가 문자열 함수를 쉽게 사용하라고 만들어 놓은 것 입니다.
C언어를 사용하던 시절에 문자열 프로그래밍을 하면서 얼마나 많은 사람들이 고생을 했는지 현대의 문자열 클래스는 정말 편하게 사용할 수 있도록 되어 있습니다.
컴퓨터가 사용하는 데이터는 궁극적으로는 0과 1이지만 그 중에서도 인간에게 중요한 것은 문자열입니다. 프로그래머나 사용자나 결국은 문자열을 보기 위해 컴퓨터를 쓰는 것 입니다. 문자열을 처리하는 라이브러리가 제대로 갖춰있어야 뭘 할 수 있습니다.
여기서는 함수에서 C스타일 문자열을 조금 다루어 봤습니다. 포스팅 한 개에서 방대한 C의 문자열 세계를 다루는 것은 어렵고 관련 주제가 나올 때 하나씩 설명하고 또 문자열 전체를 한번 정리하도록 하겠습니다. C++은 하나의 언어라기 보다는 컴퓨터 소프트웨어 전체를 설명할 수 있는 방대한 시스템이기 때문에 한번에 이해가 잘 안된다고 스트레스를 받을 필요가 없습니다. 그런 심리적인 요인이 C++ 학습에 장벽이 되고 많은 사람들이 C++을 그만두는 이유가 되기도 합니다.
경험 상 보면 C++은 컴퓨터 기술을 진지하게 이해하려고 할 때 깊이를 느낄 수 있습니다. 어떤 기술을 배워서 써먹는다는 생각이라면 그것도 맞는 말이지만 그러기엔 C++은 다른 언어들에 비해 오래 걸립니다. 그런 부분은 이 포스팅 시리즈를 시작하면서 언급하기도 했는데요. (C++ 시작하기 – C++ 자습서 1)
이 C++ 자습서는 영어권에서 사용하는 tuturial (튜토리얼) 개념으로 한국말로는 참고서, 자습서, 따라해보기 정도로 만들고 있습니다. C++을 배우는데 중요한 것은 물론 지식이겠지만 멘탈적인 부분도 못지 않게 중요하다고 생각합니다. C++은 중도 포기를 많이 하기 때문입니다.
그리고 포기를 하는 대다수는 이유는 C++을 쉽게 배울 거라는 잘못된 기대감이 있기 때문이라 생각합니다. 배우기 쉬운 언어는 많습니다. 예를 들어 파이썬은 초반에 시작하기 쉬운 언어에 속합니다. 대체로 스크립트 언어들은 컴퓨터를 조금 만질 줄 알면 사용할 수 있습니다. 그것은 사람이 문자열을 사용하는 방식을 최대한 배려하는 언어입니다. 하지만 C나 C++ 의 방식은 사람보다 컴퓨터를 우선으로 놓고 보기 때문에 배우기가 어렵습니다. 실력이 안느는 것 같은 시간이 오래 지속됩니다. 하지만 오래 사용하면 그 진가를 드러냅니다. C++ 이 항상 옳다는 것은 아닙니다. 언어는 각각 개발된 목적에 따라 사용됩니다. 하지만 진지하게 컴퓨터를 하드웨어 레벨에서 바라보는 마인드가 있다면 C++은 훌륭한 언어입니다.
파이썬같은 스크립트 방식에서는 print(“hello”) 로 충분하지만 C++에서는 이것을 포인터로 하나씩 분해해서 보는 것으로 확실히 C++이 컴퓨터 구조에서 더 깊은 지식은 맞습니다. 이런 것들이 과거부터 커뮤니티에서는 오랜 논쟁이 대상이긴 하는데 때로 다수가 감정적으로 치우치는 경우도 있었습니다. 그러나 그냥 컴퓨터의 지식을 추구하고 사랑하는 사용자에게는 누가 더 나은 프로그래머인가 뭐가 더 좋은 언어인가 따지는 것은 의미가 없습니다. 시간이 지날 수록 사용하기 쉬운 언어가 계속 등장할 것 이고 그 원리적인 밑받침은 C++ 같은 시스템 프로그래밍 언어가 상당수 담당할 것이기 때문입니다.
해외 커뮤니티에도 언어에 대한 논쟁이 끓이지 않는데 대부분은 언어 자체에 대한 내용보다는 당시의 연봉에 관한 논쟁이었습니다. 연봉은 컴퓨터 기술의 영역은 아닙니다. 연봉을 얼마 받느냐는 경제학에서의 수요와 공급 법칙과 그 나라와 산업의 노동 환경에 의해 결정되는 것이지 연봉의 차이가 지식의 깊이와 100% 비례하지 않습니다.
어쨋든 오랜만에 잡설을 썼는데 C++은 참 어려운 언어입니다. 설명하기도 어렵고 듣고 이해하기도 어렵습니다. 뭐 개념 하나 듣는데 10분 20분은 그냥 지나갑니다. 어려운 것을 어렵다고 알지 못하면 더 힘들기도 합니다. 그래서 C++하다가 지칠 때는 좀 더 쉬운 언어들(타이핑이 적은 언어들)로 코드를 짜보는 것을 추천합니다.