본문 바로가기

형변환 Type Conversion 본문

💘 C++/변수범위, 변수형

형변환 Type Conversion

Hyonii 2022. 1. 17. 23:50

암시적 형변환 Implicit Type Conversion(coersion)

즉 컴파일러가 자동으로 하나의 기본 자료형을 다른 자료형으로 변환한다.

자동 형 변환 automatic type conversion 이라고 불린다.

 

이전에 변수의 값이 일련의 비트로 저장되는 것과 변수의 자료형은 컴파일러에 비트를 의미있는 값으로 해석하는 방법을 알려준다는 것을 배웠다.

다른 자료형은 "같은"숫자를 다르게 나타낼 수 있다.

예를들어 , int 값 3과 float 값 3.0은 완전히 다른 이진수 패턴으로 저장된다.

float f =3; //Initialize floating point variable with integer 3

위의 경우 컴파일러는 값 3을 나타내는 비트만 float f로 할당 할 수 없다.

대신 정수 3을 부동 소수점 숫자로 변화해야 하며, 이 값을 할당할 수 있다.

한 자료형에서 다른 자료형으로 값을 변환하는 것을 형 변환 이라고 한다.

형변환은 여러가지 경우에 발생할 수 있다.

 

-다른 자료형 값으로 변수 초기화 또는 할당하는 경우

double d(3); //Initialize double variable with integer value 3
d = 6; //assign double variable with integer value 6

-함수 매개 변수에 다른 자료형의 값을 전달하는 경우

void doSomething(long l)
{
}

doSomething(3); //pass integer value 3 to a function expecting a long parameter

-함수에서 다른 자료형의 값을 반환하는 경우

float doSomething()
{
    return 3.0; //Return double value 3.0 back to caller through float return type
}

-다른 자료형의 피연산자가 있는 이항 산술 연산자를 사용하는 경우

double division = 4.0/3; //division with a double and an integer

이러한 모든 경우, C++은 데이터를 한 자료형에서 다른 자료형으로 변환하기 위해 형 변환을 한다.

 

암시적 형 변환은 한 기본 자료형이 예상되지만 다른 기본 자료형이 제공될 때 마다 수행되며,

변환을 수행하는 방법을 컴파일러에 명시적으로 알려주지 않는다.

위에서 언급한 경우들은 모두 암시적 형변환이다.

 

- 숫자 승격 (numeric promotion)

한 자료형의 값이 더 큰 유사한 자료형의 값으로 변환하는 경우 "숫자 승격(numeric promotion)"이 일어난다.

예를들어, int 는 float로 float는 double로 승격 될 수 있다.

long l(64); //promote the integer 64 into a long
double d(0.12f); //promote the float 0.12 into a double

숫자 승격은 항상 안전하며 데이터 손실이 발생되지 않는다.

- 숫자 변환 (numeric conversion)

큰 자료형의 값이 더 작은 유사한 자료형의 값으로 변환하거나 서로 다른 자료형 간에 변환하는 경우 "숫자 변환(numeric conversion)"이 일어난다.

double d = 3; //convert integer 3 to a double (between different types)
short s = 2; //convert integer 2 to a short (from larger to smaller type)

항상 안전한 숫자 승격과는 다르게 숫자 변환으로 인해 데이터가 손실되거나 그러지 않을 수도 있다.

이 때문에 숫자 변환이 일어나는 모든 암시적 형변환의 코드는 컴파일러에 경고를 보낸다.

다음과 같은 경우들을 주의해야 한다.

 

1. 범위가 충분히 크지 않은 자료형으로 변환하면 예기치 않는 결과각 발생한다.

int main()
{
    int i = 30000;
    char c = i;
    
    std::cout << static_cast<int>(c);
    
    return 0;
}

//This prints:
//48

위 코드에서 정수 30000을 char 자료형 (범위: -128 ~ 127)에 할당했다.

이렇게 하면 오버플로가 발생해 원치 않는 결과가 출력된다.

 

2. 부동소수점 숫자에서 정수로 변환하는 것은 분수 값을 모두 손실시킨다.

int i = 3.5;
std::cout << i;

//This prints
//3 : the fractional value (.5) is lost

- 산술 표현식 평가하기 (Evaluating arithmetic expressions)

표현식을 평가할 때, 컴파일러는 각 표현식을 개별 하위 표현식으로 나눈다.

산술 연산자의 피연산자는 모두 같은 자료형이어야 하므로 컴파일러는 다음과 같은 규칙을 사용한다.

- 피연산자의 자료형이 int 보다 작은 정수인 경우, int 또는 unsigned int 로 승격된다.

- 피연산자의 자료형이 여전히 같지 않으면, 컴파일러는 가장 높은 우선순위 피연산자를 찾고 다른 피연산자를 암시적 형 변환을 통해 일치시킨다.

 

피연산자의 우선순위는 다음과 같다.

- long double (highest)

- double

- float

- unsigned long long

- long long 

- unsigned long 

- long

- unsigned int

- int (lowest)

 

example 1

#include <iostream>
#include <typeinfo> //for typeid()

int main()
{
    short a(4);
    short b(5);
    std::cout << typeid(a + b).name() << ":" << a + b << std::endl; //show us the type of a + b
    
    return 0;
}

//This prints
//int:9

short는 정수이기 때문에, 더하기가 수행되기 전에 int로 승격된다.

두 개의 int를 더한 결과는 int 다.

 

example 2

#include <iostream>
#include <typeinfo> //for typeid()

int main()
{
    double d(4.0);
    short s(2);
    std::cout << typeid(d + s).name() << ":" << d + s << std::endl; //show us the type of d + s
    
    return 0;
}

//This prints
//double : 6.0

short는 int로 승격된다. 그러나 두 피연산자는 여전히 일치하지 않기 때문에 int 보다 우선순위가 더 높은 double로 승격된다.

두 개의 double을 더한 결과는 double 이다.

 

example 3

문제를 일으킬 수 있다.

std::cout << 5u - 10; //5u means treat 5 as an unsigned integer

//This prints
//4294967291

이 같은 경우 우선순위에 의해서 부호없는 정수(unsigned int)로 승격되고 결과값의 오버플로로 인해 예상치 못한 결과를 얻을 수 도 있다.

위 예제는 부호 없는 정수를 피하는 많은 이유 중 하나다.

 

명시적 형변환 Explicit Type Concersion(casting)

형 변환을 하기 위해 형 변환 연산자(type casting operator)를 사용한다.

 

초보 프로그래머들은 float f = 10/4; 와 같은 걸 시도한다.

그러나 10과 4는 모두 정수이므로 "숫자 승격"이 일어나지 않는다.

정수값 나누기 표현식 10/4 는 2의 값을 생성하고, 그 값은 암시적으로 2.0으로 변환되어 변수 f에 할당된다.

 

위 같은 경우에는 정수 리터럴 값(ex. 2, 4)중 하나를 부동 소수점 숫자 리터럴 값(Ex. 2.0, 4.0)으로 바꾸면 

나누기는 부동 소수점 숫자 나누기로 수행될 것이다.

 

하지만 변수를 사용하는 경우 어떻게 될까? 

int i1 = 10;
int i2 = 4;
float f = i1/i2;

변수 f 의 값은 2로 끝난다.

정수 나누기 대신 부동 소수점 숫자 나누기를 한다고 컴파일러에 어떻게 말할까?

답은 " 형 변환 연산자"를 사용해야 한다.

형 변환 연산자를 사용해서 컴파일러에 명시적 형 변환을 하도록 지시해야 한다.

 

C Style Cast

표준 C 프로그래밍에서는 () 연산자에 변환할 자료형의 이름을 사용해 형 변환한다.

int i1 = 10;
int i2 = 4;
float f = i1/i2;

위 프로그램에서는 컴파일러가 i1을 부동 소수점 값으로 변환하기 위해서 float를 사용한다.

i1은 이제 부동 소수점 값이므로 i2는 부동 소수점값으로 변환되며, 정수 나누기 대신 부동 소수점 나누기를 수행한다.

 

C++에서는 다음과 같이 함수적인  C Style Cast를 사용할 수 있다.

int i1 = 10;
int i2 = 4;
float f = i1/i2;

C-style cast는 컴파일 시간에 검사되지 않으므로 오용될 수 있다. (const를 제거하는 등)

그러므로 C-style cast는 일반적으로 피하는 게 좋다.

static_cast

C++에서는 static_cast라는 형 변환 연산자를 도입했다.

char c = 'a';
std::cout << static_cast<int>(c) << std::endl; 

//This prints
//97

static_cast 는 하나의 자료형을 다른 자료형으로 변환하는데 가장 좋은 방법이다.

int i1 = 10;
int i2 = 4;
float f = static_cast<float>(i1)/i2;

static_cast의 주요 장점은 컴파일 타임에 타입 검사를 제공하여 부주의한 오류를 만들기가 더 어렵다는 것이다. satic_cast는 C-style cast보다 덜 강력해서 실수로 const를 제거하는 등 의도하지 않은 작업을 할 확률을 줄여준다.

 

컴파일러는 안전하지 않은 암시적 형 변환을 할 때마다 불평한다.

int i = 48;
char ch = static_cast<char>(i); //implicit conversion

int(4-byte)를 char(1-byte)로 변환하면 잠재적으로 안전하지 않으므로 컴파일러는 불평한다.

이 형 변환을 사용자가 인지하고 사용한다는 것을 알리기 위해 다음과 같이 형 변환 연산자를 사용하면 된다.

int i = 48;
char ch = static_cast<char>(i);

다음 프로그램에서 컴파일러는 double 을  int로 변환하면 데이터 손실이 발생할 수 있다고 불평한다.

int i = 100;
i = i/2.5;

명시적인 의미를 컴파일러에 알려주면 된다.

int i = 100;
i = static_cast<int>(i/2.5);

 

형 변환을 할 때마다 문제가 발생할 가능성이 있으므로 가능하면 형 변환을 피해야 한다.

그러나 피할 수 없는 경우, C style cast 대신 C++의 static_cast 를 사용해야 한다.

Comments