C++ 변수와 자료형 | int, double, string 완벽 정리 [초보자용]
이 글의 핵심
C++ 변수와 자료형: int, double, string [초보자용]. C++ 기본 자료형 한눈에 보기·자료형 심화.
C++ 기본 자료형 한눈에 보기
| 자료형 | 크기 | 범위 | 용도 |
|---|---|---|---|
| int | 4바이트 | -2^31 ~ 2^31-1 | 정수 |
| long long | 8바이트 | -2^63 ~ 2^63-1 | 큰 정수 |
| float | 4바이트 | 약 ±3.4e38 | 실수 (정밀도 낮음) |
| double | 8바이트 | 약 ±1.7e308 | 실수 (정밀도 높음) |
| char | 1바이트 | -128 ~ 127 | 문자 |
| bool | 1바이트 | true/false | 논리값 |
| string | 가변 | - | 문자열 (STL) |
자료형 심화
정수형 크기: int, long, long long
C++에서 정수형의 정확한 바이트 수는 구현과 플랫폼에 따라 달라질 수 있습니다. 실무에서는 <cstdint>의 고정 크기 타입(int32_t, int64_t 등)을 쓰기도 하지만, 초보 단계에서는 표준 정수형의 관계만 이해하면 됩니다.
- int: 대부분의 환경에서 4바이트(32비트). 일상적인 정수 연산의 기본 선택입니다.
- long: Windows MSVC에서는
int와 같이 4바이트인 경우가 많고, Linux 64비트에서는long이 8바이트인 경우도 있습니다. 이식성이 필요하면long에 의존하지 말고 범위가 크면long long을 쓰는 편이 안전합니다. - long long: 최소 64비트 이상이 보장됩니다. 큰 수, 오버플로우를 피해야 하는 누적 합, 파일 크기 등에 적합합니다.
#include <iostream>
int main() {
int a = 2147483647; // int 최댓값 근처
long long b = 2147483648LL; // int를 넘는 값은 LL 접미사 권장
std::cout << a << ", " << b << "\n";
return 0;
}
요약: int로 충분한지 먼저 보고, 범위가 부족하면 long long으로 올리세요. long은 플랫폼별 차이를 기억해 두세요.
부호 있는 타입과 없는 타입 (signed, unsigned)
- signed: 기본적으로
int,short등은 부호가 있습니다(음수·양수).signed int와int는 같은 의미입니다. - unsigned: 음수를 표현하지 않고 0 이상만 표현합니다. 같은 비트 폭이면 양의 범위가 약 두 배 넓어집니다.
unsigned int u = 4000000000U; // int로는 못 담을 수 있는 큰 양수
unsigned char flags = 255; // 0~255
주의: unsigned와 int를 섞어 연산하면 보통 unsigned 쪽으로 승격되어, 음수가 있던 변수가 거대한 양수로 해석되는 버그가 납니다. 비교·뺄셈 전에 타입을 맞추거나 static_cast로 의도를 명확히 하세요.
int x = -1;
unsigned int y = 10;
if (x < y) { } // 기대와 다르게 동작할 수 있음 (-1이 unsigned로 변환되면 큰 양수)
실수형 정밀도: float vs double
| 구분 | float | double |
|---|---|---|
| 대략적인 유효 숫자 | 6~7자리 | 15~16자리 |
| 용도 | 메모리 절약, GPU 등 | 일반 계산의 기본 |
일반 애플리케이션에서는 double을 기본으로 쓰는 것이 좋습니다. 3.14 같은 리터럴도 C++에서는 기본적으로 double입니다. float 리터럴은 접미사 f를 붙입니다 (3.14f). |
float f = 3.14f;
double d = 3.14; // double
auto x = 3.14; // 역시 double
char vs string
- char: 문자 하나를 담습니다(ASCII 한 글자 등). 작은따옴표
'A'를 사용합니다. - std::string: 문자열(여러 글자). 큰따옴표
"hello"를 사용합니다.
#include <string>
char letter = 'A'; // 한 글자
std::string name = "Hong"; // 문자열
// char wrong = "Hello"; // 오류: 문자열을 char에 넣을 수 없음
한글처럼 멀티바이트 문자는 한 char에 다 들어가지 않을 수 있습니다. 입문 단계에서는 ASCII와 std::string 위주로 이해하고, 유니코드가 필요하면 이후에 std::u8string 등을 별도로 학습하면 됩니다.
변수 선언과 초기화
선언만 하기 vs 초기화하기
- 선언만: 변수의 이름과 타입만 정하고, 값은 아직 넣지 않습니다. 지역 변수는 읽기 전에 초기화하지 않으면 쓰레기 값이 들어갈 수 있습니다.
- 초기화: 선언과 동시에(또는 그 직후) 첫 값을 넣습니다. 가능하면 선언과 동시에 초기화하는 습관이 안전합니다.
int score = 100; // 선언 + 초기화 (권장)
double pi = 3.14159;
char grade = 'A';
bool passed = true;
std::string user = "홍길동";
기본 선언 예제
#include <iostream>
#include <string>
int main() {
// 선언만 (지역 변수는 초기화 전 사용 금지)
int age;
// 선언과 동시에 초기화 (권장)
int score = 100;
double pi = 3.14159;
char grade = 'A';
bool isPassed = true;
std::string name = "홍길동";
age = 20; // 나중에 대입
return 0;
}
중괄호 초기화 {} (Uniform initialization)
C++11 이후에는 중괄호 초기화를 많이 씁니다. 좁은 변환(narrowing)이 잡혀 실수를 줄여 줍니다.
int a{42};
double b{3.14};
int c = {7}; // 동일한 의미
// int x{3.14}; // 일부 컴파일러에서 narrowing 에러 — 의도하지 않은 실수→정수 방지
{} 초기화는 벡터 등 컨테이너 초기화와도 문법이 통일되어 읽기 좋습니다.
auto 키워드
오른쪽 표현식의 타입을 컴파일러가 추론합니다. 타입이 길거나 복잡할 때 유용합니다.
auto x = 10; // int
auto y = 3.14; // double
auto name = std::string("Kim"); // std::string
auto z = "text";는 const char*로 잡힐 수 있어, std::string이 필요하면 명시적으로 std::string을 쓰는 편이 낫습니다.
const와 constexpr
- const: 실행 중에 바꿀 수 없는 읽기 전용 값입니다. 런타임에 결정되는 값으로 초기화할 수 있습니다.
const int MAX = 100;
const double PI = 3.14159;
// MAX = 200; // 오류
- constexpr: 컴파일 타임에 값이 확정되는 상수에 사용합니다. 배열 크기 등에 쓰면 성능·명확성에 유리합니다.
constexpr int buf_size = 1024;
int arr[buf_size]; // C++에서 constexpr 크기 허용
constexpr int square(int x) { return x * x; }
constexpr int nine = square(3);
입문 단계에서는 const로 “변하지 않는 변수”를 표현하고, 컴파일 상수가 필요할 때 constexpr을 추가로 학습하면 됩니다.
여러 변수 한 번에 선언
// 같은 타입 여러 개
int a = 1, b = 2, c = 3;
// 권장: 한 줄에 하나씩 (가독성·디버깅)
int a = 1;
int b = 2;
int c = 3;
타입 변환
암시적 변환 (자동)
연산자나 대입 시 컴파일러가 타입을 맞추기 위해 자동으로 변환합니다.
int i = 10;
double d = i; // int → double (대체로 안전)
double x = 9.99;
int j = x; // 소수 부분 버림 (9). 의도한 경우에만 사용
int a = 5, b = 2;
double r = a / b; // 정수 나눗셈 먼저 → 2.0 (암시적 변환의 함정)
작은 타입이 큰 타입으로 갈 때는 정보 손실이 적고, 큰 타입을 작은 타입으로 넣을 때는 잘림·오버플로우에 주의하세요.
명시적 캐스팅: static_cast
C 스타일 (double)a 대신 static_cast를 쓰면 검색이 쉽고 의도가 분명합니다.
double r1 = static_cast<double>(5) / 2; // 2.5
int rounded = static_cast<int>(3.99); // 3
포인터 간 변환 등은 다른 종류의 캐스트(reinterpret_cast 등)가 필요할 수 있지만, 변수와 기본 타입 사이에서는 static_cast가 기본입니다.
형 변환 주의사항
- 부호 있는 정수 ↔ unsigned: 부호 없는 쪽으로 맞추면 음수가 큰 양수로 바뀝니다.
- 큰 정수 → 작은 정수: 상위 비트가 잘려 값이 달라집니다.
- 실수 → 정수: 소수는 버려지며, 범위를 넘으면 정의되지 않은 동작이 될 수 있습니다.
- 정수 나눗셈: 피연산자가 모두 정수면 결과도 정수입니다. 실수 나눗셈이 필요하면 한쪽을
double로 바꾸세요.
실전 예제
예제 1: 계산기 프로그램 (0으로 나누기 방지)
#include <iostream>
int main() {
double num1 = 0, num2 = 0;
char op = 0;
std::cout << "첫 번째 숫자: ";
if (!(std::cin >> num1)) {
std::cerr << "숫자가 아닙니다.\n";
return 1;
}
std::cout << "연산자 (+, -, *, /): ";
std::cin >> op;
std::cout << "두 번째 숫자: ";
if (!(std::cin >> num2)) {
std::cerr << "숫자가 아닙니다.\n";
return 1;
}
double result = 0;
bool ok = true;
if (op == '+') result = num1 + num2;
else if (op == '-') result = num1 - num2;
else if (op == '*') result = num1 * num2;
else if (op == '/') {
if (num2 == 0.0) {
std::cerr << "0으로 나눌 수 없습니다.\n";
return 1;
}
result = num1 / num2;
} else {
std::cerr << "지원하지 않는 연산자입니다.\n";
ok = false;
}
if (ok) std::cout << "결과: " << result << std::endl;
return 0;
}
double로 읽어 정수 나눗셈 실수를 피하고, 나눗셈 전에 0 여부를 검사했습니다.
예제 2: 온도 변환기 (섭씨 ↔ 화씨)
#include <iostream>
int main() {
double celsius = 0;
std::cout << "섭씨 온도: ";
std::cin >> celsius;
double fahrenheit = celsius * 9.0 / 5.0 + 32;
std::cout << celsius << "°C = " << fahrenheit << "°F\n";
// 화씨 → 섭씨: C = (F - 32) * 5/9
double back = (fahrenheit - 32.0) * 5.0 / 9.0;
std::cout << "역변환 확인: " << back << "°C\n";
return 0;
}
9.0 / 5.0처럼 실수 리터럴을 써서 정수 나눗셈(9/5 == 1)이 되지 않게 했습니다.
예제 3: 입력 검증 (범위·타입)
#include <iostream>
#include <limits>
int main() {
int age = 0;
std::cout << "나이 (1~150): ";
if (!(std::cin >> age)) {
std::cerr << "숫자를 입력하세요.\n";
std::cin.clear();
std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
return 1;
}
if (age < 1 || age > 150) {
std::cerr << "범위를 벗어났습니다.\n";
return 1;
}
std::cout << "입력한 나이: " << age << std::endl;
return 0;
}
std::cin 실패 시 clear와 ignore로 버퍼를 비우지 않으면 다음 입력이 꼬일 수 있어, 반복 입력 루프를 만들 때 특히 중요합니다.
예제 4: BMI 계산기
#include <iostream>
#include <iomanip> // setprecision
int main() {
double height = 0, weight = 0;
std::cout << "키(cm): ";
std::cin >> height;
std::cout << "몸무게(kg): ";
std::cin >> weight;
double heightM = height / 100.0;
double bmi = weight / (heightM * heightM);
std::cout << std::fixed << std::setprecision(1);
std::cout << "BMI: " << bmi << std::endl;
if (bmi < 18.5) std::cout << "저체중" << std::endl;
else if (bmi < 23) std::cout << "정상" << std::endl;
else if (bmi < 25) std::cout << "과체중" << std::endl;
else std::cout << "비만" << std::endl;
return 0;
}
키를 미터로 바꿀 때 100.0으로 나누어 실수 나눗셈이 되도록 했습니다.
흔한 실수
1. 초기화하지 않은 변수 사용
지역 변수는 자동으로 0이 되지 않습니다. 읽기 전에 반드시 값을 넣으세요.
// ❌ 위험
int x;
std::cout << x;
// ✅ 안전
int x = 0;
2. 정수 나눗셈 (5 / 2 == 2)
int끼리 나누면 몫만 나옵니다. 평균·비율에는 double이나 static_cast를 사용하세요.
int a = 5, b = 2;
double wrong = a / b; // 2.0
double right = static_cast<double>(a) / b; // 2.5
3. 오버플로우
int 최댓값을 넘기면 정의된 범위에서 래핑되어 음수가 될 수 있습니다.
int max = 2147483647;
std::cout << max + 1 << "\n"; // -2147483648 (일반적인 2의 보수 환경)
long long big = 2147483647LL;
std::cout << big + 1 << "\n"; // 2147483648
큰 합·곱이 예상되면 처음부터 long long을 쓰거나 범위를 검사하세요.
4. 타입 불일치 (특히 unsigned와 혼합)
unsigned와 int를 비교하면 암시적 변환으로 논리 오류가 날 수 있습니다. 혼합 연산 전에 타입을 통일하거나 static_cast로 의도를 드러내세요.
5. char와 string 혼동
// ❌ 컴파일 에러
// char name = "Hong";
// ✅
char initial = 'H';
std::string name = "Hong";
자주 하는 실수 (요약 예제)
실수 1: 초기화하지 않은 변수 사용
// ❌ 위험한 코드
int x;
std::cout << x; // 쓰레기 값 출력
// ✅ 올바른 코드
int x = 0;
std::cout << x; // 0 출력
실수 2: 정수 나눗셈
// ❌ 예상과 다른 결과
int a = 5, b = 2;
double result = a / b; // 2.0 (정수 나눗셈)
// ✅ 올바른 코드
double result = static_cast<double>(a) / b; // 2.5
// 또는
double result = a / static_cast<double>(b); // 2.5
실수 3: char vs string
// ❌ 컴파일 에러
// char name = "Hong"; // char는 한 글자만
// ✅ 올바른 코드
char initial = 'H';
std::string name = "Hong";
형변환 (Type Casting) — 빠른 참고
암시적 형변환 (자동)
int i = 10;
double d = i; // int → double (안전)
double d2 = 3.14;
int i2 = d2; // double → int (소수점 버림, 주의!)
명시적 형변환 (수동)
// C 스타일 (가능하면 피하기)
double d = (double)5 / 2;
// C++ 스타일 (권장)
double d = static_cast<double>(5) / 2;
추가 자료형
unsigned (부호 없는 정수)
unsigned int age = 25; // 0 ~ 4,294,967,295
unsigned long long big = 18446744073709551615ULL; // 매우 큰 수
사용 시기: 음수가 절대 없는 경우 (나이, 개수 등). 단, unsigned와 signed를 섞지 않도록 주의하세요.
auto (자동 타입 추론) - C++11
auto x = 10; // int로 추론
auto y = 3.14; // double로 추론
auto str = std::string("Hong"); // std::string
장점: 타입이 복잡할 때 편리합니다.
단점: auto만 보고 타입이 안 보일 수 있으니, 팀 규칙에 맞게 사용하세요.
const (상수)
const int MAX_SCORE = 100; // 변경 불가
// MAX_SCORE = 200; // 컴파일 에러!
const double PI = 3.14159;
const std::string COMPANY = "ABC Corp";
FAQ
Q1: int와 long의 차이는 무엇인가요?
A: 크기가 다릅니다.
| 타입 | 크기 | 범위 |
|---|---|---|
| int | 4바이트 | -2,147,483,648 ~ 2,147,483,647 |
| long | 4바이트 (Windows) / 8바이트 (Linux) | 플랫폼 의존적 |
| long long | 8바이트 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 |
권장: 큰 수가 필요하면 long long 사용 (플랫폼 독립적) |
Q2: float와 double 중 무엇을 써야 하나요?
A: 대부분 double을 사용하세요.
이유:
float: 정밀도 낮음 (소수점 6-7자리)double: 정밀도 높음 (소수점 15-16자리)- 성능 차이: 거의 없음 (현대 CPU) float 사용 시기:
- 메모리가 매우 제한적인 경우
- GPU 프로그래밍
- 대량의 실수 배열
Q3: string은 왜 std::를 붙여야 하나요?
A: string은 C++ 표준 라이브러리(STL)의 클래스이기 때문입니다.
#include <string>
// 방법 1: std:: 붙이기
std::string name = "Hong";
// 방법 2: using 선언
using std::string;
string name = "Hong";
// 방법 3: using namespace (비추천)
using namespace std;
string name = "Hong";
Q4: 변수 이름 규칙은 무엇인가요?
A: 다음 규칙을 따르세요. 가능:
- 영문자, 숫자, 언더스코어(_)
- 영문자나 언더스코어로 시작
- 대소문자 구분
// ✅ 올바른 이름
int age;
int user_count;
int maxScore;
int _temp;
// ❌ 잘못된 이름
int 2age; // 숫자로 시작 불가
int user-count; // 하이픈 불가
int max score; // 공백 불가
네이밍 컨벤션 (권장):
// camelCase (일반 변수)
int userAge = 25;
int maxScore = 100;
// snake_case (일부 프로젝트)
int user_age = 25;
int max_score = 100;
// UPPER_CASE (상수)
const int MAX_USERS = 1000;
const double PI = 3.14159;
Q5: 왜 초기화를 해야 하나요?
A: 초기화하지 않으면 “쓰레기 값”이 들어갑니다.
#include <iostream>
int main() {
int x; // 초기화 안 함
std::cout << x; // 쓰레기 값 출력 (예: 32767, -1, 0 등 랜덤)
if (x > 100) { // 예상 못한 동작
}
return 0;
}
해결법: 항상 초기화하세요.
int x = 0; // 안전
Q6: 오버플로우는 무엇인가요?
A: 자료형의 범위를 넘어서는 경우입니다.
#include <iostream>
int main() {
int max = 2147483647; // int 최댓값
std::cout << max << std::endl; // 2147483647
std::cout << max + 1 << std::endl; // -2147483648 (오버플로우!)
long long big = 2147483647LL;
std::cout << big + 1 << std::endl; // 2147483648 (정상)
return 0;
}
주의: 코딩테스트에서 자주 나오는 함정입니다.
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ string | “문자열 처리” 완벽 가이드 [실전 함수 총정리]
- C++ 클래스와 객체 | “초보자를 위한” 완벽 가이드 [그림으로 이해]
- C++ 포인터 | “어렵다는 포인터” 5분 만에 이해하기 [그림으로 설명]
관련 글
심화 부록: 구현·운영 관점
이 부록은 앞선 본문에서 다룬 주제(「C++ 변수와 자료형 | int, double, string 완벽 정리 [초보자용]」)를 구현·런타임·운영 관점에서 다시 압축합니다. 도메인별 세부 구현은 글마다 다르지만, 입력 검증 → 핵심 연산 → 부작용(I/O·네트워크·동시성) → 관측의 흐름으로 장애를 나누면 원인 추적이 빨라집니다.
내부 동작과 핵심 메커니즘
flowchart TD A[입력·요청·이벤트] --> B[파싱·검증·디코딩] B --> C[핵심 연산·상태 전이] C --> D[부작용: I/O·네트워크·동시성] D --> E[결과·관측·저장]
sequenceDiagram participant C as 클라이언트/호출자 participant B as 경계(런타임·게이트웨이·프로세스) participant D as 의존성(API·DB·큐·파일) C->>B: 요청/이벤트 B->>D: 조회·쓰기·RPC D-->>B: 지연·부분 실패·재시도 가능 B-->>C: 응답 또는 오류(코드·상관 ID)
- 불변 조건(Invariant): 버퍼 경계, 프로토콜 상태, 트랜잭션 격리, FD 상한 등 단계별로 문장으로 적어 두면 디버깅 비용이 줄어듭니다.
- 결정성: 순수 층과 시간·네트워크·스케줄에 의존하는 층을 분리해야 테스트와 장애 분석이 쉬워집니다.
- 경계 비용: 직렬화, 인코딩, syscall 횟수, 락 경합, 할당·GC, 캐시 미스를 의심 목록에 둡니다.
- 백프레셔: 생산자가 소비자보다 빠를 때 버퍼·큐·스트림에서 속도를 줄이는 신호를 어디에 둘지 정의합니다.
프로덕션 운영 패턴
| 영역 | 운영 관점 질문 |
|---|---|
| 관측성 | 요청 단위 상관 ID, 에러율·지연 p95/p99, 의존성 타임아웃·재시도가 대시보드에 보이는가 |
| 안전성 | 입력 검증·권한·비밀·감사 로그가 코드 경로마다 일관적인가 |
| 신뢰성 | 재시도는 멱등 연산에만 적용되는가, 서킷 브레이커·백오프·DLQ가 있는가 |
| 성능 | 캐시·배치 크기·커넥션 풀·인덱스·백프레셔가 데이터 규모에 맞는가 |
| 배포 | 롤백 룬북, 카나리/블루그린, 마이그레이션·피처 플래그가 문서화되어 있는가 |
| 용량 | 피크 트래픽·디스크·FD·스레드 풀 상한을 주기적으로 검증하는가 |
스테이징은 데이터 양·네트워크 RTT·동시성을 프로덕션에 가깝게 맞출수록 재현율이 올라갑니다.
확장 예시: 엔드투엔드 미니 시나리오
앞선 본문 주제(「C++ 변수와 자료형 | int, double, string 완벽 정리 [초보자용]」)를 배포·운영 흐름에 맞춰 옮긴 체크리스트입니다. 도메인에 맞게 단계 이름만 바꿔 적용할 수 있습니다.
- 입력 계약 고정: 스키마·버전·최대 페이로드·타임아웃·에러 코드를 경계에 둔다.
- 핵심 경로 계측: 요청 ID, 단계별 지연, 외부 호출 결과 코드를 로그·메트릭·트레이스에서 한 흐름으로 본다.
- 실패 주입: 의존성 타임아웃·5xx·부분 데이터·락 대기를 스테이징에서 재현한다.
- 호환·롤백: 설정/마이그레이션/클라이언트 버전을 되돌릴 수 있는지 확인한다.
- 부하 후 검증: 피크 대비 p95/p99, 에러율, 리소스 상한, 알림 임계값을 점검한다.
handle(request):
ctx = newCorrelationId()
validated = validateSchema(request)
authorize(validated, ctx)
result = domainCore(validated)
persistOrEmit(result, idempotentKey)
recordMetrics(ctx, latency, outcome)
return result
문제 해결(Troubleshooting)
| 증상 | 가능 원인 | 조치 |
|---|---|---|
| 간헐적 실패 | 레이스, 타임아웃, 외부 의존성, DNS | 최소 재현 스크립트, 분산 트레이스·로그 상관관계, 재시도·서킷 설정 점검 |
| 성능 저하 | N+1, 동기 I/O, 락 경합, 과도한 직렬화, 캐시 미스 | 프로파일러·APM으로 핫스팟 확인 후 한 가지씩 제거 |
| 메모리 증가 | 캐시 무제한, 구독/리스너 누수, 대용량 버퍼, 커넥션 미반납 | 상한·TTL·힙/FD 스냅샷 비교 |
| 빌드·배포만 실패 | 환경 변수, 권한, 플랫폼 차이, lockfile | CI 로그와 로컬 diff, 런타임·이미지 버전 핀 |
| 설정 불일치 | 프로필·시크릿·기본값, 리전 | 스키마 검증된 설정 단일 소스와 배포 매트릭스 표준화 |
| 데이터 불일치 | 비멱등 재시도, 부분 쓰기, 캐시 무효화 누락 | 멱등 키·아웃박스·트랜잭션 경계 재검토 |
권장 순서: (1) 최소 재현 (2) 최근 변경 범위 축소 (3) 환경·의존성 차이 (4) 관측으로 가설 검증 (5) 수정 후 회귀·부하 테스트.
배포 전에는 git add → git commit → git push 후 npm run deploy 순서를 권장합니다.
이 글에서 다루는 키워드 (관련 검색어)
C++, 변수, 자료형, int, double, string, 형변환 등으로 검색하시면 이 글이 도움이 됩니다.