C++은 변수의 타입이 엄격한 언어이다. 모든 변수는 선언시 자료형이 결정되며 변수가 소멸할 때까지 자료형이 바뀌지 않는다. 이러한 성질을 type-safe하다고 표현하며, 프로그래머의 실수에 의한 변수의 오용을 상당히 막아줄 수 있다.
C++ 언어 스펙상 내장되어 있는 기본 자료형이 있다. 사용자 정의 리터럴이 아닌 대부분의 리터럴은 기본 자료형을 갖는다. 리터럴이란 소스 코드 상에 수 또는 문자열 등으로 하드코딩된 값을 말합니다. 다음은 C++의 기본 자료형과 각 자료형별 리터럴에 대한 설명이다.
데이터 타입 | 설명 |
---|---|
char | 부호 비트가 있으며 1byte 크기의 정수를 나타낸다. 따라서 수의 범위는 -2^7 ~ 2^7-1(-128 ~ 127)이다. 일반적으로 아스키 코드 범주에 속하는 하나의 문자를 표현하는데 쓰인다. 문자 하나를 따옴표로 감싸면 해당 문자에 매칭되는 아스키 코드값(수)를 반환한다. 따라서 다음과 같이 활용할 수 있다. char c = 'a'; 따옴표는 일종의 단항 연산자이며 char타입 리터럴은 위와 같이 따옴표 연산자를 통해서만 표현된다. 1byte지만 멀티 바이트 문자(2byte 이상의 바이트 열이 하나의 문자를 표현)의 개별 바이트를 저장하는 데 사용될 수 있어 char 배열로 한글 등의 문자열을 저장할 수 있지만 문자셋(각 문자를 표현하는 바이트 또는 바이트 열을 규정한 것), 바이트 오더(하나의 데이터가 2byte 이상인 경우 시스템에서 해당 데이터에 접근할 때 바이트를 읽고 쓰는 순서) 등이 시스템 플랫폼에 따라 다를 수 있기 때문에 외부 시스템과의 입출력에서는 사용하지 않는 것이 좋다. |
int | 부호 비트가 있는 정수를 나타낸다. 표현 가능한 크기는 컴파일러에 따라 다르지만 일반적으로 4byte 크기의 2의 보수법 표기를 사용한다. 따라서 수의 범위는 -2^31 ~ 2^31-1이다. 표현 가능한 수의 범위에 포함되는 정수 리터럴은 기본적으로 int 타입이다. |
long long (int) | 최소 long 이상의 크기를 가지는 부호 비트가 있는 정수를 나타낸다. 일반적으로 8byte로 -2^63 ~ 2^63-1의 정수를 표현할 수 있다. int 범위를 벗어나는 정수는 기본적으로 long long 타입이며, int 범위라고 하더라고 정수 뒤에 ll또는 LL 접미사를 붙여 long long 타입의 리터럴을 표현할 수 있다. long long ll1 = 123LL; long long ll2 = 987654321987; // int 범위를 벗어나는 정수 |
float | 단정도(비교적 낮은 정밀도 수준을 나타내는 표현) 부동소수점 수를 나타내며 보통 IEEE_754 binary32 표준을 따른다. 소수점을 포함하는 수 뒤에 접미사 f를 붙여 리터럴을 표현할 수 있다. float f = 1.2f; IEEE754 표준에 따라 8개의 지수 비트, 23개의 가수 비트를 사용한다면 유효 자릿수는 10진수로 대략 6자리이고 2진수로 23자리이다. |
double | 배정도 부동소수점 수를 나타내며 보통 IEEE754 binary64 표준도 따른다. 소수점을 포함하는 수는 기본적으로 double 타입이다. IEEE754 표준에 따라 11개의 지수 비트, 52개의 가수 비트를 사용한다면 유효 자리수는 10진수로 대략 15자리이고 2진수로 52자리이다. |
wchar_t | 다양한 문자셋을 나타내기 위한 정수 타입으로 컴파일러에 따라 크기가 다르다. 접두사 L을 붙여 wchar_t 타입의 리터럴을 나타낼 수 있다. wchar_t wc = L'가'; c++17부터 문자 인코딩 변경을 위한 codecvt라는 편의 클래스를 제공한다. |
bool | true 또는 false값을 가지는 논리 타입이다. 각 값을 나타내는 실제 비트열은 컴파일러 구현에 따라 다를 수 있다. 이론적으로 1bit면 표현이 가능하지만 컴퓨터 구조상 1byte 단위로 메모리를 관리하기 때문에 bool 타입은 1byte의 크기를 갖는다. |
데이터 타입 | 설명 |
---|---|
short (int) | 부호 비트가 있는 정수를 나타내며 long 타입보다 좁은 범위를 표현한다. 보통은 2byte의 크기를 가져 -2^15 ~ 2^15-1의 정수를 표현할 수 있다. int 키워드를 생략하여 short로 선언이 가능하며, short 타입 리터럴은 없다. |
long (int) | 부호 비트가 있는 정수를 나타내며 short 타입보다 넓은 범위를 표현한다. Windows 계열은 4byte, Unix, Linux, macOS 계열은 8byte이다. int 키워드를 생략하여 long으로 선언 가능하며, 다음과 같이 정수 뒤에 l 또는 L 접미사를 붙여 long 타입의 리터럴을 표현할 수 있다. long l = 123L; |
unsigned (int) unsigned short (int) unsigned long (int) unsigned long long (int) | 부호 비트 없이 음이 아닌 정수만 나타낸다. 비트 수는 각각 unsigned가 붙지 않은 타입과 동일하다. 부호 있는 타입의 리터럴 접미사 앞에 u또는 U를 붙여 리터럴을 표현할 수 있다. unsigned long long = 123ULL; |
long double | 최소한 double 이상의 정밀도를 갖는 부동 소수점 수를 나타내지만 보통 double과 같아서 잘 쓰이지 않는다. (HP-UX, SPARC, MIPS, ARM64, z/OS 등 일부 서버용 OS에서 IEEE754 binary128 포맷) 소수점을 포함하는 수에 l또는 L을 붙여 long double 타입의 리터럴을 표현할 수 있다. |
char8_t | 8bit 크기의 정수 타입으로서 UTF-8으로 인코딩 된 문자를 나타내기 위해 만들어진 타입이다. 접두사 u8을 붙여 char8_t 타입의 리터럴을 표현할 수 있다. char8_t c8 = u8'm' |
char16_t | 16bit 크기의 정수 타입으로서 UTF-16으로 인코딩 된 문자를 나타내기 위해 만들어진 타입이다. 접두사 u을 붙여 char16_t 타입의 리터럴을 표현할 수 있다. char16_t c16 = u'm'; |
char32_t | 32bit 크기의 정수 타입으로서 UTF-32으로 인코딩 된 문자를 나타내기 위해 만들어진 타입이다. 접두사 U을 붙여 char32_t 타입의 리터럴을 표현할 수 있다. char32_t c32 = U'm'; |
산술 연산 등에서 산술 오버플로우(자료형이 표현 가능한 값의 범위를 벗어나는 것)가 발생할 수 있음을 항상 염두해 둬야한다. 예를 들어 다음의 코드에서는 오버플로우에 의한 정보의 소실(슬라이싱)현상이 발생한다.
long long result = 1987654321 + 1987654321; // 오류
두 피연산자는 int 타입의 리터럴이며 int 타입과 int 타입의 산술 연산은 리턴 타입도 int 타입이다. 따라서 계산하는 과정에서 산술 오버플로우가 발생하며, result가 long long 이더라도 이미 대입하는 표현식 자체(+연산의 결과)가 슬라이싱이 발생한 int 타입 임시 객체이다.
산술 연산시 피연산자가 더 넓은 범위의 타입의 피연산자와 연산을 하게되면 자동으로 캐스팅(형변환)이 발생한다.
long long result = 1987654321LL + 1987654321;
이 경우 두번째 피연산자는 + 연산 전에 자동으로 long long 타입으로 캐스팅된 후 연산하므로 슬라이싱이 발생하지 않는다.
float, double, long double 등이 실수 타입 다루는 경우는 항상 어느 정도의 오차를 염두에 둬야한다. 예를들어 어떤 두 실수가 같은지 비교하는 함수를 다음과 같이 구현할 수 있다.
bool absoluteEqual(double a, double b) { return fabs(a - b) < 1e-10; }
보통 이러한 기준 오차를 입실론 상수라고 하는데 이렇게 고정된 상수값으로는 입력 값에 따라 오차가 커지는 것에 대응하지 못하기 때문에 안정적인 비교가 불가능하다. 만약 주어질 입력의 범위를 알고 있고 계산 중 발생할 수 있는 오차의 최대값을 미리 계산 가능하다면 이러한 방법이 유효할 수 있다.
그러나 주어질 입력의 범위를 알 수 없다면 다른 방법을 시도해야 하는데 상대오차를 통해 비교를 하면 큰수에서 발생하는 오차에는 적절하게 대응하지만 오히려 작은 수 사이의 비교에서는 잘못된 결과를 얻을 수 있다. 따라서 절대오차와 상대오차를 모두 고려하여 다음과 같은 방식으로 비교해야 한다.
bool doubleEqual(double a, double b) { double diff = fabs(a - b); if (diff < 1e-10) return true; return diff <= 1e-8 * max(fabs(a), fabs(b)); // 오차가 더 큰 수의 0.000001% 이하이면 true }
만약 실수 중 유리수만 다룬다고 하면 분자와 분모를 각각 관리하는 유리수 클래스 등을 통해 정확한 사칙연산을 구현할 수 있다. 그러나 분자, 분모를 정수타입으로 정의하면 표현할 수 있는 유리수의 범위가 상당히 제한되기 때문에 이런 경우는 배열등을 활용하여 구현된 큰 정수 클래스 등을 함께 구현해야할 수 있다.
어떤 프로그램의 실행 과정에서 발생하는 오차들이 일련의 연산 과정으로 증폭되어 그 결과가 정확한 계산 결과로부터 큰 펀차를 갖게 되는 것을 수치적 불안정이라고 하며 반대로 편차가 항상 일정 수준 이하로 보장되는 것을 수치적 안정이라고 한다. 세심히 설계된 알고리즘에서는 수치적으로 안정적일 수 있으나 수치적으로 안정적인 코드를 작성하기란 꽤 어렵고 자칫 작은 변경사항에도 그 수치적 안정성이 깨지기 쉽다. 따라서 만약 실수 연산을 완전히 우회할 수 있다면 그렇게 하는 것이 가장 바람직하다.
예를 들어 2차원 평면에서 두 점 사이의 거리를 다룬다면 거리 대신 거리의 제곱을 취급함으로써 실수 연산을 우회할 수 있다.
기본 자료형은 아니지만 변수 선언 시 사용될 수 있는 특별한 키워드가 존재한다. 이들은 초기화가 필수이며 초기화에서 넘겨준 값 또는 표현식 등을 통해 타입을 추론하여 변수를 선언해준다. 다음은 이러한 키워드에 대한 설명과 사용 예이다.
데이터 타입 | 설명 |
---|---|
auto | 초기값에 따라서 타입을 추론해준다. 컴파일 타임에 타입이 결정되어 변하지 않는 것이기 때문에 초기값이 반드시 필요하다. 추론 알고리즘은 다소 복잡하지만 일반적으로 사용자가 기대하는 바대로 작동한다. 다음과 같이 변수 선언시 타입을 나타내는 키워드처럼 사용한다. auto x = 123; //x는 int 타입 const auto cx = x; //cx는 const int 타입 const auto& rx = x; //rx는 const int& 타입 auto&& fref1 = x; //fref1은 int& 타입 auto&& fref2 = cx; //fref2은 const int& 타입 auto&& fref3 = 27; //fref3은 int&& 타입 const char str[] = “abcd”; //str은 const char[5] 타입 auto arr1 = str; //arr1은 const char* 타입 auto& arr2 = str; //arr2은 const char(&)[5] 타입 auto y = {21}; //y는 initializer_list 타입 auto z {35}; //z는 컴파일러에 따라 initializer_list or int 타입 |
decltype ([expression]) | declared type의 약자이다. 이름과 같이 선언된 형식을 그대로 알려주는 키워드이다. 0개 이상의 변수를 통해 평가될 수 있는 식을 표현식이라고 한다. 이 표현식에 따라 cvr 한정사 등을 포함한 구체적인 형식을 알려준다. const int i = 0; // delytype(i)는 const int bool f(const Widget& w) // decltype(w)는 const Widget&, decltype(f)는 bool(const Widget&) struct Point { // decltype(Point::x)는 int int x, y; }; Widget w; // decltype(w)는 Widget if (f(w)) ... // decltype(f(w))는 bool vector v(1); // decltype(v)는 vector, decltype(v[0])는 int& |
decltype(auto) | auto와 같이 초기값 표현식에 따라 타입을 추론하지만, 추론하는 과정에서 decltype의 규칙을 따른다. 주로 함수의 반환형을 나타낼 때 사용된다. decltype(auto) f1() { int x = 0; ... return x; // int타입 반환, 만약 return(x)인 경우 int&타입 반환 } |