💡 C++은 멀티 패러다임 언어이다
전역 ⇒ 절자지향
클래스와 객체 ⇒ 객체지향
템플릿 ⇒ 제네릭 프로그래밍, 템플릿 메타 프로그래밍
#include <iostream>
/*
[템플릿 함수]
템플릿 : 자료형을 마치 변수처럼 사용하는 문법
- 컴파일러는 템플릿함수를 삭제하고, 사용되는 자료형을 넣은 함수를 만들어낸다
- 자료형만 다르고 같은 내용을 가진 함수를 여러개 만들어 오버로딩할 때 유용하다
- 사용할 자료형으로 치환했을 때 혹시 오류가 없는지 확인해주는게 좋다
*/
/*
void Print(int _Value)
{
std::cout << _Value << std::endl;
}
void Print(const char* _Value)
{
std::cout << _Value << std::endl;
}
void Print(int* _Value)
{
std::cout << *_Value << std::endl;
}
void Print(bool* _Value)
{
std::cout << *_Value << std::endl;
}
...etc.
*/
template<typename Type> // typename : 변수처럼 사용하고 싶은 자료형 이름
void Print(Type _Value)
{
std::cout << *_Value << std::endl;
}
/*
템플릿 특수화
- 같은 이름의 템플릿 함수가 존재할 때, 예외를 만들어주는 것
- 오버로딩이나 마찬가지이다
*/
template<>
void Print(const char* _Value)
{
std::cout << _Value << std::endl;
}
class MyPrintObject
{
public:
template<typename Type>
void Print(Type _Value)
{
std::cout << *_Value << std::endl;
}
};
int main()
{
{
std::cout << 1; // int
std::cout << "dfkadfdf"; // const char*
std::cout.operator<<(true); // bool
}
{
int Value = 100;
int* Ptr = &Value;
bool bValue = true;
bool* bPtr = &bValue;
const char* Text = "aaaa";
std::cout << Text << std::endl; // const char*
std::cout << *Ptr << std::endl; // int
std::cout << *bPtr << std::endl; // bool
/*
템플릿 인자추론
- 정식 호출방식은 자료형과 함께 써주어야 한다
- 하지만 그냥 써도... 알아서 추론해준다
*/
Print(Text); // Print<const char*>("aaaa");
Print(Ptr); // Print<int*>(Ptr);
Print(bPtr); // Print<bool*>(bPtr);
MyPrintObject Object;
Object.Print(Text);
Object.Print(Ptr);
Object.Print(bPtr);
}
}
// [Main.cpp]
#include <iostream>
#include "MyTemplateClass.h"
// [템플릿 클래스]
template<typename MemberType>
class TemplateClass
{
public:
MemberType Value;
};
class TemplateClassint
{};
class TemplateClassbool
{};
int main()
{
{
/*
1. 인자추론이 불가능하다
- 값형인 것은 스택에 위치하기 때문
- main()이 실행될 때 이미 클래스의 크기가 정해져 있어야 한다
- 클래스의 크기는 내부의 멤버변수의 크기에 의해 결정된다
*/
TemplateClass NewType; // 불가능
TemplateClass<int> NewType; // 가능
}
{
/*
2. .h 파일과 .cpp 파일로 분할이 불가능하다
- 전부 헤더파일에 구현해야 한다
- 1번 요인으로 인해 2번도 불가능한 것
*/
MyTemplateClass<int> MyClass;
MyClass.Test();
MyTemplateClass<bool> MyboolClass;
MyboolClass.Test();
// 만약 구현부가 헤더파일에 존재하지 않았다면,
// MyTemplateClass<bool>은 Test()를 사용할 수 없다
}
{
/*
3. 템플릿 클래스간의 호환은 완전히 동일한 템플릿 클래스끼리만 가능하다
- 애초에 동일한 이름의 템플릿 클래스라고 해서 전부 같은 자료형은 아니다
*/
MyTemplateClass<int> MyClass;
MyTemplateClass<bool> MyboolClass;
MyClass = MyboolClass; // 불가능
// 아래와 같이 전혀 다른 클래스를 같은 자료형이라고 착각하는 것과 같다
TemplateClassint NewInt;
TemplateClassbool NewBool;
NewInt = NewBool; // 불가능
}
}
// [MyTemplateClass.h]
#pragma once
#include <iostream>
template<typename ValueType>
class MyTemplateClass
{
public:
// 헤더파일에 모든 구현부가 들어가 있어야 한다
void Test()
{
std::cout << Value << std::endl;
}
ValueType Value;
};
// [MyTemplateClass.cpp]
#include "MyTemplateClass.h"
void MyTemplateClass::Test() // 불가능
{
std::cout << Value << std::endl;
}
// 사실 인자를 명시해주면 구현할 수 있다
// 하지만 필요한 자료형마다 따로 구현해줘야 해서 템플릿을 쓰는 의미가 없어진다
void MyTemplateClass<int>::Test() // 인자가 int인 경우의 Test()
{
std::cout << Value << std::endl;
}
#include <iostream>
#include <ConsoleEngine/EngineDebug.h>
// 어떤 자료형으로도 사용 가능한 MyArray 구현
template<typename DataType>
class CArray
{
public:
CArray()
{
}
CArray(int _Size)
{
ReSize(_Size);
}
CArray(const CArray& _Other)
{
Copy(_Other);
}
~CArray()
{
Release();
}
void operator=(const CArray& _Other)
{
Copy(_Other);
}
DataType& operator[](int _Count)
{
return ArrPtr[_Count];
}
int Num()
{
return NumValue;
}
void Copy(const CArray& _Other)
{
NumValue = _Other.NumValue;
ReSize(NumValue);
for (int i = 0; i < NumValue; i++)
{
ArrPtr[i] = _Other.ArrPtr[i];
}
}
void ReSize(int _Size)
{
if (_Size <= 0)
{
MsgBoxAssert("배열의 크기가 0일수 없습니다");
}
DataType* Ptr = ArrPtr;
ArrPtr = new DataType[_Size];
int CopySize = NumValue <= _Size ? NumValue : _Size;
for (int i = 0; i < CopySize; i++)
{
ArrPtr[i] = Ptr[i];
}
NumValue = _Size;
if (Ptr != nullptr)
{
delete[] Ptr;
Ptr = nullptr;
}
}
void Release()
{
if (nullptr != ArrPtr)
{
delete[] ArrPtr;
ArrPtr = nullptr;
}
}
private:
int NumValue = 0;
DataType* ArrPtr = nullptr;
};
class Monster
{
public:
void StatusRender()
{
printf_s("몬스터 스테이터스 렌더");
}
private:
int Hp = 20;
int Att = 10;
};
int main()
{
LeakCheck;
{
CArray<int> TestArray = CArray<int>(10);
for (int i = 0; i < TestArray.Num(); i++)
{
TestArray[i] = i;
}
// [0][1][2][3][4][5][6][7][8][9]
for (int i = 0; i < TestArray.Num(); i++)
{
std::cout << TestArray[i] << std::endl;
}
}
{
CArray<const char*> TestArray = CArray<const char*>(10);
TestArray[0] = "aaa";
TestArray[1] = "bbb";
TestArray[2] = "ccc";
TestArray[3] = "ddd";
TestArray[4] = "eee";
TestArray[5] = "fff";
TestArray[6] = "ggg";
TestArray[7] = "hhh";
TestArray[8] = "iii";
TestArray[9] = "jjj";
// [aaa][bbb][ccc][ddd][eee][fff][ggg][hhh][iii][jjj]
for (int i = 0; i < TestArray.Num(); i++)
{
std::cout << TestArray[i] << std::endl;
}
}
{
// 인자로 클래스도 얼마든지 들어올 수 있다
CArray<Monster> TestArray = CArray<Monster>(10);
for (int i = 0; i < TestArray.Num(); i++)
{
TestArray[i].StatusRender();
}
}
{
// 템플릿 클래스 안에 템플릿 클래스를 넣을 수 있다
CArray<CArray<int>> TestArray = CArray<CArray<int>>(10); // 2차원 배열
for (int i = 0; i < TestArray.Num(); i++)
{
TestArray[i].ReSize(10);
}
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 10; x++)
{
TestArray[y][x] = x;
}
}
for (int y = 0; y < 10; y++)
{
for (int x = 0; x < 10; x++)
{
std::cout << TestArray[y][x];
}
std::cout << std::endl;
}
/*
이중 포인터로 구현한 2차원 배열이나 마찬가지이다
char** Ptr;
Ptr = new char* [10];
for (int i = 0; i < 10; i++)
{
Ptr[i] = new char[10];
}
혹은 템플릿 클래스에 동적 할당을 하는 것과 같다(???)
CArray<int>* Array = new CArray<int>[10];
*/
}
}
: 특정 객체의 메모리 할당, 탐색, 삭제 등의 방법을 모두 아우르는 개념
/*
[std]
C++ 스탠다드 라이브러리
- C++ 프로그래머들이 편하게 프로그래밍할 수 있도록 미리 함수나 클래스를 만들어둔 것
- 거의 모든 종류의 사용해야하거나 편리한 클래스 등을 OS 및 C++에서 제공해준다
- ...만약 std가 지원하지 않으면 github에 public으로 올라와 있는 프로젝트들에서 가져와서 사용할 수 있다
- 검색능력과 C++ 실력을 갖추면 얼마든지 편리한 것을 찾아 사용할 수 있다
[stl]
C++ 스탠다드 템플릿 라이브러리
std > stl > 자료구조
=> std가 가장 포괄적인 개념이고, 그 안에 stl이, 또 그 안에 자료구조가 속해 있다
*/
#include <iostream>
#include <vector>
int main()
{
// std를 사용할 때에는 std:: 를 앞에 붙여 사용한다
std::vector<int> ArrVector = std::vector<int>(5);
/*
인터페이스
- 클래스와 함수의 사용방식
- 통용되는 익숙한 클래스 사용방법, 멤버변수, ... 등이 있다
*/
ArrVector.resize(10);
for (int i = 0; i < static_cast<int>(ArrVector.size()); i++)
{
ArrVector[i] = i;
}
for (int i = 0; i < static_cast<int>(ArrVector.size()); i++)
{
std::cout << ArrVector[i] << std::endl;
}
}
: 클래스 템플릿으로 만들어진, 같은 타입의 여러 객체를 저장해두는 곳
📢 자료구조 면접의 단골손님
1. vector
2. map
3. liststring, queue, stack, …
// <과제> Resize()로 배열의 크기를 수정할 수 있도록 하자
#include <iostream>
#include <ConsoleEngine/EngineDebug.h>
class IntArray
{
public:
IntArray(int _Size)
{
ReSize(_Size);
}
IntArray(const IntArray& _Other)
{
Copy(_Other);
}
~IntArray()
{
Release();
}
void operator=(const IntArray& _Other)
{
Copy(_Other);
}
int& operator[](int _Count)
{
return ArrPtr[_Count];
}
int Num()
{
return NumValue;
}
void Copy(const IntArray& _Other)
{
NumValue = _Other.NumValue;
ReSize(NumValue);
for (int i = 0; i < NumValue; i++)
{
ArrPtr[i] = _Other.ArrPtr[i];
}
}
// 과제
void ReSize(int _Size)
{
if (_Size <= 0)
{
MsgBoxAssert("배열의 크기가 0일수 없습니다");
}
// 깊은 복사 ver. => 이 경우, 굉장히 비효율적이다
/*
// 기존 ArrPtr 내용을 ArrCopy에 복사하여 보존
int* ArrCopy = new int[NumValue];
for (int i = 0; i < NumValue; i++)
{
ArrCopy[i] = ArrPtr[i];
}
// ArrPtr을 delete 후 다시 동적 할당
if (ArrPtr != nullptr)
{
Release();
}
ArrPtr = new int[_Size];
*/
// 얕은 복사 ver.
int* ArrCopy = ArrPtr; // 기존 ArrPtr이 가리키고 있던 메모리를 ArrCopy가 가리키게 만듬
ArrPtr = new int[_Size]; // ArrPtr은 새로운 메모리를 동적 할당
if (_Size > NumValue) // 새로 받은 size가 기존의 size보다 클 경우
{
for (int i = 0; i < NumValue; i++)
{
ArrPtr[i] = ArrCopy[i];
}
}
else // 새로 받은 size가 기존의 size보다 작은 경우
{
for (int i = 0; i < _Size; i++)
{
ArrPtr[i] = ArrCopy[i];
}
}
NumValue = _Size;
if (ArrCopy != nullptr) // safe delete 코드
{
delete[] ArrCopy;
ArrCopy = nullptr;
}
}
void Release()
{
if (nullptr != ArrPtr)
{
delete[] ArrPtr;
ArrPtr = nullptr;
}
}
private:
int NumValue = 0;
int* ArrPtr = nullptr;
};
int main()
{
LeakCheck;
IntArray NewArray = IntArray(5); // [?][?][?][?][?]
for (int i = 0; i < NewArray.Num(); i++)
{
NewArray[i] = i; // [0][1][2][3][4]
}
NewArray.ReSize(10); // [0][1][2][3][4][?][?][?][?][?]
for (int i = 0; i < NewArray.Num(); i++)
{
std::cout << NewArray[i] << std::endl;
}
}