여러가지 생성자
디폴트 생성자
생성자는 여러개 만들 수 있는데, 그 중 인수를 취하지 않는 생성자를 디폴트 생성자라고 부른다.
Human 클래스의 디폴트 생성자는 Human()이다. 인수를 받지 않으므로 멤버에 특정값을 대입하지는 못하며 무난한 값으로 초기화하는 역할을 한다.
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
class Human
{
private:
char name[12];
int age;
public:
Human()
{
strcpy(name, "이름없음");
age = 0;
}
void intro()
{
printf("이름 : %s\n", name);
printf("나이 : %d\n", age);
}
};
int main()
{
Human momo;
momo.intro();
}
Human() 디폴트 생성자는 name을 "이름없음"으로, age를 0으로 초기화한다. 두 값은 실존하는 객체의 속성이 아니라 아직 초기화하지 않았다는 정도만 표시한다. 최소한 쓰레기값보다는 무난한 값이 들어있으면 멤버함수에서 초기화를 했는지 안했는지에대한 여부를 알 수 있다.
if( age == 0) { 아무거나 }
만약 나이가 0인건 말이안되므로 이 객체는 초기화되지 않은 것으로 판단할 수 있어 에러를 처리하거나 늦게라도 초기화할때 이러한 과정이 도움이 될 수 있다.
디폴트 생성자를 호출할 때는 객체 선언문에 빈 괄호를 붙이지 않으며 아예 괄호가 없어야한다.
아니면 명시적으로 생성자를 호출하면서 괄호를 붙일 수는 있다.
// 맞음
Human momo;
Human momo = Human();
// 틀림
Human momo();
디폴트 생성자 호출문에 괄호를 붙이면 이는 객체를 선언하는 것이 아니라 Human 타입의 객체를 리턴하는 함수의 원형을 선언하는 것이다. Human momo;와 Human momo();는 완전히 다른 뜻이다.
이것의 대표적인 예시가 변수 선언과 함수 호출을 볼 수 있다.
int sub; // 정수형 '변수'
int sub(); // 정수를 리턴하는 '함수'
생성자를 정의하지 않았다면 컴파일러가 디폴트 생성자를 자동으로 정의한다.
위 예제에서 Human()을 주석처리하면 빈 생성자 함수가 암시적으로 정의된다.
대신, 인수를 초기화하지 않았기때문에 쓰레기값이 들어있는 것으로 출력될 것이다.
// 생성자를 주석처리
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
class Human
{
private:
char name[12];
int age;
public:
/*Human()
{
strcpy(name, "이름없음");
age = 0;
}*/
void intro()
{
printf("이름 : %s\n", name);
printf("나이 : %d\n", age);
}
};
int main()
{
Human momo;
momo.intro();
}
컴파일러가 디폴트 생성자를 만드는 경우는 다른 생성자가 전혀 없을때뿐이다. 단 하나라도 생성자가 있다면 암시적인 디폴트 생성자는 정의되지 않으며 이 경우 빈 객체나 객체의 배열을 생성할 수 없다.
// 파일이름 : NoDefCon.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
class Human
{
private:
char name[12];
int age;
public:
/*Human() // 디폴트 생성자
{
strcpy(name, "이름없음");
age = 0;
}*/
Human(const char* aname, int aage)
{
strcpy(name, aname);
age = aage;
}
void intro()
{
printf("이름 : %s\n", name);
printf("나이 : %d\n", age);
}
};
int main()
{
// 디폴트 생성자가 없으면 에러처리됨
// Human momo // 에러
// Human arFriend[3]; // 에러
Human arFriend[3] =
{
{ Human("김길동", 30)},
{ Human("홍길동", 31)},
{ Human("최길동", 32)}
};
arFriend[2].intro(); // 배열의 3번째값 출력(0~2)
}
두 개의 인수를 받는 생성자를 정의하면 컴파일러는 디폴트 생성자를 만들지 않는다.
이 상태에서 주석 처리된 두 문장은 에러로 처리되는데,
Human momo; 선언문은 인수가 없는 생성자를 필요로하는데 이 함수가 없으니 에러이다.
Human arFriend[3]; 배열 선언문이 에러가 되는 이유도 같은 이유이다.
디폴트 생성자가 없는 상태에서 객체의 배열을 선언하려면 초기식에서 생성자를 일일이 호출한다.
= {} 로 생성자를 명시적으로 호출하면 각 배열 요소에 대해 생성자를 순서대로 호출하여 차례대로 초기화한다.
복사생성자
컴파일러가 자동으로 생성자, 소멸자를 호출하는데, 복사생성자도 자동으로 호출한다.
이미 선언된 객체로부터 같은 타입의 객체를 또 만드는 것이 아주 흔한 일이다.
정수형 변수를 하나 선언하고 똑같은 값을 가지는 변수를 하나 더 만들 수 있는데 다음과 같이 할 수 있다.
int a = 12;
int b = a;
똑같은 타입의 객체를 하나 더 생성하는 것을 복사 생성이라고 하는데, 사본을 만드는 일은 아주 흔하다.
클래스는 일종의 타입이므로 객체도 기본형과 마찬가지로 복사 생성을 할 수 있으며 똑같은 방식으로 동작해야한다.
#include <stdio.h>
class Time
{
private:
int hour, min, sec;
public:
Time(int h, int m, int s)
{
hour = h;
min = m;
sec = s;
}
void OutTime()
{
printf("현재 시간은 %d:%d:%d 입니다. \n", hour, min, sec);
}
};
int main()
{
Time now(12, 34, 56);
Time then = now;
// Time then(now); 위 코드와 동일함
// 코드 작업은 안했지만 복사 생성자를 호출해주기때문에 복사해서 만들어줌.
then.OutTime();
}
main 함수를 보면 now 객체를 생성해 놓고 then 객체를 생성하면서 now로 초기화했다.
이때 복사 생성자가 호출되어 원본 객체로부터 사본을 만드는 역할을 한다.
예제에서는 복사생성자가 따로 없지만 컴파일러가 다음과 같은 디폴트 복사 생성자를 만들어 주기 때문에 잘 동작하는 것을 볼 수 있다.
레퍼런스로 받아서 객체의 멤버를 생성하는 객체의 멤버에 복사된다. (hour, min, sec가 일대일로 복사된다)
이때 멤버들은 원본이기때문에 변경이 일어나면 안되기때문에 const를 붙여주는것이 좋다.
멤버끼리 일대일로 값을 복사해서 새로 생성되는 객체는 원본 객체와 완전히 같다. 두 객체는 생성 시점에만 같은 값을 가질 뿐 완전히 독립적이다.
이후 now와 then은 각각의 값을 저장하며 하나가 바뀌어도 다른 쪽이 전혀 영향을 받지 않는다.
그러나 일대일 복사는 단순 타입에 대해서는 완전한 사본을 만들어주지만, 동적 할당한 메모리에 대해서는 얕은 복사만 수행하기때문에 위험할 수도 있다.
// 파일이름 : HumanCopy.cpp
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
class Human
{
private:
char* pname;
int age;
public:
Human(const char* aname, int aage)
{
pname = new char[strlen(aname) + 1];
strcpy(pname, aname);
age = aage;
}
~Human()
{
delete[] pname;
}
void intro()
{
printf("이름 : %s\n", pname);
printf("나이 : %d\n", age);
}
};
int main()
{
Human kang("강감찬", 1424);
Human boy = kang;
boy.intro();
}
예제를보면 kang 객체를 만들고 이 객체로부터 boy 객체를 복사 생성했다.
boy가 kang의 사본이므로 intro 메서드를 호출하면 정보가 잘 출력된다. 그러나 두 객체가 소멸할때 런타임 에러가 뜬다.
디폴트 복사 생성자는 멤버값을 일대일로 대입할 뿐이어서 두 객체의 포인터가 같은 번지를 가리키고 있다.
이렇게되면 완전한 사본이 아니며, 한 쪽을 변경하면 다른쪽이 영향을 받게될 것이다.
더 큰 문제는 두 객체가 소멸할때 메모리를 이중으로 정리한다는 점이다.
먼저 소멸하는 객체가 pname을 이미 해체한 상태에서 나중에 소멸하는 객체가 해제한 메모리를 또 해제하니 런타임 오류가 뜨는 것이다.
포인터를 가진 클래스는 완전한 사본을 만들기 위해 깊은 복사를 해야한다.
그래서 단순 대입만 하는 디폴트복사 생성자를 쓸 수 없으며 직접 복사 생성자를 정의하여 사본에 별도의 메모리를 할당해야한다.
// Humancopy.cpp 에서 복사생성자를 생성
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
class Human
{
private:
char* pname;
int age;
public:
Human(const char* aname, int aage)
{
pname = new char[strlen(aname) + 1];
strcpy(pname, aname);
age = aage;
}
Human(const Human& other)
{
pname = new char[strlen(other.pname) + 1];
strcpy(pname, other.pname);
age = other.age;
}
~Human()
{
delete[] pname;
}
void intro()
{
printf("이름 : %s\n", pname);
printf("나이 : %d\n", age);
}
};
void printHuman(Human who)
{
who.intro();
}
int main()
{
Human kang("강감찬", 1424);
Human boy = kang;
printHuman(boy);
}
복사 생성자 Human(Human &other)는 같은 타입의 다른 객체에 대한 레퍼런스를 받는다.
인수로 받은 pname 멤버의 길이만큼 메모리를 새로 할당한 후 문자열을 복사한다.
이렇게되면 객체별로 별도의 메모리를 가져 독립적인 사본이 된다.
이제 kang과 boy는 완전히 다른 객체가되며, 둘 중 하나를 바꾸더라도 서로 영향을 받지 않는다.
메모리를 할당하거나 DB 연결이나 네트워크 연결 등 독점적인 자원이 필요한 클래스는 반드시 복사생성자를 정의하여 독립된 사본을 만들어야한다.
복사생성자는 같은 타입의 객체 레퍼런스(Human &)를 받아야하는데 이때 &를 빼서 값으로 받으면 안된다.
객체를 값으로 받으면 어떻게 되는지 살펴보기위해 Human 타입을 받는다고 가정해보자.
// 복사생성자의 매개변수에 &가 빠진 상태
Human(Human other)
{
pname = new char[strlen(other.pname) + 1];
strcpy(pname, other.pname);
age = other.age;
}
문법적으로는 말이 되는것 같지만 실제로는 무한 루프에 걸려서 프로그램에 에러가 걸릴것이다.
왜냐하면 복사 생성자도 함수인데 이 함수를 호출하는 과정에서 other 인수를 전달하며 복사가 발생하기때문이다.
결국 복사 생성자가 자기 자신을 종료 조건 없이 무한히 재귀호출하는 것과 같은 원리가 되버린다.
컴파일러는 이 상황을 에러로 처리해서 실행을 하지못하게 하는 것이다.
복사 생성을 하는 동안에 또 다른 값의 복사가 발생하기때문에 객체를 값으로 넘기면 안된다.
그렇다면 포인터는 객체가 아닌 번지값일 뿐이므로 일단 무한호출은 방지할 수 있다.
// 복사생성자에서 *를 붙인 상태
Human(Human *other)
{
pname = new char[strlen(other.pname) + 1];
strcpy(pname, other.pname);
age = other.age;
}
Human * 타입의 other를 인수로 받고 본체에서는 -> 연산자로 멤버를 읽으면 된다.
이 생성자는 기능적으로 아무 문제가 없고 실제로 컴파일도 잘 된다.
그러나 객체를 초기화하는 선언문과 원형이 일치하지 않는다. 생성자가 포인터를 받으므로 선언문도 포인터를 넘겨야한다.
Human boy = &kang;
선언문을 이렇게 고치면 복사생성자로 포인터가 전달되어 잘 초기화 될것이다.
하지만 이는 일반적인 변수선언과 달라 비상식적이라고 볼 수 있다.
처음에 말했던 기본 타입은 int a = 12; int b = a로 사본을 만들지 int b = &a; 로 사본을 만들지 않는다.
클래스가 완전한 타입이 되려면 int a = b; 형식을 사용할 수 있어야 한다.
이러한 이유로 레퍼런스가 꼭 필요해졌다. 레퍼런스는 객체 이름에 대해 암시적으로 &를 붙이고 함수는 포인터를 받아 암시적으로 *연산자를 적용한다.
Human boy = kang; 으로 써도 kang의 번지가 전달되고 복사 생성자는 전달받은 번지로부터 kang 객체를 찾아 멤버값을 읽는다.
C에서 없던 레퍼런스가 필요한 이유는 클래스를 기본 타입과 똑같이 만들기 위해서라고 볼 수 있다.
멤버 초기화 리스트
생성자는 멤버 변수의 값을 초기화해준다. 인수로 전달 받은 값을 멤버 변수에 대입하는 것이 보통이다.
특별한 처리 없이 단순 대입만하면 초기화리스트를 사용하는 것이 간편하다.
함수 정의문과 본체 사이에 ':' 를 찍고 '멤버:초기값' 의 목록을 콤마로 구분하여 나열한다.
일반 멤버는 본체에서 대입하는 것과 초기화 리스트에서 초기화하는 것이 별 차이가 없어 둘 중 편한 방법을 사용하면 된다. 그러나 특수한 멤버는 반드시 본체가 시작되기 전인 초기화 리스트를 사용해야한다.
const를 초기화하는 예제를 살펴보자.
// 파일이름 : InitConstMember.cpp
#include <stdio.h>
class Some
{
public:
const int total; //
Some(int atotal) : total(atotal){} // const라서
// 일반 변수를 멤버 변수로 초기화시킬때 생성자(근데 total이 const라서 변하는값이아니기때문에 에러임)
// => 특수한 경우일때는 일반적인 대입연산을 통한 초기화 방식은 안됨.
/*Some(int atotal)
{
total = atotal;
}*/
void OutTotal() { printf("%d\n", total); }
};
int main()
{
Some S(5);
S.OutTotal();
}
Some 클래스는 정수형 상수 total을 멤버로 가진다. 상수를 원래 선언과 동시에 초기값을 주어야하지만 클래스 내의 멤버는 초기값을 줄 수 없다.
초기화 리스트는 객체가 생성되기 이전에 할당과 동시에 값을 대입할 수 있는 특별한 영역이다. 생성자가 호출되면 이미 객체가 만들어졌기 때문에 상수를 변경할 수 없어 생성자 이전의 더 빠른 초기화가 필요하다.
#include <stdio.h>
class Some
{
public:
int& total;
Some(int& atotal) : total(atotal) {}
void OutTotal() { printf("%d\n", total); }
};
int main()
{
int value = 8;
Some S(value);
S.OutTotal();
}
레퍼런스는 다른 변수에 대한 별명이며 초기화 이후에는 대상을 변경할 수 없다.
Some 클래스의 total 멤버는 정수형 변수에 대한 별명이며 객체 생성시에 대상을 밝혀야한다. 하지만 생성자의 본체에서는 다음 코드를 쓸 수 없다.
Some(int &atotal)
{
total = atotal;
}
본체에서 total에 대한 대입은 레퍼런스의 대상을 지정하는 것이 아니라 그 대상체에 대한 대입이다.
그래서 레퍼런스도 생성자의 본체가 시작되기 전인 초기화 리스트에서만 초기화할 수 있다.
포인터와는 달리 대상이없는 레퍼런스는 존재할 수 없어 레퍼런스를 초기화하지 않으면 에러 처리한다.
상수와 레퍼런스는 사용하기 전에 값과 대상이 정해져야한다.
생성자의 본체는 이미 객체가 할당된 후여서 늦다. 그래서 본체 이전에 초기화할 대상을 위해 초기화 리스트를 제공한다.
그 외에 포함된 객체나 상속받은 멤버를 초기화할 때도 초기화 리스트가 필요한데 차후에 관련 부분에서 보도록하자.
변환생성자
대입연산자의 좌우변은 타입이 일치하는 것이 원칙이다.
클래스의 객체도 마찬가지로 비슷한 타입끼리는 암시적으로 변환되는데 그러기 위해서는 변환 장치가 있어야한다.
변환생성자는 다른 타입의 값으로부터 객체를 만든다.
#include <stdio.h>
class Time
{
private:
int hour, min, sec;
public:
Time(int h, int m, int s)
{
hour = h;
min = m;
sec = s;
}
Time(int abssec)
{
hour = abssec / 3600;
min = (abssec / 60) % 60;
sec = abssec % 60;
}
void OutTime()
{
printf("현재 시간은 %d:%d:%d입니다.\n", hour, min, sec);
}
};
void printTime(Time when)
{
when.OutTime();
}
int main()
{
Time noon(40000);
Time now = 60000;
now.OutTime();
now = 70000;
now.OutTime();
printTime(80000);
}
예제는 정수형의 절대 초를 입력받아 시, 분, 초로 변환하는 생성자가 정의되어있기때문에 정수로부터 Time 객체를 생성했다. noon 객체처럼 생성자를 명시적으로 호출해도 되고 now 객체처럼 정수값을 초기식에 써도된다.
또는 실행 중에 정수를 바로 대입해도 상관없다.
위 예제에서 int와 Time은 원래 호환되지 않는 타입이지만 변환 생성자가있어서 now = 70000;을 대입할 수 있다.
컴파일러는 우변의 정수에 대한 변환 생성자를 호출하여 Time 임시 객체를 생성하고 그 객체를 now에 대입한다.
변환생성자는 정수 하나를 시, 분, 초 멤버가 값을 나누어주는 식으로 Time 객체를 만든다.
정수가 Time 객체로 바뀔 수 있으니 Time 객체를 요구하는 함수에 정수를 주는것도 가능하다.
printTime(80000);처럼 정수를 줘도 변환생성자에의해 80000이 Time 객체로 바뀌어 전달된다.
그런데 Time 객체의 변환이 편리해보이지만 위험할때도 있다.
printTime(80000); 이 의도된 호출이라면 상관없지만 아무 생각없는 정수를 잘못 전달했으면 컴파일러가 이를 잡아내는것이 어렵다.
타입간의 변환은 편리하지만 타입의 구분을 모호하게 만드는 맹점이 있고 컴파일러의 엄격한 타입 체크를 방해하여
버그가되기도한다. 이런 부작용을 방지하려면 변환생성자에대한 explicit 키워드를 붙여 명시적인 변환만 허가할 수 있다.
explicit Time(int abssec) // explicit : 명시적인 변환만 허용하겠다는 의미
{
hour = abssec / 3600;
min = (abssec / 60) % 60;
sec = abssec % 60;
}
int main()
{
Time noon(40000);
noon.OutTime();
// noon = 70000;
}
explicit가 붙은 생성자는 암시적인 형 변환에 사용되지 않도록 금지되어 컴파일러의 임의적인 판단을 방지한다.
하지만 생성자 호출이나 캐스트 연산자는 변환 의사를 분명히 밝힌 것이므로 여전히 허용된다.
noon = 70000; 코드를 보면 에러처리되어있는데,
이 코드가 절대 초를 전달해서 Time 클래스에 시, 분, 초를 할라고했는지 7만이라는 상수를 아무 생각없이 전달하려고 했는지 알 수가 없다. 이러한 모호성때문에 컴파일러가 에러로 처리한다.
Time now(30000); // 가능
Time now = 30000; // 불가능
Time now = (Time)30000; // 가능
printTime(30000); // 불가능
printTime((Time)30000); // 가능
변환생성자도 필요한만큼 여러 개 정의할 수 있다. 즉, 오버로딩이 가능하다는 것이다.
실수로부터 Time 객체를 변환생성하는 또 다른 Time(double) 생성자를 예로 들어보자.
// 파일이름 : double2Time.cpp
#include <stdio.h>
class Time
{
private:
int hour, min, sec;
public:
Time(int h, int m, int s)
{
hour = h;
min = m;
sec = s;
}
Time(int abssec)
{
hour = abssec / 3600;
min = (abssec / 60) % 60;
sec = abssec % 60;
}
Time(double d)
{
hour = int(d) % 24;
min = int((d - int(d)) * 100) % 60;
sec = 0;
}
void OutTime()
{
printf("현재 시간은 %d:%d:%d입니다.\n", hour, min, sec);
}
};
int main()
{
Time now(3.14);
now.OutTime();
}
실수를 어떻게 Time 객체로 바꿀 것인지 명확히 정의해야하는데 이 예제의 경우 정수부는 시간, 실수부는 분으로 사용하며 초는 0으로 정의했다. 실수 3.14는 3시 14분이라는 Time 객체가 되는것이다.
이 방식은 하나의 값으로 시, 분을 표현할 수 있어 간편하다.
변환 생성자는 A를 B로 바꾸는 일대일의 연산을 수행하므로 인수를 하나만 취하며 인수가 둘 이상이면 변환생성자가 아니다.
위 예제에서 보다시피 Time now(3.14)는 변환 대상 하나만 인수로 취한다.
Time now = 3.14 대입문은 이항 연산을 하지만 좌변이 객체 자신을 고정되어있어 피연산자는 우변 하나밖에 없다.
변환함수
양방향으로 상호 변환할 수 있어야 호환되는 타입이며 두 타입이 호환되면 대입도 가능해진다.
객체를 다른 타입으로 변환하려면 변환함수를 정의한다.
변환함수는 특정 타입으로 변환하는 캐스트 연산자이다.
operator 변환타입()
{
본체
}
operator 키워드 다음에 변환할 타입을 밝히고 본체에 객체를 해당 타입으로 변환하여 리턴하는 코드를 작성하면된다.
변환 함수의 변환 대상은 자기 자신이고 변환 결과는 지정한 타입이므로 인수와 리턴값을 모두 생략한다.
// 파일이름 : Convertint.cpp
#include <stdio.h>
class Time
{
private:
int hour, min, sec;
public:
Time(int h, int m, int s)
{
hour = h;
min = m;
sec = s;
}
Time(int abssec)
{
hour = abssec / 3600;
min = (abssec / 60) % 60;
sec = abssec % 60;
}
operator int()
{
return hour * 3600 + min * 60 + sec;
}
void OutTime()
{
printf("현재 시간은 %d:%d:%d 입니다.\n", hour, min, sec);
}
};
int main()
{
Time now(12, 34, 56);
int i = now; // 변수에 객체를 대입
printf("i = %d\n", i);
}
예제의 int() 함수는 Time형 객체를 정수형으로 변환해주는 변환함수이다.
변환하는 방법은 시간에 3600을 곱하고 분에 60을 곱해 더하고 마지막으로 초를 더하면 절대 초가 된다.
이 함수에 의해 Time 객체는 정수로 변환된다. int i = now; 대입문에 대해 컴파일러는 now 객체의 int() 변환함수를 호출하여 절대 초를 i에 대입한다. int와 호환된다는 것은 곧 모든 수치형과 호환된다는 얘기이다.
변환생성자와 마찬가지로 암시적변환이 가능해져 다소 위험한 면이있다. 정수형을 취하는 함수에 Time 객체를 넘겨도 잘 동작하니 엉뚱한 실수를 할 가능성이 있다.
변환 함수는 explicit 지정자를 쓸 수 없기때문에 주의가 필요한데 정 문제가 된다면 TimeToInt, IntToTimer과 같은 명시적인 함수를 정의하는 것이 안전하다.
클래스간의 변환
기본 타입과 클래스 간의 변환에 대해 연구해봤는데 일반적으로 얘기하자면 적절한 방법만 지정하면 모든 타입끼리 변환이 가능하다.
// 클래스 간의 변환
#include <stdio.h>
class Fahrenheit;
class Celsius
{
public:
double Tem;
Celsius() {}
Celsius(double aTem) : Tem(aTem) {}
operator Fahrenheit();
void OutTem() { printf("섭씨 = %f\n", Tem); }
};
class Fahrenheit
{
public:
double Tem;
Fahrenheit() {}
Fahrenheit(double aTem) : Tem(aTem) {}
operator Celsius();
void OutTem() { printf("화씨 = %f\n", Tem); }
};
Celsius::operator Fahrenheit()
{
Fahrenheit F;
F.Tem = Tem * 1.8 + 32;
return F;
}
Fahrenheit::operator Celsius()
{
Celsius C;
C.Tem = (Tem - 32) / 1.8;
return C;
}
int main()
{
Celsius C(100);
Fahrenheit F = C;
C.OutTem();
F.OutTem();
printf("\n");
Fahrenheit F2 = 120;
Celsius C2 = F2;
F2.OutTem();
C2.OutTem();
}
화씨와 섭씨는 둘 다 온도를 나타내는 단위이며 범위와 간격이 다를 뿐 같은 대상에 대한 값이므로 간단한 공식에 의해 상호변환이 가능하다.
C = (F - 32) / 1.8;
F = C * 1.8 + 32;
각 변환 함수는 변환해주는 공식에따라 자신의 정보로부터 상대편의 임시 객체를 만든 후 그 객체를 리턴한다.
두 객체는 암시적으로 상호 변환이가능하여 서로 초기식에 사용할 수 있고 실행중에 대입하거나 함수의 인수로 전달할 수도 있다.
두 클래스가 상호의 타입으로 변환하는 함수를 제공하는 대신 한쪽 클래스가 변환생성자와 변환 함수를 동시에 제공해도 상관없다.
변환 함수가 왜 필요할까?
기본 타입은 내장되어 있어 마음대로 수정할 수 없기 때문이다.
int를 Time으로 변환할때는 Time에 변환생성자를 정의한다. 그러나 반대로 Time을 int로 바꿀때는 int에 변환생성자를 정의할 수 없다. 내장타입인 int가 Time을 인식할 수 없기 때문에 Time이 int로 변환하는 함수가 꼭 필요하다.
클래스간 변환은 함수로 정의하므로 규칙만 명백하다면 모든 변환이 가능하다.
그러나 논리적인 호환성이 있어야 변환에 의미가 있다. 시간과 절대 초는 수치값이라는 면에서 유사성이 있고, 섭씨와 화씨는 둘 다 같은 물리량을 표현하므로 변환이 가능하다.
하지만 사람과 탱크, 자동차와 커피 등의 연관성이 거의없을때 변환은 실용성이 없을 것이다.
'개발자과정준비 > C++' 카테고리의 다른 글
[C++] 연산자 오버로딩 (0) | 2021.06.07 |
---|---|
[C++] 캡슐화, 프렌드, 정적멤버, 상수멤버 (0) | 2021.06.04 |
[C++] 생성자, 소멸자(파괴자) (0) | 2021.06.02 |
[C++] 클래스 (0) | 2021.06.01 |
[C++] 디폴트 인수, 오버로딩, 인라인 함수 (0) | 2021.05.31 |