연산자는 피연산자의 개수/타입이 고정되어있다.
피연산자의 개수가 1개인 경우도 존재 (++a/a++)
[연산자 함수] 를 정의 한다.
함수도 멤버함수 vs 전역함수가 존재하는 것 처럼, 연산자 함수도 두가지 방식으로 만들 수 있다.
이렇게 만들 어 줄 수 있다.
이게 사실은
위에는 보기 편하라고 한것이고 아래처럼해도 아무런 상관이 없다.
a op b 형태에서 왼쪽을 기준으로 실행된다.
a가 클래스여야 가능, a를 기준으로 "기준 피연산자"라고한다.
이런식의 버젼으로도 만들어 사용할 수 있다.
그런데
기준 피연산자의 위치를 바꾼다면 더이상 실행이 안된다는 단점이 존재한다.
대안은 뭐냐?
a op b 형태라면, a, b 모두를 연산자 함수의 피연산자로 만들어 준다.
이런 전역버젼으로 만들어서 다시 실행하면은 빌드가 통과를 한다.
전자의 버젼 멤버 연산자 방법대로만 만들 수 있는 것들도 있기 때문에
두가지 다 알아야한다.
대표적으로 "대입 연산자" (a = b)는 "전역 연산자 version"
으로는 만들지 못한다.
먼저 클래스 내부에서
이렇게 대입 연산자를 만들어 주도록 하자.
그리고 Position pos5를 만들고 그다음 줄에서
Pos5 = 5;를 하면 operator = 가 호출된다.
그런데 햇갈릴 만한게
Position pos5 = 5;
를하게되면
생성자에 5라는 값을 넣는다는 것이다.
생성자와 소멸자
https://velog.io/@starkshn/%EC%83%9D%EC%84%B1%EC%9E%90%EC%99%80-%EC%86%8C%EB%A9%B8%EC%9E%90
즉,
class Position
{
public:
Position() // 기본 생성자
:
{}
Position(int value) // 인자를 하나 받는 생성자.
:
{}
};
이상태에서
기타 생성자인 Position (int value) 생성자를 생성자로 호출한 것인데
이 "기타 생성자"는 == "타입 변환 생성자"라고도 한다.
#include <iostream>
using namespace std;
// 1. 타입 변환 생성자 테스트
class Player
{
public :
int m_iHp;
int m_iAttack;
int m_iDefense;
public:
Player()
:
m_iHp(100),
m_iAttack(20),
m_iDefense(5)
{
cout << "Player 기본 생성자!" << endl;
}
Player(int id)
:
m_iHp(200),
m_iAttack(40),
m_iDefense(10)
{
cout << "Player ID 받는 버젼 생성자!" << endl;
}
};
class Knight : public Player
{
public:
int m_iStat;
public:
Knight()
:
m_iStat(10)
{
cout << "Knight 기본 생성자!" << endl;
}
Knight(int id)
:
Player(id),
m_iStat(20)
{
cout << "Knight id 받는 버젼 생성자!" << endl;
}
};
int main()
{
Knight k1;
k1 = 3;
return 0;
}
현재 Knight k1을 만들게 되면은
k1의 주소(0x000000718D0FF688)는 이런값으로 안에 데이터들이 스택에 자리를 차지하게된다.
이후 k1 = 3; 를 하게되면
Knight k1은 스택에 할당되어있는데
타입 변환 생성자를 통해 만들어진 객체를
k1 = 3 해주었기 때문에
암시적 형변환으로 만들어진 객체를 대입연산자로 k1의 주소에
멤버 변수 하나하나를 다 덮어 쓴 형태이다?
인프 질문글
이거는 지금 생성되자마자 5로 초기화 되는 느낌이다.
5라는 인자를 받는 생성자를 찾게된다.
전역으로 operator = 를 만들어 주었을때는 당연히 안되는데
상식적으로 생각을 해보면 코드가 오른쪽에서 왼쪽으로 흐르는데
int x = 10; 이 10을 x에 대입을 하는 것인데
전역으로 operaetor = 왼쪽 피연산자가 오른쪽으로 흐르게 한다면
말이 안된다.
"대입"의 느낌을 생각해보면 된다.
-> 위험하다 문법적으로 막음.
원래 이런식으로도 동작을 하는데
이게 operator = 의 반환 타입이 참조라서 이런게 가능한 것이다.
그래서 this는 호출한 쪽의 주소이니까
이것에 접근 '*' 해서 그 자신을 참조로 반환을 한다.
용어가 햇갈린다.
복사 생성자 / 대입 연산자 / 복사 대입 연산자
복사 대입 연산자 : 대입 연산자 중, 자기 자신의 참조 타입을 인자로 받는 것.
이런 형태이기는 한데
매개변수로 자신과 동일한 타입으로 받아준다.
복사 생성자, 복사 대입 연산자 등 복사 시리즈가 특별 대우를 받는 이유는
말 그대로 객체가 "복사" 되길 원하는 특징이 있기 때문에.
이녀석을 직접적으로 만들어 주지않으면
"컴파일러가 알아서 만들어 준다."
생성자는 객체가처음 만들어 질 때 실행되는
탄생을 알리는 함수이다.
또한 생성자를 여러가지 버젼으로 만들 수 있는데
그중에서도 이런식으로 자기자신을 참조타입으로 받게 만들 수 있다.
Position k1;
Position k2 = k1;
Position k3(k1);
이런식으로 복사 생성자 코드 작성이 가능하다.
"복사 대입 연산자"와 는 다르다.
Position p1;
Position p2;
p2 = p1;
이게 복사 대입 연산자를 사용한 부분이다.
복사 생성자 / 복사 대입 연산자 는 클래스 내에서 구현해놓지 않으면
컴파일러가 알아서 만들어 준다.
67 => 복사 생성자
71 => 복사 대입 연산자.
모든 연산자를 다 오버로딩 할 수는 없다
ex) :: , . , .*
모든 연산자가 2개의 항이 있는 것은 아니다. ++, --가 대표적(단항 연산자)
++ --
전위형 : ++a operator ++ ()
후위형 : a++ operator ++ (int)
이거 그냥 규칙임.
후위 같은 경우 미리 ret로 받아 증가시켜 준뒤,
증가되기 이전값을 뱉어 준다.
Position ret = *this; 복사 생성자로 만든다음에
this->_x++;, this->_y++; 해주고
복사생성자로 만들어진 ret를 반환한다.
지금 이 경우에는 굉장히 위험한 상황이다.
스택메모리의 주소값을 전달하는 것인데
ret는 operator++ 호출 종료되면 사라지는 메모리 값이기 때문이다.
후위 연산자의 경우
전위와 후위의 코드는 이렇게 작성을 하고
대입연산자는 이런식으로 작성을해준다.
그런데 얼핏 보기에는 아무 문제가 없어보이지만
이런식으로 대입을 할려고하면 문제가 생긴다.
=> operator = 의 매개변수 값을 참조를 받을려고하는데
opaertor ++ 후위연산자의 경우 복사본을 전달해주기 때문에
발생하는 문제이다.
그래서 대입 연산자에다가 const를 붙여주어서 수정하지 않겟다~
라고 해주어야한다.
어지간해서는 수정할 일 없으면 const ref& 이거 셋트로 기억을 하면 좋다.