본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
2장 : 변수와 데이터 타입

형변환

암시적 변환과 명시적 캐스팅의 동작을 비교하며 데이터 손실과 의도하지 않은 타입 변환을 피하는 기준을 익힙니다.

프로그램은 단순히 데이터를 저장하는 것을 넘어, 다양한 타입의 데이터 간에 연산을 수행해야 할 때가 많습니다.

예를 들어 정수와 실수를 함께 계산하거나, 문자를 숫자로 변환하는 등의 작업이 필요할 수 있습니다.

이 과정에서 필연적으로 발생하는 것이 바로 형 변환(Type Conversion)입니다.

형 변환은 한 데이터 타입을 다른 데이터 타입으로 변경하는 과정을 의미합니다.

이 장에서는 C++에서 형 변환이 어떻게 이루어지는지, 특히 자동으로 발생하는 암시적 형 변환(Implicit Conversion)과 프로그래머가 명시적으로 지시하는 명시적 형 변환(Explicit Conversion)을 다룹니다.

먼저 컴파일러가 자동으로 맞춰도 되는 변환과 개발자가 의도를 드러내야 하는 변환을 구분해 봅니다.


형 변환이란 무엇인가?

컴퓨터 내부적으로 각 데이터 타입은 다른 방식으로 데이터를 저장하고 처리합니다.

예를 들어, 정수 5와 실수 5.0은 수학적으로는 같은 값처럼 보이지만, 컴퓨터 메모리에서는 전혀 다른 형태로 저장됩니다.

메모리에 저장되는 예시
int integer_five = 5;       // 메모리에 정수 5로 저장
double double_five = 5.0;   // 메모리에 실수 5.0으로 저장 (부동 소수점 방식)

만약 integer_fivedouble_five를 함께 연산해야 한다면 두 값을 동일한 형식으로 맞추는 과정이 필요합니다.

이때 형 변환이 발생합니다.

형 변환은 주로 다음과 같은 상황에서 일어납니다.

  • 서로 다른 데이터 타입의 값을 함께 연산할 때.
  • 함수에 전달되는 인자의 타입이 함수의 매개변수 타입과 다를 때.
  • 함수가 반환하는 값의 타입이 함수의 반환 타입과 다를 때.
  • 변수에 값을 할당할 때 할당하려는 값의 타입이 변수의 타입과 다를 때.

암시적 형 변환 (Implicit Conversion)

암시적 형 변환은 컴파일러가 자동으로 수행하는 형 변환입니다.

프로그래머가 특별한 지시를 하지 않아도, C++ 컴파일러는 문맥상 필요하다고 판단될 때 데이터 타입을 자동으로 변환합니다.

이는 주로 정보 손실이 없는 방향으로 이루어집니다.

더 작은 타입에서 더 큰 타입으로의 변환은 일반적으로 암시적으로 이루어집니다.

암시적 형 변환이 발생하는 경우

대입 연산 시: 더 작은 범위의 타입이 더 큰 범위의 타입 변수에 할당될 때 발생합니다.

대입 연산 예시
int num_int = 10;
double num_double = num_int; // num_int (int)가 num_double (double)로 암시적 형 변환됨
std::cout << "num_double: " << num_double << std::endl; // 출력: 10.0

반대로, 더 큰 범위의 타입이 더 작은 범위의 타입 변수에 할당될 때는 정보 손실(Data Loss)이 발생할 수 있으므로 주의해야 합니다.

이 경우 컴파일러가 경고를 발생시킬 수 있지만, 오류는 아닐 수 있습니다.

정보 손실 예시
double pi = 3.141592;
int integer_pi = pi; // pi (double)가 integer_pi (int)로 암시적 형 변환됨
                        // 소수점 이하가 잘려나가 정보 손실 발생 (3.141592 -> 3)
std::cout << "integer_pi: " << integer_pi << std::endl; // 출력: 3

산술 연산 시: 서로 다른 숫자 타입의 피연산자가 연산될 때, 일반적으로 더 큰 범위의 타입으로 자동 변환된 후 연산이 수행됩니다.

이를 승격(Promotion)이라고 합니다.

산술 연산 예시
int a = 10;
double b = 3.5;
double result = a + b; // a (int)가 double로 암시적 형 변환된 후 b와 더해짐
                        // 결과도 double 타입
std::cout << "result: " << result << std::endl; // 출력: 13.5

함수 호출 시: 함수의 매개변수 타입과 전달하는 인자의 타입이 다를 때, 매개변수 타입으로 자동 변환됩니다.

함수 호출 예시
void printNumber(double num) {
    std::cout << "받은 숫자: " << num << std::endl;
}

int main() {
    int my_int = 123;
    printNumber(my_int); // my_int (int)가 num (double)로 암시적 형 변환됨
    return 0;
}

charbool의 정수형 변환: char 타입은 1바이트 정수 타입으로 간주될 수 있으며, bool 타입도 정수 연산에서 0 또는 1로 변환될 수 있습니다.

char와 bool의 정수형 변환 예시
char ch = 'A'; // ASCII 65
int ascii_value = ch; // ch (char)가 int로 변환
std::cout << "ASCII value of 'A': " << ascii_value << std::endl; // 출력: 65

bool flag = true;
int boolean_int = flag; // flag (bool)가 int로 변환 (true -> 1)
std::cout << "boolean_int: " << boolean_int << std::endl; // 출력: 1

암시적 형 변환은 코드를 간결하게 만들지만, 때로는 의도치 않은 정보 손실이나 예상과 다른 결과(특히 정수 나눗셈)를 초래할 수 있으므로 주의 깊게 살펴봐야 합니다.


명시적 형 변환 (Explicit Conversion)

명시적 형 변환은 프로그래머가 특정 데이터 타입을 다른 타입으로 변환하도록 의도적으로 지시하는 것입니다.

이는 암시적 형 변환만으로는 충분하지 않거나, 정보 손실이 예상될 때 프로그래머의 의도를 명확히 전달하기 위해 사용됩니다.

명시적 형 변환을 캐스팅(Casting)이라고도 부릅니다.

C++에서는 여러 가지 캐스팅 방법이 있습니다.

C-스타일 캐스팅 (C-style Casting): 가장 오래되고 단순한 형태의 캐스팅입니다.

괄호 안에 변환하고자 하는 타입을 명시합니다.

C-스타일 캐스팅
int numerator = 10;
int denominator = 3;

// (double) numerator를 사용하여 정수 나눗셈이 아닌 실수 나눗셈을 유도
double result = (double)numerator / denominator;
std::cout << "result (C-style cast): " << result << std::endl; // 출력: 3.333333...

이 방식은 모든 종류의 변환에 사용될 수 있지만, 어떤 종류의 캐스팅(예: 포인터 캐스팅, 상수성 제거)이 발생하는지 명확히 드러나지 않아 위험할 수 있습니다.

C++에서는 더 안전하고 명확한 캐스팅 연산자를 권장합니다.

C++ 스타일 캐스팅 (C++-style Casting): C++은 안전하고 용도에 따라 세분화된 네 가지 캐스팅 연산자를 제공합니다.

이들을 사용하면 코드의 가독성이 높아지고, 컴파일러가 잠재적인 오류를 더 잘 감지할 수 있도록 돕습니다.

  • static_cast<new_type>(expression): 가장 흔히 사용되는 캐스팅 연산자입니다.

    논리적으로 변환 가능한 타입(예: 기본 타입 간의 변환, 상속 관계에 있는 클래스 간의 변환)에 사용됩니다.

    컴파일 시점에 타입 체크가 이루어집니다.

    정보 손실이 발생할 수 있는 변환(예: double에서 int)도 허용하지만, 명시적이기 때문에 프로그래머의 의도를 명확히 합니다.

    static_cast 예시
    int i_val = 10;
    double d_val = 3.14;
    
    int cast_to_int = static_cast<int>(d_val); // double -> int (정보 손실)
    double cast_to_double = static_cast<double>(i_val); // int -> double
    char char_from_int = static_cast<char>(65); // int -> char
    
    std::cout << "static_cast<int>(3.14): " << cast_to_int << std::endl; // 출력: 3
    std::cout << "static_cast<double>(10): " << cast_to_double << std::endl; // 출력: 10.0
    std::cout << "static_cast<char>(65): " << char_from_int << std::endl; // 출력: A
  • dynamic_cast<new_type>(expression): 주로 다형성(Polymorphism)을 사용하는 클래스 계층 구조에서 안전한 다운캐스팅(Downcasting)을 수행할 때 사용됩니다.

    런타임에 타입 체크가 이루어지며, 유효하지 않은 변환일 경우 nullptr을 반환하거나 예외를 발생시킵니다.

    나중에 객체 지향 프로그래밍을 배울 때 중요하게 다룰 것입니다.

  • const_cast<new_type>(expression): 객체의 상수성(const 속성)을 제거하거나 추가할 때 사용됩니다.

    매우 제한적인 상황에서만 사용되며, const가 아닌 객체를 const로 만드는 것은 안전하지만, const 객체의 상수성을 제거하고 값을 변경하려 하면 예측 불가능한 결과가 발생할 수 있습니다.

  • reinterpret_cast<new_type>(expression): 가장 강력하고 위험한 캐스팅입니다.

    어떤 타입의 포인터든 다른 타입의 포인터로 재해석할 때 사용됩니다.

    비트 단위로 값을 단순히 재해석하므로, 컴파일러가 타입 안정성을 거의 보장하지 않습니다.

    하드웨어 제어, 낮은 수준의 시스템 프로그래밍에서 제한적으로 사용됩니다.

    일반적인 애플리케이션 개발에서는 피하는 것이 좋습니다.

괄호 초기화 {}와 형 변환: C++11에서 도입된 중괄호 초기화({})는 명시적 형 변환과 유사하게 작동하여 정보 손실을 방지하는 효과가 있습니다.

만약 초기화하려는 값의 타입이 변수 타입으로 암시적으로 변환될 때 정보 손실이 발생한다면, 컴파일러는 오류를 발생시킵니다.

int main() {
    int x {3.14}; // 컴파일 오류! double에서 int로 변환 시 정보 손실 발생.
                  // 명시적 형 변환이 필요함을 알림
    int y = 3.14; // 경고 발생 (컴파일러에 따라 오류가 아닐 수 있음)
                  // 하지만 3으로 잘려나감

    int z = static_cast<int>(3.14); // 명시적 형 변환으로 오류 없이 3으로 초기화

    return 0;
}

따라서 중괄호 초기화는 정보 손실을 막아주는 안전한 초기화 방식이며, 타입 안정성을 높이는 데 기여합니다.


형 변환의 중요성과 주의사항

  • 정확한 연산 결과: 특히 정수와 실수가 섞인 연산에서 형 변환은 결과에 큰 영향을 미칩니다. int / int는 정수 나눗셈을 수행하지만, double / int (또는 int / double)는 실수 나눗셈을 수행합니다.
    정수와 실수 나눗셈 형변환 예시
    int total_score = 100;
    int num_students = 3;
    
    double average_int = total_score / num_students; // 100 / 3 = 33 (정수 나눗셈)
    double average_double = static_cast<double>(total_score) / num_students; // 100.0 / 3 = 33.33... (실수 나눗셈)
    
    std::cout << "정수 나눗셈 평균: " << average_int << std::endl;    // 출력: 33
    std::cout << "실수 나눗셈 평균: " << average_double << std::endl; // 출력: 33.333333
  • 정보 손실 경고/오류: 암시적 형 변환으로 인해 정보 손실이 발생할 수 있는 경우(예: 큰 타입에서 작은 타입으로 변환) 컴파일러는 경고를 발생시킵니다. 심지어 C++11 이후 중괄호 초기화를 사용하면 오류로 처리하여 코드의 안정성을 높입니다.
  • 가독성: 명시적 형 변환은 프로그래머의 의도를 명확하게 보여주므로 코드의 가독성을 높입니다.
  • 안전성: static_cast와 같은 C++ 스타일 캐스팅은 C-스타일 캐스팅보다 더 엄격한 타입 체크를 수행하여 잠재적인 오류를 줄여줍니다. dynamic_cast, const_cast, reinterpret_cast는 특정 목적에 맞게 설계되어 오용을 줄이는 데 도움을 줍니다.

아래 다이어그램은 산술 표현식 안에서 형 변환을 어느 시점에 적용해야 결과 타입이 바뀌는지 보여줍니다.

핵심은 변환을 숨기지 않고, 연산이 시작되기 전에 필요한 타입을 명확히 만드는 것입니다.