새소식

인기 검색어

게임 개발/언리얼 강의 (클라-서버)

[언리얼 MMORPG pt1] 데이터 갖고 놀기

  • -

1. 환경설정

비주얼 스튜디오 사용

2. 정수

#include <iostream>


int hp = 100;

// 초기값이 0이거나, 초기값이 없는 변수라면 .bss 섹션에 저장됨
// signed 는 생략가능
char a; // 1바이트 (-128 ~ 127)
short b; // 2바이트 (-32768 ~ 32767)
int c; // 4바이트 (-2147483648 ~ 2147483647)
__int64 d; // 8바이트 long long d; (−9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807)

unsigned char ua; // 1바이트 (0 ~ 255)
unsigned short ub; // 2바이트 (0 ~ 65535)
unsigned int uc; // 4바이트 (0 ~ 4294967295)
unsigned __int64 ud; // 8바이트 (0 ~ 18,446,744,073,709,551,615)

// 참고) 이론적으로 양수만 존재할 수 있는 데이터는 unsigned 로 하는게 맞나?
// 무조건 unsigned 를 사용할지는 의견이 갈림
// - 레벨이 음수라는 것은 말이 안된다 -> 그럼 차라리 그 자리에서 프로그램을 크래시 내서 버그를 빨리 찾는게 낫다
// - unsigned <-> signed 사이의 변환이 버그를 발생시킬 수 있다.

// 귀찮은데 그냥 4바이트로 가면 안될까?
// -> 콘솔/모바일 게임 -> 메모리가 늘 부족하다
// -> 온라인 게임 -> 4바이트 * 1만명

int main()
{
    // 정수 오버플로우
    b = 32767;
    b = b + 1; // -32768
    std::cout << b << std::endl;

    // 정수 언더플로우
    ub = 0;
    ub = ub - 1; // 65535
    std::cout << ub << std::endl;

    std::cout << "체력이 " << hp << "남았습니다." << std::endl;
}

3. 불리언과 부동소수점

#include <iostream>
using namespace std;


// 불리언(bool)과 실수

// 불리언(boolean) 참/거짓
bool isHighLevel = true;
bool isPlayer = true;
bool isMale = false;
int isFemale = 1;

// [Note]
// 사실 bool은 그냥 1바이트 정수에 불과
// 왜 정수 시간에 안 다뤘을까?
// 어셈블리에서 bool이라는 것은 없음
// bool만 봐도 참/거짓 둘 중 하나라는 힌트를 준다. (가독성)

// bool 왜 1바이트로 했을까? 1비트만 해도 되지 않을까?
// -> 컴퓨터는 1바이트 단위로 읽어들임

// 실수 (부동소수점)
// float double
// 3.14
// 쩜 앞/뒤 기준으로 16/16비트로 나눠서 저장하면?
// (0-65535).(0-65535) -> 범위가 너무 작음

// 부동(floating)소수점
// .을 유동적으로 움직여서 표현하는 방법

// 3.1415926535
// 3.1415926535 = 0.31415926535 * 10^1 = 314.15926535 * 10^-2
// 1) 정규화 = 0.31415926535 * 10^1
// 2) 31415926535 (유효숫자) 1 (지수)

// float 부호(1) 지수(8) 유효숫자(23) = 32비트 = 4바이트
// double 부호(1) 지수(11) 유효숫자(52) = 64비트 = 8바이트

float attackSpeed = -3.375f; // float은 뒤에 f를 붙여줘야 함, 4바이트
double attackSpeed2 = 123.4123; // double은 뒤에 f를 붙이지 않음, 8바이트


// ex) -3.375라는 값을 저장
// 1) 2진수 변환 = (3) + (0.375) = 0b11 + 0b0.011 = 0b11.011
// 0.375 = 0.5 * 0 + 0.25 * 1 + 0.125 * 1 = 0b0.011
// 2) 정규화 = 0b11.011 * 2^0 = 0b1.1011 * 2^1
// 1(부호) 1(지수) 1011(유효숫자)
// 단 지수는 unsigned byte라고 가정하고 숫자+127 만들어줌
// 예상 결과 : 0b1 10000000 10110000000000000000000

// 프로그래밍 할 때 부동소수점은 항상 '근사값'이라는 것을 염두해두어야 함
// 특히 수가 커질수록 오차 범위도 매우 커짐
// 실수 2개를 == 으로 비교하는 것은 지양	
// 0.1 + 0.1 + 0.1 == 0.3 // false

int main()
{
	if (isFemale == 1) // 가독성 이슈, 1이 뭔지 모름. 그래서 bool로 쓰는 것이 좋음
	{
		// 여성 갯수? 뭔말이지?
	}
	cout << isHighLevel << endl; // 1
}

4. 문자와 문자열

#include <iostream>
using namespace std;

// 문자와 문자열
// bool은 그냥 정수지만, 참/거짓을 나타내기 위해 사용한다 했다.
// 사실 char도 마찬가지. 그냥 정수지만 '문자' 의미를 나타내기 위해 사용되는 경우 많음.

// char: 알파벳, 숫자, 문자, 특수문자 등을 저장할 수 있는 자료형
// wchar_t: 유니코드 문자를 저장할 수 있는 자료형

// ASCII (American Standard Code for Information Interchange)
// '문자'의 의미로 작은 따옴표를 사용한다.

//char ch = 97; // 아스키 코드 97은 a
char ch = 'a'; // 문자 a
char ch2 = '1';
char ch3 = 'a' + 1; // b

// 국제화 시대에는 영어만으로 서비스 할 수 없음
// 전 세계 모든 문자에 대해 유일 코드를 부여한 것이 유니코드 (unicode)
// 참고) 유니코드에서 가장 많은 번호를 차지하는게 한국어/중국어~ (뚥쿩웱 이런 이상한 글자도 많아서)

// 유니코드는 표기 방식이 여러가지가 있는데 대표적으로 UTF-8, UTF-16, UTF-32가 있다.
// UTF-8
// - 알파벳, 숫자 1바이트
// - 유럽 지역의 문자는 2바이트
// - 한글, 한자 등은 3바이트
// UTF-16
// - 알파벳, 숫자, 한글, 한자 등 거의 대부분 문자 2바이트
// - 매~~우 예외적인 고대 문자만 4바이트(사실상 무시해도 됨)

// utf-16 은 wchar_t로 표현한다.
wchar_t wch = L'안'; // L을 붙여서 유니코드 문자임을 표시한다.

// Escape Sequence
// 표기하기 애매한 애들을 표현
// \t = 아스키코드 9번 = 탭
// \n = 아스키코드 10번 = LineFeed (한줄 아래로)
// \r = 아스키코드 13번 = Carriage Return (커서를 맨 앞으로)
// \' = 아스키코드 39번 = 작은 따옴표
// \0 = 아스키코드 0번 = 널 문자 (문자열의 끝을 알리는 문자)

// 문자열
// 문자들이 열을 지어서 모여 있는 것 (문자 배열?)
// 정수 (1~8바이트) 고정 길이로 저장
// ex) Hello = H e l l o \0 (문자열의 끝을 알리는 널 문자)
// 끝은 NULL 문자로 표시한다. (아스키코드 0번)

char str[] = { 'H', 'e', 'l', 'l', 'o', '\0' }; // 문자열
// 널 문자를 생략하면?
char str2[] = { 'H', 'e', 'l', 'l', 'o' }; // 문자열 생략하면 끝에 이상한 문자가 붙는다.
// 일반적으로는 이렇게 표현한다. 널 문자를 매번 넣기 귀찮으니까
char str3[] = "Hello"; // 문자열
wchar_t wstr[] = L"안녕하세요"; // 유니코드 문자열

int main()
{
	cout << ch << endl;
	cout << ch2 << endl;
	cout << ch3 << endl;

	// cout은 char 전용
	cout << wch << endl;

	wcout.imbue(locale("kor")); // 유니코드 출력을 위한 준비
	wcout << wch << endl; // wcout은 wchar_t 전용

	cout << str2 << endl;
}

5. 산술연산

스킵

6. 비교연산과 논리 연산

스킵

 

7. 비트 연산과 비트 플래그

#include <iostream>
using namespace std;

// 오늘의 주제: 데이터 연산
// 데이터를 가공하는 방법에 대해 알아본다



int main()
{
#pragma region 비트 연산
	// 언제 필요한가? (사실 자주 쓰진 않음)
	// 비트 단위의 조작이 필요할 때
	// - 대표적으로 BitFlag

	// - bitwise not (~)
	// 단일 숫자의 모든 비트를 반전시킨다 (1 -> 0, 0 -> 1)

	// bitwise and (&)
	// 두 숫자의 비트를 비교해서 둘 다 1이면 1, 아니면 0

	// bitwise or (|)
	// 두 숫자의 비트를 비교해서 둘 중 하나라도 1이면 1, 아니면 0

	// bitwise xor (^)
	// 두 숫자의 비트를 비교해서 둘 중 하나만 1이면 1, 아니면 0
	// 두 번 연속으로 xor 연산을 하면 원래 값으로 돌아온다

	// << 비트 좌측 이동
	// 비트열을 N만큼 좌측으로 이동시킨다
	// 왼쪽의 넘치는 비트는 버린다. 새로 생긴 오른쪽 비트는 0으로 채운다
	// *2의 N승과 같은 효과

	// >> 비트 우측 이동
	// 비트열을 N만큼 우측으로 이동시킨다
	// 오른쪽의 넘치는 비트는 버린다. 새로 생긴 왼쪽 비트는 부호 비트와 같은 값으로 채운다
	// unsigned 타입의 경우 0으로 채운다, signed 타입의 경우 부호 비트와 같은 값으로 채운다

	// 실습
	// 0b0000 [무적][변이][스턴][공중부양]
	unsigned char flag; // 부호를 없애야 >> 를 하더라도 부호비트가 딸려오지 않음

	// 무적 상태로 만든다
	flag |= (1 << 3);

	// 변이 상태를 추가한다 (무적 + 변이)
	flag |= (1 << 2);

	// 무적인지 확인하고 싶다? (다른 상태는 관심 없음)
	// bitmask
	bool invincible = (flag & (1 << 3)) != 0;

	// 무적이거나 스턴 상태인지 확인하고 싶다면?
	//bool invincibleOrStun = (flag & ((1 << 3) | (1 << 1))) != 0;
	bool invincibleOrStun = (flag & 0b100010) != 0;

#pragma endregion

}

8. const와 메모리 구조

#include <iostream>
using namespace std;

// 오늘의 주제: 데이터 연산
// 데이터를 가공하는 방법에 대해 알아본다

// 한번 정해지면 절대 바뀌지 않을 값들
// constant의 약자인 const를 붙여서 표현한다 (변수를 상수화 한다고 함)
// 상수화된 변수는 초기화를 반드시 해야 한다

// 대문자로 표기하는 것이 관례
// 누군가가 실수로 값을 바꾸지 않도록 하기 위함
// 그러면 const도 바뀌지 않는 읽기 전용
// .rodata? (read only data)
// 사실 C++ 표준에서 꼭 그렇게 하라는 말이 없음
// 그냥 컴파일러 (VS, GCC, Clang) 마음대로 라는 것
const int AIR = 0;
const int STUN = 1;
const int POLYMORPH = 2;
const int INVINCIBLE = 3;

// 전역 변수

// [데이터 영역]
// .data (초기값 있는 경우)
int a = 2;

// .bss (초기값 없는 경우)
int b;

// .rodata (상수)
const char* msg = "Hello World";

int main()
{
	// [스택 영역]
	int c = 3;

#pragma region 비트 연산
	// 언제 필요한가? (사실 자주 쓰진 않음)
	// 비트 단위의 조작이 필요할 때
	// - 대표적으로 BitFlag

	// - bitwise not (~)
	// 단일 숫자의 모든 비트를 반전시킨다 (1 -> 0, 0 -> 1)

	// bitwise and (&)
	// 두 숫자의 비트를 비교해서 둘 다 1이면 1, 아니면 0

	// bitwise or (|)
	// 두 숫자의 비트를 비교해서 둘 중 하나라도 1이면 1, 아니면 0

	// bitwise xor (^)
	// 두 숫자의 비트를 비교해서 둘 중 하나만 1이면 1, 아니면 0
	// 두 번 연속으로 xor 연산을 하면 원래 값으로 돌아온다

	// << 비트 좌측 이동
	// 비트열을 N만큼 좌측으로 이동시킨다
	// 왼쪽의 넘치는 비트는 버린다. 새로 생긴 오른쪽 비트는 0으로 채운다
	// *2의 N승과 같은 효과

	// >> 비트 우측 이동
	// 비트열을 N만큼 우측으로 이동시킨다
	// 오른쪽의 넘치는 비트는 버린다. 새로 생긴 왼쪽 비트는 부호 비트와 같은 값으로 채운다
	// unsigned 타입의 경우 0으로 채운다, signed 타입의 경우 부호 비트와 같은 값으로 채운다

	// 실습
	// 0b0000 [무적][변이][스턴][공중부양]
	unsigned char flag; // 부호를 없애야 >> 를 하더라도 부호비트가 딸려오지 않음

	// 무적 상태로 만든다
	flag |= (1 << INVINCIBLE);

	// 변이 상태를 추가한다 (무적 + 변이)
	flag |= (1 << POLYMORPH);

	// 무적인지 확인하고 싶다? (다른 상태는 관심 없음)
	// bitmask
	bool invincible = (flag & (1 << INVINCIBLE)) != 0;

	// 무적이거나 스턴 상태인지 확인하고 싶다면?
	//bool invincibleOrStun = (flag & ((1 << 3) | (1 << 1))) != 0;
	bool invincibleOrStun = (flag & 0b100010) != 0;

#pragma endregion

}

9. 유의사항

#include <iostream>
using namespace std;

// 오늘의 주제: 유의사항

// 1) 변수의 유효범위

// 전역 변수
int hp = 10;

// 스택
// { } 중괄호의 범위가 생존 범위
// 같은 이름 두번 사용할 때 문제가 생길 수 있다.

// 2) 연산 우선순위
// 짝수 여부
// bool isEven = ((hp % 2) == 0); // 그냥 조금 더 명확하게 괄호를 쳐주는 것이 좋다.

// 3) 타입 변환

// 4) 사칙 연산 관련
// 
// 곱셈
// 오버플로우 주의
// 
// 나눗셈
// 0으로 나누는 것은 금지
//
// 실수 관련
// int hp = 123;
// int maxHp = 1000;
// float ratio = hp / maxHp; // 0.123이 나와야 하는데 0이 나온다.
// float ratio = (float)hp / maxHp; // 0.123 둘 중 하나를 캐스팅해줘야 한다.
// float ratio = hp / (float)maxHp; // 0.123

int main()
{
	int hp = 77777;
	cout << hp << endl;
	
	// 짝수 여부
	bool isEven = ((hp % 2) == 0);

	// 짝수거나 3으로 나뉘는 값인지
	bool isEvenOrDividedBy3 = ((hp % 2) == 0) || ((hp % 3) == 0);

	// 바구니 교체 (캐스팅)
	short hp2 = hp; // short hp2 = (short)hp;
	float hp3 = hp; // float hp3 = (float)hp;
	unsigned int hp4 = hp; // unsigned int hp4 = (unsigned int)hp;

	cout << hp2 << endl;
	// 왜 12141 이 나오는가?
	// 77777을 2진수로 바꾸면 0000 0000 0000 0001 0010 1111 1101 0001
	// short으로 바꾸면 0010 1111 1101 0001 -> 12141

	cout << hp3 << endl;
	// 실수로 변환할 때 정밀도 차이가 있기 때문에 데이터 손실이 발생할 수 있다.

	cout << hp4 << endl;
	// 만약 hp = -1 을 unsigned int 로 변환한다고 하면,
	// 1111 1111 1111 1111 1111 1111 1111 1111 -> 4294967295
	// 비트 단위로 보면 똑같은데, 분석하는 방법이 달라져서 다른 값이 나온다.
}

void Test()
{

}
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.