29
char 타입에 한글이 들어가면
한글이
char타입 변수에 저장된다는건char타입이 2바이트로 늘어나는게 아니라 한글의 바이트 단위 조각이 각각 저장된다는 뜻 한글 처리는 "문자 인코딩" 문제
| 인코딩 | 한글 1글자 크기 | |
|---|---|---|
| ASCII | ❌ (한글 없음) | |
| EUC-KR | 2바이트 | |
| CP949 (Windows 기본) | 2바이트 | |
| UTF-8 | 3바이트 | |
| UTF-16 | 2바이트 | |
| UTF-32 | 4바이트 |
Windows(CP949)
char str[] = "가";// "가" -> 0xB0 0xA1str[0] = 0xB0str[1] = 0xA1str[2] = '\0'UTF-8
char str[] = "가";// "가" -> 0xEA 0xB0 0x80str[0] = 0xEAstr[1] = 0xB0str[2] = 0x80str[3] = '\0'mblen: 멀티바이트의 길이
strlen: 문자열 전체의 길이 mblen: 주어진 문자열 시작에 있는 한 멀티바이트 문자의 길이
파일입출력
- 메타데이터를 파일로 직렬화시킬 때, 곧이곧대로 저장하면 악용될 여지가 있으므로 별도의 암호화 단계가 필요하다
Value Category
- RValue 모음
int main(){ 0; Player(); Function();}- Value Reference 에러 모음
int main(){ int& lvalue = 10; int&& rvalue = count;}- Move 생성자/대입 연산자
class Player { public: // 기본 생성자. Player() { } // 복사 생성자. Player(const Player& other) { } // 대입 연산자. Player& operator=(const Player& other) { } // Move 생성자. Player(Player&& other) { } // 오히려 const 붙이면 안된다 // Move 대입 연산자. Player& operator=(Player&& other) { }};- 복사 생성자는 무겁다
class Player { public: Player(const Player& other) : playerID(other.playerID); { //playerID = other.playerID; size_t length = strlen(other.name) + 1; // 메모리 할당은 기본적으로 운영체제 커널 함수를 호출해야하므로 무겁다 name = new char[length]; strcpy_s(name, length, other.name); // 문자열 자체도 무거움 } int playerID; char* name; };- 반면 Move 생성자는 주소만을 핸들링하므로 가볍다
class Player{public: Player(Player&& other) { playerID = other.playerID; // 주소값 복사 name = other.name; // 주소값 복사. 이동이라 표현하지만 실제론 복사 행위다 other.name = nullptr; // other란 임시 r-value의 소유권 해제 } int playerID; char* name;};#include <iostream>#include <string>class NPC{public: NPC() { std::cout << "기본 생성자\n"; } NPC(int npcCode, const std::string& name) { std::cout << "인자 있는 생성자\n"; } NPC(const NPC& other) { std::cout << "복사 생성자\n"; } NPC& operator=(const NPC& other) { std::cout << "대입 연산자\n"; return *this; } NPC(NPC&& other) { std::cout << "Move 생성자\n"; } NPC& operator=(NPC&& other) { std::cout << "Move 대입 연산자\n"; return *this; } int npcCode; std::string name;};int main(){ std::cout << "1\n"; NPC npc1(NPC(10, "Orge1")); //NPC npc1(10, "Orge1"); // 컴파일러가 이런식으로 최적화함. std::cout << "\n2\n"; NPC npc2(11, "Orge2"); NPC npc3 = npc2; std::cout << "\n3\n"; NPC npc4; NPC npc5; npc5 = npc4; std::cout << "\n4\n"; NPC npc6 = NPC(12, "Orge3"); std::cout << "\n5\n"; NPC npc7; NPC npc8; npc8 = std::move(npc7); //std::move는 인자를 R-Value로 바꿈 // 여기서부터는 npc7을 사용하면 안됨(읽히긴 하나 언젠간 해제되어 보장이 안되기 때문). std::cin.get();}#include <iostream> int main() { int lvalue1; int&& rvalueRef1 = lvalue1; // Error int&& rvalueRef2 = std::move(lvalue1); // OK. int&& rvalueRef3 = rvalueRef2; // Error. RValue 참조를 RValue 참조로 초기화할 수 x std::cin.get(); }- 템플릿의 인자추론이 망가질 때가 종종 있다. 이럴 땐
std::forward()를 사용할 것
#include <iostream>struct X{};void func(X&& x) { std::cout << "RValue\n"; }void func(X& x) { std::cout << "LValue\n"; }template<typename T>void foo(T&& t) { func(t); } // LValue LValuevoid foo(T&& t) { func(std::move<T>(t)); } // RValue RValuevoid foo(T&& t) { func(std::forward<T>(t)); } // LValue RValue, Perfect Forwarding!int main(){ X x; foo(x); foo(X()); std::cin.get();}- 깊은 복사보다 얕은 복사가 더 빠르다(힙 영역에 메모리 할당 vs 주소 소유권만 이전)
- 다만 LValue를 move로 처리한다면 기존 LValue가 쓰였던 곳의 fallback 처리를 잘 해야한다.
언리얼: 이동 시맨틱
TArray,TMap,TSet,FString과 같은 모든 주요 컨테이너 타입에는 move 생성자와 move 대입 연산자가 있습니다. 이러한 타입을 값으로 전달 또는 반환할 때 종종 자동으로 사용되지만,std::move의 UE 해당 버전인MoveTemp를 통해 명시적으로 호출할 수도 있습니다.값으로 컨테이너나 스트링을 반환하는 것은 보통 임시로 복사하는 비용이 없어 표현성에 유용하게 작용할 수 있습니다. 값 전달 관련 규칙 및
MoveTemp사용법은 아직도 확립 중이지만, 최적화된 코드베이스 영역 일부에서는 이미 찾아볼 수 있습니다.
함수 포인터
int Function(){ return 100;}int main(){ // FunctionPointer 함수 포인터 변수에 Function 함수 저장. // 함수를 함수 포인터에 저장할 때는 함수의 이름을 그대로 대입. int (*FunctionPointer)() = Function; // FunctionPointer 변수를 통해 함수 호출. // 포인터 타입이기 때문에 null 비교가 가능. if (FunctionPointer != nullptr) { FunctionPointer(); }}- callback
// 두 값을 서로 교환하는 함수.template<typename T>void Swap(T& a, T& b){ T temp = a; a = b; b = temp;}// 두 값을 비교하는 함수.bool CompareValues(int a, int b){ return a > b;}// 버블 정렬 함수.typedef bool (*Comparer)(int, int); using Comparer = bool (*)(int, int); // typedef나 using을 활용하면 Comparer comparer로 간소화가 가능하다.void BubbleSort(int array[], int length, bool (*Comparer)(int, int)){ // 버블 정렬. for (int ix = 0; ix < length - 1; ++ix) { for (int jx = 0; jx < length - 1; ++jx) { // 두 개씩 비교. //if (array[jx] > array[jx + 1]) if (Comparer(array[jx], array[jx + 1])) { // 현재 값과 다음 값 교환. Swap<int>(array[jx], array[jx + 1]); } } }}int main(){ int array[] = { 5, 2, 8, 4, 1 }; int length = sizeof(array) / sizeof(array[0]); BubbleSort(array, length, CompareValues);}std::function<functional>헤더를 통해 함수 포인터를 정의할 수 있다.- 성능이 좋지 않다(템플릿 추론 때문에)
- 람다
[]() -> void { return ;}
frontBuffer, backBuffer
https://blog.naver.com/znfgkro1/80174839762 콘솔 게임은 이미지 버퍼 플립이 안돼서 콘솔 핸들 2개를 플립하는 구조로 구현
콘솔게임 유의사항
- 좌측상단이 (0, 0)
- 화면 우측 끝을 넘어가면 좌측 화면으로 넘어옴(값은 0이 아닌 양수)
- 화면 하단 끝을 넘어가면 스크롤 + 상단으로 넘어오므로 주의
- 스크롤 구현하려면 내부 로직을 직접 만들어주는게 편함