언리얼 - C++ 21 : 문자, 문자열

김정환·2025년 3월 20일

Unreal C++

목록 보기
21/37

메모리 영역

  1. 스택 영역 - 함수가 사용하는 영역
  2. 데이터 영역 - 프로그램 실행 시, 사용하는 영역
  3. ROM(Read Only Memory, 코드 영역) - 읽기 전용
  4. 힙 영역

문자 Character

char 타입 : 1 Byte 정수형 자료형 (문자)

// 컴파일 시, 넣은 자료형에 맞게 해석해서 보여줌
char c = 1; // 문자 1~
bool b = 1; // true

int i = 0;

wchar_t wc = 49;	// 2 Byte 문자 (`1`)
wc = 65;			// 아스키코드 ('A')
wc = 97;			// 아스키코드 ('a')

short s = 49;		// 49(1)
  • 메모리 상에서는 65, 97의 이진수 값이지만
    프롬프트상에서는 문자로 해석되어 A, a 와 같이 보이는 것.
  • 그러므로 메모리상 1과 문자 '1'은 서로 아예 다른 값임.
char c = 1;		// 이진값 : 1, 문자 : ~~
c = '1';		// 이진값 : 49, 문자 : '1'

문자열

  • 이런 문자들을 나열한 것
wchar_t wc = 459;		// 2 Byte 공간에 459라는 수(이진수)를 넣은 것
wc = "459";				// '4', '5', '9' (문자) => 52, 53, 57 (이진수)
  • 접근 방식 자체가 다름.
    • 문자 하나하나에 대해 대응되는 이진수 값을 저장하고 이것을 문자로 해석해내는 것.
  • 문자열을 사용할 때, 그 끝이 어디인지 어떻게 알 수 있나?
    • 메모리에선 이 끝을 명시해줄 필요가 있음.
    • 0을 마지막에 넣어줌으로써 끝을 명시함.
      • 0에 대응하는 문자 : null 문자 \0
      • 공백 문자와는 아예 별개임.

문자 자료형

  • char
    • 1 Byte : 0~255
    • utf-8 해석 체계에 따라서 실제로는 절반인 0~127까지 사용
  • wchar
    • 2 Byte : 문자를 2 Byte로 표시하기 때문에 추가 표시가 붙음.
      • 16진법 표기 때와 유사하게 별개의 체계에 대한 표현법이라 생각하면 좋음
char c = 'a';
wchar_t wc = L'a';		// 문자를 2 Byte로 표시하겠다는 것.

char szChar[10] = "abcdef";
wchar_t szWChar[10] = L"abcdef";
  • 차이점 1. 문자열은 일반 배열로 표시하는 것과 좀 다름.
    • 문자열 끝에는 항상 \0가 있으므로 문자열의 크기 + 1이 실제 크기임
short arrShort1[10] = L"abcdef"; // 불가능
short arrShort2[10] = {97,98,99,100,101,102,}; // 정수형 자료형이므로 이렇게 표현

wchar_t szWChar[10] = {97,98,99,100,101,102,}; // 가능
  • 차이점 2. 초기화 방식 차이
    • 둘 다 2 Byte 크기지만, char, wchar_t는 문자를 표현하기 위한 전용 자료형임.
    • 문자열 자료형의 경우에는 정수 배열로 초기화할 순 있으나, 편의상 문자열로 초기화.

문자열과 포인터

char szChar[10] = "abcdef";
wchar_t szWChar[10] = L"abcdef";

const wchar_t* pChar = L"abcdef"; // 이렇게 가능
  • 포인터인데 값을 바로 넣을 수 있음.
  • 문자열 => 문자의 배열 => 배열의 이름은 포인터 => 배열의 시작 주소
    • 문자열의 정체 == 주소값
  • const wchar_t* pChar = L"abcdef";
    • 문자열 앞의 L : 2 Byte 형태의 문자
      • 포인터는 마찬가지로 2 Byte 문자로 받아야함. wchar_t*
    • 직접적으로 문자열을 가리키고 있음.
  • wchar_t szWChar[10] = L"abcdef";
    • 똑같이 문자열은 있음. L"abcdef0";
    • wchar_t szWChar[10] : 10칸의 공간을 마련해놓고 하나하나 값을 복사해옴.

wchar_t szWChar[10] = L"abcdef";
const wchar_t* pChar = L"abcdef"; // 이렇게 가능

szWChar[1] = 'z';		// azcdef
// pChar[1] = 'z';		// 어디를 수정하겠다는 것인가? 어떤 b를 가리키는가

차이

배열 접근

szWChar[1] = 'z'; : 값을 복사해온 배열의 b를 변경 => 복사해온 배열을 변경

포인터 접근

pChar[1] = 'z'; : 문자열 L"abcdef"의 b를 변경 => 원본을 변경

  • *(pChar + 1) = 'z'
    • 10 = 11; : 이 동작을 하겠다는 것
      • 실시간으로 코드를 수정하는 것.
    • L"abcdef" 같은 값은 ROM:코드 영역에 저장되어 수정이 불가능함.
      • 이런 값은 취합해서 특정 메모리 영역(코드 영역)에 모아서 저장하고
        사용할 때 불러와서 사용하도록 함.
      • pChar[1] = 'z'; 이 동작은 읽기 전용인 코드 영역에 직접 접근해서 수정하려고 한 것.
      • 그래서 const wchar_t* pChar;로 선언한 것. (수정을 방지한 것)
  • 이 문제가 심각한 이유는 컴파일러 오류에서 나오지 않고 런타임 중에 발생함.

문자의 표현

char : 1 Byte
wchar_t : (Wide Byte) 2 Byte, L을 붙여 사용.

멀티 바이트

  • 모든 문자를 1 Byte로 표현하진 않음.
  • 문자에 따라 가변 길이로 대응함.
    • 어떤 문자는 1 Byte로 대응하지만
      다른 몇몇 문자는 2 Byte 길이를 사용하기도 함.
char sz[10] = "abc한글";
  • 한글의 경우 1 Byte로 표현할 수 없음.
  • 이렇게 복합적으로 사용할 경우, 모두 1 Byte로 사용하진 않고
    배열을 2 Byte 씩 다시 묶어서 한글을 표현.
    • 상황에 따라 가변적으로 사용한다는 것

유니코드

  • 이 멀티 바이트는 더 이상 표준으로 사용하지 않고 있음.
    • 멀티바이트에서는 비트를 가지고 한 글자가 몇개로 구성됐는지 체크하는 불편함이 있었음.
  • 현재는 모든 문자를 2 Byte (wide byte)로 표현하는 유니코드 시스템을 사용
    wchar_t sz[10] = "abc한글";
    • 한 칸 한 칸에 글자를 사용.
    • 대체라 많은 SW에서 기본값으로 유니코드를 사용하는 추세

문자열 길이

#include <wchar.h>

int main()
{
    wchar_t szName[10] = L"Raimond";	// 문자 자체는 7개

    // 문자열의 길이를 반환.
  	int iLen = wcslen(szName);			// const wchar_t* 요구함 (읽기 전용)
    // 배열 이름 넣어주어도 상관없음. 배열의 이름은 시작 주소이므로
}
  • L"Raimond" 의 위치
    • ROM(ReadOnly Memory) 읽기 전용 영역에 저장. => 더 정확하게는 데이터 초기화 영역에 있음.
      • 코드 영역
      • 데이터 초기화 영역
  • int wcslen(const wchar_t* wCharArr) : 문자열의 길이를 반환
    • const 포인터 요구 : 포인터(주소값)를 받되 읽기 전용으로만 쓸 것이다를 명시

직접 구현

unsigned int GetLength(const wchar_t* _pStr)
{
    int i = 0;
    while (_pStr[i] != '\0') //(*(_pStr + i) != '\0')
    {
      ++i;
    }
    
    return i;
}

습관

while ('\0' != _pStr[i]) 
{
  ++i;
}
  • 혹여나 실수해서 \0 = _pStr[i] 이와 같이 작성하더라도 컴파일 오류로 알수 있음.
while (_pStr[i] = '\0') 
{
  ++i;
}
  • 아래의 경우 실수하면 바로 알 수가 없다보니 발견하기 어려울 수 있음.

문자열 이어 붙이기

#include <wchar.h>

int main()
{
	wchar_t cArr[10] = L"tttttt";
	wchar_t c2Arr[20] = L"aaaaaaa";
	wcscat_s(c2Arr, cArr); // aaaaaaatttttt
    
	return 0;
}
  • wcscat_s(wchar_t* destination, const wchar_t* source)
    • wchar_t* destination : 문자열을 더할 결과 변수는 일반 포인터로 받음
    • const wchar_t* source : 문자열에 더할 변수는 const 포인터로 받음 (읽기)
  • 오버로딩 되어 있어서 2가지 방법을 쓸 수 있음.

함수 오버로딩 Overloading

  • 같은 이름의 함수를 매개변수를 달리하여 여러 타입의 매개변수로 사용할 수 있도록 하는 것

함수 오버라이딩 Overriding

  • 클래스의 상속관계에서 발생하는 것으로
    상위 클래스에서 선언된 가상 virtual, 추상 abstract 메서드를
    하위 클래스에서 재정의하는 것.

직접 구현

void Concat(wchar_t* _dest, unsigned int _bufSize, const wchar_t* _src)
{
	// _bufSize : 합치다가 원본의 크기를 넘어서면 안되기 때문에 예외처리용으로 받는 것
    // 1. dest 문자열 끝 확인
    // 2. dest 문자열의 마지막부터 src의 첫글자 ~ 마지막까지 넣기
    // 3. src 마지막을 만나면 정지

    int destLen = wcslen(_dest);
    int srcLen = wcslen(_src);

    if (destLen + srcLen > _bufSize - 1) // // null 문자 고려
    {
        assert(nullptr);
        return;
    }


    for (int i = 0; i < srcLen + 1; ++i)  // null 문자까지 포함
        _dest[destLen + i] = _src[i];
}

tip

  • 기능을 작성할 때, 먼저 해야할 작업을 정리하고 진행하는 습관을 들일 것.
  • 큰 기능을 만들다보면 도중에 어떤 작업을 해야하는지 모호해지는 경우가 생김.
    • 이 부분에서 생산성 차이가 발생.
    • 또한, 누군가에게 전달할 때도 작업 진행도를 알려줄 수 있음.
    • 또는, 어떤 상황에 막혔을 때, 정확히 어디에서 막혔는지 알려줄 수 있음.
  • 이런 습관을 들이는 것이 쉽지 않은 것은
    우연하게도 이 정리하지 않고 급하게 한 작업이 우연찮게 빠르게 작업을 끝낸 것.
    • 억지부려서 이 맛을 들이면 문제가 크게 발생하는 것.
  • 처음부터 이런 건 맛을 들이면 안됨.
    차라리 정리해서 차근차근 넘어가는 것이 더 좋음.

문제

wcscmp 구현

  • 두 문자열을 받아서, 양 문자열을 비교하는 것
  • 결과
    • 완벽히 일치하면 0
    • 첫번째 매개변수가 크다 : 1
    • 두번째 매개변수가 크다 : -1
int Compare(const wchar_t* _str1, const wchar_t* _str2) 
{
	// str1를 기준으로 순회
	// 결과가 같은데 str1의 길이가 더 짧다면 str2가 큰것
	// 결과가 같은데 str1의 길이가 더 길다면 str1이 큰것
	// 순회 중, 값이 다르다면 값이 큰 것을 반환
	
	int str1Len = wcslen(_str1);
	int str2Len = wcslen(_str2);

	for (int i = 0; i < str1Len; ++i) 
	{
		if (i >= str2Len)
			return 1;

		int iStr1 = (int)_str1[i];
		int iStr2 = (int)_str2[i];

		if (iStr1 > iStr2)
			return 1;
		else if (iStr1 < iStr2)
			return -1;
		else
			continue;
	}

	if (str1Len == str2Len)
		return 0;
	else
		return -1;
}
  • 문제를 풀다가 어떻게 해야할지 감조차 잡히지 않을 때가 있음.
    • 이때 포기하지 말고, 포럼, 질문, 검색 등등으로 계속 시도해볼 것
    • 그러다보면 대략적인 윤곽이 잡힘.
      그렇게 조금씩 힌트가 쌓이며 구체적으로 보이는 것.
    • 그렇게해서 1주일이든 2주일이든 해낸다는 경험이 중요한 것.
      • 이런 문제해결 경험이 중요한 것.
profile
만성피로 개발자

0개의 댓글