본문 바로가기
1학년/C++

[C++ 프로그래밍] 13주차 overriding : 가상함수(virtual function)static

by heeaeeeee 2023. 11. 30.

1. protected과 private 접근 속성의 공통점과 차이점

공통점
private과 protected는 둘 다 클래스의 외부에서 직접적인 접근을 제한합니다. 즉, 클래스 외부에서 이 멤버에 접근하려고 하면 컴파일 에러가 발생합니다.

차이점
private : private으로 선언된 멤버는 해당 클래스 내에서만 접근이 가능합니다. 즉, 해당 클래스의 멤버 함수에서만 이 멤버를 사용할 수 있습니다. 또한, private 멤버는 상속받은 자식 클래스에서도 접근할 수 없습니다.
protected : protected로 선언된 멤버는 해당 클래스 내부와, 해당 클래스를 상속받은 자식 클래스 내에서 접근이 가능합니다. 즉, 자식 클래스의 멤버 함수에서도 protected 멤버를 사용할 수 있습니다. 하지만, 클래스 외부에서는 여전히 접근할 수 없습니다.

2. 아래 코드에서 :A(i)는 쓰는 이유는?

#include <iostream>
using std::cout;
using std::endl;
class A {
	int a;
public:
	A(int i) {
		cout << "A의 생성자\n";
		a = i;
	}
	~A() { cout << "A의 소멸자\n"; }
	void showA() { cout << a << '\n'; }
};
class B :public A {
	int b;
public:
	B(int i, int j) :A(i) {// i는 기본클래스 생성자의 매개변수로 전달
		cout << "B의 생성자\n";
		b = j;
	}
	~B() { cout << "B의 소멸자\n"; }
	void showB() { cout << b << endl; }
};
int main()
{
	B bb(10, 20);
	bb.showA();
	bb.showB();
	return 0;
}

:A(i)는 C++에서 멤버 이니셜라이저 리스트(Member Initializer List)라는 기능을 사용한 것입니다. 멤버 이니셜라이저 리스트는 클래스의 생성자에서 멤버 변수를 초기화하는 방법 중 하나입니다.
클래스 B는 클래스 A를 상속받고 있습니다. 따라서 B의 객체를 생성할 때는 A의 생성자도 함께 호출되어야 합니다. :A(i)는 B의 생성자에서 A의 생성자를 호출하는 방법을 나타냅니다. 여기서 i는 A의 생성자에 전달될 매개변수입니다.
B(int i, int j):A(i)에서 :A(i) 부분이 없다면, A의 기본 생성자가 호출되게 됩니다. 하지만 이 경우 A 클래스에는 기본 생성자가 없으므로 컴파일 에러가 발생합니다. 따라서 B의 생성자에서 A(i)를 통해 A의 생성자를 명시적으로 호출해주는 것입니다.
멤버 이니셜라이저 리스트를 사용하면 생성자 내부에서 멤버 변수에 값을 할당하는 것보다 효율적인 경우가 많습니다. 특히 상속 관계에서는 부모 클래스의 생성자를 명시적으로 호출할 수 있어서 유용합니다.

3. 다중 상속을 지원하는 프로그램 언어

1. C++
2.Python
3. Objective-C
4. Perl
5. PHP
6. Tcl
7. Eiffel
8. Clipper
9. Common Lisp (CLOS)
10. Dylan
11. Curl

4. 파이썬에서 다중 상속을 지원하는 소스

# 클래스 A 정의
class A:
    def __init__(self):
        super().__init__()
        print("Class A's constructor")

    def methodA(self):
        print("Method A")

# 클래스 B 정의
class B:
    def __init__(self):
        super().__init__()
        print("Class B's constructor")

    def methodB(self):
        print("Method B")

# 클래스 C 정의. 이 클래스는 A와 B를 다중 상속 받음
class C(A, B):
    def __init__(self):
        super().__init__() # 부모 클래스들의 생성자 호출
        print("Class C's constructor")

# 메인 실행
c = C()
c.methodA()
c.methodB()

5. 2개의 기본 클래스 상속 1

#include <iostream>
using std::cout;
using std::endl;
class A1 // 아버지
{
	int a;
public:
	A1(int i) { a = i; }
	int getA() { return a; }
};
class A2 // 어머니
{
	int b;
public:
	A2(int i) { b = i; }
	int getB() { return b; }
};
class B :public A1, public A2
{
	// 기본 클래스 A1과 A2로부터
	// 상속 받은 파생 클래스
	int c;
public:
	B(int i, int j, int k) :A1(i), A2(j) { c = k; }
	// i는 기본클래스 A1의 생성자로,
	// j는 기본클래스 A2의 생성자로
	// 각각 매개변수 전달
	void show() {
		cout << getA() << ' ' << getB() << ' ' << c << endl;
	}
};
int main()
{
	B bb(1, 2, 3);
	bb.show();
	return 0;
}

6. 2개의 기본 클래스 상속 2

#include <iostream>
using std::cout;
using std::endl;
class B1 { //아버지
	double d;
public:
	B1(double dd) { d = dd; }
	double getD() { return d; }
};
class B2 { //어머니
	int i;
public:
	B2(int ii) { i = ii; }
	int getI() { return i; }
};
class D :public B1, public B2 {
	char c;
public:
	D(double dd, int ii, char cc) :B1(dd), B2(ii)
	{
		c = cc;
	}
	void print() {
		cout << "Double : " << getD() << endl;
		cout << "Int : " << getI() << endl;
		cout << "Char : " << c << endl;
	}
};
int main()
{
	D d(1.23, 10, 'H');
	cout << d.getD() << ',' << d.getI() << endl;
	d.print();
	return 0;
}

7. 여러 개의 기본 클래스를 상속 받을 때, 생성자와 소멸자의실행 순서

#include <iostream>
using std::cout;
class A1 // 기본 클래스 1
{
	int a;
public:
	A1() { cout << "A1의 생성자.\n"; }
	~A1() { cout << "A1의 소멸자.\n"; }
};
class A2 // 기본 클래스 2
{
	int b;
public:
	A2() { cout << "A2의 생성자.\n"; }
	~A2() { cout << "A2의 소멸자.\n"; }
};
class B : public A1, public A2
	// 기본 클래스 1과 2로부터
	// 상속 받은 파생 클래스
{
	int c;
public:
	B() { cout << "B의 생성자.\n"; }
	~B() { cout << "B의 소멸자.\n"; }
};
int main()
{
	B bb;
	return 0;
}

생성자는 기본 클래스가 지정된 순서대로 왼쪽에서 오른쪽으로 실행되고, 소멸자는 역순으로 실행된다.

8. 이름과 전화번호를 관리(기본클래스 Name, 파생클래스 Phone)

#include <iostream>
using std::cout;
#include <string>
class Name { //기본 클래스는 이름만 처리
	std::string name;
public:
	void get_name(std::string s) { name = s; }
	void print_name() { cout << name << "의 전화번호는"; }
};
class Phone : public Name { //파생클래스는 전화번호처리
	std::string phone;
public:
	void get_phone(std::string s) { phone = s; }
	void print_phone() {
		print_name();
		cout << phone;
	}
};
int main()
{
	Phone h;
	h.get_name("Smile Han");
	h.get_phone("1234-5678");
	h.print_phone();
	return 0;
}

class diagram

9. 학생(Student) 클래스와 교수(Teacher) 클래스가 사람(Man) 클래스를 상속받는 코드 만들기

//1
int main()
{
	//Student ksd("김학생", 20, "B반", "202312345");
	//Teacher ltc("이교수", 40, "컴공", "C++프로그래밍");
	//kks.s_show();
	//hsh.t_show();
	return 0;
}

//2 클래스에는 스트링형 이름과 인트형 나이가 있다.
#include <iostream>
class Man {
	std::string name;
	int age;
}; //} 뒤에 꼭 ; 하기!

int main()
{
	//Student ksd("김학생", 20, "B반", "202312345");
	//Teacher ltc("이교수", 40, "컴공", "C++프로그래밍");
	//kks.s_show();
	//hsh.t_show();
	return 0;
}

//3 부모 클래스 작성 완료
#include <iostream>
using std::string;
using std::cout;
using std::endl;
class Man {
protected:
//private: //별다른 말이 없으면 private로(생략가능)
	string name;
	int age;
public:
	Man(string name, int age) { //생성자, void 안써도 됨
		this->name = name;
		this->age = age;
	}
	void m_show() { //리턴형이 없는 경우 void 써야함
		cout << "이름: " << name << endl;
		cout << "나이: " << age << endl;
	}
}; //} 뒤에 꼭 ; 하기!

int main()
{
	//Student ksd("김학생", 20, "B반", "202312345");
	//Teacher ltc("이교수", 40, "컴공", "C++프로그래밍");
	//kks.s_show();
	//hsh.t_show();
	return 0;
}

//4 상속 완료
#include <iostream>
using std::string;
using std::cout;
using std::endl;
class Man {
protected:
	string name;
	int age;
public:
	Man(string name, int age) { //생성자, void 안써도 됨
		this->name = name;
		this->age = age;
	}
	void m_show() {
		cout << "이름: " << name << endl;
		cout << "나이: " << age << endl;
	}
}; //} 뒤에 꼭 ; 하기!
class Student : public Man {
	string ban;
	string hakdan;
public:
	Student(string name, int age, string ban, string hakdan) : Man(name, age) {
		this->ban = ban;
		this->hakdan = hakdan;
	}
	void s_show() {
		m_show();
		cout << "반: " << ban << endl;
		cout << "학번: " << hakdan << endl;
	}
};
class Teacher : public Man {
	string major;
	string subject;
public:
	Teacher(string name, int age, string major, string subject) : Man(name, age) {
		this->major = major;
		this->subject = subject;
	}
	void t_show() {
		m_show();
		cout << "전공: " << major << endl;
		cout << "담당과목: " << subject << endl;
	}
};

int main()
{
	Student kks("김학생", 20, "B반", "202312345");
	Teacher hsh("이교수", 40, "컴공", "C++프로그래밍");
	kks.s_show();
	hsh.t_show();
	return 0;
}

class diagram  # : protected + : public - : private 상속 : 화살표는 빈 화살표!!

10. self 와 this 키워드를  사용하는 프로그램 언어

self 키워드를 사용하는 언어
 - Python: 메소드의 첫 번째 매개변수로 self를 사용하여 현재 객체를 참조합니다.

this 키워드를 사용하는 언어
- Java: this를 통해 현재 객체를 참조하고, 멤버 변수와 지역 변수를 구분하는 데 사용합니다.
- C++: this 포인터를 통해 현재 객체를 참조합니다.
- JavaScript: this를 통해 현재 객체를 참조합니다. 하지만 this의 값은 실행 컨텍스트에 따라 달라집니다.
- PHP: this를 통해 현재 객체를 참조합니다.

11. 오버로딩과 오버라이딩의 개념을 c++로 설명

오버로딩(Overloading) : 같은 이름의 함수를 여러 개 정의하는 것을 의미합니다. 이 때 함수들은 매개변수의 타입이나 개수가 다르게 정의됩니다. 이를 통해 같은 이름의 함수를 여러 가지 방법으로 사용할 수 있게 됩니다.

#include <iostream>
using namespace std;

void print(int i) {
    cout << "Here is int " << i << endl;
}
void print(double f) {
    cout << "Here is float " << f << endl;
}

int main() {
    print(10);
    print(10.10);
    return 0;
}

오버라이딩(Overriding) :  상속 관계에 있는 클래스에서 같은 이름과 형태를 가진 멤버 함수를 재정의하는 것을 의미합니다. 이를 통해 기본 클래스의 멤버 함수를 파생 클래스에서 원하는 방식으로 변경할 수 있습니다.

#include <iostream>
using namespace std;

class Base {
public:
    void print() {
        cout << "Base class print function" << endl;
    }
};

class Derived : public Base {
public:
    void print() {
        cout << "Derived class print function" << endl;
    }
};

int main() {
    Base b;
    Derived d;
    b.print();
    d.print();
    return 0;
}

12. virtual 있을 때와 없을 때의 차이

// virtual 없는 경우
#include <iostream>
using std::cout;
class Dot 
{
public:
	void draw() { cout << "Dot::draw() \n";}
	void print() {
		cout << "Dot 클래스 \n";
		draw();
	}
};
class Line :public Dot 
{
public:
	void draw() { cout <<"Line::draw() \n";}
};
int main() {
	Line line;
	line.print();
	return 0;
}

// virtual 있는 경우
#include <iostream>
using std::cout;
class Dot 
{
public:
	virtual void draw() { cout << "Dot::draw() \n";} //무시하고 싶은 함수 앞에
	void print() {
		cout << "Dot 클래스 \n";
		draw();
	}
};
class Line :public Dot 
{
public:
	void draw() override { //써도되고 안써도 됨 매개변수 괄호 닫고 다음에 쓰면 됨(c++11 ~)
		cout <<"Line::draw() \n";
	}
};
int main() {
	Line line;
	line.print();
	return 0;
}

13. virtual과 override 키워드의 사용법 설명

1. C++
- virtual: 기본 클래스에서 멤버 함수를 가상 함수로 선언하기 위해 사용합니다. 가상 함수는 파생 클래스에서 오버라이딩할 수 있습니다.
- override: C++11부터 사용 가능한 키워드로, 파생 클래스에서 멤버 함수를 오버라이딩할 때 사용합니다. 이 키워드는 컴파일러에게 해당 함수가 기본 클래스의 가상 함수를 오버라이딩하도록 의도된 것임을 알려줍니다.

class Base {
public:
    virtual void print() {
        cout << "Base class print function" << endl;
    }
};

class Derived : public Base {
public:
    void print() override {
        cout << "Derived class print function" << endl;
    }
};

2. C#
- virtual: 기본 클래스에서 멤버 함수를 가상 함수로 선언하기 위해 사용합니다. 가상 함수는 파생 클래스에서 오버라이딩할 수 있습니다.
- override: 파생 클래스에서 멤버 함수를 오버라이딩할 때 사용합니다.

public class Base {
    public virtual void Print() {
        Console.WriteLine("Base class Print function");
    }
}

public class Derived : Base {
    public override void Print() {
        Console.WriteLine("Derived class Print function");
    }
}

3. Java
- @Override: 파생 클래스에서 멤버 함수를 오버라이딩할 때 사용합니다. Java에서는 virtual 키워드 대신 모든 멤버 함수가 기본적으로 가상 함수로 간주됩니다.

public class Base {
    public void print() {
        System.out.println("Base class print function");
    }
}

public class Derived extends Base {
    @Override
    public void print() {
        System.out.println("Derived class print function");
    }
}

4. Python
Python에서는 virtual과 override 키워드가 없습니다. 대신 모든 멤버 함수는 기본적으로 오버라이딩할 수 있습니다.

class Base:
    def print(self):
        print("Base class print function")

class Derived(Base):
    def print(self):
        print("Derived class print function")

 

 

C++ 강의 자료 참고했습니다.