👀 CPP02 ex00부터는 Orthodox Canonical Form
에 따라 객체를 생성해야 한다!! 그게 뭐냐???
Default constructor 기본 생성자
Copy constructor 복사 생성자
Copy assignment operator 복사 대입 연산자
Destructor 소멸자
위 네가지를 기본으로 객체를 만들어야 하는 것이다.
🥨 기본 생성자
🥨 복사 생성자
ClassName(const ClassName &obj);
이다.임시 객체
를 무한으로 만들게 될테니까...포인터
때문이다 🤦🏻♀️ 얕은 복사
와 깊은 복사
의 개념을 알아야 한다🥨 객체의 얕은 복사, 깊은 복사
얕은 복사
: 얕게 복사한다. 그냥 복사 객체에다가 원본 객체 변수 주소를 때려 박는다. 따라서 값을 수정하지 않는 한 일반 상수와 같은 값을 복사하는 데 있어서는 그다지 큰 문제가 되지 않을 수 있다. 포인터에 비해서는.... 근데 문제는 또 그 놈의 포인터다. 얘는 포인터 변수에 대해 공간을 새로 만들지 않고 원본 포인터 변수 주소를 그냥 그대로 넣어논다. 그래서 얕은 복사라 한다.
SampleClass (const &SampleClass sample) {
char_pointer = sample.char_pointer;
idx = sampe.idx;
}
기본으로 주어지는 복사 생성자가 바로 위와 같은 얕은 복사로 이루어져있다. 만약 어떤 객체가 char*와 int를 멤버 변수로 가지고 있다고 해보자. 이때 char 포인터 변수는 복사대상의 주소를 그대로 가지게 된다.
그래서 모가 문제냐고요?
- 저 복사 생성자가 어떤 함수 내나, 스코프 안 지역변수에서 활용된다고 해보자. 그 범위를 벗어나면 복사 생성자로 생성된 객체는 소멸될 것이다.
깊은 복사
: 그래서 포인터 변수가 있다면 깊은 복사를 활용하여 복사 생성자를 필수로 사용자화 해야한다. 깊은 복사는 단순히 주소를 대입하는 것이 아니라 새로운 포인터 변수를 만들어 그 안에 값을 넣어주는 식으로 진행한다. 원본 객체와 값은 같지만 주소는 서로 다른 포인터 변수를 갖게 되기 때문에 문제 해결!!
결국 얕은 복사
는 원본 값이 훼손될 여지가 매우 크다
는 점에서 주의가 필요하다. 깊은 복사
를 통해 복사 객체를 수정해도 원본 객체에는 영향을 미치지 않도록
해야한다.
🥨 복사 대입 연산자 오버로딩
ClassName& operator?(const ClassName& obj);
가 기본형이다.a = b = c = d와 같은 연속 대입
을 가능하게 하기 위해서라는 데 이 말이 이해가 안돼서 걍 일반형으로 해봤다. 무한루프 참사가 일어났다. 위의 복사생성자와 같은 이유다. 말그대로 '대입' 연산자이다보니 return값이 필요한데, 이때 참조형으로 return하지 않으면 복사생성자의 무한 굴레를 벗어날 수 없게 되는 것이다. wow!👀 ex01은 이제 본격적으로 고정소수점에 대해 알아가는 과제였다. ex00도 고정소수점 형태를 만들기는 하지만, 그건 그냥 과제 말처럼 0.0에 지나지 않으니까!
🥨 고정소수점
고정소수점은 2진수로 변환하는 수를 지정한 소수점 위치를 기준으로 정수와 소수를 나누어 그대로 박아넣는 것이다
기본적으로 32bit int 자료형에는 위와 같이 357이라는 10진수가 2진수로 변환되어 맨 앞 부호비트 1bit를 포함하여 32bit로 2,147,483,647 ~ -2,147,483,648까지의 정수가 표현될 수 있다.
이 357을 고정소수점으로 표현하면 어떻게 될까?
- 바로 요렇게 된다. 이때 소수점은 과제 기준으로 8bit를 산정했지만, 소수점은 정의하기 나름이다. 이처럼 고정소수점은 말 그대로 고정되어있는 임의의 소수점 비트를 기준으로 왼쪽은 정수, 오른쪽은 실수를 표현하며 빈값은 다 0으로 채워진다.
임의의 정수를 고정소수점으로 변환할 때는 2가지 방법이 있다.
num << _bits
num * 2^_bits
그렇다면 실수는 어떨까? 실수의 소수부를 2진수로 변환하는 방법과 부동 소수점은 다른 문서를 참고하시기 바란다.. 나도 이번에 처음 알았다.. ㅎ
- 42.625라는 실수를 2진수로 변환하면 101010.101이 되고 이를 고정소수점으로 표현하면 위와 같이 표현할 수 있다.
😟 근데 실수는 bit연산이 불가능하잖아 어떻게 해?
- num * (1 << _bits)
이렇게 꼼수를 쓰면 돼. 2^_bits를 걍 냅다 해버리는 거지. 결국은 num2^8과 같은 말!
- 근데 이때 roundf(num (1 << _bits))를 해주면 원래 실수와 더 가까운 값을 얻을 수 있어. 42.42f 같은 경우에 roundf를 하지 않으면 42.418이라는 값을 얻을 수 있지만, roundf를 해주면 42.4219라는 값을 얻을 수 있지. 소수점 버리지말고 반올림해주자구 👍
그래서 저걸 뭐 어디에 어떻게 쓰는건데?
1. 실수의 정수를 얻고 싶다!: num >> _bits
, (int)num / 2^_bits
(float)(num / (1 << _bits))
같은 꼼수를 쓰거나 (float)2^_bits
로 나눠주면 돼~!🥨 고정 소수점 vs 부동 소수점
🌟정확도
: 부동 소수점 > 고정 소수점, 연산 속도
: 고정 소수점 > 부동 소수점
요 정도의 차이를 갖는다 할 수 있겠다. 고정 소수점의 경우 32bit를 정수부와 실수부로 쪼개서 사용하다보니 표현할 수 있는 수에 한계가 있을 수밖에 없다
당장 우리의 과제만 봐도 그렇다. 8bit를 왼쪽으로 밀게 되는데, 이때 왼쪽으로 짤리게 된 bit들이 생길 수 있다. 그 경우는 원래의 값을 잃을 수밖에 없다.
고정 소수점이 부동 소수점보다 표현할 수 있는 수의 범위가 훨씬 적다 이 말! 따라서 고정 소수점이 실제로 쓰이는 곳은 별로 없다고 한다 🤔 한정된 값만 필요한 곳이나 소규모에서 빠른 연산이 필요할 때 쯤?
하 힘들었다.. 고정소수점..
🥨 '<<' 연산자 오버로딩
<<
연산자는 출력을 위한 연산자로 ostream
클래스에 여러 함수로 오버로딩되어 구현되어있다.👀 ex02는 여러 연산자들을 오버로딩하는 과제였다.
🥨 비교 연산자
: >, <, >=, <=, ==, !=
🥨 산술 연산자
: +, 0, *, /
🥨 증감 연산자(전위 연산 + 후위 연산)
: ++a, a++, --a, a--
이 과제를 통해 새롭게 개념을 인식하게 된 전위 연산과 후위 연산
이 연산자들은 this의 값이 즉시 바뀌는 할당 연산자로 어디서 연산이 이루어지냐에 따라 반환값
자체는 나 자신일수도, 나 자신이 아닐수도 있다
- 이게 무슨 개 풀 뜯어먹는 소리야 싶을 수 있다. 당연함. 나도 그랬음.
먼저 전위 연산은 연산이 끝나기 전에 증감이 완료되어야 한다. 따라서 나의 값을 증가시키고 반환도 증가된 나를 참조형
으로 반환하면 된다.
후위 연산은 연산이 종료된 후에 그 값이 결정되어야 한다. 근데 위에서 this의 값이 '즉시' 바뀐다고 하였다. 근데 또 종료 뒤에 값이 결정되어야 한다니? 싶다.
- 정리하자면 그렇다 이전까지 우리는, 혹은 나는 후위 연산자가 연산 다음행에서 값을 증감시킨다고 생각했다. 근데 다시 생각해보면 이미 코드는 흘러갔는데 대체 어디서 증감을 한단 말인가? 다음행에 눈에 보이지 않는 코드를 컴파일러가 넣어놓는다?
임시 객체
를 만들어 반환해야 한다. 정리하자면
int main(void) {
int a = 3;
++a = 6;
std::cout << "a = " << a << std::endl;
// a = 6 출력
}
요건 가능해도
int main(void) {
int a = 3;
a++ = 6; // 컴파일 에러
}
요건 불가능하다는 말이다.
3 = 6
이라는 이상한 식이 되어버리는 것이다. 그러니 컴파일 에러가 생기는 것이고!객체를 상수화
시켜주어야 한다. 그렇지 않으면 걍 컴파일이 되고, 이루어지면 안되는 식이 되게끔 되어버린다. (a++)++;
과 같은 것도 불가능해야 한다는 말!🥨 전역 멤버 함수, min - max
🥨 함수 뒤에 붙는 const
ClassName memberFunction() const
; 가 뜻하는 바는 함수 내 멤버 변수들을 상수화 하겠다는 뜻이다. 고로 이 때의 const는 해당 객체의 this 포인터
에 영향을 미친다. 즉 멤버 함수 내에서 this 포인터의 타입은 const ClassName*이 되는 것이다.
그런데 다들 아는 바와 같이 static 멤버 함수는 this 포인터를 가지고 있지 않다
. 따라서 컴파일 에러가 발생할 수밖에 없다.
🥨 함수 앞에 붙는 const
static const ClassName memberFunction()
와 같이 만들어야 한다. 이는 함수의 반환값
을 상수화하겠다는 뜻이다. 🤔 근데 왜 굳이 const 버젼과 아닌 버젼을 만들라고 했는 지 모르겠다. 그냥 const의 차이에 대해 생각해보라는 건가?
❓ 그리고 hpp에서 static 멤버 함수로 선언한 함수는 cpp파일에서 다시 static const ~ 이런 식으로 쓸 수 없었다. 선언 시에만 static 키워드를 쓸 수 있다고 컴파일 에러가 뜨던데,, 뭐 바인딩 어쩌고 저쩌고 하는 글들을 찾긴 찾았는데 정확한 이유는 모르겠다. 그냥 static은 한 번만 선언될 수 있다하고 마음으로 이해하기로 했다.
👀 ex03은 주어진 point가 삼각형 내부에 있는 점인지 판단하는 과제였다.
🥨 초기화 리스트
🥨 const
이제 겨우 3개 끝난거 실화냐 ㅠㅠ!