2024. 11. 26. 11:52ㆍC++프로그래밍/수업 내용 및 수업 후 과제
출처 : 스마일한의 c++ 프로그래밍

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
가상함수(virtual function)의 필요성
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
오버라이딩: 가상함수 구현 (시험에 잘 나옴)


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을 쓰면 원래 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 )
- 모든 객체가 공유하는 멤버변수
- 한 클래스로부터 객체가 여러 개 만들어지더라도 이 멤버변수는 하나만 생성됨
- 여러 객체들에서 공유해야 하는 정보는 정적 멤버변수로 선언
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
실습 9-2: 정적(static) 멤버변수


에러의 원인
- 정적 멤버 변수의 정의 누락: C++에서 정적 멤버 변수는 클래스 내부에서 선언되고
클래스 외부에서 정의되어야 합니다. 현재 코드에서는 count가 선언만 되어 있고 정의되지 않았습니다.
- 링커 에러: 이로 인해 링커 단계에서 count 변수의 정의를 찾을 수 없어 에러가 발생합니다.
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
가상함수(virtual function)
- 오버라이딩을 구현하는 방법ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
C++에서 새로 도입된 cast 연산자
#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) 제일 중요한 내용
// 실습 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 키워드를 사용하여 정적 멤버 변수를 선언
'C++프로그래밍 > 수업 내용 및 수업 후 과제' 카테고리의 다른 글
| C++ 14주차 수업 내용 및 수업 후 과제 (1) | 2024.12.03 |
|---|---|
| C++ 12주차 수업 내용 및 수업 후 과제 (1) | 2024.11.19 |
| C++ 수업내용 및 수업 후 과제 (0) | 2024.11.12 |
| c++ 수업 내용 및 수업 후 과제 (3) | 2024.11.05 |
| c++ 9주차 수업 내용 및 수업 후 과제 (0) | 2024.10.29 |