C++ 변수와 자료형 | int, double, string 완벽 정리 [초보자용]

C++ 변수와 자료형 | int, double, string 완벽 정리 [초보자용]

이 글의 핵심

C++ 변수와 자료형에 대한 실전 가이드입니다. int, double, string 완벽 정리 [초보자용] 등을 예제와 함께 상세히 설명합니다.

C++ 기본 자료형 한눈에 보기

자료형크기범위용도
int4바이트-2^31 ~ 2^31-1정수
long long8바이트-2^63 ~ 2^63-1큰 정수
float4바이트약 ±3.4e38실수 (정밀도 낮음)
double8바이트약 ±1.7e308실수 (정밀도 높음)
char1바이트-128 ~ 127문자
bool1바이트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 intint는 같은 의미입니다.
  • unsigned: 음수를 표현하지 않고 0 이상만 표현합니다. 같은 비트 폭이면 양의 범위가 약 두 배 넓어집니다.
unsigned int u = 4000000000U;  // int로는 못 담을 수 있는 큰 양수
unsigned char flags = 255;     // 0~255

주의: unsignedint를 섞어 연산하면 보통 unsigned 쪽으로 승격되어, 음수가 있던 변수가 거대한 양수로 해석되는 버그가 납니다. 비교·뺄셈 전에 타입을 맞추거나 static_cast로 의도를 명확히 하세요.

int x = -1;
unsigned int y = 10;
if (x < y) { }  // 기대와 다르게 동작할 수 있음 (-1이 unsigned로 변환되면 큰 양수)

실수형 정밀도: float vs double

구분floatdouble
대략적인 유효 숫자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을 쓰는 편이 낫습니다.

constconstexpr

  • 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가 기본입니다.

형 변환 주의사항

  1. 부호 있는 정수 ↔ unsigned: 부호 없는 쪽으로 맞추면 음수가 큰 양수로 바뀝니다.
  2. 큰 정수 → 작은 정수: 상위 비트가 잘려 값이 달라집니다.
  3. 실수 → 정수: 소수는 버려지며, 범위를 넘으면 정의되지 않은 동작이 될 수 있습니다.
  4. 정수 나눗셈: 피연산자가 모두 정수면 결과도 정수입니다. 실수 나눗셈이 필요하면 한쪽을 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 실패 시 clearignore버퍼를 비우지 않으면 다음 입력이 꼬일 수 있어, 반복 입력 루프를 만들 때 특히 중요합니다.

예제 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와 혼합)

unsignedint를 비교하면 암시적 변환으로 논리 오류가 날 수 있습니다. 혼합 연산 전에 타입을 통일하거나 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;  // 매우 큰 수

사용 시기: 음수가 절대 없는 경우 (나이, 개수 등). 단, unsignedsigned를 섞지 않도록 주의하세요.

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: 크기가 다릅니다.

타입크기범위
int4바이트-2,147,483,648 ~ 2,147,483,647
long4바이트 (Windows) / 8바이트 (Linux)플랫폼 의존적
long long8바이트-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++ 시리즈 전체 보기
  • C++ Adapter Pattern 완벽 가이드 | 인터페이스 변환과 호환성
  • C++ ADL
  • C++ Aggregate Initialization