C++ (14) - 문자열
본 내용은 어소트락 무료 강의 <C/C++ 무료강의>을 기반으로 공부한 내용을 정리한 것이다. 틀린 내용이 있을 수도 있음.
문자열 (1)
문자를 나타내기 위한 자료형으로 두 가지가 존재한다.
1. char (1 byte)
2. wchar (2 byte)
표현 방식 자체는 정수형이다. 이를 배열로 선언하면 문자열을 만들 수 있다.
wchar는 문자 하나를 2바이트로 표현하겠다는 뜻이다.
1바이트로 나타낼 수 있는 정수 가짓수는 0 ~ 255로 알고 있지만, 맨 앞 비수가 1을 써야되기 때문에 결과적으로 7비트만 사용하여 0 ~ 127 까지를 나타낸다.
때문에 아스키 코드 표에도 127까지만 존재한다.
한글도 각 문자에따라 숫자가 배정되어 있으며, 1바이트 보다는 2바이트가 한글의 여러 가짓수를 표현하기에 적합하다.
문자열에 각 바이트마다 넣는 코드는 다음과 같다.
// 1. char의 경우
char szChar[10] = "abcdef";
// 2. wchar의 경우
wchar_t szWChar[10] = L"abcdef";
이렇게 wchar는 char와 다르게 L표시를 해준다. 이게 2바이트를 문자 표현시 사용한다는 뜻!
예시 코드의 경우 문자 인덱스의 개수는 10개이지만 6개의 문자가 들어갔다. 그렇게 되면 나머지 f 뒤로 4칸은 모두 0으로 초기화가 된다.
그렇다면 두 자료형만 문자열 초기화가 가능할까? 거의 그렇다고 보면 된다. short를 예시로 보여주겠다.
// 3. short의 경우?
short arrShort[10] = {97, 98, 99, 100, 101, 102, ,};
short는 앞의 두 자료형과 달리 문자 대입시 아스키 코드표를 참조한 값을 넣어야 한다. 떄문에 char, wchar만 사용하는게 좋다.
앞전에 4개의 메모리 영역 중 읽기 전용 영역인 ROM에 대해 언급했었다. 다음 코드를 보자.
const wcahr_t* pChar = L"abcdef";
포인터 개념과 함께 생각해보자. 이건 앞전과 무슨 차이를 보일까?
여기서 유추할 수 있는 것은 포인터를 사용했다는 것은 문자열이 반환하는 것이 주소값이었기에 반환할 수 있었다는 것을 알 수 있다.
떄문에 문자열은 어딘가의 주소를 주는 것이라고 본다. 그래서 주소 변수를 통해 값을 받고 주소를 통해 가보면 그곳에 문자가 한 칸에 2바이트씩 차지하고 있으며, 그 문자가 시작되는 a의 시작 주소를 받아왔다는 것이 된다.
그렇다면 배열은 무슨 의미인가? 문자열이 있고, 그것을 충분히 저장할 수 있는 바이트를 준비해놓고 만들어낸 배열로 그 문자를 하나씩 복사해서 옮겨 오는 것이다.
// 1. "배열"의 b가 z로 바뀜.
szWChar[1] = 'z';
// 2. 주소값의 2바이트 만큼 다음으로 이동해 그 값인 b를 z로 변경.
pChar[1] = 'z';
이 두 경우를 구분하는 것이 중요하다!!
문자열은 내가 작성한 코드 그 자체이다. 따라서 배열의 초기화 구문이 의미하는 바는 프로그램 실행 시 스택 메모리의 문자를 복사하는 것이다.
포인터는 메인 함수 스택에 포인터 변수가 있고, 작성해놓은 코드 자체가 어딘가에 있을 때, 그곳의 문자 코드 시작 주소를 포인터 변수에 준 것이다.
ROM은 실행 도중 변경될 수 없는 공간이다. 애초에 실행 중 코드가 변경될 수 있는 프로그램은 존재하지 않는다. 코드 상에 작성 된 문자열은 따로 저장이 되어있다.
때문에 앞서 const 예시 코드가 const를 사용한 이유가 바로 주소값의 변경이 이루어지지 않도록 하는 목적이다. 문자열의 반환 타입이 수정할 수 없는 타입이기 때문에 const 포인터로 맞춰서 받아와야 한다.
따라서 읽기 전용 영약에 존재하는 주소값을 변경하면 안되기 떄문에 포인터를 잘 다루어야 한다.
문자열 (2)
특정 문자를 1바이트로 표현하고 다른 문자들을 2바이트로 표현하고자 하는 방식을 멀티 바이트 표현방식이라 한다.
이때 문자에 따라 가변 길이로 대응한다.
char szTest[10] = "abc한글";
wchar_t = szTestW[10] = L"abc한글";
a, b, c 는 1바이트, 한글은 2바이트로 표현한다.
근데 여기서는 멀티 바이트 방식은 자세히 다루지 않겠다.
이번엔 문자열 안에 문자가 몇개 있는지 확인하는 법을 보자.
#include <wchar.h>
wcahr_t szName[10] = L"Raimond";
int ilen = wcslen(szName);
헤더파일을 참조해 함수를 불러오고 이를 이용해 문자열의 길이를 알아낸다.
문자의 끝을 알려주는 0 은 개수에 포함되지 않는다.
여기서 해당 함수는 const 포인터 const wchar_t*를 요구한다. 이유는 시작 주소를 알아야 길이를 잴 수 있으니 문자열의 초기 주소값을 받아야 하기 때문이다.
그리고 주소를 전달하는 과정에서 주소의 값이 변경될 우려가 있으며, 함수의 목적이 문자열의 길이 체크 용도지 값을 변경하는 함수가 아니기 때문에 값을 변경하지 않는 const 포인터를 사용하는 것이다.
다음 목차에서 이 함수를 헤더파일을 참조하지 말고 직접 만들어보자.
문자열 (3)
2바이트 문자 길이를 알려주는 함수를 만들어보자.
#include <stdio.h>
unsigned int GetLength(const wchar_t* _pStr)
{
int i = 0;
while ('\0' != _pStr[i]) // 횟수 제한을 모르니 while문을 사용, 해당 문자의 주소갑의 인덱스가 0이 아닐 경우 실행.
{
wchar_t c = *(_pStr + i); // 주소값 첫번째 위치로부터 i번째 값을 포인터 변수 저장.
if ('\0' == c) // 만약 그 값이 null문자 이면
{
break; // 반복문을 종료
}
++i; // 아닐 경우 인덱스의 수 i값을 1 증가.
}
return i; // i값 = 총 문자 길이 수 를 반환한다.
}
int main()
{
wchar_t szName[10] = L"abcdef";
int A = GetLength(szName);
printf("%d", A);
return 0;
}
길이값을 저장한 A를 출력 시 abcdef 6개이므로 6이 출력된다. 함수 내 while 구문을 더욱 간단하게 하면 다음과 같이 만들 수 있다.
while ('\0' != _pStr[i])
{
++i;
}
return i;
문자열 (4)
하나의 문자열과 다른 하나의 문자열을 합칠 수 있다.
wchar_t szString[100] = L"abc";
wcscat_s(szString, 100, L"def");
wcscat_s(szString, 100, L"ghi");
wcscat_s() 함수를 이용해 다음과 같이 문자열을 계속 붙혀 나갈 수 있다. 이때 해당함수에서 abc를 목적지(destination) 상수, def, ghi와 같이 불러올 값을 소스 상수(source)를 요구한다.
이처럼 동일한 이름의 함수가 여러개 만들어 질 수 있는 것이 오버로딩이라 한다.
이전 목차처럼 이 기능도 직접 구현해보자.
#include <stdio.h>
#include <assert.h> // assert 헤더파일 참조 -> 예외처리 구문
void StrCat(wchar_t* _pDest, unsigned int _iBufferSize, const wchar_t* _pSrc) // 목적지 주소, 배열 공간수, 소스 주소
{
int iDestLen = GetLength(_pDest); // 목적지 문자의 길이를 함수를 사용(모듈화)후 변수값 저장
int iSrcLen = GetLength(_pSrc); // 소스 문자도 동일하게
if (_iBufferSize < iDestLen + iSrcLen + 1) // if 문 통해 두 문자와 null문자 (+1)를 합친게 저장 공간을 넘어서면
{
assert(nullptr); // assert매크로 구문을 통해 예외처리
}
iDestLen; // 목적지 문자열의 끝 인덱스
for (int i = 0 ; i < iSrcLen + 1; ++i) // 반복적으로 소스 문자열을 목적지 문자열 끝 위치에 복사
{ // 소스 문자열의 끝을 만나면 종료
_pDest[iDestLen + i] = _pSrc[i]; // 목적지 문자열의 끝에 소스 문자열의 첫번째를 붙힌다.
}
}
int main()
{
wchar_t szString[10] = L"abc";
StrCat(szString, 10, L"def");
return 0;
}
문자가 합쳐진 abcdef 문자열이 완성된다!!
문자열 문제 풀이 : wcscmp 함수 구현하기
wcscmp(); 함수는 두개의 문자열을 전달 받아서 두 문자의 상태값을 체크해 양쪽 문자열이 완벽하게 일치할 경우 0, 좌측 문자열의 우열이 높으면 1, 우측 문자열의 우열이 높으면 -1을 출력한다.
힌트 : 문자값을 동일한 위치별로 비교할텐데 어떤 문자의 우열이 높고 작은지를 아스키코드에서 알파벳 순서로 숫자가 증가한다는 사실과 연관지어 생각해보자.
(예시)
int iRet = wcscmp(L"abc", L"abc"); -> 0
int iRet = wcscmp(L"cbc", L"abc"); -> 1 // 알파벳 상 c가 a보다 후순위 라고 생각하면 편하다.
int iRet = wcscmp(L"abc", L"cbc"); -> -1
(나의 풀이)
int wcscmp(wchar_t* _pLeft, wchar_t* _pRight)
{
int iLeftLen = GetLength(_pLeft); // 위에서 동일하게 GetLegnth 함수 사용 (모듈화)
int iRightLen = GetLength(_pRight);
for (int i = 0 ; i < iLeftLen && iRightLen + 1 ; ++i ) // for문으로 두 문자열의 길이 + null문자 만큼 반복문 수행
{
if (_pLeft[i] == _pRight[i]) // 조건 1. 두 문자열의 각 인덱스끼리 비교시 같을 경우
{
return 0; // 1. "0"을 반환
}
else if (_pLeft[i] > _pRight[i]) // 조건 2. 두 문자열의 각 인덱스 끼리 비교시 좌측 문자열의 우열이 높을 경우
{
return 1; // 2. "1"을 반환
}
else if (_pLeft[i] < _pRight[i]) // 조건 3. 두 문자열의 각 인덱스 끼리 비교시 우측 문자열의 우열이 높을 경우
{
return -1; // 3. "-1"을 반환
}
}
}
int main()
{
int Answer = wcscmp(L"ddddffff", L"cccccccc"); // 메인 함수에서 만들어낸 wcscmp함수 호출 후 두 문자열을 받아온다.
printf("%d", Answer); // 받아온 함수 리턴값을 변수에 저장후 해당 변수를 출력
return 0;
}
(선생님 풀이) 만약 두 문자열중 하나가 더 길다면? 우열이 높은걸까 낮은걸까? 사전을 생각하면 짧은쪽이 우열이 더 높다.
int StrCmp(const wchar* _left, const wchar_t* _right)
{
int leftLen = GetLength(_left);
int rightLen = GetLength(_right);
int iLoop = leftlen;
int iReturn 0; // 두 문자열 길이가 같을 경우
if(leftLen < rightLen) // 왼쪽 문자열이 더 작을 경우
{
iLoop = leftLen;
iReturn = -1;
}
else if(leftLen > rightLen)
{
iLoop = rightLen;
iReturn = 1;
}
for (int i = 0; i < iLoop; ++i>)
if (_left[i] < _right[i])
{
return -1;
}
else if (_left[i] > _right[i])
{
return 1;
}
return iReturn;
}
int main()
{
iRet = StrCmp(L"abc", L"abcdef")
}