C++ 13주차 수업 내용 및 수업 후 과제

2024. 11. 26. 11:52C++프로그래밍/수업 내용 및 수업 후 과제

반응형

출처 : 스마일한의 c++ 프로그래밍

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

가상함수(virtual function)의 필요성

- 클래스를 상속 받아서 쓰는데, 상속 받은 클래스의 함수 중 고치고 싶은 함수가 있을 때
- 기존 클래스의 모든 내용을 모두 현재 프로그램에 사용할 수 있는데, 특정 함수가 마음에 들지 않음
- 기존의 클래스를 고치느니 상속을 받고, 가상함수를 이용하여 기존의 함수를 고쳐서 사용
- 상속 받은 함수(부모)를 실행하지 않고 자신(자식)의 함수를 실행
- 가상함수의 구현을 오버라이딩(overriding)이라고 함

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

오버라이딩: 가상함수 구현 (시험에 잘 나옴)

- 가상함수는 부모 클래스 내에서 정의된 멤버함수를 자식 클래스에서 재정의하고자 할 때 사용
- 부모 클래스의 멤버함수와 같은 이름의 함수자식 클래스에서 재정의하여 사용
- 자식 클래스에서 재정의되는 가상함수는 부모 클래스와 함수의 리턴값, 매개변수 개수, 형이 완전히 같아야 함
- 가상함수의 구현을 overriding이라 함 

 

C++에서 오버로딩(Overloading)과 오버라이딩(Overriding)은 다형성을 구현하는 중요한 개념입니다. 

두 개념의 차이점을 예시와 함께 설명하겠습니다.

 

오버로딩 (Overloading)
오버로딩은 같은 이름의 함수를 여러 개 정의하되, 매개변수의 타입이나 개수를 다르게 하는 것입니다1. 이는 컴파일 시간 다형성의 예입니다
예시:

cpp
class Calculator {
public:
    int add(int a, int b) {
        return a + b;
    }
    
    double add(double a, double b) {
        return a + b;
    }
    
    int add(int a, int b, int c) {
        return a + b + c;
    }
};

int main() {
    Calculator calc;
    cout << calc.add(5, 3);       // 정수 덧셈
    cout << calc.add(3.14, 2.5);  // 실수 덧셈
    cout << calc.add(1, 2, 3);    // 세 정수 덧셈
    return 0;
}



이 예시에서 add 함수는 매개변수의 타입과 개수에 따라 다르게 동작합니다
오버라이딩 (Overriding)
오버라이딩은 상속 관계에서 부모 클래스의 메서드를 자식 클래스에서 재정의하는 것입니다.

이는 런타임 다형성의 예입니다
예시:

cpp
class Shape {
public:
    virtual double area() {
        return 0;
    }
};

class Rectangle : public Shape {
private:
    double width, height;
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    double area() override {
        return width * height;
    }
};

class Circle : public Shape {
private:
    double radius;
public:
    Circle(double r) : radius(r) {}
    
    double area() override {
        return 3.14 * radius * radius;
    }
};

int main() {
    Shape* shape1 = new Rectangle(5, 3);
    Shape* shape2 = new Circle(2);
    
    cout << shape1->area();  // 사각형 넓이 출력
    cout << shape2->area();  // 원 넓이 출력
    
    delete shape1;
    delete shape2;
    return 0;
}


이 예시에서 Shape 클래스의 area 메서드가 Rectangle과 Circle 클래스에서 각각 오버라이드되어 다르게 구현

 

주요 차이점
오버로딩은 같은 스코프 내에서 발생하지만, 오버라이딩은 상속 관계에서 발생
오버로딩은 컴파일 시간에 결정되고, 오버라이딩은 런타임에 결정
오버로딩된 함수들은 서로 다른 시그니처를 가져야 하지만, 오버라이딩된 함수는

부모 클래스의 함수와 동일한 시그니처를 가져야함

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

바인딩(binding)

- 변수와 함수에 대한 메모리 할당이나 함수 호출이 발생했을 때 실제로 처리해야 하는 명령어들이 결정되는 시점
- 정적(static) 바인딩, early
    컴파일시 변수의 위치와 함수가 실행할 명령이 결정되는 경우
    static 변수, overloading
- 동적(dynamic) 바인딩, late
   실제 실행할 때(run time) 결정되는 경우
   지역 변수, overriding
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

 

- static을 쓰면 원래 x값이 먼저 나오고 y값이 생겨야하는데 static을 사용해서 y값이 먼저 생김

- y는 컴파일할떄 결정이 이미 되어 있다.

 ㅡ> 실행파일에 10이라는 값으로 들어가 있음

= 주석 참고

 

- y는 프로그램이 끝날때 사라짐

 

x++;

y++;

을 지나고  지역변수안에 있기때문에  x는 값이 증가되어서 11이 되었다가 사라지지만(동적 바인딩)

y는 값이 증가 되어 11이 유지가 된다(정적 바인딩)

 

따라서 코드를 다시 쓰면

실습 9-1: 동적바인딩(지역 변수)과 정적바인딩(static변수)

#include <iostream>
using std::cout;
void sub();
int main()
{
  cout << "start\n";
	sub();
	sub();
	sub();
  return 0;
}
void sub()
{
  int x=10;    
 //동적 바인딩, run-time시
	static int y=10;
 //정적 바인딩, y의 초기값은 컴파일시 10으로
 //정해지며 실행시에 이 선언문은 실행하지 않음
	cout<<x<<y<<'\n';
	x++;
	y++;
}


출력결과

start
1010
1011
1012

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

 

 

c언어에서는 auto가 기억 클래스였지만, c++에서는 동적으로 타입을 정할 수 있게 변경 된다.

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

정적(static) 멤버변수

class Point{

  int x;

  int y;

 static int count;

public:

  Point(int i, int j){x=i;y=j;}

 int getX() {return x;}

 int getY() {return y;}

};

클래스의 멤버 변수 선언문에 static이라는 키워드 붙임
C언어의 static변수와 의미가 다름
객체들이 정보를 공유할 목적으로 사용하는 변수(시험에 잘 나옴)
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

클래스로부터 객체를 만들때 

멤버변수는 객체마다 따로 생성되고 멤버함수는 모든 객체가 공유함

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

정적 멤버변수의 특징 ( static )

- 모든 객체가 공유하는 멤버변수

- 한 클래스로부터 객체가 여러 개 만들어지더라도 이 멤버변수는 하나만 생성됨

- 여러 객체들에서 공유해야 하는 정보는 정적 멤버변수로 선언

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

실습 9-2: 정적(static) 멤버변수

마우스가 눌린 좌표를 저장하는 Point클래스 구현
마우스를 누를 때 마다 좌표 값을 저장하기 위해 객체 생성
눌린 좌표 값 저장하기 위해 멤버변수 x, y선언
눌린 횟수를 저장하는 정적 멤버변수 count 선언
클래스 선언이 기억 장소를 할당하는 것이 아니므로 정적 멤버변수는 클래스 밖에서 초기화를 함
 (정의함, 초기화 값이 없으면 0으로 초기화 됨)
실행시(동적 바인딩) p1, p2, p3 객체가 만들어지면서 각각의 xy가 스택에 할당
컴파일시(static 바인딩) count의 값이 하나 만들어짐 

에러의 원인


- 정적 멤버 변수의 정의 누락: C++에서 정적 멤버 변수는 클래스 내부에서 선언되고

  클래스 외부에서 정의되어야 합니다. 현재 코드에서는 count가 선언만 되어 있고 정의되지 않았습니다.
- 링커 에러: 이로 인해 링커 단계에서 count 변수의 정의를 찾을 수 없어 에러가 발생합니다.

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ


가상함수(virtual function)

- 오버라이딩을 구현하는 방법
- 동적(실행시) 바인딩, late binding
- 가상함수를 정의하기 위해서는 기본 클래스의 멤버함수 앞에 'virtual'이라는 키워드를 씀
- 가상함수는 하나의 이름(인터페이스)으로 여러 개의 수단과 방법을 제공하는
   객체지향 프로그래밍의 다형성을 구현하는 한 방법 
- 부모 클래스에서 상속 받았는데 자식 클래스에서 멤버함수를 재정의하는 이유는
  부모 클래스의 멤버함수를 버리고 자식 클래스의 멤버함수를 사용하려는 것  > 오버라이딩
- 기존 클래스의 모든 내용을 모두 현재 프로그램에 사용할 수 있는데 특정 함수가 걸맞지 않을 경우,
   기존의 클래스를 고치느니 상속을 받고 가상함수를 이용하여 마음에 들지 않는 함수만 자식이 고쳐서 사용함
 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

C++에서 새로 도입된 cast 연산자

- static_cast
  컴파일시 타입 변환 수행. 안전성 검사를 하지 않아 잘못된 타입 변환을 시도할 경우 런타임 오류 발생
- dynamic_cast 
  런타임에 타입 변환 수행. if문으로 안전성 검사를 수행하여 안전하게 변환 가능
 
#include <iostream>

using namespace std;

int main()

{

    int x = 10, y = 4;

 

    cout << x / y << endl;

    cout << (double)x / y << endl;

    cout << static_cast<double>(x) / y << endl;

    return 0;

}

(double)x / y 와static_cast<double>(x) / y는 같은 소스라고 생각하면 된다.

출력결과가 시험에 가끔 나옴 

2
2.5
2.5

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

순수가상함수(pure virtual function) 제일 중요한 내용

- virtual double area()=0; 
     앞에 ‘virtual’, 뒤에 ‘=0’
- 추상 클래스(abstract class)정의하는 데 사용
- 부모 클래스에서 자식 클래스에서 반드시 구현해야 하는 메서드를 정의
- 순수 가상 함수가 있는 클래스는 인스턴스를 만들 수 없음
    자식 클래스에서 순수 가상함수를 오버라이드하여 구체적인 동작을 정의한 후 자식 인스턴스를 만들어 사용
- 클래스가 제공해야 하는 인터페이스를 정의
    자식 클래스가 어떤 메서드를 구현해야 하는지 강제하고 명시
- 순수 가상 함수를 갖는 클래스는 추상 클래스(abstract class)
    완전한 클래스가 아니므로 인스턴스를 생성할 수 없음
// 실습 9 - 5: 순수가상함수(pure virtual function)

#include <iostream>
using std::cout;
using std::endl;
class Shape {//추상클래스(abstract class)
protected:
	double x, y;
public:
	virtual double area() { return 0; } //주석처리
	// 일반 가상 함수
	// virtual double area()=0;     //주석 해제
};   // 순수 가상 함수
class Rectangle : public Shape {
private:
	double height, width;
public:
	Rectangle(double h, double w)
	{
		height = h; width = w;
	}
	double area() { return(width * height); }
};
class Triangle : public Shape {
private:
	double height, width;
public:
	Triangle(double h, double w)
	{
		height = h; width = w;
	}
	double area() { return(width * height / 2.0); }
};
int main()
{
	Shape ss; // virtual double area()=0; 
	// 윗줄 주석과 같이 순수 가상함수가 있으면 추상클래스이고 
	// 추상클래스는 객체(인스턴스)를 만들 수 없음
	Shape* p;  //포인터 객체는 가능
	Rectangle nemo(10.0, 20.0);
	Triangle semo(10.0, 20.0);

	p = &nemo; //자식의 주소를 부모 포인터에 대입
	cout << "네모면적:" << p->area() << endl; //Rectangle::area()
	p = &semo;
	cout << "세모면적:" << p->area() << endl; //Triangle::area()
	return 0;
}


출력결과:
네모면적:200
세모면적:100

↑ 순수 가상함수가 하나라도 있는 함수를 추상 클래스라고 하는데 일반 가상 함수를 주석처리하고, 

   순수 가상 함수를 주석 해제시키면 오류가 나는데 f4키를 눌러 어디서 뭐가 잘못된지 캡쳐.

 

추상클래스는 자체적으로 객체(인스턴스 만들 수 없다)를 만들 수 없다.

=> 인스턴스화 할 수 없다.

따라서 코드를 수정하면

// 실습 9 - 5: 순수가상함수(pure virtual function)

#include <iostream>
using std::cout;
using std::endl;
class Shape {//추상클래스(abstract class)
protected:
	double x, y;
public:
//	virtual double area() { return 0; } //주석처리
	// 일반 가상 함수
	 virtual double area()=0;     //주석 해제
};   // 순수 가상 함수
class Rectangle : public Shape {
private:
	double height, width;
public:
	Rectangle(double h, double w)
	{
		height = h; width = w;
	}
	double area() { return(width * height); }
};
class Triangle : public Shape {
private:
	double height, width;
public:
	Triangle(double h, double w)
	{
		height = h; width = w;
	}
	double area() { return(width * height / 2.0); }
};
int main()
{
	//Shape ss; // virtual double area()=0; 
	// 윗줄 주석과 같이 순수 가상함수가 있으면 추상클래스이고 
	// 추상클래스는 객체(인스턴스)를 만들 수 없음
	Shape* p;  //포인터 객체는 가능 - 부모
	Rectangle nemo(10.0, 20.0); - 자식
	Triangle semo(10.0, 20.0); - 자식

	p = &nemo; //자식의 주소를 부모 포인터에 대입
	cout << "네모면적:" << p->area() << endl; //Rectangle::area()
	p = &semo;
	cout << "세모면적:" << p->area() << endl; //Triangle::area()
	return 0;
}


출력결과
네모면적:200
세모면적:100

부모클래스에서 도형의 면적 구하는 공식은 다 다름으로 자식에게 넘겨서 계산하도록 하는것

++

순수가상함수는 파생클래스에서 반드시 재정의 해야 함

순수가상함수를 갖는 클래스를 추상클래스라 함

추상클래스는 객체를 생성할  수 없음

부모 클래스의 포인터는 이 클래스로부터 파생된 어떤 자식 객체도 가리킬 수 있다.

가상 함수를 호출할 때 기본 클래스의 포인터 p가 가리키는 객체의 클래스에 따라

자동적으로 해당 클래스에 소속된 멤버 함수를 호출한다.

자식의 주소를 부모가 받을 수 있다.

 

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

실습 9-6: virtual과 override가 있을 때와 없을 때의 차이 
시험에 꼭 나오는 문제


#include <iostream>
using std::cout;

class Dot
{
public:
	void draw() { cout << "Dot\n"; }
	void print() {
		cout << "Dot 클래스\n";
		draw();
	}
};
class Line :public Dot
{
public:
	void draw() { cout << "Line\n"; }
};
int main()
{
	Line line;
	line.print();
	return 0;
}



출력결과
Dot 클래스
Dot

위의 코드에 부모클래스 앞에 virtual 함수를 구현(오버라이딩)
Line을 출력
추가로 가시성을 위해 부모는 virtual을 사용하지만 자식은 override 추가

#include <iostream>
using std::cout;

class Dot
{
public:
	virtual void draw() { cout << "Dot\n"; }
	void print() {
		cout << "Dot 클래스\n";
		draw();
	}
};
class Line :public Dot
{
public:
	void draw() { cout << "Line\n"; }
};
int main()
{
	Line line;
	line.print();
	return 0;
}

출력결과
Dot 클래스
Line

아래 코드 정리한 사진

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

override 키워드는 객체 지향 프로그래밍에서 메서드 오버라이딩을 명시적으로 나타내는 데 사용됩니다. 각 언어별로 override 키워드의 사용법을 예시와 함께 설명하겠습니다.

## Python

Python에서는 명시적인 override 키워드가 없지만, 메서드 오버라이딩은 자동으로 수행됩니다.

```python
class Animal:
    def make_sound(self):
        print("Some generic animal sound")

class Dog(Animal):
    def make_sound(self):
        print("Woof!")

dog = Dog()
dog.make_sound()  # 출력: Woof!
```

## Java

Java에서는 @Override 어노테이션을 사용합니다.

```java
class Animal {
    public void makeSound() {
        System.out.println("Some generic animal sound");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        System.out.println("Woof!");
    }
}
```

## JavaScript

JavaScript에서도 명시적인 override 키워드는 없지만, 메서드를 재정의하여 오버라이딩할 수 있습니다.

```javascript
class Animal {
    makeSound() {
        console.log("Some generic animal sound");
    }
}

class Dog extends Animal {
    makeSound() {
        console.log("Woof!");
    }
}

const dog = new Dog();
dog.makeSound();  // 출력: Woof!
```

## PHP

PHP에서도 명시적인 override 키워드는 없지만, 부모 클래스의 메서드를 같은 이름으로 재정의하여 오버라이딩합니다.

```php
class Animal {
    public function makeSound() {
        echo "Some generic animal sound\n";
    }
}

class Dog extends Animal {
    public function makeSound() {
        echo "Woof!\n";
    }
}

$dog = new Dog();
$dog->makeSound();  // 출력: Woof!
```

## C#

C#에서는 override 키워드를 명시적으로 사용합니다[4].

```csharp
class Animal {
    public virtual void MakeSound() {
        Console.WriteLine("Some generic animal sound");
    }
}

class Dog : Animal {
    public override void MakeSound() {
        Console.WriteLine("Woof!");
    }
}
```

## C++

C++에서는 virtual 키워드로 기본 클래스의 메서드를 선언하고, override 키워드로 파생 클래스에서 오버라이딩을 명시합니다[1].

```cpp
class Animal {
public:
    virtual void makeSound() {
        cout << "Some generic animal sound" << endl;
    }
};

class Dog : public Animal {
public:
    void makeSound() override {
        cout << "Woof!" << endl;
    }
};
```

이러한 방식으로 각 언어에서 메서드 오버라이딩을 구현할 수 있습니다. 

override 키워드(또는 유사한 메커니즘)를 사용함으로써 코드의 가독성을 높이고 의도를 명확히 할 수 있습니다.

 

스테틱 맴버 변수 특징
클래스의 멤버 변수 선언문에 static(정적)이라는 키워드 붙임
모든 객체가 공유하는 멤버변수


가상함수의 필요성
상속 받은 함수(부모)를 실행하지 않고 자신(자식)의 함수를 실행



바인딩

정적바인딩 스태틱, 얼리
컴파일시 변수의 위치와 함수가 실행할 명령이 결정되는 경우
스테틱변수, 오버로딩

동적 바인딩,레이트
실제 실행할떄 결정되는 경우
지역변수, 오버라이딩

가상함수(virtual function)
기본 클래스의 멤버함수 앞에 'virtual'이라는 키워드를 씀
부모 클래스의 멤버함수를 버리고 자식 클래스의 멤버함수를 사용하려는 것
가상함수를 이용하여 마음에 들지 않는 함수만 자식이 고쳐서 사용함

순수가상함수(pure virtual function)
 virtual double area()=0; 
 앞에 ‘virtual’, 뒤에 ‘=0’
추상 클래스(abstract class)를 정의하는 데 사용
순수 가상 함수를 갖는 클래스는 추상 클래스(abstract class)
완전한 클래스가 아니므로 인스턴스를 생성할 수 없음


클래스 내부에 static 키워드를 사용하여 정적 멤버 변수를 선언