class Test
{
public:
int Value;
void Function()
{
this->Value = 20;
}
};
Test* CreateTest()
{
return nullptr;
}
int main()
{
{
// [zero division]
int Value = 0;
int Test = 100 / Value;
}
{
// [null reference exception]
Test* NewTest = CreateTest();
NewTest->Function();
}
}
~ to be written ~
/*
문자를 표현하는 방식 = 인코딩
숫자 1개와 문자 1개를 매칭시키는 방식
[아스키] (초기형 방식)
- 7비트
- 현재는 ansi, 멀티바이트 방식이라고 불리는 것에 통합되었다
[ansi]
- 1바이트 (char => 0 ~ 255, 'A' = 67)
- 아스키의 확장형
- 초창기에는 한글을 고려할 필요가 없었기 때문에, 255개의 숫자에
코딩에 필요한 모든 문자가 잘 매칭되어 있었다 (+, -, *, /, [, ], ...도 포함)
[멀티바이트]
- 상황에 따라 1바이트 or 2바이트
- 65536개까지 가능...하지만 전세계의 글자를 다 넣을순 없었다
- 그래서 국가코드가 탄생했다
- 운영체제에 설정된 국가를 따르기 때문에 운영체제의 영향을 받는다
- 몇바이트를 사용할지 판단해야 하므로 좀 느리다...
[유니코드] = 와이드바이트
- 2바이트
- 여전히 국가코드간 변환을 해야한다는 불편함이 있다
- 전용 자료형인 wchar_t를 C++에서 지원한다
- 문자열 앞에 L을 붙이는 표현식을 사용한다
[UTF-8]
- 1 ~ 4바이트 안에 전세계 모든 글자를 넣었다
- 국가간 변환이 필요하지 않다(?)
- 예를 들어 U+0000부터 U+007F 범위에 있는 아스키 문자들은 1바이트만으로 표시된다
- 이모지(...)도 포함될 정도로 다양한 문자의 형태가 포함되어 있다
- UTF-16은 잘 사용하지 않는다...
일반적으로 코딩할 때에는, 내가 사용하는 문자열의 인코딩방식이 무엇인지 인지해야 한다.
함수를 사용하다보면 문자열을 인자로 받는 경우가 있는데, 이 때 만약 제대로 동작하지 않는다면
내가 넣어준 문자열의 값이 잘못된 것이 아니라, 인코딩이 잘못되었을 수 있음을 인지하고 있어야 한다.
*/
int main()
{
char Arr0[3] = "가"; // 멀티바이트 (2 + 1바이트)
wchar_t Arr1[2] = L"가"; // 유니코드 (1 + 1바이트)
char8_t Arr2[4]= u8"가"; // UTF-8 (3 + 1바이트)
// 기본적으로 멀티바이트를 사용하고, 필요할 때 와이드바이트나 UTF-8 방식으로 변환하자!
}
/*
[string]
- 어댑터 컨테이너 자료구조?
- char형 벡터라고 생각하면 편하다
*/
#include <string>
#include <iostream>
class MyString
{
public:
int Size = 0;
char* Arr = nullptr;
MyString(const char* _Ptr)
{
// 깊은 복사
Size = strlen(_Ptr) + 1;
Arr = new char[Size] {0, };
for (size_t i = 0; i < Size; i++)
{
Arr[i] = _Ptr[i];
}
}
MyString(const MyString& _Other) // 복사 생성자
{
// 깊은 복사
Size = _Other.Size;
Arr = new char[Size] {0, };
for (size_t i = 0; i < Size; i++)
{
Arr[i] = _Other.Arr[i];
}
}
~MyString()
{
if (Arr != nullptr)
{
delete Arr;
Arr = nullptr;
}
}
void operator =(const MyString& _Other)
{
// 얕은 복사
Arr = _Other.Arr;
}
};
void TestFunction(std::string Text)
{
}
void TestMyFunction(const MyString& Text)
{
/*
인자가...
MyString Text 인 경우
- 복사 생성자로 들어가 깊은 복사가 발생한다
const MyString& Text 인 경우
- 레퍼런스이기 때문에 값을 새로 생성할 필요가 없어 생성자를 거치지 않고
- 새로이 메모리를 소모하지 않는다
*/
}
int main()
{
// 멀티바이트 인코딩
// std::vector<char>
std::string Text0;
Text0.reserve(3);
Text0 = "가";
std::string Text1;
Text1.reserve(3);
Text1 = "나";
std::string Result = Text0 + Text1; // "가나"
// 와이드바이트 인코딩 (유니코드)
// std::vector<wchar_t>
std::wstring wText0 = L"가";
std::wstring wText1 = L"나";
std::wstring wResult = wText0 + wText1; // "가나"
// 문자열을 인자로 받을 경우
MyString String0 = "aaaaaaa";
TestMyFunction(String0);
// 굳이 깊은 복사를 할 필요가 없다
// 문자열을 복사할 경우
MyString String1 = MyString("aaaaaaa"); // 생성자
MyString String2 = String0; // 복사 생성자
String2 = String0; // 대입 연산자 -> 소멸자 단계에서 터진다
// 이 경우 깊은 복사가 필요하다
// [TIL] 24-01-08 참고
// 하지만 동적할당을 남발할 경우 자원을 크게 소모한다는 것을 인지하고 있어야 한다
// 특히 게임은 프로젝트가 거대한만큼 더 심각하니 필요하지 않은 곳에서는 지양하자
}
💡 메모리 단편화
RAM의 메모리 중간에 틈새가 생기고 정리되어 있지 않은 상태메모리를 할당한다 ⇒ 느려진다
할당된 메모리를 삭제한다 ⇒ 느려진다.
새로이 메모리를 할당하기 위해 빈 공간을 찾는다 ⇒ 느려진다
/*
[Debug 모드]
- 코드를 최적화하는 과정을 거치지 않고 인간이 인식하는 그대로를 보여준다
- 코드 중에 불피요한 부분을 삭제 또는 치환하는 최적화를 하지 않는다
- 인간이 이해하기 쉬운 모드로 변환하고, 함수의 이름, 변수의 이름 등을 전부 기억해둔다
- int A = 0; 과 같은 변수가 있다면 그대로 A라는 이름으로 남겨두고 사용자에게 보여준다
[Release 모드]
- 최적화가 완료되어 컴퓨터가 가장 빠르다고 생각하는 모습으로 코드를 내부에서 변경시켜버린다
- 중단점이 걸리긴 하지만, 값을 제대로 확인할 수 없기 때문에 의미가 없다
- 마지막에 가서 release 모드로 빌드하면 그때까지의 코드 중 어디서 문제가 발생했는지 알 수 없다
- 따라서 release빌드를 사용할 때에는 자주 빌드해보고 커밋하는 것이 중요하다
- debug 모드에 비해 3~5배 이상 프레임이 오르기 때문에 실제 출시도 release빌드로 한다
*/
/*
[x64] = 64비트
[x86] = 32비트
- 4GB까지의 RAM만을 인식할 수 있다
*/
int main()
{
int Ptr = sizeof(int*);
// x64 => 8바이트
// x86 => 4바이트
}
/*
[인터페이스]
어떤 동작을 하는 코드를 사용하기 위한 모든 것
e.g.
소리를 재생하기 위한 함수를 제공한다.
=> 소리를 재생하기 위한 인터페이스를 제공한다.
소리를 재생하려면 xxx클래스를 사용해야 한다.
=> 소리를 재생하려면 xxx클래스를 통한 인터페이스를 제공받아야 한다.
[WinAPI]
- API = Application Programming Interface
- Windows를 사용하기 위한 모든 방법을 종합한 인터페이스
(변수, 함수, 클래스, 멤버변수, 멤버함수, ... 등등)
*/
솔루션 → 추가 → 새 프로젝트 → Windows 데스크톱 애플리케이션
⇒ 윈도우에서 앱을 만들기 위한 기본적인 방법을 담아둔 프로젝트
⇒ 프로젝트 속성 → 구성 속성 → 링커 → 시스템 → 하위 시스템
윈도우 프로젝트 : 창(/SUBSYSTEM:WINDOWS)
콘솔 프로젝트 : 콘솔(/SUBSYSTEM:CONSOLE)
#include "framework.h"
#include "WindowsProject1.h"
#define MAX_LOADSTRING 100
// 전역 변수 :
HINSTANCE hInst; // 현재 인스턴스입니다.
WCHAR szTitle[MAX_LOADSTRING]; // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING]; // 기본 창 클래스 이름입니다.
// 이 코드 모듈에 포함된 함수의 선언 :
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
// 진입점 = main() :
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
/*
APIENTRY = __stdcall
- static 멤버함수 등 전역함수 전용 함수호츌규약
- 사실 굳이 적지 않아도 되지만 명시해주는 것
(__cdecl - C전역함수 전용)
(__thiscall - 멤버함수 전용)
_In_
- 아마 여러 환경을 대비한 것이지만,..
- 어짜피 윈도우에서 하기 때문에 삭제해도 된다.
*/
{
// 사용하지 않은 인자 사용 :
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
/*
프로젝트 속성 -> 구성 속성 -> C/C++ -> 일반 -> 경고 수준 -> 모든경고사용
- ...일 경우 인자를 사용하지 않기만 해도 에러가 나기 때문에 이를 회피하기 위함
- 하지만 경고 수준을 굳이 높일 이유가...?
(경고를 오류로 처리하는 설정은 켤 수도 있다)
*/
// TODO: 여기에 코드를 입력합니다.
// 전역 문자열 초기화 :
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 애플리케이션 초기화 :
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
MSG msg;
// 기본 메시지 루프 :
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
/*
함수: MyRegisterClass()
용도: 창 클래스를 등록합니다.
*/
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WINDOWSPROJECT1));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WINDOWSPROJECT1);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
// 윈도우 창 클래스 만들기 :
return RegisterClassExW(&wcex);
/*
프로그램이 윈도우에게 창을 띄워달라고 요청할 때,
WNDCLASSEW wcex에 입력한 모양으로 띄워달라고 요청하는 것
그 모양의 정보를 담은 클래스 이름이 swWindowClass
*/
}
/*
함수: InitInstance(HINSTANCE, int)
용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
주 프로그램 창을 만든 다음 표시합니다.
*/
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
// 인스턴스 핸들 :
hInst = hInstance;
/*
내 프로그램의 제어 권한이자 표식
인스턴스 핸들을 전역 변수에 저장한다.
*/
// 윈도우 창 생성 :
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
/*
윈도우를 제어할 수 있는 권한이자 표식
마치 포인터처럼, 이 인스턴스? 창?에 일련번호를 붙혀주고 그것을 return한다.
윈도우가 할당되어 있는 진짜 메모리 주소를 주면 위험하므로 대신 이 번호를 받는다.
*/
if (!hWnd)
{
return FALSE;
}
// 윈도우 창 띄우기 :
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
/*
함수: WndProc(HWND, UINT, WPARAM, LPARAM)
용도: 주 창의 메시지를 처리합니다.
WM_COMMAND - 애플리케이션 메뉴를 처리합니다.
WM_PAINT - 주 창을 그립니다.
WM_DESTROY - 종료 메시지를 게시하고 반환합니다.
*/
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 메뉴 선택을 구문 분석 :
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// 정보 대화 상자 메시지 처리기 :
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == 2)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}