반응형

 

※ 연산자 오버로딩 기본 개념 참고

 

C++ 연산자 오버로딩

※ firend의 개념을 모른다면 다음 포스팅을 먼저 숙지할 것. C++ friend friend 선언 - 접근지시자에 관계없이 모든 멤버의 접근을 허용하는 선언이다. ※ 그렇기 때문에, 클래스의 friend 선언은 객체

srdeveloper.tistory.com


 

 

대입 연산자

- 정의하지 않으면 디폴트 대입 연산자가 삽입된다.

- 복사 생성자와 마찬가지로 멤버 대 멤버의 복사가 일어난다.

- 디폴트 대입 연산자는 얕은 복사이기 때문에 깊은 복사가 필요하다면 직접 정의해야한다.

#include <iostream>
using namespace std;

class SomeClass
{
private:
    int n1;
    
public:
    SomeClass(int n1 = 0) : n1(n1) { }
    void ShowData()
    {
        cout << n1 << endl;
    }
    SomeClass& operator=(const SomeClass& ref)
    {
        cout << "SomeClass operator=()" << endl;
        this->n1 = ref.n1;
        return *this;
    }
};

void main()
{
    SomeClass sc1(10);
    SomeClass sc2;
    sc2 = sc1;
    sc2.ShowData();
    
    //결과
    //SomeClass operator=()
    //10
}
  • sc2 선언시 바로 대입 연산자를 사용하면 대입 연산자가 호출 되는것이 아닌 복사 생성자가 호출이 된다.

 

 

 

디폴트 대입 연산자의 문제점

- 복사 생성자에서 생기는 문제와 유사하다. (얕은 복사의 문제)

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
using namespace std;

class Name
{
private:
    char* name;
    
public:
    Name(const char* name) 
    {
        int len = strlen(name) + 1;
        this->name = new char[len];
        strcpy(this->name, name);
    }
    void ShowData()
    {
        cout << name << endl;
    }
    Name& operator=(const Name& ref)
    {
        delete[] name;
        int len = strlen(ref.name) + 1;
        this->name = new char[len];
        strcpy(this->name, ref.name);
        return *this;
    }
    ~Name()
    {
        delete[] name;
    }
};

void main()
{
    Name n1("Lee");
    Name n2("Yoon");
    n2 = n1;
    n2.ShowData();
    
    //결과 : Lee
}
  • 대입 연산자를 통한 얕은 복사에서 일어나는 메모리 누수를 막기위해 name 멤버 변수에 할당된 기존 데이터를 할당 해제하고 새롭게 할당하여 깊은 복사를 진행한다.

 

 

 

상속 구조에서의 대입 연산자

- 자식 클래스에서 대입 연산자를 오버로딩 하는 경우, 부모 클래스의 대입 연산자를 명시해 주지 않으면 부모 클래스의 대입 연산자는 호출이 되지 않는다.

- 따라서, 디폴트 대입 연산자의 경우 부모 클래스의 대입 연산자까지 호출되는 것을 확인 할 수 있다.

#include <iostream>
using namespace std;

class Parent
{
private:
    int n1, n2;
    
public:
    Parent(int n1 = 0, int n2 = 0) : n1(n1), n2(n2) { }
    void ShowData()
    {
        cout << n1 << " / " << n2 << endl;
    }
    Parent& operator=(const Parent& ref)
    {
        cout << "Parent operator=()" << endl;
        this->n1 = ref.n1;
        this->n2 = ref.n2;
        return *this;
    }
};

class Child : public Parent
{
private:
    int n1, n2;
    
public:
    Child(int n1, int n2, int n3, int n4) : Parent(n1, n2), n1(n3), n2(n4) { }
    void ShowData()
    {
        Parent::ShowData();
        cout << n1 << " / " << n2 << endl;
    }
    Child& operator=(const Child& ref)
    {
        cout << "Child operator=()" << endl;
        //부모 클래스 대입 연산자 호출
        Parent::operator=(ref);
        this->n1 = ref.n1;
        this->n2 = ref.n2;
        return *this;
    }
};

void main()
{
    Child c1(1, 2, 3, 4);
    Child c2(0, 0, 0, 0);
    c2 = c1;
    c2.ShowData();
    
    //결과
    //Child operator=()
    //Parent operator=()
    //1 / 2
    //3 / 4
}
  • Child 클래스에서 대입 연산자 오버로딩에 Parent::operator(ref)를 주석처리하고 실행해보면 부모 클래스의 멤버는 대입 연산자가 작용하지 않은것을 확인할 수 있다.

 

 

 

인덱스 연산자

- C, C++의 배열은 '경계검사를 하지 않는다'는 단점을 가지고 있다.

- 즉 배열의 범위에 벗어난 인덱스 값을 넣어도 실행하는데 무리없이 진행이 되기 때문에, 안정성을 고려해서 배열 클래스를 만든다.

#include <iostream>
using namespace std;

class IntArray
{
private:
    int* arr;
    int arrLen;

public:
    IntArray(int len) : arrLen(len)
    {
        arr = new int[len];
    }
    int& operator[](int index)
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    ~IntArray()
    {
        delete[] arr;
    }
};

void main()
{
    IntArray ia(3);
    for(int i = 0; i <= 3; i++)
    {
        ia[i] = i + 1;
    }
    
    //결과 : Arr index out of range
}
  • 결과와 같이 배열에 대한 잘못된 접근을 차단함으로써 안정성을 보장받을 수 있다.

 

 

 

※ 배열 클래스의 복사에 대해서..

- 배열은 저장소의 일종이고, 저장소에 저장된 데이터는 '유일성'이 보장되어야 하기 때문에, 저장소의 복사는 잘못된 일로 간주된다.

- 따라서, 복사 생성자와 대입 연산자는 private 멤버로 지정하여 복사를 원칙적으로 막는것이 좋은 선택이 되기도 한다.

//IntArray 클래스의 private에 복사 생성자와 대입 연산자를 멤버로 둔다.
IntArray(const IntArray& ref) { }
IntArray& operator=(const IntArray& ref) { }

 

 

 

 

const를 통한 배열 안정성 보장하기

- 어떠한 함수를 정의할 때, 매개변수를 const로 지정하면 해당 함수 내에서는 값의 변경이 불가능하므로 안정성을 보장받을 수 있지만, 위와 같은 코드에서는 인덱스 연산자에서 const 선언이 되어있지 않기 때문에 const로 지정된 매개변수로 받으면 컴파일 에러가 난다.

//전역함수
void ShowData(const IntArray& ref)
{
    for(int i = 0; i < 3; i++)
    {
        cout << ref[i] << endl;	//컴파일 에러 발생
    }
}

 

- 해당 컴파일 에러를 해결하려면 const함수를 오버로딩하면 해결이 된다.

#include <iostream>
using namespace std;

class IntArray
{
private:
    int* arr;
    int arrLen;
    
    IntArray(const IntArray& ref) { }
    IntArray& operator=(const IntArray& ref) { }

public:
    IntArray(int len) : arrLen(len)
    {
        arr = new int[len];
    }
    //배열 안에 값을 넣을 때 호출됨.
    int& operator[](int index)
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    //const 매개변수로 선언된 배열 안의 값을 찾을 때 호출된
    int operator[](int index) const
    {
        if (index < 0 || index >= arrLen)
        {
            cout << "Arr index out of range" << endl;
            exit(1);
        }
        return arr[index];
    }
    ~IntArray()
    {
        delete[] arr;
    }
};

void ShowData(const IntArray& ref)
{
    for(int i = 0; i < 3; i++)
    {
        cout << ref[i] << endl;
    }
}

void main()
{
    IntArray ia(3);
    for(int i = 0; i < 3; i++)
    {
        ia[i] = i + 1;
    }
    ShowData(ia);
    
    //결과
    //1
    //2
    //3
}
  • 기존의 인덱스 연산자에서 const를 붙이는게 아닌 오버로딩을 한 이유는, 배열 멤버에 대한 저장이 불가능해지기 때문이다.
  • 따라서 const 선언을 이용해 인덱스 연산자를 오버로딩하여 해결한다.

 

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 네임스페이스(namespace), using  (0) 2021.12.12
C++ 연산자 오버로딩  (0) 2021.12.05
C++ friend  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
반응형

 

 

namespace

- 특정 영역에 이름을 붙여주기 위한 문법적 요소

- namespace가 다르면 같은 이름의 선언이 허용된다.

- 여러 외부 라이브러리 사용 중 발생하는 문제를 해결하기 위해 만들어졌다.

- namespace는 다른 namespace 안에 삽입될 수 있다.

- namespace의 별칭 지정이 가능하다.

 

namespace 예제

#include <iostream>

namespace A
{
    void Func()
    {
        std::cout << "Namespace A : Func()" << std::endl;
    }
}

namespace B
{
    void Func()
    {
        std::cout << "Namespace B : Func()" << std::endl;
    }
}

void main()
{
    A::Func();
    B::Func();
    
    //결과
    //Namespace A : Func()
    //Namespace B : Func()
}
  • 위의 예제에서 ::을 범위지정 연산자라고 한다.

 

namespace 중첩 예제

#include <iostream>

namespace A
{
    int num = 0;
    
    namespace B
    {
        int num = 1;
    }
    
    namespace C
    {
        int num = 2;
    }
}

void main()
{
    std::cout << A::num << std::endl;
    std::cout << A::B::num << std::endl;
    std::cout << A::C::num << std::endl;
    
    //결과
    //0
    //1
    //2
}

 

 

namespace 별칭 지정 예제

#include <iostream>

namespace A
{
    namespace B
    {
        int num = 1;
    }
}

void main()
{
    namespace AB = A::B;
    std::cout << AB::num << std::endl;
    
    //결과
    //1
}

 

 

 

 

using

- namespace에 선언된 모든것에 대해 namespace 지정의 생략을 명령한다.

- 편리함은 증가하지만 무분별하게 사용하면 충돌이 발생할 확률이 상대적으로 높아진다.

 

using 예제

#include <iostream>
using namespace A;
using namespace C;

namespace A
{
    int num = 0;
    
    namespace B
    {
        int num = 1;
    }
	
    namespace C
    {
        int num = 2;
    }
}

void main()
{
    std::cout << num << std::endl;		//모호함으로 인해 에러 발생
    std::cout << B::num << std::endl;	//namespace A 생략 가능
    std::cout << C::num << std::endl;	//namespace A 생략 가능
}

 

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 연산자 오버로딩 (대입, 인덱스 연산자)  (0) 2021.12.26
C++ 연산자 오버로딩  (0) 2021.12.05
C++ friend  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
반응형

 

 

※ firend의 개념을 모른다면 다음 포스팅을 먼저 숙지할 것.

 

C++ friend

friend 선언 - 접근지시자에 관계없이 모든 멤버의 접근을 허용하는 선언이다. ※ 그렇기 때문에, 클래스의 friend 선언은 객체 지향의 정보 은닉 개념에 위배된다. - friend는 사용되는 클래스에서 선

srdeveloper.tistory.com

 

연산자 오버로딩

- 사용자 정의 자료형(클래스)에 연산자를 재정의한다.

- operator 키워드와 연산자를 묶어서 함수이름을 정의하면 연산자 오버로딩이 이루어진다.

  • 예 : class operator+(const class& ref)

- 연산자 오버로딩은 멤버함수에 의한 연산자 오버로딩, 전역함수에 의한 연산자 오버로딩이 있다.

  • 멤버함수 연산자 오버로딩 : class.operator+(class)
  • 전역함수 연산자 오버로딩 : class.operator+(class, class)

 

 

  • 멤버함수, 전역함수 연산자 오버로딩 예제
#include <iostream>
using namespace std;

class OverloadingOP
{
private:
    int m_iNum1, m_iNum2;

public:
    OverloadingOP(int n1 = 0, int n2 = 0) : m_iNum1(n1), m_iNum2(n2) { }
    void ShowMember()
    {
        cout << m_iNum1 << " / " << m_iNum2 << endl;
    }
    //멤버함수 연산자 오버로딩
    OverloadingOP operator+(const OverloadingOP& ref)
    {
        return OverloadingOP(m_iNum1 + ref.m_iNum1, m_iNum2 + ref.m_iNum2);
    }
    //전역함수 연산자 오버로딩
    friend OverloadingOP operator+(const OverloadingOP& ref1, const OverloadingOP& ref2);
};

OverloadingOP operator+(const OverloadingOP& ref1, const OverloadingOP& ref2)
{
    return OverloadingOP(ref1.m_iNum1 + ref2.m_iNum1, ref1.m_iNum2 + ref2.m_iNum2);
}

void main()
{
    OverloadingOP op1(3, 4);
    OverloadingOP op2(10, 10);
    OverloadingOP op3 = op1 + op2;
    
    op3.ShowMember();
    
    //결과 : 13 / 14
}

 - 전역함수로 연산자 오버로딩을 할 시, 해당 클래스의 멤버에 접근할수 있어야하므로 friend 선언을 해준다. (friend 키워드의 적절한 사용예)

 

 

 

연산자 오버로딩 주의점

- 연산자 오버로딩이 멤버함수, 전역함수로 둘 다 존재할 경우, 멤버함수로 오버로딩된 함수가 우선시되어 호출되므로, 특별한 경우가 아니라면 멤버함수 기반으로 연산자 오버로딩을 하는것이 좋다.

- 매개변수의 디폴트값 설정이 불가능하다.

- 연산자의 우선순위와 결합성은 바뀌지 않는다. (연산자가 지니는 우선순위와 결합성은 그대로 따라간다.)

 

 

 

단항연산자의 오버로딩

- 단항연산자(++, --)는 매개변수가 없다.

  • 멤버함수 연산자 오버로딩 : class.operator++()
  • 전역함수 연산자 오버로딩 : operator++(class)

- 단항연산자의 전위, 후위 구분은 int로 구분한다.

  • 전위 : operator++()
  • 후위 : operator++(int)

  ※ int는 데이터를 전달하는 뜻이 아닌, 단순히 전위와 후위를 구분하기 위해서 사용하는것이다.

 

 

  • 단항연산자 오버로딩 예제
#include <iostream>
using namespace std;

class OverloadingOP
{
private:
    int m_iNum1, m_iNum2;

public:
    OverloadingOP(int n1 = 0, int n2 = 0) : m_iNum1(n1), m_iNum2(n2) { }
    void ShowMember()
    {
        cout << m_iNum1 << " / " << m_iNum2 << endl;
    }
    //전위
    OverloadingOP& operator++()
    {
        m_iNum1++;
        m_iNum2++;
        return *this;
    }
    //후위
    const OverloadingOP operator++(int)
    {
        const OverloadingOP returnValue(m_iNum1, m_iNum2);
        m_iNum1++;
        m_iNum2++;
        return returnValue;
    }
};

void main()
{
    OverloadingOP op(5, 10);
    (++op).ShowMember();
    (op++).ShowMember();
    
    //결과
    //6 / 11
    //6 / 11
}

 - C++가 ++(++obj)의 연산은 허용하되 (obj++)++를 허용하지 않는 연산특성을 가지고 있으므로 후위증감연산자의 경우 반환값을 const로 지정한다.

 

 

 

 

단항연산자의 오버로딩 시 교환법칙의 문제 해결하기

- 교환법칙 : A + B와 B + A의 결과가 서로 같음을 뜻한다. 즉, 연산자를 중심으로 한 피연산자의 위치는 연산 결과값이 같다는 법칙이다. (곱셈, 덧셈 연산이 있음)

- 자료형이 다른 두 피연산자를 대상으로 연산을 해야하는 경우, 형 변환으로 하여 자료형을 맞춘 다음에 연산이 이루어져야한다.

- 연산자 오버로딩을 이용하면 위와 같은 규칙에 예외를 둘 수 있다.

 

 

  • 교환법칙 예외두기 예제
#include <iostream>
using namespace std;

class OverloadingOP
{
private:
    int n;

public:
    OverloadingOP(int x) : n(x){ }
    void ShowMemver()
    {
        cout << n << endl;
    }
    //이 오버로딩은 OverloadingOP * int만 허용한다.
    OverloadingOP operator*(int i)
    {
        return OverloadingOP(n * i);
    }
    //이 오버로딩은 int * OverloadingOP만 허용한다.
    friend OverloadingOP operator*(int i, OverloadingOP& ref);
};

OverloadingOP operator*(int i, OverloadingOP& ref)
{
    return OverloadingOP(ref.n * i);
    //or
    return ref * i;
}

void main()
{
    OverloadingOP op1(5);
    OverloadingOP op2 = op1 * 2;
    OverloadingOP op3 = 2 * op1;
    
    op2.ShowMemver();
    op3.ShowMemver();
    
    //결과
    //10
    //10
}

 - 멤버함수의 형태로 오버로딩을 하게되면, 멤버함수가 정의된 클래스의 객체가 오버로딩 된 연산자의 왼편에 있어야 컴파일이 가능해진다.

 - 우측에 클래스의 객체를 둔 연산이 필요할 때에는 전역함수의 형태로 오버로딩을 해야 컴파일이 가능해진다.

 

 


 

C++ 연산자 오버로딩 (대입, 인덱스 연산자)

※ 연산자 오버로딩 기본 개념 참고 C++ 연산자 오버로딩 ※ firend의 개념을 모른다면 다음 포스팅을 먼저 숙지할 것. C++ friend friend 선언 - 접근지시자에 관계없이 모든 멤버의 접근을 허용하는 선

srdeveloper.tistory.com

 

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 연산자 오버로딩 (대입, 인덱스 연산자)  (0) 2021.12.26
C++ 네임스페이스(namespace), using  (0) 2021.12.12
C++ friend  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
반응형

 

 

friend 선언

- 접근지시자에 관계없이 모든 멤버의 접근을 허용하는 선언이다.

  ※ 그렇기 때문에, 클래스의 friend 선언은 객체 지향의 정보 은닉 개념에 위배된다.

- friend는 사용되는 클래스에서 선언을 해줘야한다.

- friend 선언은 클래스나 특정 멤버함수, 전역함수를 대상으로 사용이 가능하다.

 

  • 클래스 대상 friend
#include <iostream>
using namespace std;

class A
{
private:
    int n1;
    float f1;

public:
    A(int i, float f) : n1(i), f1(f) { }
    friend class B;
};

class B
{
private:
    int n1;
    float f1;

public:
    B() { }
    void Show()
    {
        cout << n1 << " / " << f1 << endl;
    }
    void Copy(A& a)
    {
        this->n1 = a.n1;
        this->f1 = a.f1;
    }
};

void main()
{
    A a(10, 10.5f);
    B b;
    b.Copy(a);
    b.Show();
    
    //결과 : 10 / 10.5
}

 

 

  • 멤버함수, 전역함수 대상 friend
#include <iostream>
using namespace std;
class Point;

class PointCalc
{
public:
    PointCalc() { }
    Point Add(const Point& p1, const Point& p2);
};

class Point
{
private:
    int x, y;

public:
    Point(int _x, int _y) : x(_x), y(_y) { }
    //1. PointCalc의 Add 함수에 대해 friend선언을 하고있다.
    friend Point PointCalc::Add(const Point& p1, const Point& p2);
    //2. 전역함수에 대해 friend선언을 하고있다.
    friend void ShowPoint(const Point& p1);
};

//1. 멤버함수 구현
Point PointCalc::Add(const Point& p1, const Point& p2)
{
    return Point(p1.x + p2.x, p1.y + p2.y);
}

//2. 전역함수 구현
void ShowPoint(const Point& p1)
{
    cout << "x : " << p1.x << " / y : " << p1.y << endl;
}

void main()
{
    Point p1(10, 20);
    Point p2(30, 40);
    PointCalc pc;
    
    ShowPoint(pc.Add(p1, p2));
    
    //결과 : x : 40 / y : 60
}

 

 

 

 

friend 선언시 주의점

- 위에서 언급했듯이 friend선언을 하게되면 모든 멤버에 접근이 가능하게 되므로 객체지향의 은닉성을 위배한다.

- 얽혀있는 클래스의 관계를 풀기위한 friend선언은 더 큰 문제를 야기하므로 사용하지 않는것이 좋다.

- friend 선언의 좋은 예는 연산자 오버로딩을 할 시, 교환법칙을 성립시키기 위한 사용법이 있다.

 

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 네임스페이스(namespace), using  (0) 2021.12.12
C++ 연산자 오버로딩  (0) 2021.12.05
C++ 다중상속  (0) 2021.11.22
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
C++ 순수 가상함수, 추상 클래스  (0) 2021.11.11
반응형

 

다중 상속 (Multiple Inheritance)

- 부모 클래스를 둘 이상 동시에 상속하는 것을 말한다.

- C++은 다중 상속을 지원하는 언어이다.

- 득보다 실이 많은 문법이라 알아만 두고 사용은 하지말것.

  ※ JAVA, C#에서만 보더라도 다중 상속을 지원하지 않는다.

 

 

 

다중 상속의 모호성

1. 다중 상속의 둘 이상의 부모 클래스의 동일한 이름의 멤버가 존재하는경우 문제가 발생한다.

#include <iostream>
using namespace std;

class Parent_1
{
public:
    void Func()
    {
        cout << "Parent_1 func." << endl;
    }
};

class Parent_2
{
public:
    void Func()
    {
        cout << "Parent_2 func." << endl;
    }
};

class Child : public Parent_1, public Parent_2
{
public:
    void ChildFunc()
    {
        //에러
        Func();
        Func();
    }
};

void main()
{
    Child child;
    child.ChildFunc();
}
  • 코드를 실행하면 Func()함수의 모호성을 컴파일에러로 뱉는다.

- 위와 같은 모호성에는 다음과 같이 호출 함수 앞에 클래스를 명시해줘야한다.

void ChildFunc()
{
    Parent_1::Func();
    Parent_2::Func();
}

 

 

2. 다중상속의 대상이 되는 부모 클래스(B, C)가 같은 부모 클래스(A)를 상속을 한 클래스일 때, 자식 클래스의 객체 내에 두 개의 A 클래스 멤버가 존재하는 문제가 발생한다.

#include <iostream>
using namespace std;

class A
{
public:
    A()
    {
        cout << "A Constructor" << endl;
    }
    void Func()
    {
        cout << "A func." << endl;
    }
};

class B : public A
{
public:
    B()
    {
        cout << "B Constructor" << endl;
    }
};

class C : public A
{
public:
    C()
    {
        cout << "C Constructor" << endl;
    }
};

class D : public B, public C
{
public:
    D()
    {
        cout << "D Constructor" << endl;
    }
    void ChildFunc()
    {
        B::Func();
        C::Func();
    }
};

void main()
{
    D d;
    d.ChildFunc();
    
    //결과
    //A Constructor
    //B Constructor
    //A Constructor
    //C Constructor
    //D Constructor
    //A Func.
    //A Func.
}
  • ChildFunc() 함수를 보면 간접적으로 상속한 A 클래스의 Func()함수를 호출하기 위해 함수 앞에 A를 명시해 모호성을 제거해줬다.
  • 결과를 확인해보면 D 클래스의 객체에서 A 클래스가 2개 존재하는것을 확인할 수 있다.
  • 위와 같은 중복 상속을 해결하기 위한 문법이 '가상 상속'이다.

- 위 코드에서 A 클래스는 중복 상속될 필요가 없으므로 B, C 클래스에서 'virtual' 키워드로 가상 상속한다.

class B : virtual public A { . . };
class C : virtual public A { . . };
class D : public B, public c { . . };

//결과
//A Constructor
//B Constructor
//C Constructor
//D Constructor

 

반응형

'Stack > C++' 카테고리의 다른 글

C++ 연산자 오버로딩  (0) 2021.12.05
C++ friend  (0) 2021.12.05
C++ 멤버함수, 가상함수 동작원리  (0) 2021.11.17
C++ 순수 가상함수, 추상 클래스  (0) 2021.11.11
C++ 업캐스팅, 오버라이딩, 가상함수  (0) 2021.11.08

+ Recent posts