타입의 정의
대부분의 프로그래밍 언어에서는 데이터를 효과적으로 처리하기 위하여 "타입(Type)" 개념을 지원한다. 타입이란 처리 대상이 되는 데이터들의 집합인 "데이터 집합"과 데이터에 적용 가능한 연산의 집합인 "연산 집합"의 결합이다. 타입은 기본족으로 변수의 속성 중 하나에 해당되고, 타입이 지정되면 해당 변수는 지정된 타입의 데이터의 값만 가질 수 있고 지정된 타입의 연산 집합에 속하는 연산만 적용가능하다.
어떤 프로그램 내 모든 연산 및 함수에 대해 적용 전/후의 타입이 연산 및 함수의 정의와 일치하면 그 프로그램은 타입 안전하다고 한다. 타입 오류가 발생하지 않으면 타입 안전하다고 할 수 있다. 타입 안전성에 대한 수학적은 다음과 같다.
💡 (정의) 타입 안전성 💡
함수 $f$의 타입이 $f(x): A \rightarrow B$, 즉 입력 타입이 $A$이고 출력 타입이 $B$라고 하면, $a \in A$ 인 모든 $a$에 대하여 $f(a) \in B$이다.
타입 안전성에 따라 프로그래밍 언어를 구분하면 다음과 같다.
- 강타입 언어(Strongly Typed): 프로그램에 존재하는 모든 타입 오류를 검출해 내는 언어, 대표적으로 Haskell, GO, ML 등이 있다.
- 약타입 언어(Weakly Typed): 타입 오류를 검출하나 일부 타입 오류를 허용하는 언어, 대표적으로 C, C++이 있다.
- 무타입 언어(Typeless): 타입 선언문이 없고 어떤 대상의 타입이 계속 바뀔 수 있는 언어, 대표적으로 JavaScript, PHP 등이 있다.
타입의 분류
데이터의 형태에 따라 아주 다양한 종류의 타입이 존재한다. 이러한 타입들을 분류할 수 있는 방법들도 여러가지가 있다.
첫 번째, 타입의 정의에 사용자가 개입할 수 있는지 여부에 따라 구분할 수 있다.
- 원시 타입(Primitive Type): 프로그래밍 언어에서 기본적으로 제공하는 타입, 사용자가 개입할 여지가 없는 타입으로 대부분의 언어들은
정수형,실수형,문자형,논리형등을 원시타입으로 제공한다. - 사용자 정의 타입(User-Defined Type): 사용자가 직접 정의해서 사용하는 타입, 대표적으로
배열과구조체가 있다.
두 번째, 데이터 요소의 형태에 따라 분류할 수 있다.
- 단순 타입(Simple Type): 데이터 집합의 요소가 하나의 데이터로만 구성된 타입이다.
- 복합 타입(Structure Type): 데이터 집합의 요소가 데이터들의 구조로 구성된 타입. 단순타입 또는 또다른 복합타입을 활용하여 데이터 요소를 구성한다.
단순타입
지금부터 단순타입에 포함된 타입의 데이터 집합과 연산 집합에 대해 알아보도록 하겠다. 주로 C/C++ 언어를 활용하여 타입들을 알아보도록 하겠다.
정수형 (Integer Type)
정수 데이터를 다루는 타입으로, 정수형의 데이터 집합은 일부 범위 내의 정수만 포함한다. 하나의 프로그래밍 언어 안에서도 정수형 타입은 여러가지 존재할 수 있다. C/C++ 언어의 경우 다음의 정수형 타입들이 존재한다.
타입명 | 최소 사용 비트 | 최소 데이터 집합 범위 |
short | $16$ | $-2^{15}$ ~ $2^{15}-1$ |
int | $16$ | $-2^{15}$ ~ $2^{15}-1$ |
long | $32$ | $-2^{31}$ ~ $2^{31}-1$ |
long long | $64$ | $-2^{63}$ ~ 2$^{63}-1$ |
특별히 C/C++ 언어에는 정수형에 unsigned를 붙인 무부호 정수형 타입이 존재한다. 이는 데이터 집합의 범위가 음수 부분이 사라지고 양수 부분이 확장된다. 다만 사용하는 비트는 동일하다.
타입명 | 최소 사용 비트 | 최소 데이터 집합 범위 |
unsigned short | $16$ | $0$ ~ $2^{16}-1$ |
unsigned int | $16$ | $0$ ~ $2^{16}-1$ |
unsigned long | $32$ | $0$ ~ $2^{32}-1$ |
unsigned long long | $64$ | $0$ ~ $2^{64}-1$ |
정수형의 연산 집합은 기본적으로 덧셈, 뺄셈, 곱셈, 나눗셈의 사칙 연산을 포함한다. 이때, 나눗셈의 경우 몫은 언제나 정수로 표현됨에 주의하자. 또한 데이터 집합의 범위는 유한하기에 연산의 결과값 또한 언제나 범위 내의 정수로 포함된다, (이에 대해서 궁금하다면 "여기"에서 오버플로우에 대해서 알아보도록 하자)
실수형 (Real Type)
실수 데이터를 다루는 타입으로, 정수형과 마찬가지로 사용 비트의 크기에 따라 일부 범위 내의 실수만 포함한다. 실수 데이터는 사용 비트를 부호, 지수부, 가수부로 나누어 표기하는 "부동소수점 방식"을 사용하는 것이 일반적이다. (부동소수점 방식에 대해서 더 자세히 알고 싶다면 "여기"를 참조하기를 바란다.)
C/C++ 언어의 경우 다음 2가지의 실수형이 존재한다.
타입명 | 최소 사용 비트 | 최소 데이터 집합 범위 |
float | $32$ | 양수: $1.175 \times 10^{-38}$ ~ $3.403 \times 10^{38}$ 0 음수: $-3.403 \times 10^{38} ~ 1.175 \times 10^{-38} $ |
double | $64$ | 양수: $2.2 \times 10^{-308}$ ~ $1.8 \times 10^{308}$ 0 음수: $-1.8 \times 10^{308}$ ~ $2.2 \times 10^{-308} $ |
실수형의 연산 집합 또한 기본적으로 덧셈, 뺄셈, 곱셈, 나눗셈의 사칙 연산을 포함한다. 다만 연산의 결과 값이 실수형의 최댓값 또는 최솟값을 벗어나면 무한대(inf / -inf)로 표현된다.
문자형 (Character Type)
하나의 문자 데이터를 다루는 타입으로, 문자형의 데이터 집합은 ASCII 코드로 표현되는 128개의 문자를 포함한다. 최근에는 전 세계의 다양한 글자를 포함하는 유니코드 문자를 데이터 집합에 포함하기도 한다.
C/C++ 언어에서는 문자형으로 char 타입이 존재한다. C/C++에서는 char 타입을 1바이트를 사용하는 정수형으로 간주하여 문자 데이터에 대한 연산을 대응되는 코드인 정수 데이터에 대한 연산으로 처리한다. (ASCII 코드를 이용하여 문자열 연산을 진행한다)
'A' < 'B'
// 'A'는 ASCII코드로 65 값을 가지고, 'B'는 ASCII코드로 66값을 가진다.
// 따라서 위의 연산은 65 < 66 이기에 참을 반환할 것이다.
논리형 (Logical Type)
참과 거짓의 논리 데이터를 다루는 타입으로 논리형의 데이터 집합은 오직 "참(True)"과 "거짓(False)" 두 데이터만 포함한다. 논리형의 연산집합은 논리곱(and), 논리합(or), 부정(not) 등의 논리 연산을 포함하며 연산의 결과 값은 동일한 논리형이다. C/C++에서는 논리형으로 bool 타입이 존재한다.
열거형 (Enumeration Type)
열거형은 순서 관계가 있는 이름들을 테이터로 다루는 타입으로, 사용자 정의 타입이다. 열거형의 데이터 집합에서는 사용자가 직접 지정한 이름들을 포함하며 각 이름들은 0 이상의 정수와 대응되어 이름들 사이의 순서 관계를 정할 수 있다. C/C++의 열거형으로는 enum 타입이 있다.
enum Months { January, February, March,
April, May, June, July, August,
September, October, November, December };
위 코드와 같이 enum 타입에서는 순서대로 January는 0, February는 1... December는 11에 대응되며 순서관계가 정해진다. 열거형의 연산집합은 관계 연산을 포함하며 연산의 결과 값은 논리형이다. 또한 대응되는 정수 값을 기반으로 사칙연산도 가능하다.
복합타입
지금부터 복합타입에 포함된 타입의 데이터 집합과 연산 집합에 대해 알아보도록 하겠다. 마찬가지로 C/C++ 언어를 활용하여 타입들을 알아보도록 하겠다.
배열 (Array)
배열은 같은 데이터의 모음으로 구성된 타입이다. 이때 모여진 각 데이터를 원소라고 하며, 배열이 가지는 원소의 개수는 배열의 크기이다. 배열은 사용자 정의 타입이기에 배열의 원소 타입과 배열의 크기를 사용자가 직접 지정하게 된다. C/C++에서 배열을 선언하는 법은 다음과 같다.
// 크기가 4이며, double 타입인 배열 변수 선언
double arr[4];
배열에서 각 원소는 "인덱스(Index)"를 통해 접근할 수 있다. 이때 인덱스는 0부터 시작한다. 크기가 4인 배열의 경우, 0~3까지의 인덱스를 가지며 인덱스를 통하여 접근한 각 원소에 값을 할당할 수 있다.
double arr[4] = {1.1, 3.5, 17.6, -0.7} // 배열 초기화
arr[0] = 5.1 // arr 배열의 0번째 원소를 5.1로 변경한다.
arr[3] = 293.2 // arr 배열의 3번째 원소를 293.2로 변경한다.
다음과 같이 배열에 사용되는 인덱스의 개수를 여러 개 사용할 수 있다. 배열에 사용되는 인덱스의 개수를 "차원(Dimension)"이라고 한다. 2개의 인덱스가 사용되면 2차원 배열, 3개의 인덱스가 사용되면 3차원 배열이다. 인덱스의 개수가 2개 이상인 다차원 배열의 경우 인덱스의 순서를 정하는 방법은 "행 우선 저장", "열 우선 저장" 이렇게 두 가지가 있다.
- 행 우선 저장: 한 행의 원소들을 모두 배정한 후 다음 행으로 넘어간다. (0행0열~0행 N-1열 -> 1행0열~1행 N-1열 -> ...)
- 열 우선 저장: 한 열의 원소들을 모두 배정한 후 다음 열로 넘어간다. (0행0열~0행 N-1열 -> 1행0열~1행 N-1열 -> ...)
대부분의 프로그래밍 언어는 행 우선 저장 방식을 따른다.
// 4 X 3 2차원 배열
int arr[4][3];
배열을 구현할 때는 두 가지를 고려하여 구현해야 한다. 고려 사항은 다음과 같다.
- 배열의 데이터를 저장할 수 있도록 원소 타입의 크기와 배열의 크기를 곱한 만큼의 저장공간을 확보해야 한다.
- 인덱스를 통하여 원소의 주소를 쉽게 찾을 수 있어야 한다.
문자열형 (String Type)
문자열형은 문자열 데이터를 다루는 타입으로, 문자열은 문자들의 나열이다. C/C++에서 문자열형은 문자형 1차원 배열을 사용하여 구현한다.
// 아래의 두 코드는 동일한 방식으로 동작하는 코드이다.
char str[] = "abc";
char str[] = {'a', 'b', 'c', NULL} // 마지막의 NULL 문자는 문자열의 끝을 나타낸다.
연관배열 (Associative Array Type, Hashing)
연관배열은 동일한 데이터의 순서 없는 모음으로 구성된 타입이다. 원소들 사이의 위치 관계는 의미가 없으며, 각 원소는 "키(Key)"로 구별한다. 키를 통하여 원소들의 값을 얻을 수 있다. 연관배열의 데이터 집합은 키의 타입에 해당하는 데이터 집합과 값에 해당하는 데이터 집합의 쌍이 모인 집합이다. 이때 주의할 것은 하나의 연관배열에서 각 키는 유일하게 정의되며 중복해서 사용할 수 없다.
Python에서는 "딕셔너리(Dictionary)", JavaScript에서는 "오브젝트(Object)" 타입이 사용된다. C++에서의 연관배열로는 map 타입이 있다.
#include <map> // 전처리기
map<int, string> testMap; // Map Template
/* 1. insert() */
testMap.insert(pair<int, string>(1, "map1"));
/* 2. operator[] */
testMap[2] = "map2";
구조체와 공용체 (Structure Type, Union Type)
구조체는 데이터의 모음으로 구성된 타입으로, 각 데이터들의 타입을 같을 수도 있고 다를 수도 있다. 구조체에서 각 원소는 이름으로 구별하며 이름은 원소의 타입으로 선언된 변수명이다. 구조체의 데이터 집합은 각 원소의 타입에 해당하는 데이터 집합들이 모인 집합이기에 원소의 개수와 각 원소의 타입에 따라 다양한 타입의 구조체가 존재하며 심지어 구조체의 원소로 구조체가 올 수도 있다. C/C++에서의 구조체로는 struct 타입이 있다.
struct person {
char name[20]; // 문자열 타입;
int birth; // 정수형 타입;
double height; // 실수형 타입;
double weight; // 실수형 타입;
bool married; // 논리형 타입;
};
공용체는 저장공간을 공유하는 데이터의 모음으로 구성된 타입이다. 이때 모여진 원소들의 구성형태는 원소의 타입은 서로 같을 수도 있고 다를 수도 있으며, 각 원소는 이름으로 구별한다. 그러나 원소들의 저장공간이 공유되기에 한 원소의 값이 바뀌면 다른 원소들의 값도 영향을 받게 된다. 공용체는 과거에 메모리 절약을 위하여 주로 사용했으나, 현재는 하드웨어의 발달로 인해 잘 사용되지는 않는다. C/C++에서의 공용체로는 union 타입이 있다.
// 구조체와 공용체의 생김새는 거의 동일하나, 메모리 공유 부분에서 차이가 있다.
union uni {
int i;
char a;
double f;
};
포인터형과 참조형 (Pointer Type, Reference Type)
포인터형은 특정 데이터가 저장되는 주소 자체를 데이터로 다루는 타입으로, 이때 특정 데이터의 타입은 단순타입, 복합타입 모두 가능하며 심지어 포인터형까지도 가능하다. 포인터형 변수 앞에 역참조 연산자 *를 붙여 값으로 가리키는 주소에 저장된 데이터를 사용하게 된다. C/C++에서 정수형 포인터형 변수를 선언하는 방법은 다음과 같다.
int k = 4; // 정수형 변수 선언
int *ptr; // 정수형 포인터 변수 선언
ptr = k; // 정수형 변수 k의 주소를 ptr에 대입
참조형은 사용자가 지정하는 타입의 데이터가 저장된 주소 자체를 자신의 주소로 사용하는 타입이다. 참조형은 지정한 타입의 저장공간이 기존에 반드시 존재해야 한다. 기존의 데이터를 참조하는 변수를 생성하면, 만들어진 참조 변수에서도 참조 대상의 값을 마음껏 변경할 수 있게 된다.
int k = 4; // 정수형 변수 선언
int &ref = k; // 정수형 변수 k 참조
ref = 5; // k = 5가 된다.