26. 대수학 기초

최대 비행시간 t를 구하는 방법

특정 해를 구하는건 위와 같은 방식으로 풀면 된다. 하지만 특정 해가 아닌 t에 대한 일반적인 방정식으로 만들어두어야 게임에서 실시간으로 연산이 가능할 것이다.

 

이런식으로 모든 항에 대해 방정식을 만들 수 있다.

 

 

27. 부등식

양변에 음수를 곱하거나 나눌때는 부등호가 반대가 되어야한다.

 

 

29. 기울기와 절편

기울기

절편 : 함수의 그래프가 x축, y축과 만나는 점의 좌표. x절편은 y=0일 때의 좌표, y절편은 x=0일 때의 좌표이다.

 

 

30. 선형 방정식

일차함수 방정식

일차함수의 그래프가 평면좌표에서는 직선이기 때문에 직선의 방정식과도 동일하다.

수평선은 기울기가 0인 방정식이 성립되지만 수직선은 x1=x2이기 때문에 분모가 0이라서 방정식을 구할 수 없다.

대신 x=d라는 식을 사용한다.

* 근데 직선의 방정식이랑 선형 방정식도 같은건가?

 

 

32. 연립방정식 (치환)

치환을 통한 풀이

치환을 통해 단항식으로 만들어서 푼다.

 

 

33. 연립방정식 (소거)

소거를 통한 풀이

두 방정식을 계산하여 단항식으로 만들어서 푼다.

연립방정식을 푸는 방법은 그래프 도식화, 치환, 소거 세 가지 방법이 있고 상황에 맞춰서 골라 사용하면 된다.

단, 여기까지는 단일 해가 존재할때의 얘기이다.

 

 

34. 평행선

두 직선의 방정식을 연립하였을 때 항이 모두 제거되고 일치하지 않는 상수값이 나오면 평행, 일치하는 상수값이 나오면 두 방정식은 동일하므로 완전히 겹친 직선이 된다.

16. 변화율

y = 2^x 그래프

x=6의 기울기를 구하는 방법이다.

 

참고로 y=2^x의 기울기는 언제나 y보다 작고 y=3^x의 기울기는 언제나 y보다 크다.

2와 3의 거듭제곱 수를 살펴본 이유는 자연상수가 2~3 사이의 값이기 때문이다.

 

 

17. 마법의 숫자 e

e^x의 기울기는 항상 y의 값과 동일하다. 이 개념은 자연로그의 기초가 된다.

 

 

18. 로그 기초

어떤 결과값을 내기 위해 거듭제곱을 몇번 해야하는지를 도출하는 방법.

 

 

19. 로그 - 밑변환 공식

x는 원하는 밑을 적어주면 된다. 보통 계산기는 밑이 10인 상용로그 계산을 제공하기 때문에 10으로 두면 된다.

 

 

22. 과학적 표기법

실수부의 범위는 1~10 이내로 한다.

 

과학적 표기법의 사칙연산

◾ 덧셈, 뺄셈 : 양쪽의 지수를 맞추고 실수부끼리 더하거나 뺀다

◾ 곱셈, 나눗셈 : 실수부끼리 곱하고 지수끼리 더한다

3. 어림수

◾ round(어림수) : 가장 가까운 수로 반올림 한다. 보통 라이브러리가 round(x, p) 형태로 제공함.

2.4 = 2.0, 2.6 = 3.0

 

◾ floor(하한) : 항상 가까운 정수로 내림. 보통 라이브러리가 floor(x) 형태로 제공함.

2.4 = 2

 

◾ ceiling, ceil(상한) : 항상 가까운 정수로 올림. 보통 라이브러리가 ceil(x) 형태로 제공함.

2.4 = 3

 

 

5. 나눗셈

일반적으로 곱셈보다 나눗셈의 연산 속도가 느리다.

 

 

 7. BODMAS

괄호(Brackets), 지수(Order), 나눗셈(Division), 곱셈(Multiplication), 덧셈(Addition), 뺄셈(Substraction) 순서대로 계산.

곱셈/나눗셈, 덧셈/뺄셈은 우선순위가 같으므로 왼쪽에서 오른쪽으로 계산된다.

 

 

10. 소수의 제곱

 

 

11. 제곱근

 

 

12. 차트와 그래프 기초

x축은 독립변수(제어 불가능), y축은 종속변수(제어 가능)로 설정한다.

 

 

13. 그래프 아래 면적 기초

적분을 이용하면 곡선 안쪽의 면적을 구할 수 있다. 직사각형을 이용하면 근사치를 계산할 수 있다.

 

 

14. 기울기 측정의 기초

기울기

곡선의 기울기는 곡선에 접하는 선을 긋고 아주 작은 삼각형을 만들어서 계산한다.

 

 

15. 거리와 속도, 시간의 관계

Distance, Speed, Time

위 그래프에서 기울기는 가속도와 같다.

다른 개발자에게 코드를 제공할 때 특정 함수를 호출하지 못하게 하려면 해당 함수를 선언하지 않으면 된다. 그런데 C++이 자동으로 생성하는 멤버 함수들은 그럴수가 없다. 기본 생성자나 복사 생성자 같은것들 말이다.

함수의 사용을 막는 두 가지 방법에 대해서 알아보자.

 

방법 1 : private 접근 지정자와 함수의 정의를 없애기

C++98에서는 자동으로 생성하는 멤버 함수의 접근 지정자를 private으로 명시적 선언을 하되 정의를 하지 않아서 사용하지 못하게 하는 테크닉을 이용했다.

 

template<class charT, class traits = char_traits<charT>>
class basic_ios : public ios_base {
public:
    ...
private:
    basic_ios(const basic_ios&); // 정의X
    basic_ios& operator=(const basic_ios&); // 정의X
};

복사를 막기위해 private 접근 지정과 더불어서 복사 연산자와 복사 대입 연산자의 선언만 했기 때문에 호출 시도시 컴파일 에러가 발생하게 된다. 혹시라도 friend나 멤버 함수를 통한 접근을 시도하더라도 정의가 되어있지 않기 때문에 링크 에러가 발생한다.

이런 방법도 나쁘지 않지만 C++11은 좀더 명확한 목적을 가진 방법이 추가되었다.

 

방법 2 : 함수를 사용하지 않는다고 선언하기

template <class charT, class traits = char_traits<charT>>
class basic_ios : public ios_base {
public:
    ...
    basic_ios(const basic_ios&) = delete;
    basic_ios& operator=(const basic_ios&) = delete;
};

위처럼 아예 해당 함수를 사용하지 않는다고 선언하는 것이다.

방법 1과 결과적으로 크게 달라보이지 않아서 그저 취향 문제로 보일수도 있지만 매우 큰 차이가 하나 있다.

방법 1은 멤버 함수나 friend에서 혹시라도 접근하게 되면 링크 단계까지 가서야 문제가 파악되지만 방법 2는 컴파일 단계에서 바로 파악된다.

또한 삭제된 함수는 public으로 선언하는것이 관례이다. 컴파일러별로 함수의 삭제보다 접근 지정자의 문제를 먼저 걸고 넘어지는 경우가 있기 때문에 에러 메시지를 보고 사용자가 오해할 소지가 있기 때문이다.

 

또하나의 차이가 있다. 방법 1은 접근 지정자를 사용하기 때문에 오직 멤버 함수에만 적용이 가능하지만 방법 2는 어떤 함수도 삭제할 수 있다는 것이다.

 

bool isLucky(int number);

if (isLucky('a')) ... // 정수만 받아야 하는데..?
if (isLucky(true)) ...
if (isLucky(3.5)) ...

어떤 함수가 반드시 정수만 받아야 한다면 어떻게 해야할까?

방법 1이라면 해결 방법이 없다. 멤버 함수가 아니기 때문에 접근 지정자가 사용이 불가능하기 때문이다.

하지만 방법 2라면 타입별로 오버로딩하고 해당 함수들을 삭제처리 하면 된다.

 

bool isLucky(int number);
bool isLucky(char) = delete;
bool isLucky(bool) = delete;
bool isLucky(double) = delete; // float, double 둘 다 배제

삭제된 함수들은 비록 사용할수는 없어도 어쨌든 코드에 존재하는 프로그램의 일부이기 때문에 함수 오버로딩의 선택지에 포함이 된다. 하지만 사용하지 않는다고 선언했기 때문에 컴파일 에러가 발생하게 된다.

 

 

템플릿 특수화와 조합하면 원치 않은 템플릿의 인스턴스화도 막을 수 있다.

 

template<typename T>
void processPointer(T* ptr);

template<>
void processPointer<void>(void*) = delete;
// const void에 대해서도 처리해야 한다

template<>
void processPointer<char>(char*) = delete;
// const char에 대해서도 처리해야 한다

좀더 확실하게 하려면 const volatile등 관련된 모든 타입에 대한 특수화도 삭제해야 할 것이다.

 

 

클래스 내 함수 템플릿의 일부를 인스턴스화 시키지 않기 위해 부분 특수화를 적용하는 경우가 발생한다면 방법 1로는 해결할 수 없다는 단점도 있다.

 

class Widget {
public:
    template<typename T>
    void processPointer(T* ptr);
private:
    template<> // 오류 발생
    void processPointer<void>(void*);
};

이게 왜 안되냐면 템플릿 특수화는 클래스 범위가 아니라 이름 공간의 범위에서 작성해야 하기 때문이다.

 

class Widget {
public:
    template<typename T>
    void processPointer(T* ptr);
};

template<>
void Widget::processPointer<void>(void*) = delete;

방법 2는 문제 없이 삭제할 수 있다. 방법 2가 방법 1에 비해 모든 면에서 예외없이 상위호환이기 때문에 함수를 삭제할거라면 정의되지 않은 비공개 함수대신 delete를 사용하면 된다.

 

◾ 정의되지 않은 비공개 함수보다 삭제된 함수를 선호하라.

◾ 비멤버 함수와 템플릿 인스턴스를 비롯한 그 어떤 함수도 삭제할 수 있다.

일반적으로 중괄호 쌍 안에서 선언된 이름은 해당 중괄호의 범위 안에서만 유효하지만 C++98 스타일의 enum은 예외적으로 규칙이 적용되지 않는다.

 

enum Color { black, white, red };

auto white = false; // error

위와같은 열거자들은 범위를 새어 나가기 때문에 범위 없는(unscoped) enum라고도 한다.

 

enum class Color { black, white, red };

auto white = false; // ok
Color c = white; // error
Color c = Color::white; // ok

C++11부터는 열거자들이 범위를 새어나가게 하지 않기 위해 범위 있는(scoped) enum을 추가하였다. enum class라고도 한다.

범위 있는 enum은 열거자들이 범위 밖으로 새어나가지 않기 때문에 이름 공간을 더럽히지 않는것과 더불어서 암시적 타입 변환에 의한 문제도 방지해준다.

범위 없는 enum은 암시적으로 정수 타입으로 변환되는 반면 범위 있는 enum은 암시적 타입 변환이 이루어지지 않기 때문이다.

 

enum Color { black, white, red };

Color c = red;
if (c < 14.5) { ... } // Color->int->double 암시적 변환


enum class Color { black, white, red };

Color c = Color::red;
if (c < 14.5) { ... } // 컴파일 에러

만약 범위 있는 enum의 타입 변환이 필요하다면 직접 캐스팅을 통해 타입 변환을 해주면 된다.

 

또한 범위 있는 enum은 전방 선언도 가능하다.

그런데 전방 선언이 왜 장점일까? 그것은 컴파일 의존 관계와 관련이 있다.

 

enum Status {
    good = 0,
    failed = 1,
    incomplete = 100,
    corrupt = 200,
    indeterminate = 0xFFFFFFFF
};

만약 이런 enum이 존재하고 시스템 전반적으로 사용된다고 가정해보자. 여기서 새 열거자가 추가되는 등의 수정 작업이 이루어지면 시스템 전체가 다시 컴파일되는 극심한 낭비가 발생하게 된다.

 

enum class Status;
...
void continueProcessing(Status s);

하지만 전방 선언을 사용하면 의존 관계가 크게 줄어들기 때문에 컴파일 타임이 크게 줄어들 수 있다.

 

그런데 전방 선언은 해당 타입의 크기를 알아야만 사용할 수 있는 문법이다. 컴파일러는 enum의 타입을 어떻게 알 수 있을까?

범위 있는 enum은 기본 타입이 int이기 때문에 별도의 타입을 지정하지 않아도 컴파일러가 타입을 알 수 있다. 하지만 범위 없는 enum은 기본 타입이 없기 때문에 전방 선언이 안된다. 그렇지만 타입을 임의로 지정해주면 범위 없는 enum도 전방 선언이 가능해진다.

 

enum Status; // 기본 타입 int
enum class Status; // 기본 타입 없음. 전방 선언 X

enum Status : std::uint32_t; // 사용자 정의 타입 uint32_t
enum class Status : std::uint32_t; // 사용자 정의 타입 uint32_t

 

 

범위 있는 enum이 범위 없는 enum보다 더 좋아보이기 때문에 범위 없는 enum은 사용할 필요가 아예 없을것처럼 보인다. 하지만 사용할만한 곳이 적어도 하나는 존재한다.

 

using UserInfo = std::tuple<std::string, std::string, std::size_t>;
UserInfo uInfo;
...
auto val = std::get<1>(uInfo); // 1의 필드는 대체 무슨 뜻?

바로 튜플을 사용할 때이다. 튜플은 각 필드를 std::get을 통해 접근하게 된다. 하지만 리터럴로 접근하면 코드를 읽는 다른 사람이 무슨 뜻인지 바로 알아볼수가 없다.

 

enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;
...
auto val = std::get<uiEmail>(uInfo); // email 필드의 값이구나!

범위 없는 enum의 열거자들은 정수 타입으로 암시적 변환되며 각 필드를 접근하게 된다. 코드를 읽는 사람도 무슨 필드인지 명확하게 알 수 있기 때문에 가독성이 좋아진다.

 

하지만 범위 있는 enum을 사용한다면 반드시 캐스팅을 통한 타입 변환이 이루어져야 하기 때문에 템플릿 인수의 길이가 매우 길어질 수 있다.

범위 없는 enum에 의한 이름 공간 오염이 걱정된다면 열거자 하나를 받아서 std::size_t값을 돌려주는 함수를 직접 작성하면 된다. 그렇지만 꽤 까다로울 것이다.

 

std::get은 함수 템플릿이라서 컴파일 타임에 평가되기 때문에 함수의 반환값 역시 컴파일 타임에 산출되어야만 한다.

그러려면 해당 함수는 반드시 constexpr이어야 하고 예외를 던지지 않을 것을 알기 때문에 noexcept로 선언되어야 한다. 게다가 모든 종류의 enum에 대해서도 작동해야 하기 때문에 함수 템플릿으로 작성되어야 한다. 함수 템플릿을 고려한다면 반환 타입도 일반화 되어야 하기 때문에 이제는 std::size_t가 아니라 매개변수로 받은 열거자의 기본 타입을 반환시켜주어야 하므로 타입 특성을 이용해야 한다.

즉 1) constexpr, 2) noexcept, 3) 함수 템플릿, 4) type_traits 네가지를 고려해서 작성해야 한다.

 

template<typename E> // C++11
constexpr typename std::underlying_type<E>::type toUType(E enumerator) noexcept
{
    return static_cast<typename std::underlying_type<E>::type>(enumerator);
}


template<typename E> // C++14
constexpr auto toUType(E enumerator) noexcept
{
    return static_cast<std::underlying_type_t<E>>(enumerator);
}

enum의 기본 타입을 알아내는 것은 underlying_type을 사용하면 된다.

 

enum class UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;
...
auto val = std::get<toUType(UserInfoFields::uiEmail)>(uInfo);

해당 함수 템플릿을 사용하면 조금 더 번거롭고 코드가 살짝 길어졌지만 범위 없는 enum을 사용했을 때처럼 리터럴 값을 사용하지 않아도 된다. 게다가 범위 있는 enum이기 때문에 이름 공간의 오염과 의도하지 않은 암시적 타입 변환을 걱정할 필요가 없다.

 

◾ C++98 스타일의 enum을 이제는 범위 없는 enum이라고 부른다.

◾ 범위 있는 enum의 열거자들은 그 안에서만 보인다. 이 열거자들은 오직 캐스팅을 통해서만 다른 타입으로 변환된다.

◾ 범위 있는 enum과 범위 없는 enum 모두 기본 타입 지정을 지원한다. 범위 있는 enum의 기본 타입은 int이다. 범위 없는 enum에는 기본 타입이 없다.

◾ 범위 있는 enum은 항상 전방 선언이 가능하다. 범위 없는 enum은 해당 선언에 기본 타입을 지정하는 경우에만 전방 선언이 가능하다.

+ Recent posts