[TIL] 24-01-09

Lev·2024년 1월 9일
0

📓TIL Archive

목록 보기
20/33

템플릿 Template

💡 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;
}

MyArray 구현

#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

/*
[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;
	}
}

stl container

: 클래스 템플릿으로 만들어진, 같은 타입의 여러 객체를 저장해두는 곳

  1. 시퀀스 컨테이너 (vector, list, …)
    ⇒ 자료를 추가할 때, 순서가 변경되지 않는다
  2. 연관 컨테이너 (map, …)
    ⇒ 자료를 추가할 때, 특정 규칙에 따라 순서가 변경된다
    ⇒ 자료를 정렬해준다는 뜻은 아니다
  3. 어댑터 컨테이너 (string, stack, queue, …)

📢 자료구조 면접의 단골손님
1. vector
2. map
3. list

string, queue, stack, …


과제

240109_ArrayResize

// <과제> 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;
    }
}
profile
⋆꙳⊹⋰ 𓇼⋆ 𝑻𝑰𝑳 𝑨𝑹𝑪𝑯𝑰𝑽𝑬 ⸝·⸝⋆꙳⊹⋰

0개의 댓글