cpp02

YP J·2022년 6월 15일
2

CppModule

목록 보기
3/9

함수선언시 const 위치에 따른 의미

bool	operator>( Fixed &fixed ) const;
const Fixed &max( const Fixed &a, const Fixed &b);
  • 함수 뒤에 const :
    • 해당 함수의 멤버 변수를 모두 RDONLY로 쓰겠다.
  • 매개변수 앞에 const :
    • 해당 매개변수를 RDONLY로 쓰겠다.
  • 함수 앞에 const: 해당 함수의 리턴 밸류를 RDONLY로 쓰겠다.
    • -> 사실상 함수앞의 const 는 반환값이 reference가 아닌이상 의미 없다
  • https://pangtrue.tistory.com/16
  • https://kldp.org/node/71134 (함수 앞 const에 대한 글)

The Orthodox Canonical Calss Form (OCCF)

Canonical Form 이란 정해진 규칙을 정확히 따르며 개발하는 프로그래밍을 의마한다. 일종의 code convention 이라 볼수있다.

기본생성자

기본 생성자는 별도로 생성자를 구현하지 않을시 사용되는 생성자이다.
매개변수는 없고 멤버 변수가 0,NULL 등으로 초디화 된다.

복사 생성자

ClassName::ClassName(const ClassName &ref)
{ ... }

복사 생성자는 같은 클래스의 다른 객체의 참조자(Reference)를 인자로 받아,
그 참조자를 이용해서 자신의 객체를 초기화하는 클래스함수.
기존 값을 복사하여 전달해주므로 const 로 받는다.
복사 생성자를 직접 명시하지 않은 경우 기본복사생성자가 자동으로 생성된다.
이런한 기본 복사생성자는 얕은 복사가 수행되며
만약 깊은 복사가 필요하다면 사용자가 직접 정의해야한다.

  • 얕은 복사 (Shallow copy)

    • 얕은 복사는 원본 객체의 모든 클래스 변수의 데이터를 단순히 복사하여 객체를 생성한다.
    • 그로므로 얕은 복사로 생성한 객체의 클래스 변수는 원본 객체와 동일한 메모리 위치를 참조한다.
    • 따라서 원본객체를 수정할경우, 얕은 복사로 생성한 객체의 데이터도 변경되며
    • 복사한걸 수정해도 원본객체도 수정된다.
  • **깊은 복사 (Deep Copy)

    • 깊은 복사는 원본 객체의 모든 클래스 변수를 복사하기 위해 새로ㄴ운 메모리 공간을 할당받아 원본 객체의 변수 값을 복사한다.
    • 깊은 복사는 복사 생성자를 명시적으로 정의해야하
    • 그 과정에서 원본 객체에서 복사할 모든 클래스 변수에 대해 메모리 할당을 받아야 한다.
    • 깊은 복사로 생성한 객체는 원본 객체와 다른 메모리 주소를 참조하고 있기 때문에 두 객체는 서로 영향을 주지 않는다

연산자 오버로딩

C++ 에서는 함수의 이름이 같아도 매개변수가 다르면 선언 및 정의가 가능하다.
오버로드(Overload)같은 이름을 가진 복수의 객체가 각각 다른 기능을 하도록 만드는것이다.

객체끼리 연산자(+,-,*,/ 등)를 사용한 연산은 불가능 하기 때문에 이를 가능하게 하려면 사용자가 연산자 오버로딩을 통해 연산자의 기존 기능을 재정의 해야한다.

오버로딩 방법은 다음과 같이 operator 키워드 뒤에 오버로딩할 연산자를 붙여주면 된다

ClassName& ClassName::operaotr=(const ClassName& ref)
{ ... }

소멸자

소멸자는 객체의 수명이 다했을때 객체를 제거하기 위한 목적으로 사용된다.
객체의 수명이 끝나면 컴파일러가 자동으로 소멸자 함수를 호출하게되고 클래스명 앞에 ~기호를 붙여 정의할 수있다.

Copy Constructor VS Assignment operator

ex00

Turn-in directory : ex00/
Files to turn in : Makefile, main.cpp, Fixed.{h, hpp}, Fixed.cpp
Forbidden functions : None
Fixed a;
Fixed b(a);    // 복사 생성자
Fixed c;

c = b;		   // 대입 연산자 오버로딩;
/* 복사 생성자 */
Fixed::Fixed(const Fixed *fixed)
{
	std::cout << "Copy constructor called" << std::endl;
    this->fixed_point_value = fixed.fixed_point_value;
}
/* 대입 연산자 오버로딩 */
Fixed &Fixed::operator=(const Fixed &fixed)
{
	std::cout << "Copy assignment operator called" << std::endl;
    this->fixed_point_value = fixed.fixed_point_vlaue;
    return *this;
}

복사생성자 & 대입연산자 오버로딩

  • 복사생성자를 따로 정의하지 않는 경우 시스템에서 기본적으로 복사생성자를 정의해 준다.
  • 하지만 문제점은 기본으로 생성된 복사생성자는 얕은복사만을 사용함.
  • 메모리를 할당한 변수를 가지고 있는경우, 복사시에도 메모리를 새로 할당하고 복사하는 깊은복사를 사용해야만 나중에 소말자 호출시 메모리가 이중해제되는 문제를 막을수 있다.
  • https://modoocode.com/188
  • https://www.geeksforgeeks.org/copy-constructor-in-cpp/

ex01

Turn-in directory : ex01/
Files to turn in : Makefile, main.cpp, Fixed.{h, hpp}, Fixed.cpp
Allowed functions : roundf (from <cmath>)

고정소수점

  • 부동소수점 vs 고정소수점 (https://jiminish.tistory.com/81)
  • 소수점이 고정되어있다고 고정소수점, 움직인다고 부동소수점이 아님
  • 고정소수점 표현에 따르면 비트는 부호비트, 정수부비트, 소수부 비트로 나뉨
  • ex01에선 fractional bits를 8비트로 고정해 놓고 있음.
  • 때문에 int 형 인자를 받으면 value << fractional_bits로 left shift를 해줘서 저장( 오른쪽에 0, 8개로 채워짐).
  • float형 인자의 경우 shift연산을 쓸수 없음
  • 2^fractional_bits 를 곱해서 오른쪽에 0을 채워줌, 이땐 roundf 사용

1) 고정 소수점(Fixed point)

  • 움직이지 않고 고정된 소수점
  • 특정 숫자의 소수점 위치를 고정하는 방식
  • ex)123.45

컴퓨터 언어에서 데이터 타입은 항상 최대 길이가 고정되어 있다.

부호비트 : 양수면 0, 음수면 1
지수비트 : 정수부
가수비트 : 소수부

고정 소수점은 소수점이 붙어 있는 수를 2진수로 변환하는 과정에서, 10진수를 2진수로 바꾼 결과를 그대로 대입하는 방식이다.

  • 예시로 7.625 라는 실수를 2진수로 변환하면 111.101 이 될것이다(16비트 체계)


고정 소수점은 구현하기 편리하지만 범위의 정밀도가 낮아 소규모 시스템에서 간혹 쓰인다.

2) 부동 소주점 (Floating point)

  • 고정 되지 않고 움직이는 소수점
  • float, double 에서 사용하는 방식
  • ex) 1.23456 10^2 = 12.3456 10

부동 소수점은 고정 소수점과 다르게 정규화(Normaliztion)과정을 거친다.
이는 2진수로 변환시 정수부에 1만 남겨두고 나머지를 소수부에 넣는 방식이다. (1.xxx * 2^n 형태로 변환)

부호비트는 양수면 0, 음수면 1을 저장한다.

지수부 에는 2^n 에서 n 에 해당하는 지수를 넣게 되는데 그냥 넣는게 아니라 지우세 bias를 더한 값을 저장하게 된다.
bias 계산법은 2^(n-1) - 1 이고 32비트 기준 127 이다(2^(8-1) -1 = 127)

가수부 에는 소수값을 표현하고 나머지는 모두 0으로 채운다.

7.625 를 부동 소수점으로 나타내면 다음과 같다.

7.625 -> 111.101 -> 1.11101 * 2^2
지수는 2 이고 bias는 42비트 기준 127 이므로 지수부에는 2 + 127 = 129 가 변환되어 저장된다.

1.정수/실수(부동 소수값) ↔ 고정 소수값

고정 소수값은 2진수 정수 혹은 부동 소수값의 소수 부분을 넣을 비트 수를 정해두고 표현 한느것이다.
과제에서 소수 부분을 넣을 비트(fractional bits)를 8로 정해주었기 때문에 32비트 체계에서는 앞의 24비트를 정수부분, 8 비트를 소수 부분으로 표현한다.

1) 정수 -> 고정 소수값

정수를 고정 소수값으로 표현하기 위해서는 앞으로 8비트만큼 << 비트 시프트 연산을 해서 8비트만큼의 빈 공간을 만들면 된다.
임의의 정수 n을 고정 소수값으로 변환하려면 n << 8 해주면 된다.
그래야 마지막 8칸은 n.00000000 와 같이 소수로 표현할수 있기 때문이다.

  • n << 8 == n * 256

2) 고정 소수값 -> 정수

이경우는 특정 값을 고정 소수점으로 변환한 것을 다시 원위치 시키면 된다.
즉, 변환 하고자 하는 값을 n이라고 한다면 n >> 8 하면 된다.

  • n >> 8 == n / 256

3) 실수(부동 소수값) -> 고정 소수값

정수와 동일하게 << 연산을 통해 Fractional Bits의 공간을 마련해 주면되지만 float 나 double과 같은 부동 소수점의 경우 직접적으로 시프트 연산이 불가능하기 때문에 간접적으로 해야한다.

또한 현재 고정 소수점으로 저장할 클래스의 변수 타입이 int형 이므로 일부 데이터가 누락 될 수있다.
그러므로 비교적 정확한 변환을 위해 허용 함수인 roundf 를 통해 반올림을 진행할 필요가 있다.

임의의 실수 f를 고정 소수값으로 변환 하려면 roundf(f *(1<<8))과 같이 변환 하면 된다.

  • roundf(f(1<<8)) == roundf(f 256)

4) 고정소수값 -> 실수(부동 소수값)

이경우는 실수를 고정 소수값으로 변환 한것을 반대로하면 된다. 다만 해당 과제에서 고정 소수값에서 실수로의 변환은
부동 소수점 -> 고정 소수점 -> 부동 소수점 의 변환 방식을 의미한다.

그러므로 기존 클래스에 저장하고 있는 고정소수점을 float형으로 강제 형변환 하여 부동 소수점으로 변환한뒤,
고정 소수점의 Fractional Bits를 표현하기 위해 << 연산한 값을 다시 되돌리면 된다.

즉 변환 하고자 하는 값을 f라고 한다면 (float)f / (1 << 8) 과 같이 변환 하면 된다

  • (float)f / (1 << 8) == (float)f / 256

<< 연산자 오버로딩

cout 는 ostream의 객체이다. 그로므로 클래스를 cout 로 출력 하고 싶다면,
ostream클래스의 operator<< 를 재정의 하면된다.
과제에서 요구하는 operator<< 은 클래스에 저장된 고정 소수점 값을 toFloat함수를 통해 부동 소수점으로 출력 하는것이다.

float Fixed::toFloat(void) const
{
	return ((float)_value / (1 << _bits));
}
std::ostream& operator<<(std::ostream& os, const Fixed& obj)
{
	os << obj.toFloat();
    return os;
}
std::ostream& operator<<(std::ostream&os, const Fixed &fixed);
  • "하지만 우리는 클래스의 연산자 함수를 추가하는 방법으로, 멤버함수를 사용하는것 말고도 한가지 더 알고있다.
  • 바로 ostream 클래스 객체와 Complex객체 두개를 인자로 받는 전역 operator<<함수 를 정의 하면 됩니다."
  • https://modoocode.com/203

ex01

  • 우리는 int 값에 고정 소수점을 저장해야한다.
  • 따라서 정수 값을 고정 소수점 값에 저장할 땐 8비트 왼쪽으로 이동시켜 저장해야하고,
  • 실수 값을 고정소수점 값에 저장할 땐 256(=1<<8 = 28)\rm 256(=1<<8\ = \ 2^8) 값을 나누어서 가져오면 된다.

그리고 하나 알아두어야 할 것은 아래와 같이 Fixed객체를 출력하는 방법을 알아야 한다.

Fixed a;
std::cout << "a is " << a << std::endl;

std::ostream 에 Fixed 객체를 파라미터로 가지는 operator<< 함수가 있어야 한다는말인데
하지만 표준 라이브러리를 직접 변경하는것은 불합리하기 때문에
외부에서 std::ostream& operator<<(const Fixed& fixed) 라는 함수를 재정의 해야한다.



ex02

Now we’re talking
Turn-in directory : ex02/
Files to turn in : Makefile, main.cpp, Fixed.{h, hpp}, Fixed.cpp
Allowed functions : roundf (from <cmath>)

fixed 클래스에 다음의 연산자 및 함수들을 public 멤버함수로 오버로드 해야한다.

  • 비교 연산자 : > , < , <= , >= , == , !=
  • 산술 연산자 : + , - , * , /
  • 증감 연산자 : n++ , ++n , n-- , --n (전위 후위 모두)
  • min 함수 :
    • 두개의 고정 소수점 값에 대한 참조를 가져와 가장 작은 값에 대한 참조를 반환하는 정적 멤버 함수
  • min함수를 오버로드 하여 가장 작은 상수 값에 대한 참조를 반환하는 min함수
  • max 함수 :
    • 두개의 고정 소수점 값에 대한 참조를 가져와 가장 큰 값에 대한 참조를 반환하는 정적 멤버 함수
  • max 함수를 오버로드 하여 가장 큰 상수 값에 대한 참조를 반환하는 max함수

비교연산자 오버로딩

증감 연산자 오버로딩

  • https://modoocode.com/203
  • 전위 증감 연산자와 후위 증감 연산자를 구분하기 위해 전위 증감 연산자는 operator++ (void); , operator--(void);꼴로,
  • 후위 증감 연산자는 operator++(int); , operator--(int); 꼴로 나누어 구분한다
  • 후위 증감 연산자의 매개변수는 사용할 일은 없다. (단순히 구분을 위해 추가해 준것.)
  • 후위 증감 연산자의 경우 증감 연산이 일어나기 전의 객체를 리턴해줘야 하기 때문에 추가로 복사생성자를 호출하게 된다. 따라서 후위 연산이 전위연산보다 느림!
    //
    Fixed &operator++ (void); 
     Fixed operator--(void);
    이 두개의 차이 반환 값이 참조 이냐 그냥이나의 차이
  • 참조 반환값은 함수 내부에서 값을 복사하지 않고 Class 맴버 변수 메모리에 접근해서 가져 오는 형식
  • 그냥 함수는 내부에서 복사해서 복사된 값을 반환 하는 형식
  • 여기선 후위는 전 상태를 반환해야 하기 때문에 복사 해서 해야 한다.

    출력문 분석

    #include <iostream>
    int main( void ) {
    	Fixed a;
    	Fixed const b( Fixed( 5.05f ) * Fixed( 2 ) );
       
    	std::cout << a << std::endl;
    	std::cout << ++a << std::endl;
    	std::cout << a << std::endl;
    	std::cout << a++ << std::endl;
    	std::cout << a << std::endl;
       
    	std::cout << b << std::endl;
    	std::cout << Fixed::max( a, b ) << std::endl;
       
    	return 0;
    }
    Default constructor called	// Fixed a;
    Float constructor called	// Fixed( 5.05f );
    Int constructor called		// Fixed( 2 )
    Float constructor called	// Fixed const b;
    Destructor called	// Fixed( 5. 05f ) 의 소멸자
    Destructor called	// Fixed( 2 ) 의 소멸자
    0			// 기본소멸자로 생성된 a. 0으로 초기화 했던 값이 나옴
    0.00390625	// ++a라고 1이 아니라 fixed_point_value를 ++하는 거기 때문에
    			// (1 / 2^8)한 값이 float형태로 출력되는 것.
    0.00390625
    Copy constructor called	// 후위 증감 연산자에서는 복사 생성자가 호출됨
    0.00390625				// 후위 증감 연산자이기 때문에 연산이 일어나기 전의 값이 리턴
    Destructor called		// 복사 생성자의 소멸자
    0.0078125
    10.1016
    10.1016
    Destructor called	// b의 소멸자
    Destructor called	// a의 소멸

ex02

<friend 키워드>
통상적으로 자기 자신을 리턴하지 않는 이항 연산자들,
예를 들어 +,-,*,/ 들은 모두 외부 함수로 선언하는것이 원칙 입니다.
반대로 자기자신을 리턴하는 이항 연산자들,
예를들어 +=, -= 같은 애들은 모두 멤버함수로 선언하는것이 원칙!

  • operator+, operator-, operator*, operator/ 와 같은 연산자들은 Fixed클래스를 반환 하는것으로 나타나는데
  • 이를 참조자가 붙은 형태로 반환해도 되지 않을까?? 하는 의문이 들수 있다
  • 결론부터 말하면 , 이는 권장되지 않는다.
  • 예를 들어 Fixed 클래스인 a,b 가 있다고 해보자.
  • a+b를 처리한 결과를 임시로 생성한 Fixed클래스에 할당하고,
  • 이를 참조자로 반환하면 Dangling Reference 문제에 직면하게 된다.
  • 즉, 반환된 참조자는 이미 해제된 객체가 된다.
  • 따라서 결과적으로 a + b를 수행할때는 a에 b값을 더하여 a 를 반환하거나 b 에 a 를 더해서 b 를 반환해야한다
  • 그러면 Danglin Reference 를 피할수 있다.
  • 이때문에 operator+,operator-,operator... 와 같은 연산자들은 값의 복사가 있더라도
  • 참조자가 아닌 값을 반환하는것으로 정의하는 것이 낫다.

    멤버변수나 멤버함수 뿐만 아니라 객체에도 const 키워드를 사용할수 있다.
    객체 생성시에 const 키워드를 사용하면
    그 객체는 상수로 취급되어 초기화된 데이터 외의 다른 데이터로 변경할수 없다.

비교연산시 toFloat()로 변환하면 오차가 생긴 값을 비교하게 된다.
그래서 getRawBits() 로 비교를 해줘야 한다.



ex03

BSP
Turn-in directory : ex03/
Files to turn in : Makefile, main.cpp, Fixed.{h, hpp}, Fixed.cpp,
Point.{h, hpp}, Point.cpp, bsp.cpp
Allowed functions : roundf (from <cmath>)

BSP( Binary space partitioning ) : 이진공간분할법

profile
be pro

0개의 댓글