ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Lecture 2 - Operator, Overloading and Vector
    DSP/Lecture 2024. 4. 25. 13:00

    연산자 오버로딩에 대한 설명

    연산자 오버로딩은 프로그래머가 정의한 연산자(예를 들어 +, -, *, / 등)에 대한 사용자 정의 구현을 가능하게 합니다. 이 기능을 통해 클래스와 같은 사용자 정의 타입에 대해서도 이 연산자들을 직관적이고 자연스럽게 사용할 수 있습니다.

    1. "overloaded operators are like normal functions, but are defined using a pre-defined operator symbol" 
      • 연산자가 일반함수처럼 동작한다는 것
        • 프로그래밍에서 함수는 특정 작업을 수행하거나 값을 계산하는 데 사용됩니다. 예를 들어, 두 숫자를 더하는 함수를 생각해 볼 수 있어요. 연산자 오버로딩을 사용하면, 우리는 +- 같은 기호를 사용하여, 마치 함수처럼 이 연산자들을 정의할 수 있습니다. 이렇게 하면, 이 연산자들을 우리가 만든 클래스의 객체에 대해 사용할 수 있게 됩니다.
      • 사전에 정의된 연산자 기호를 사용한다는 것
        • 연산자 오버로딩에서는 +, -, * 같은 이미 정해진 기호를 사용하여 함수를 만듭니다. 이 기호들은 프로그래밍 언어에서 특별한 의미를 가지고 있어요. 연산자 오버로딩을 통해 이런 기호들을 사용하여, 우리의 데이터 유형에 맞게 특별하게 기능을 정의할 수 있습니다. 예를 들어, 두 개의 Vector 객체를 더할 때 + 기호를 사용하도록 정의할 수 있어요.
    2. "You cannot change the priority and associativity of an operator."
      • 설명: 이 문장은 C++에서 연산자 오버로딩을 할 때, 연산자의 우선 순위(priority)와 결합 방향(associativity)을 변경할 수 없다는 것을 설명합니다. 연산자 오버로딩을 통해 연산자의 기능을 사용자 정의하는 것은 가능하지만, 연산자가 갖는 기본적인 속성(우선 순위와 결합 방향)은 변경할 수 없습니다. 이는 프로그래밍 언어의 일관성과 예측 가능성을 유지하기 위한 조치입니다.
    3. "The operators are selected by the compiler based on the static type of the specified operands."
      • 설명: 이 문장은 컴파일러가 연산자를 선택할 때 지정된 피연산자의 정적 타입(컴파일 시점에 결정되는 타입)에 기반하여 선택한다는 점을 설명합니다. 이는 C++이 타입-안전한 언어라는 것을 강조하며, 연산자 오버로딩이 실행되는 방식에 있어서 타입 체크가 중요한 역할을 한다는 것을 나타냅니다.
    4. "using operator overloading, we can naturally express a data type specific notion of arithmetic (adding, multiplying and so on)"
      • 설명: 연산자 오버로딩을 사용하면 특정 데이터 타입에 대한 산술 연산(더하기, 곱하기 등)을 자연스럽게 표현할 수 있다는 의미입니다. 이를 좀 더 쉽게 설명하겠습니다. 
        • 연산자 오버로딩(Operator Overloading)
          • 연산자 오버로딩은 프로그래밍에서 특정 연산자(+, -, * 등)의 기능을 사용자 정의 타입(예를 들어, 클래스로 만든 복잡한 데이터 구조)에 맞게 변경하는 기술입니다. C++ 같은 언어에서 이 기능을 사용하면 기존 연산자들을 새로운 방식으로 사용할 수 있어, 코드가 더 직관적이고 이해하기 쉬워집니다.
        • 자연스러운 표현(Natural Expression)
          • 연산자 오버로딩을 통해, 우리는 복잡한 타입의 객체들에 대해 마치 기본 데이터 타입(int, float 등)을 사용하듯이 연산을 적용할 수 있습니다. 예를 들어, 두 벡터를 더하는 연산을 vector1 + vector2처럼 간단하게 표현할 수 있습니다. 이는 벡터 덧셈을 구현하는 복잡한 함수 호출을 숨기고, 더하기 연산자를 사용하여 직관적으로 표현할 수 있게 합니다.
        • 데이터 타입 특화(Arithmetic for Specific Data Types)
          • 각 데이터 타입은 고유한 산술 연산을 필요로 할 수 있습니다. 예를 들어, 벡터의 덧셈은 숫자의 덧셈과는 다르게 동작합니다. 연산자 오버로딩을 사용하면, 이러한 특수한 연산을 해당 데이터 타입에 적합하게 정의할 수 있으며, 프로그램에서 이를 자연스럽게 사용할 수 있습니다.
    class Vector {
    public:
        int x, y;
    
        Vector(int x, int y) : x(x), y(y) {}
    
        // 연산자 오버로딩을 사용하여 벡터 덧셈 정의
        Vector operator+(const Vector& other) {
            return Vector(x + other.x, y + other.y);
        }
    };
    
    int main() {
        Vector v1(1, 2);
        Vector v2(3, 4);
        Vector v3 = v1 + v2;  // v1과 v2를 더하는 것을 자연스럽게 표현
        cout << "Result: (" << v3.x << ", " << v3.y << ")" << endl;
    }

    이 예제에서 Vector 클래스는 두 벡터의 덧셈을 + 연산자로 자연스럽게 표현할 수 있도록 연산자 오버로딩을 사용하여 정의하고 있습니다. 이런 방식으로 코드를 작성하면 더 간결하고 이해하기 쉬워지며, 특정 데이터 타입에 맞는 산술 연산을 효과적으로 구현할 수 있습니다.


    멤버 연산자(Member Operators)

    • 멤버 연산자는 클래스의 인스턴스(객체)에 대해 정의된 메소드와 같이 작동합니다.
    • 이 연산자는 호출되는 객체 자체를 가리키는 this 포인터를 첫 번째 인자로 암묵적으로 받습니다. 즉, 연산자의 좌변이 항상 현재 객체(this 객체)입니다.
    • 예를 들어, a + b에서 a가 this 객체이고, + 연산자가 클래스의 멤버 함수로 정의되어 있을 경우 a는 자동으로 this로 전달되고, b는 메소드의 인자로 전달됩니다.

    임시(Ad Hoc) 연산자

    • 임시 연산자는 클래스의 멤버가 아닙니다. 즉, 특정 객체에 속하지 않으므로 this 포인터가 없습니다.
    • 연산자는 두 개의 독립적인 인자를 필요로 하며, 이들 인자는 연산자를 적용할 두 객체를 나타냅니다.
    • 예를 들어, Vector2D 객체에 대한 + 연산자를 임시로 정의할 경우, 두 벡터 객체 모두 인자로 명시적으로 제공되어야 하며, 이 두 객체에 대해 연산이 수행됩니다.

    벡터의 덧셈

     

    • 함수 정의: 이 함수는 operator+로서 두 벡터를 더하는 연산을 정의합니다.
    • 매개변수: const Vector2D& aOther는 덧셈에서 오른쪽 피연산자인 다른 Vector2D 객체에 대한 참조입니다.
    • 반환 타입: 새로운 Vector2D 객체를 반환합니다.
    • 연산: 호출 객체(this 객체)와 aOther 객체의 x와 y 좌표를 더합니다. 여기서 x()와 y() 함수는 Vector2D 클래스의 멤버 함수로 각각 벡터의 x, y 좌표를 반환하는 것으로 추정됩니다.
    • 예외 명세: noexcept는 이 함수가 예외를 발생시키지 않을 것임을 나타냅니다.

    벡터의 뺄셈

    • 함수 정의: 이 함수는 operator-로서 두 벡터를 빼는 연산을 정의합니다.
    • 매개변수: 덧셈과 마찬가지로 다른 Vector2D 객체에 대한 상수 참조를 받습니다.
    • 반환 타입: 새로운 Vector2D 객체를 반환합니다.
    • 연산: aOther의 x와 y 좌표를 호출 객체의 x와 y 좌표에서 빼는 연산을 수행합니다.
    • 예외 명세: 역시 noexcept로 표시되어 있습니다.

    일반적인 설명

    • 멤버 연산자: 두 연산 모두 멤버 연산자로 구현되어 있으며, 왼쪽 피연산자는 메서드가 호출된 객체(this 객체)이고, 오른쪽 피연산자는 인자로 전달됩니다.
    • 효율성 및 안전성: 매개변수에 참조를 사용함으로써 객체의 불필요한 복사를 피하고, 연산을 효율적으로 만듭니다. const 사용은 원본 벡터가 덧셈이나 뺄셈 연산에 의해 수정되지 않도록 보장합니다.

    스칼라 곱셈

    • 벡터에 스칼라(숫자 하나)를 곱하는 연산을 의미합니다. 이 연산은 벡터의 각 좌표에 스칼라를 곱하여 벡터의 크기를 조절합니다.
    • 예를 들어, 벡터 (x, y)에 스칼라 a를 곱하면, 결과는 (ax, ay)가 됩니다.

    내적 (Dot Product)

    • 두 벡터 간의 내적은 각 좌표의 곱을 모두 더하는 연산입니다. 이 값은 두 벡터 간의 각도 정보를 포함하며, 두 벡터가 얼마나 같은 방향을 향하고 있는지를 수치로 나타냅니다.
    • 벡터 A(x₁, y₁)와 B(x₂, y₂)의 내적은 x₁x₂ + y₁y₂로 계산됩니다.

    외적 (Cross Product)

    • 2차원에서의 벡터 외적은 두 벡터를 이용하여 만들어진 평행사변형의 면적의 부호 있는 크기를 계산하고, 이를 통해 두 벡터 사이의 회전 방향(왼쪽 또는 오른쪽)을 알 수 있습니다.
    • 벡터 A(x₁, y₁)와 B(x₂, y₂)의 외적은 x₁y₂ - y₁x₂로 계산되며, 결과적으로 하나의 스칼라 값을 얻습니다.

    벡터의 길이

    벡터의 길이(또는 크기)는 벡터의 각 좌표 제곱의 합의 제곱근으로 계산됩니다. 벡터 v=(𝑥,𝑦)의 길이는 𝑥2+𝑦2로 계산됩니다. 이 값은 벡터가 원점에서 얼마나 떨어져 있는지를 나타내는 값입니다.

    단위 벡터

    단위 벡터는 길이가 1인 벡터를 의미합니다. 어떤 벡터 v의 단위 벡터는 원래 벡터를 그 벡터의 길이로 나누어 계산됩니다. 즉, v의 단위 벡터는 v∣v∣ 입니다.

    교수님의 강의 노트에 따르면, 이러한 연산들은 const 키워드와 noexcept 키워드를 사용하여 정의되어 있습니다. const 키워드는 함수가 객체의 상태를 변경하지 않음을 보증하며, noexcept는 함수가 예외를 발생시키지 않을 것임을 의미합니다. 이렇게 정의하는 것은 연산 중 객체가 변경되거나 예외 상황이 발생하지 않음을 보장하여 안정성과 예측 가능성을 높여줍니다.

    코드 구현

    float Vector2D::length() const noexcept {
        return std::sqrt(x() * x() + y() * y());
    }
    
    Vector2D Vector2D::normalize() const noexcept {
        return *this * (1.0f / length());
    }
    • length() 함수: 이 함수는 벡터의 x, y 좌표를 사용하여 길이를 계산합니다. 계산된 길이는 더 정확하고 예측 가능한 값으로 만들기 위해 100으로 곱한 후 반올림하고 다시 100으로 나누어 두 자리 소수점으로 조정합니다.
    • normalize() 함수: 이 함수는 벡터를 그 벡터의 길이로 나누어 단위 벡터를 생성합니다. 이 과정에서 원 벡터는 변경되지 않고 새로운 단위 벡터가 반환됩니다.

    이 코드를 통해 벡터의 기본적인 특성을 다루고 조작하는 방법을 배울 수 있으며, 이는 벡터를 사용하는 다양한 수학적 및 물리적 문제 해결에 적용될 수 있습니다 .

     

    노말라이제이션(normalization)은 벡터를 단위 벡터(unit vector)로 변환하는 과정을 말합니다. 단위 벡터는 길이가 1인 벡터를 의미하며, 방향은 원래 벡터와 동일하지만 크기는 정규화되어 있습니다. 이 과정은 벡터의 크기를 표준화하여 다양한 수학적 및 물리적 계산을 용이하게 합니다.

    노말라이제이션의 수학적 정의

    어떤 벡터 𝑣 가 있을 때, 이 벡터의 노말라이제이션은 벡터를 그 벡터의 길이(또는 크기)로 나누어 계산됩니다. 수식으로 표현하면 다음과 같습니다: 단위 벡터=𝑣∣𝑣∣ 여기서 ∣𝑣∣는 벡터 𝑣의 길이를 의미하며, 이는 𝑥2+𝑦2 (2차원의 경우)로 계산됩니다.

    노말라이제이션의 중요성

    노말라이제이션은 벡터의 방향은 유지하면서 길이를 1로 조정합니다. 이렇게 하면 벡터의 크기에 영향을 받지 않고 방향성만을 고려할 수 있어, 각종 계산에서 변수의 영향을 최소화할 수 있습니다. 예를 들어, 물리학에서 힘의 방향을 표현하거나, 컴퓨터 그래픽에서 광원의 방향을 계산할 때 유용하게 사용됩니다.

    노말라이제이션을 통해 얻은 단위 벡터는 벡터 간의 각도를 비교하거나, 다른 벡터와의 연산을 수행할 때 계산을 단순화시키는 데 도움을 줍니다.

    벡터의 방향 계산

    벡터의 방향을 계산하는 코드는 아래와 같습니다:

    float Vector2D::direction() const noexcept {
        float val = std::atan2(y(), x()) * 180.0f / static_cast<float>(M_PI);
        return std::round(val * 100.0f) / 100.0f;
    }
    • std::atan2(y(), x()) 함수는 y좌표와 x좌표를 인자로 받아 아크탄젠트(atan2)를 계산하여 라디안 단위의 각도를 반환합니다.
    • 이 각도를 도(degree) 단위로 변환하기 위해 π(파이)로 나누고 180을 곱합니다.
    • 결과값을 보다 읽기 쉽게 만들기 위해 100을 곱한 후 반올림하고 다시 100으로 나눕니다.

    벡터의 정렬(Align) 계산

    벡터를 특정 각도로 회전시키는 align 함수는 다음과 같이 구현됩니다:

    Vector2D Vector2D::align(float aAngleInDegrees) const noexcept {
        float lRadians = aAngleInDegrees * static_cast<float>(M_PI) / 180.0f;
        return length() * Vector2D(std::cos(lRadians), std::sin(lRadians));
    }
    • 입력된 각도를 라디안으로 변환합니다.
    • 코사인과 사인 값을 사용하여 새로운 x, y 좌표를 계산하고, 이를 통해 벡터를 회전시킵니다.
    • 원 벡터의 길이를 유지하면서 방향만 변경된 새 벡터를 생성합니다.
Designed by Tistory.