[Unreal] 언리얼 기초 C++_02
본 문서는 어소트락 언리얼엔진 게임프로그래머 양성과정의 강의를 토대로 필기한 내용입니다
배열
- 하나의 이름으로 변수 여러개를 선언할 수 있게 해주는 자료구조
변수타입 배열명[배열 크기]
- 배열의 이름은 해당 배열의 메모리 시작점의 주소이다
- 배열의 요소에 접근하기 위해서는 인덱스를 사용한다
- 인덱스의 범위 : 0 ~ (배열 크기 - 1)
배열명[인덱스] = 102;
- 배열의 초기화
#include <iostream>
#define TEXT(text) L##text // 유니코드 문자열을 만들어 줄 수 있는 전처리기
using namespace std;
int main()
{
// 한글 언어설정
setlocale(LC_ALL, "");
setlocale(LC_ALL, "korean");
_wsetlocale(LC_ALL, L"korean");
int array[100];
cout << array << endl; // 00000038928FF660, 배열의 메모리 시작점의 주소 값
printf("%d\n", array[0]); // 초기화 하지 않은 배열에는 쓰레기 값이 들어있다
int array1 = {1, 2, 3, 4}; // 0번부터 3번 인덱스까지 값을 1, 2, 3, 4로 초기화하고 나머지는 0이 자동으로 초기화 됨
for(int i = 0; i < 100; i++)
{
array[i] = i + 1;
}
char text[64] = "문자열입니다";
wchar_t text1[64] = L"유니코드 문자열입니다"; // 문자열의 끝은 null 문자이기 때문에 초기화가 중요
wchar_t text2[64] = TEXT("유니코드 문자열 테스트");
printf(text);
wprintf(L"wchar_t : %s\n", text1);
wcout << text2 << endl;
int array2[10][5] = {};
array2[3][2] = 100;
return 0;
}
구조체
- 다양한 타입의 변수를 모아서 사용자 정의 변수 타입을 만들어주는 기능
struct 구조체명
{
원하는 변수 선언
}
#include <iostream>
#define TEXT(text) L##text // 유니코드 문자열을 만들어 줄 수 있는 전처리기
using namespace std;
namespace EJob
{
enum Type : short {
Knight,
Archer,
Magician
};
}
struct FPlayer
{
wchar_t Name[64];
EJob::Type Job; // 구조체 멤버 맞춤 기본값이 4바이트
// char myChar; // 남은공간 2바이트중에 1바이트를 활용할 수 있다
int Attack;
int Defense;
int HP;
int HPMax;
int MP;
int MPMax;
};
namespace EItemType
{
enum Type
{
Weapon,
Armor,
Helmet,
Glove
};
}
struct FItem
{
wchar_t Name[64];
EItemType::Type Type;
int Option;
int Price;
int Sell;
};
int main()
{
_wsetlocale(LC_ALL, L"korean");
FPlayer player = {};
FPlayer playerArray[10];
printf("%lld\n", sizeof(player)); // 구조체 멤버 맞춤, 서버/클라이언트 맞춰줄 필요가 있다
player.Job = EJob::Knight;
player.Attack = 30;
printf("player atk : %d\n", player.Attack); // player atk : 30
playerArray[2].Attack = 50;
printf("playerArray[2] atk : %d\n", playerArray[2].Attack); // playerArray[2] atk : 50
FItem inventorySlot[20] = {};
inventorySlot[0].Type = EItemType::Weapon; //inventorySlot[0].Name = TEXT("목검"); // 선언 및 초기화를 하지않을 때 문자열을 할당할 수 없다
wcscpy_s(inventorySlot[0].Name, TEXT("목검")); // 문자열 복사 함수
wprintf(L"이름 : %s\n", inventorySlot[0].Name); // 이름 : 목검
return 0;
}
포인터
- 메모리 주소를 저장하는 변수 타입
변수타입 *포인터명; - 모든 변수 타입은 포인터 타입 변수를 선언할 수 있다
- 다른 변수의 메모리 주소를 가지고 있으며 해당 변수의 주소에 접근하여 값을 변경하거나 얻어와서 사용할 수 있다
- 포인터는 다른 변수를 참조하는 경우가 아니라면 어떤 일도 할 수 없다
- 포인터는 메모리 주소를 저장하기 때문에 모든 타입의 포인터 변수의 크기는 32bit 에서는 4바이트, 64bit에서는 8바이트를 차지하게 된다
- 역참조
- 포인터 변수가 다른 변수의 메모리 주소를 가지고 있을 경우 해당 주소에 접근하는 것
- 포인터 변수 앞에 역참조연산자(*)를 붙여서 역참조
- 간접 멤버연산자 (->) : 구조체 포인터를 이용하여 멤버에 접근할 때 사용하는 연산자
struct FMonster
{
wchar_t name[64];
int attack;
int defense;
};
int main()
{
// 포인터 선언 및 초기화
int num = 10;
float num2 = 3.14f;
int* ptr = # // 주소 지정 연산자 (&)
printf("%p : %d\n", ptr, *ptr);
// ptr = (int*)&num2; // 강제 형변환
float* ptr2 = &num2; // 각 자료형에 맞는 포인터 타입을 생성하는 것이 좋다
*ptr = 500; // 역참조연산자(*)를 통해 주소값에 접근 및 할당
printf("Number = %d\n", num);
printf("*Pointer = %d\n", *ptr);
// 배열의 시작 주소를 포인터 변수에 저장하고 배열 인덱스의 주소값에 접근
int arr[100] = {};
int* ptrArr = arr; // ptrArr 변수에는 배열의 시작 주소를 대입되었다
// 배열의 시작주소를 제외하고는 배열 인덱스의 주소값에 접근할 수 있는 방법은 없다
// ptrArr 변수를 활용해서 배열 인덱스의 주소값에 접근 가능
arr[3] = 500;
ptrArr[3] = 1200;
printf("Array[3] = %d\n", arr[3]);
// 구조체와 포인터
FMonster monster;
monster.attack = 20;
FMonster* monsterPtr = nullptr; // 포인터 초기화
monsterPtr = &monster;
(*monsterPtr).attack = 500; // 역참조연산자(*)의 우선순위는 멤버 참조연산자(.) 보다 낮다
monsterPtr->attack = 200; // 간접멤버연산자(->) : 구조체 포인터를 이용하여 멤버에 접근할 때 사용하는 연산자
printf("Monster Atk : %d\n", monster.attack);
// 상수 포인터와 포인터 상수
const int numConst = 100;
const int* constPtr = # // 상수 포인터, 값이 변경되면 안되는 상수에 지정
int num3 = 200;
constPtr = &num3; // 상수 포인터는 포인터 주소는 변경 가능하다
// *constPtr = 1000; // Error // 상수 포인터는 포인터가 가리키는 주소값은 변경 불가 (역참조 불가)
int* const ptrConst = # // 포인터 상수, 참조하는 대상을 변경할 수 없다. 선언 및 초기화시에 사용한 메모리 주소만을 사용
// ptrConst = &num3; // Error
*ptrConst = 500; // 역참조는 가능
const int* const constPtrConst = # // 상수 포인터 상수
// constPtrConst = &num3; // Error
// *constPtrConst = 500; // Error
return 0;
}
다중 포인터
- 다중포인터도 결국은 포인터 변수이다
- 주소를 저장하는 대상의 차이가 있을 뿐 메모리 주소를 저장하는 것은 같다
- 이중 포인터는 포인터 변수의 메모리 주소를 저장하기 위한 변수
- 삼중 포인터는 이중포인터 변수의 메모리 주소를 저장하기 위한 변수
int main()
{
int num = 10;
int* ptr = nullptr;
int** pptr = nullptr;
ptr = #
pptr = &ptr;
printf("number = %d\n", num);
printf("number address = %p\n", &num);
printf("pointer = %p\n", ptr);
printf("pointer address = %p\n", &ptr);
printf("*pointer = %d\n", *ptr);
printf("double pointer = %p\n", pptr);
printf("double pointer address = %p\n", &pptr);
printf("**double pointer = %d\n", **pptr);
return 0;
}
/*
number = 10
number address = 000000CE29CFFC84
pointer = 000000CE29CFFC84
pointer address = 000000CE29CFFCA8
*pointer = 10
double pointer = 000000CE29CFFCA8
double pointer address = 000000CE29CFFCC8
**double pointer = 10
*/
void 포인터
- void는 타입이 없다는 의미이기 때문에 일반 변수를 선언하여 사용하는 것이 불가능
- void의 포인터 타입 변수는 선언하여 사용이 가능하다
- void 포인터 타입의 변수는 어떠한 메모리 주소든 모두 저장이 가능하지만 역참조가 불가능
- 역참조가 필요하다면 원하는 타입으로 형변환을 해준 후에 역참조를 진행한다
- 단, 현변환을 할 때 int변수의 메모리 주소를 담고 있다면 int 포인터 타입으로 형변환을 하여 접근해야 한다
int main()
{
void* voidPtr = nullptr;
int num = 500;
voidPtr = #
int* intPtr = (int*)voidPtr; // 다시 담고 있는 변수의 타입으로 형변환을 해야한다
cout << *intPtr << '\n'; // intPtr 주소값을 출력 : 500
float num2 = 3.14f;
voidPtr = &num2;
intPtr = (int*)voidPtr; // float 타입을 담고있는 voidPtr을 int형으로 형변환 하는 경우
cout << *intPtr << '\n'; // float 타입을 int 타입으로 강제 형변환 할 때 값 손실이 일어날 수 있다
return 0;
}
참조형 변수와 포인터
- 참조형 변수
- 다른 변수를 참조할 때 사용
- 레퍼런스는 처음 레퍼런스 변수가 선언될 때 반드시 참조할 대상을 지정해야 한다
변수타입& ref- 일단 초기화되면 값을 변경할 수 없다
- 매개변수에 ref 변수를 사용해서 인자로 넘겨주며 참조 대상을 지정하는 식으로 많이 사용한다
- const 키워드와 함께 함수 안에서 값이 변경되지 않게 사용하는 것이 안전하다
- 인수의 값을 수정하려는 경우나 인수의 비싼 복사본을 만들지 않으려는 경우 함수 매개변수로 자주 사용
- 포인터
- 다른 변수의 주소를 가지고 있는 변수
int value = 5;
int* const ptr = &value;
int& ref = value;
// *ptr과 ref는 동일하게 평가된다
*ptr = 5;
ref = 5;
// Call by Value
void ChangeNumber(int n)
{
n = 555;
}
// Call by Address
void ChangeNumber(int* n)
{
*n = 555;
}
// Call by Reference
void ChangeNumberRef(int& n)
{
n = 555;
}
int main()
{
int num = 60;
// 값으로 전달
cout << "num" << num << '\n';
ChangeNumber(num);
cout << "changed number : " << num << '\n';
// 주소로 전달 (포인터)
n = 60;
cout << "num" << num << '\n';
ChangeNumber(&num);
cout << "changed number : " << num << '\n';
// 참조로 전달
n = 60;
cout << "num" << num << '\n';
ChangeNumberRef(num);
cout << "changed number : " << num << '\n';
return 0;
}
// 인수의 비싼 복사본을 만들지 않으려는 경우 함수 매개변수로 사용
struct FTestStruct
{
wchar_t test1[64];
wchar_t test2[64];
};
void OutputTestStruct(const FTestStruct& str) // 구조체를 인자로 넘겨줄 때 ref로 지정
{
//str.test1[2] = 1020; // 참조하는 대상의 값을 바꿀 수 없다
}
함수
- 특정 동작을 하는 코드를 미리 작성하고 필요할 때 해당 코드를 동작시킬 수 있게 해주는 기능
- 전달인자(argument)
- 함수를 호출할 때, 필요로 하는 데이터나 변수가 있을 경우 전달인 자를 이용해서 데이터 변수를 함수로 넘겨준다
- 매개변수(parameter)
- 함수 영역 안에서 쓰이는 변수
- 함수를 호출할 때 주어지는 전달인자의 값을 대체하는 용도로 쓰임
반환타입 함수명(매개변수)
{
동작 코드
}
int Add(int a, int b)
{
return a + b;
}
void Output()
{
cout << "Output Function" << endl;
}
int main()
{
cout << Add(1, 2) << "\n";
return 0;
}
함수 오버로딩
- 같은 이름의 함수를 다양한 형태로 만들어줄 수 있는 기능
- 함수 이름이 같고 매개변수의 타입 혹은 개수가 다르다면 오버로딩이 성립된다
- 반환타입은 오버로딩과 관련이 없다
- 디폴트 매개변수
- 매개변수에 기본 값을 미리 설정해두는 기능
- 이 함수를 호출하며 매개변수에 전달인수를 전달할 경우 기본 값은 무시되며 전달된 값을 사용하게 된다
- 이 함수를 호출하며 인자에 값을 전달하지 않을 경우 인자의 값은 기본값으로 설정이 되어 사용하게 된다
- 함수의 디폴트 매개변수는 가장 오른쪽 매개변수부터 왼쪽으로 차례대로 설정해야한다
- 함수 오버로딩 사용 시 디폴트 매개변수의 개수가 동일한 문제가 일어날 수 있으니 주의해서 사용
int Add(int a, int b)
{
return a + b;
}
int Add(float a, float b)
{
return (int)(a + b);
}
int Add(int a, int b, int c)
{
return a + b + c;
}
// 디폴트 매개변수
void OutputNumber(int n = 1111) // 인자를 0개 혹은 1개를 전달 받을 수 있다
{
cout<< n << '\n';
}
// 함수의 디폴트 매개변수는 가장 오른쪽 매개변수부터 왼쪽으로 차례대로 설정해야한다
void OutputNumber(int n1, int n2 = 100) // 인자를 1개 혹은 2개를 전달 받을 수 있다
{
printf("Number1 = %d, Number2 = %d\n", n1, n2);
}
int main()
{
int num = Add(10, 20);
num = Add(3.14f, 2);
num = Add(1, 2, 3);
OutputNumber(); // 1111
OutputNumber(101010); // Error, 어떤 함수를 호출할 지 컴파일러가 판단하지 못함
OutputNumber(500, 200);
OutputNumber(500); // Error
return 0;
}
함수 포인터
- 함수의 이름은 해당 함수의 주소가 된다
- 함수의 타입을 결정하는 요소는 함수의 반환타입과 함수 매개변수에 의해 결정이 된다
- 형식 :
반환타입 *포인터변수이름(인자타입);
void Output()
{
cout << "Output Function" << '\n';
}
void Output1()
{
cout << "Output1 Function" << '\n';
}
int main()
{
void (*Func)(); // 함수 포인터 선언, 함수의 주소를 저장하기 위한 포인터 변수
Func = Output;
Func(); // Output Function
Func = Output1;
Func(); // Output1 Function
return 0;
}
동적할당
- new 키워드
- 메모리를 동적으로 할당하고 해당 메모리 주소를 반환해주는 기능
- 형식 :
new 할당할타입; - int의 크기인 4바이트 만큼 메모리에 공간을 만들고 만들어 준 메모리의 주소를 반환해준다
- delete 키워드
- 동적할당한 메모리는 사용이 끝나면 반드시 제거를 해주어야 한다
- 형식 :
delete 할당된메모리주소
- 동적배열 할당
new 타입[개수]를 이용해서 배열을 할당할 수 있다
// 동적할당
int* dynamicInt = new int;
// 동적할당 제거
delete dynamicInt;
// 동적배열 할당
int* dynamicIntArr = new int[100];
// 동적배열 제거
delete[] dynamicIntArr;
// 정적배열
int staticIntArr[100] = {};
int* staticIntArr = staticIntArr;
// 이중포인터의 사용 예시
void TestAllocate(int** ptr)
{
*ptr = new int;
}
int main()
{
int* intAlloc = nullptr;
TestAllocate(&intAlloc);
delete intAlloc;
return 0;
}
// intAlloc의 값으로 new int로 동적할당한 메모리 주소를 할당