기본 데이터 타입
정수·실수·문자·논리 타입의 저장 방식과 표현 범위를 비교하고 구현 환경에 맞는 데이터 타입을 선택합니다.
이제부터는 C++ 언어가 데이터를 다루는 방법에 대해 본격적으로 탐구할 시간입니다.
우리가 일상생활에서 다양한 종류의 정보(숫자, 글자, 참/거짓 등)를 사용하듯이, 컴퓨터 프로그램도 숫자, 문자, 논리 값 등 여러 형태의 데이터를 처리해야 합니다.
C++에서는 이러한 데이터의 종류를 구분하기 위해 데이터 타입(Data Type)이라는 개념을 사용합니다.
이 장에서는 C++이 기본적으로 제공하는 데이터 타입들에 대해 자세히 알아보고, 각 타입이 어떤 종류의 데이터를 저장하며 메모리에서 얼마만큼의 공간을 차지하는지, 그리고 어떤 범위의 값을 표현할 수 있는지 이해하는 데 중점을 둘 것입니다.
첫 그림은 기본 타입을 표 암기가 아니라 저장 폭, 범위, 부호 해석, 실수 정밀도를 함께 확인하는 판독 순서로 묶습니다.
플랫폼마다 크기가 달라질 수 있는 부분은 sizeof와 std::numeric_limits로 확인한다는 기준을 먼저 둡니다.
그림과 표의 크기·정밀도는 일반적인 구현 기준이며, char의 signed 여부, bool 크기, long double 정밀도처럼 구현별로 달라질 수 있는 값은 직접 확인해야 합니다.
| 타입군 | 먼저 묻는 질문 | 표준 계약 · 주의점 | 현재 구현 확인 |
|---|---|---|---|
| 부호 있는 정수 | 입력과 중간 계산 값의 범위는? | short → int → long → long long은 저장 공간이 줄지 않는다. 표현 범위를 넘는 signed 산술은 정의되지 않는다. |
sizeof(T), std::numeric_limits<T> |
| 부호 없는 정수 | 순환 산술이나 비트 패턴이 실제 의미인가? | 대응 signed 타입과 같은 폭 N, 범위 0 … 2^N−1, 산술은 2^N을 법으로 한다. |
std::numeric_limits<T>::max, signed/unsigned 혼합 식 점검 |
| 부동 소수점 | 필요한 정밀도와 허용 오차 모델은? | double은 float 이상, long double은 double 이상의 정밀도를 제공하지만 같은 표현일 수도 있다. |
radix, digits10, denorm_min(), lowest()/max(), is_iec559 |
| 문자 · 코드 단위 | 어떤 리터럴·문자열 인코딩을 다루는가? | char 부호와 일반·wide 인코딩은 구현 정의다. char8_t/16_t/32_t는 각 UTF 인코딩의 코드 단위다. |
CHAR_BIT, sizeof, 리터럴 prefix와 입출력 인코딩 |
| 논리 | 값이 계산량이 아니라 조건의 상태인가? | bool의 값은 true와 false다. 크기는 구현 정의이며 정수 개수 저장용 타입이 아니다. 두 상태를 넘는 이름 있는 상태 집합이면 enum을 검토한다. |
sizeof(bool), boolalpha 출력 여부 |
- 부호 있는 정수
- 질문 입력과 중간 계산 값의 범위는?
- 계약 표준 타입 순서에서 저장 공간은 줄지 않으며, 범위를 넘는 signed 산술은 정의되지 않습니다.
- 확인
sizeof와std::numeric_limits<T> - 부호 없는 정수
- 질문 순환 산술이나 비트 패턴이 실제 의미인가?
- 계약 범위는
0 … 2^N−1이고 산술은2^N을 법으로 합니다. - 확인 최댓값과 signed/unsigned 혼합 식
- 부동 소수점
- 질문 필요한 정밀도와 허용 오차 모델은?
- 계약 뒤 타입은 앞 타입 이상의 정밀도를 제공하지만 같은 표현일 수도 있습니다.
- 확인
radix,digits10,denorm_min(),lowest()/max(),is_iec559 - 문자 · 코드 단위
- 질문 어떤 리터럴·문자열 인코딩을 다루는가?
- 계약 일반
char의 부호와 ordinary·wide 인코딩은 구현이 정합니다. - 확인
CHAR_BIT, 크기, prefix, 입출력 인코딩 - 논리
- 질문 계산량이 아니라 조건의 상태인가?
- 계약
bool은true와false를 표현하며 크기는 구현 정의입니다. 두 상태를 넘는 이름 있는 상태 집합이면enum을 검토합니다. - 확인
sizeof(bool)과boolalpha출력
<cstdint>의 제공 여부, 바이트 순서, 직렬화 규칙을 별도 계약으로 둡니다.데이터 타입이란 무엇인가?
다음 그림은 변수에 타입을 붙이는 순간 값의 모양, 메모리 폭, 연산 규칙, 오류 가능성이 함께 정해진다는 점을 정리합니다.
값의 의미에 맞춰 타입을 정하면 아래 세 계약이 함께 결정됩니다.
- 표현 계약
- 가능한 값과 범위, 정밀도, 문자 코드 단위를 정합니다.
- 연산 계약
- 산술·비교·변환, 오버플로와 반올림 규칙을 정합니다.
- 객체 계약
- 크기·정렬과 구현별 표현 특성을 정합니다.
컴파일 결과 · 실행 의미
세 계약이 함께 관찰 가능한 동작을 만듭니다.
검증할 때는 sizeof, std::numeric_limits, 경계값 테스트로 현재 구현과 의도를 대조합니다.
데이터 타입은 프로그램이 데이터를 해석하고 처리하는 방식을 결정하는 속성입니다.
변수를 선언할 때 특정 데이터 타입을 지정하면, 컴파일러는 해당 변수가 어떤 종류의 데이터를 저장할지, 얼마나 많은 메모리 공간을 할당해야 할지, 그리고 그 데이터를 어떻게 연산해야 할지를 알게 됩니다.
예를 들어, 숫자 10을 저장한다고 할 때, 이 10이 정수 10인지, 아니면 10.00과 같은 실수 10인지에 따라 컴퓨터는 내부적으로 다른 방식으로 저장하고 처리합니다.
데이터 타입은 이처럼 데이터의 본질과 그것을 다루는 규칙을 명시하는 역할을 합니다.
정수 타입 (Integer Types)
정수는 소수점 이하가 없는 숫자를 의미합니다.
C++에서는 다양한 크기의 정수 타입을 제공하여, 필요에 따라 효율적으로 메모리를 사용할 수 있도록 합니다.
| 타입 | 표준 최소 폭 | 표현 범위 | 흔한 sizeof | 핵심 용도와 주의점 |
|---|---|---|---|---|
char | 8비트 이상 | 부호 해석은 구현이 정함 | 1 | 일반 문자 코드 단위이자 C++ 바이트. signed char와 다름 |
short | 16비트 | ~ | 2 | 작은 정수. 실제 폭과 범위는 구현에서 확인 |
int | 16비트 | ~ | 4 | 일반 정수 연산의 출발점 |
long | 32비트 | ~ | 4 또는 8 | ABI에 따라 폭이 달라지기 쉬움 |
long long | 64비트 | ~ | 8 | 표준 정수 타입 중 최소 폭이 가장 큼 |
여기서 은 해당 정수 타입의 값 표현 폭입니다. C++ 표준은 signed char → short → int → long → long long 순서에서 뒤 타입이 앞 타입보다 적은 저장 공간을 제공하지 않으며, 위 표의 최소 폭 이상이어야 한다고 정합니다. 흔한 sizeof 값은 예시일 뿐입니다.
sizeof(char)는 항상 1이지만, 이 1은 C++가 정의한 바이트 한 칸입니다. 한 바이트의 비트 수는 CHAR_BIT로 확인하며 반드시 8이라고 가정할 수는 없습니다. char는 signed char, unsigned char와 서로 다른 타입이고, 일반 char가 어느 부호 표현을 따르는지는 구현이 정합니다.
부호 있는 정수와 부호 없는 정수:
short, int, long, long long은 signed를 생략한 표기이며 양수와 음수를 표현합니다. 일반 char의 부호는 앞서 본 것처럼 별도 구현 정의입니다.
각 표준 부호 있는 정수 타입에는 같은 폭 을 가진 대응 부호 없는 타입이 있습니다. 부호 없는 타입의 범위는 0부터 이고, 산술은 을 법으로 계산됩니다. 따라서 최댓값에서 1을 더하거나 0에서 1을 빼면 범위의 반대쪽으로 순환합니다.
이 순환이 프로토콜·비트 마스크처럼 의도한 의미라면 unsigned가 알맞지만, 단순히 "음수가 없을 것"이라는 이유만으로 선택하면 뺄셈과 signed/unsigned 혼합 비교에서 버그를 숨길 수 있습니다. 수량 차이가 음수가 될 수 있거나 오류를 음수 값으로 표현한다면 먼저 signed 타입을 검토하세요. signed 산술이 표현 범위를 넘는 경우는 unsigned 순환과 달리 정의되지 않은 동작입니다.
#include <climits> // CHAR_BIT
#include <iostream>
#include <limits> // 각 타입의 최대/최소 값을 확인하기 위해 포함
int main() {
char singleChar = 'A'; // 단일 문자 저장
short smallNumber = 100;
int integerValue = 12345; // 가장 일반적으로 사용
long bigNumber = 1234567890L; // L 접미사는 long 타입을 명시 (선택적)
long long veryBigNumber = 9000000000000000000LL; // LL 접미사는 long long 타입을 명시
unsigned int positiveOnly = 42U; // U 접미사는 unsigned 타입을 명시
std::cout << "char: " << singleChar << " (크기: " << sizeof(char) << "바이트)" << std::endl;
std::cout << "short: " << smallNumber << " (크기: " << sizeof(short) << "바이트)" << std::endl;
std::cout << "int: " << integerValue << " (크기: " << sizeof(int) << "바이트)" << std::endl;
std::cout << "long: " << bigNumber << " (크기: " << sizeof(long) << "바이트)" << std::endl;
std::cout << "long long: " << veryBigNumber << " (크기: " << sizeof(long long) << "바이트)" << std::endl;
std::cout << "unsigned int: " << positiveOnly << " (크기: " << sizeof(unsigned int) << "바이트)" << std::endl;
std::cout << "한 바이트의 비트 수: " << CHAR_BIT << std::endl;
// 각 타입의 최대/최소 값 확인 (limits 헤더 필요)
std::cout << "int 최대값: " << std::numeric_limits<int>::max() << std::endl;
std::cout << "int 최소값: " << std::numeric_limits<int>::min() << std::endl;
std::cout << "unsigned int 최대값: " << std::numeric_limits<unsigned int>::max() << std::endl;
return 0;
}sizeof 연산자는 특정 데이터 타입이나 변수가 메모리에서 차지하는 C++ 바이트 수를 알려줍니다.
std::numeric_limits는 <limits> 헤더에 정의된 템플릿 클래스로, 각 숫자 타입의 최소/최대값 등 다양한 정보를 얻을 수 있게 해줍니다.
부동 소수점 타입 (Floating-Point Types)
부동 소수점 타입은 소수점이 있는 숫자(실수)를 저장하는 데 사용됩니다.
정수 타입과 달리, 실수는 컴퓨터 내부에서 약간의 오차를 가질 수 있습니다.
| 타입 | 표준이 보장하는 관계 | 흔한 구현 예시 | 직접 확인할 속성 |
|---|---|---|---|
float | 표준 부동 소수점 타입 | 4바이트, 약 6자리 십진 정밀도 | radix, digits10, denorm_min(), lowest(), max() |
double | float 이상의 정밀도와 값 집합 | 8바이트, 약 15자리 십진 정밀도 | radix, digits10, max_digits10, is_iec559 |
long double | double 이상의 정밀도와 값 집합 | 8·12·16바이트 등 구현마다 다름 | sizeof, radix, digits10, lowest(), max(), is_iec559 |
정밀도: 부동 소수점 타입은 유효 숫자 자릿수로 정밀도를 표현합니다.
흔한 radix-2 ISO 60559 구현에서는 float과 double의 digits10이 각각 약 6과 15입니다. 이는 제한된 자릿수의 십진 값을 타입으로 변환했다가 되돌릴 때 보존할 수 있는 보수적인 십진 변환 정밀도이지, 그 자릿수 이하의 모든 십진 분수가 내부에서 정확히 표현된다는 뜻은 아닙니다. 실제 기수와 표현 방식, 연산 정확도는 radix, is_iec559 등으로 확인해야 합니다. long double은 double보다 적은 정밀도를 가질 수는 없지만 같은 표현을 사용할 수도 있으므로, 이름만 보고 정밀도가 늘어난다고 가정하지 마세요.
부동 소수점 타입에서 std::numeric_limits<T>::min()은 가장 작은 양의 정규화 값입니다. 서브노멀 값을 지원하면 그보다 작은 양수도 있으며, denorm_min()이 가장 작은 양의 서브노멀 값을 돌려줍니다. 서브노멀을 지원하지 않으면 denorm_min()은 min()과 같습니다. 가장 작은 유한값은 lowest(), 가장 큰 유한값은 max()로 확인합니다.
부동 소수점 오차: 흔한 radix-2 구현에서는 0.1과 같은 일부 십진 분수를 정확히 표현할 수 없습니다. 다른 기수의 구현도 가능하므로 현재 타입의 radix를 확인해야 합니다.
이로 인해 미세한 오차가 발생할 수 있습니다. 비교할 때는 모든 값에 하나의 고정 epsilon을 기계적으로 적용하지 말고, 측정 단위와 값의 크기, 계산 횟수에 맞는 절대·상대 허용 오차나 도메인 규칙을 정해야 합니다.
#include <iostream>
#include <iomanip> // std::fixed, std::setprecision을 사용하기 위해 포함
int main() {
float pi_float = 3.1415926535F; // F 접미사는 float 타입을 명시
double pi_double = 3.14159265358979323846; // 기본적으로 double 타입으로 간주
long double pi_long_double = 3.14159265358979323846L; // L 접미사는 long double 타입을 명시
std::cout << std::fixed << std::setprecision(20); // 소수점 이하 20자리까지 고정 출력
std::cout << "float (pi): " << pi_float << " (크기: " << sizeof(float) << "바이트)" << std::endl;
std::cout << "double (pi): " << pi_double << " (크기: " << sizeof(double) << "바이트)" << std::endl;
std::cout << "long double (pi): " << pi_long_double << " (크기: " << sizeof(long double) << "바이트)" << std::endl;
// 부동 소수점 오차 예시
double result = 0.1 + 0.2;
std::cout << "0.1 + 0.2 = " << result << std::endl; // 정확히 0.3이 아닐 수 있음
return 0;
}std::fixed와 std::setprecision은 <iomanip> 헤더에 정의되어 있으며, 실수를 출력할 때 소수점 이하 자릿수를 제어하는 데 사용됩니다.
문자 타입 (Character Types)
문자 타입은 문자의 화면 모양 자체가 아니라 문자열 인코딩을 이루는 코드 단위(code unit)를 저장합니다. 문자 하나가 항상 코드 단위 하나라는 뜻은 아닙니다.
char: 일반 문자·문자열 리터럴의 코드 단위입니다. 크기는 1 C++ 바이트지만 일반 리터럴 인코딩은 구현이 정하므로 ASCII로 고정하지 않습니다.wchar_t: wide 리터럴의 코드 단위입니다. 기반 정수 타입과 wide 리터럴 인코딩이 구현 정의이므로 그 자체를 이식 가능한 Unicode 저장 타입으로 볼 수 없습니다.char8_t: C++20부터 UTF-8 코드 단위를 표현합니다.u8"..."리터럴의 원소 타입입니다.char16_t,char32_t: 각각 UTF-16과 UTF-32 리터럴의 코드 단위를 표현합니다. UTF-16에서는 하나의 Unicode 스칼라 값이 두 코드 단위를 사용할 수 있습니다.
#include <iostream>
int main() {
char grade = 'A'; // ordinary literal encoding의 한 코드 단위
std::cout << "내 학점: " << grade << std::endl;
std::cout << "이 구현의 코드 단위 값: "
<< static_cast<unsigned int>(static_cast<unsigned char>(grade))
<< std::endl;
return 0;
}논리 타입 (Boolean Type)
논리 타입은 참(true) 또는 거짓(false)이라는 두 가지 값만을 저장합니다.
프로그램의 조건문이나 반복문 등에서 특정 조건의 만족 여부를 판단할 때 사용됩니다.
bool:true또는false값을 가집니다. 대부분의 구현에서는 1바이트로 보이지만, 정확한 크기는sizeof(bool)로 확인합니다.
#include <iostream>
int main() {
bool isCppFun = true; // 참을 의미하는 true
bool hasErrors = false; // 거짓을 의미하는 false
std::cout << "C++은 재밌는가? " << isCppFun << std::endl; // true는 1로, false는 0으로 출력됨
std::cout << "오류가 있는가? " << hasErrors << std::endl;
// 논리 연산
if (isCppFun) {
std::cout << "아주 재미있습니다!" << std::endl;
} else {
std::cout << "더 노력해야겠네요..." << std::endl;
}
return 0;
}bool 타입의 값을 std::cout으로 출력하면 true는 1로, false는 0으로 표시됩니다.
만약 true나 false 문자 그대로 출력하고 싶다면 std::boolalpha 조작자를 사용할 수 있습니다.
std::cout << std::boolalpha << "C++은 재밌는가? " << isCppFun << std::endl; // "true"로 출력마지막으로 기본 타입을 고를 때는 단순히 "숫자냐 문자냐"만 보지 말고, 값의 의미, 필요한 범위, 부호·순환 산술 여부, 정밀도와 오차 모델, 인코딩, 출력 방식을 순서대로 확인해야 합니다. 정확한 폭이나 표현 특성이 외부 형식의 계약이라면 <cstdint>의 고정폭 타입 제공 여부와 직렬화 규칙까지 별도로 확인하세요.