icon
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++ 컴파일러는 문맥상 필요하다고 판단될 때 데이터 타입을 자동으로 변환합니다.

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

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

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

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

    대입 산산 예시
    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
  2. 산술 연산 시: 서로 다른 숫자 타입의 피연산자가 연산될 때, 일반적으로 더 큰 범위의 타입으로 자동 변환된 후 연산이 수행됩니다. 이를 승격(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
  3. 함수 호출 시: 함수의 매개변수 타입과 전달하는 인자의 타입이 다를 때, 매개변수 타입으로 자동 변환됩니다.

    함수 호출 예시
    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;
    }
  4. 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++에서는 여러 가지 캐스팅 방법이 있습니다.

  1. 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++에서는 더 안전하고 명확한 캐스팅 연산자를 권장합니다.

  2. 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는 특정 목적에 맞게 설계되어 오용을 줄이는 데 도움을 줍니다.