1. 기본 자료형에 대한 형 변환
형 변환이란 특정한 자료형에 속하는 값을 다른 자료형의 값으로 변환하는 것을 말합니다.
다음은 int 값을 double로 변환하는 형 변환이 이루어지는 문장입니다.
int x = 10;
double y = 10; // 자동 형 변환
int 형 자료가 double로 문제없이 복사되고 컴파일이나 실행에 문제가 없습니다.
만약 반대가 되면 어떨까?
다음 문장을 보면
double y = 3.14;
int x = y; // 문제가 있는 형 변환
원인은 자료형의 크기와 관려이 있습니다.
보통 int는 4바이트 double은 8바이트를 사용하는데 4바이트로 8바이트를 표현하기에 부리가 있기 때문에
짤려서 나타납니다.
2. 포인터의 형 변환
포인터에서도 형 변환이 사용됩니다.
그전에 포인터 타입의존재 유무를 알아보겠습니다.
알다시피 포인터는 주소값을 가리킵니다.
그런데 포인터 변수에도 타입이 존재합니다.
만약 포인터가 단순히 주소를 저장하는 거라면 타입이 없는 void형 포인터만으로 충분하지 않을까?
물론
void* p; //void 포인터는 필요할 때마다 다른 포인터로 바꾸어서 사용합니다.
int* pi;
pi = (int*)p; // p를 정수 포인터로 변경하여 pi로 대입
이래도 되지만 보통은 타입을 붙여서 사용합니다.
그럼 왜 포인터 변수에도 타입이 존재하는 것일까? 그리고 포인터 변수의 타입은 무슨 역할을 하는 것일까?
그건 포인터 변수를 *를 이용해서 역참조할 때, 역참조한 주소값에 있는 데이터를 어떻게 해석할지를 지정해 주는 것 입니다.
예를 들어서
int a = 10;
int* p = &a;
cout << a << endl;
cout << *p << endl;
위 코드는 정상적으로 둘다 10이 나옵니다.
그러면
float a = 3.14f;
int* p = (int*)&a;
cout << a << endl;
cout << *p << endl;
위 코드는 정상적으로 둘다 3.14이 나올까요?
왜 이런 결과가 나오는 것일까요?
그것은 float은 부동 소수점 방식으로, int는 이진수로 메모리 주소에 저장됩니다.
그래서 위 코드는 부동 소수점 방식을 강제적으로 C타입 형변환(오래되고 강제적인 형변환)을 통해 이진수로 읽는 겁니다.
이사이트에서 3.14를 바이너리 코드로 변환해서 보면
이 바이너리 코드를 10진수로 변환하면
https://converter.app/ko/bin-dec/
이러면 아까결과와 똑같은 값이 나옵니다.
int x = 10;
int *pi = &x;
float *pf = (float*)pi; // 포인터의 형 변환
*pf = 3.14f; // 사용은 가능. 바람직하지 않음
cout << *pf << endl; // 3.14 출력
cout << x << endl; // 이상한 값이 출력
실제로 변수가 x가 있는 메모리 공간에 float 값을 저장할 수 있습니다.
*pf를 출력하게 되면, 3.14가 출력될 것이지만 3.14f를 그대로(정수로) 출력하면 위에서 설명한 바와 같이 나옵니다.
동일한 공간의 데이터를 float와 int로 다르게 해석해 출력하기 때문입니다.
3.1 상속에서의 상향 형 변환
여러 가지 클래스의 객체들을 하나의 배열로 관리하기 위해서는 기본적으로 형 변환을 사용해야 합니다.
클래스들 중에서 Shpae와 이를 상속한 자식 클래스 Line, Circle, Rect의 내부 구조를 보면
모든 자식 객체는 각각 내부에 하나의 Shape 객체를 가지므로 자식 객체의 크기는 항상 부모인 Shape 객체 크기 이상입니다.
Line line;
Shape* ps = &line; // 자동 형 변환
부모 클래스의 포인터가 자식 객체를 가리킨다. 이 과정은 자동으로 형 변환이 이루어집니다.
ps를 통해 Shape의 멤버 변수나 함수에 접근하는 것도 가능합니다.
line은 Shape의 객체를 포함하는 더 큰 객체이기 때문에 이것이 가능하고, 이것을 상향 형 변환(up-casting)이라고 합니다.
상속 관계에서는 이와 같은 변환을 흔히 사용하는데, 실행시간 다형성은 기본적으로 상향 형 변환을 기반으로 합니다.
하지만 가상 함수와 동적 바인딩을 안 써주면 C++은 포인터 타입을 따라 가기 때문에 ps->draw()를 해도 부모 클래스의 함수가 호출 됩니다.
3.2 상속에서의 하향 형 변환
그렇다면 반대는 어떨까?
자식 클래스의 변수에 부모 객체를 대입하는 다음 문장을 보면
Shape shape;
Line* pl = &shape; // 자동 형 변환이 안 됨 => 컴파일 오류
자식 클래스의 포인터 pl이 부모 객체를 가리키는데 문제가 있습니다.
컴파일 오류만 회피하려면 다음과 같이
Shape shape;
Line* pl = (Line*)&shape; // 명시적 형 변환. 컴파일은 됨
그러나, 이것은 안전한 문장이 아닙니다.
만약, pl에서 Shape 부분을 접근하면 문제가 없습니다.
그러나, Line의 추가된 멤버인 q를 접근하면 문제가 있습니다.
현재 pl이 가리키는 객체에는 q 부분이 없기 때문입니다.
실제로 만들어진 것은 Line 객체가 아니라 Shape 객체 shape이기 때문입니다.
이와 같은 형 변환을 하향 형 변환(down-casting)이라고 하는데, 원한다면 사용할 수는 있지만 바람직하지 않은 방법입니다.
3.3 자식 클래스들 간의 형 변환
Circle cir;
Line* pl = (Circle*)○ // 명시적 형 변환. 컴파일은 됨
위의 코드는 Circle 객체를 Line의 포인터로 형 변환하는 예인데, 컴파일은 되지만 잘못 사용할 수 있는 여지가 많은 좋지 않은 코드입니다.
'C++ 프로그래밍 > 클래스' 카테고리의 다른 글
다형성-가상 함수와 동적 바인딩의 의미 (0) | 2022.08.12 |
---|---|
다형성-다형성이란? (0) | 2022.08.12 |
상속 (0) | 2022.08.08 |
.과 ->의 쓰는 용도 (0) | 2022.08.04 |
함수의 설계와 객체의 복사 (0) | 2022.07.28 |