프로그래밍 언어/C & C++ 정리

사용자 정의 타입 (user-define data type)

게임 개발 2023. 1. 21. 22:18

C++에서는 기본타입 (int, double etc), const 제한자, 선언 연산으로 생성할 수 있는 

내장 타입과 추상화 메커니즘을 바탕으로 만들어진 사용자 정의 타입

2가지로 나뉜다.

 

내장 타입 같은 경우에는 로우 레벨에 가깝게 설계돼였으며,

전통적으로 하드웨어의 능력을 직접적이고 효율적으로 사용한다.

반면에 프로그래머로 하여금

진보된 어플리케이션을 쉽게 작성하게 해주는 하이 레벨 기능을 제공하지 않는다.

 

사용자 정의 타입과 같은 경우에는

프로그래머가 스스로

적절한 표현방식과 연산을 갖춘 타입을 설계하고 구현하며,

간단하고 편리하게 사용할 수 있게 해준다.

사용자 정의 타입의 대표적인 예로 "클래스"와 "열거형"을 들 수 있다.

 

구조체 

 

새로운 타입을 만드는 첫 단계에 필요한 구성 요소를 데이터 구조로 조직화 하는 것이다.

데이터 구조를 조직화하는 키워드는 struct가 사용되며 이를 우리는 구조체 타입이라고 부른다.

 

struct Vector
{
	int sz;
    double * elem;
}

위의 소스코드는 요소의 개수와 요소를 가리키는 포인터로 이루어진 Vector 구조체를 나타낸 것이다.

Struct는 사용자가 만든 타입으로 아래와 같이, 코드 내에서 사용이 가능하다.

int main ()
{
	Vector v;
    v.sz;
    
    Vector* v1;
    v1->sz;
 }

위의 Vector 구조체 내 존재하는 double 포인터 같은 경우 double 배열을 가리킬 수 있다.

double 배열을 가리킬 요소를 추가하는 코드를 살펴보자.

void vector_init(Vector &V, int s)
{
	v.elem = new double[s];
    v.sz = s;
}

 

vector_init 코드 같은 경우 Vector를 참조연산으로 전달하여,

해당 구조체 내 정보를 초기화하는 함수이다.

elem 포인터 같은 경우 new 연산자를 이용해,

Heap 영역에 동적 메모리를 할당 받은 후 double 배열을 생성한다.

new 연산자로 생성된 배열 혹은 객체는 프로그램이 종료되거나

delete 연산을 통해 소멸할 때까지 유효하다.

 

// cin을 이용해 s개의 정수를 읽고 그 합을 반환하는 함수
double read_and_sum(int s)
{
	Vector v;
	vector_init(v,s);
    
    for (int i = 0; i != s; ++i)
    cin >> v.elem[i];
    
    double sum = 0;
    
    for (int i = 0; i != s; ++i)
    	{
        	sum += v.elem[i];
     	}
	return sum;
}

위의 코드는 간간하게 숫자를 인자로 전달받아, 전달받은 인자의 갯수만큼

double 버퍼할당하고 채워서 총합을 구하는 함수이다.

우리가 사용할 수 있는 표준 입력 출력을 이용해 간단하게 구현해 볼 수 있다.

 

클래스

사용자 정의 타입을 좀 더 실세계와 유사하게 하기 위해서는 데이터를

"표현하는 방식"과 "연산" 사이의 개선이 필요하다. 특히,

사용자가 메모리 표현에 접근하지 못하게 함으로

타입 내 데이터의 일관성을 유지하고 쉽게 사용할 수 있다는 장점이 있다.

이를 위해 도입된 사용자 정의 타입의 메커니즘이 "클래스"이다.

클래스는 "타입의 인터페이스"와 "연산에 필요한 구현"을 분리한다.

 

클래스는 데이터 뿐만 아니라 함수 타입등을 멤버로 가질 수 있다.

연산에 필요한 구현을 사용하는 부분은 외부에서 접근하지 못하도록 하고

외부에서 값을 읽을 필요가 있는 부분은 인터페이스로 만들어 제공한다.

 

클래스는 외부의 접근을 제한하기 위해 

public, private, protected 라는 접근 제한자 3가지를 제공한다.

 

  • public : 일반적으로 누구나 가져다 사용가능 (인터페이스 개념)
  • private : 외부에서 접근이 불가능 (구현에 필요한 연산에 사용되는 정보)
  • protected : 상속 개념 (추후에 다루도록 한다)

클래스 같은 경우 Default 접근 제한자가 private 이고,

구조체 같은 경우 Default 접근 제한자 pubic 이다.

 

class Vector
{
public:
	Vector(int s) : elem{new double [s]}, sz{s}{} 	//Vector 생성
    double &operator[](int i) 
    {
    	return elem[i];
    }
    int size()
    {
    	return sz;
    }
private:
	double8 elem;
    int sz;
}

위와 같이 Vector 타입을 클래스로 재정의 할 수 있다.

위와 같이, Vector 객체는 요소를 가리키는

vhdsxj (elem)와 요소의 개서를 포함하는 Handle이라고 부른다.

Vector 내 요소의 개수는 Vector 마다 다를 수 있고,

보통 Handle의 크기는 고정되며, elem과 같이 new를 이용해

Heap 영역에 할당해 참조하는 방법을 많이 사용한다.

 

Vector class는 데이터에 접근 및 사용하기 위해 Vector(), operator[], size()

인터페이스를 이용해야한다. 우리가 이전에 작성한 read_and_sum() 즉,

값을 읽은 뒤 총합을 구하는 함수를 좀 더 간소화해 구현할 수 있다.

double read_and_sum(int s)
{
	Vector v(s);
    for(int i = 0; i != v.size(); i++)
    {
    	cin >> v[i];
    }
    double sum = 0;
    for (int i = 0; i != v.size(); i++)
    {
    	sum += v[i]
    }
    
    return sum;
}

Vector의 생성자를 보면, 인자를 전달받아 배열 초기화 및 사이즈를 초기화한다.

만약 인자를 전달받지 않는다면 Compile error를 나타낼 것이며, 사용자는 쉽게 알 수 있다.

또한 해당 문법에서 brace-init 문법을 볼 수 있다. 우리는 보통 “()” 를 이용해 초기화한다.

하지만 modern C++ 에서는 “{}” 를 이용해 초기화하며, 특히 정수 값 같은 경우 많이 사용한다.

”{}”를 활용하지 않을 경우 컴파일러가 자체적으로 casting을 하게 되며,

값에서 오류를 나타내지 않는다.

반면에 “{}” 사용할 경우 컴파일러 단에서 잘못된 것을 User에게 알려주기 때문에

쉽게 파악할 수 있으며 사전에 버그를 방지할 수 있다.

생성자에서는 유용하게 사용가능하다.

우리는 인터페이스로 operator[] 인덱싱 함수를 재정의했다.

overriding은 추후에 구체적으로 다루도록 하겠다.

 

 

클래스와 구조체의 차이

 

조체와 클래스의 사이에는 근본적인 차이가 없다.

구조체에서도 클래스와 동일하게 멤버 함수로 표현이 가능하고

interface처럼 이용할 수 있다.

차이라고 보자면, 기본적인 접근 제한자가 public 이냐 private이냐 정도일 것이다.

코딩을 하다보면 코딩 스타일이나 규약을 읽게 될텐데

해당 코드에 대해 규율에 맞게 정의하고 사용하면 된다고 생각한다. 

공용체 (Union)

Union은 모든 멤버가 같은 메로리 주소에 할당되는 struct이며,

공용체의 크기는 가장 큰 멤버의 크기와 같다.

 

열거형 (Enum)

C++는 클래스 외에도 여러 값을 열거할 수 있는 간단한 사용자 타입을 제공한다.

 

열거형을 사용하는 방법은 enum class로 선언하면 된다.

열거자의 스코프는 해당 enum class로 제한한다.

동일한 이름을 다른 enum class에서 사용해도 혼동없이 사용이 가능하다.

  1. 내장 타입이 너무 로우레벨이라고 느껴진다면 잘 정의된 사용자 정의 타입을 사용하라.
  2. 연관된 데이터는 struct or class로 구조화 하라.
  3. class를 이용해 인터페이스와 구현부를 구별하라.
  4. struct는 기본적으로 public class이다.
  5. class 초기화를 보장하고 단순화 할 수 있도록 생성자를 정의하라.
  6. union을 사용할 경우 표준 라이브러리 나 tagged union을 사용하라.
  7. 열거형으로 명시된 상수 집합을 사용하라.
  8. 에러는 최소화 할 수 있도록 scope가 정의된 열거형 (enum class)를 사용하라.
  9. 열거형 (enum) 같은 경우 추가로 연산자를 재정의해 사용하도록 하자.