가상 함수
가상함수는 상속받을 클래스에서 재정의할 것으로 기대하고 부모 클래스에 정의해 놓은 함수
실행 중(런 타임)에 어떤 함수를 호출할 것인지 결정함
- 동적 바인딩(지연 바인딩)
단, 포인터나 참조를 통하여 호출될 때만 동적 바인딩을 함
가상함수와 추상메소드는 다르다.
- 가상함수가 있다고 해서 추상클래스 인 것 X
class Person {
protected:
string name;
public:
virtual void info() { //가상 함수
cout << "사람입니다~\n";
}
virtual void test() {
cout << "사람의 test 메소드! \n";
}
virtual ~Person() { //소멸자도 가상함수의 개념을 따라 Student에서 재정의 해줬다고 생각하면 이해하기 편하당
cout << "사람의 소멸자 입니다.\n";
}
};
class Student : public Person {
private:
string stu_id;
public:
void info() {
cout << "학생입니다~\n";
}
void test() {
cout << "학생의 test 메소드! \n";
}
~Student() {
cout << "학생의 소멸자 입니다.\n";
}
};
int main() {
//Person s = Student(); //자식클래스가 부모클래스의 자료형으로 선언 가능
//s.test(); //정적 바인드
Person s = Student();
s.info(); // 부모클래스 info실행
//포인터로 객체 선언시 동적 바인딩 사용 가능해짐
// 스택영역
Person* s1 = new Student();//동적 메모리 선언
s1->info(); // 동적 바인딩, 자식클래스(Student) info실행
s1->test(); // Person에 test메소드가 없었다면, Person에 없는 멤버에는 접근할 수 없음
//또 다른 호출법
(*s1).info(); // ss = 주소값, *ss = ss에 담긴 값
delete s1;
//또다른 방법(동적메모리로 만든게 아님)
// 힙 영역
Student stu;//객체 생성
Person* s2 = &stu; //주소에 값을 담아주기만 함
s2->info(); // 동적 바인딩
//delete s2 사용안함. 사용시 실행중 오류 발생(RunTime error)
//Person pers[2] = { Student(), Student() };
return 0;
}
------------------------------------------------------------------
스택 영역: 빌드할 때 생성되는 변수에 대한 메모리 저장
관리 필요X
힙 영역: 실행 중(빌드 후 콘솔창이 뜨고 난 뒤)에 할당되는 메모리에 대한 저장
관리 필요O
//포인터 배열
#include <iostream>
using namespace std;
class Person {
protected:
string name;
public:
virtual void info() { //가상 함수
cout << "사람입니다~\n";
}
virtual ~Person() { //소멸자도 가상함수의 개념을 따라 Student에서 재정의 해줬다고 생각하면 이해하기 편하당
cout << "사람의 소멸자 입니다.\n";
}
};
class Student : public Person {
private:
string stu_id;
public:
void info() {
cout << "학생입니다~\n";
}
~Student() {
cout << "학생의 소멸자 입니다.\n";
}
};
class Instructor : public Person {
private:
string stu_id;
public:
void info() {
cout << "강사입니다~\n";
}
~Instructor() {
cout << "강사의 소멸자 입니다.\n";
}
};
int main() {
//자식클래스를 부모클래스에 담은걸 "업캐스팅"이라고 한다.
//Person s = Student();
//포인터 배열
Person* per_arr[3] = { new Student(), new Instructor(), new Student() };
for (int i = 0; i < 3; i++) {
per_arr[i]->info();
}
//배열에 대한 동적메모리 해제
for (int j = 0; j < 3; j++) {
delete per_arr[j];
}
//배열 자체를 포인터로 담고있는 경우 해제방법
//int* po = new int[3];
//delete[] po;
return 0;
}
실습
(1) Candy 클래스와 Chocolate 클래스를 만들어주세요.
조건 1. Candy 클래스는 맛, 가격, 상품이름, 제조회사를 의미하는 변수를 가지고 있어야 합니다.
조건 2. Chocolate 클래스는 모양, 가격, 상품이름, 제조회사를 의미하는 변수를 가지고있어야 합니다.
조건 3. Candy클래스와 Chocolate 클래스는 모두 같은 상위 클래스(Snack)로부터 상속을 받아야 합니다.
(2) 메인 함수에 snackBasket이라는 이름의 배열을 만들어주세요,
위에서 만든 Candy 클래스와 Chocolate 클래스로 각각 두 개의 객체 만들기
만든 총 4개의 객체를 snackBasket이라는 배열에 넣어주세요.
4개의 인스턴스를 모두 넣었다면 메인 함수에서 반복문을 통해
snackBasket 안에 들어있는 간식들의 맛 or 모양과 상품 이름을 출력해주세요.
ex) 딸기맛 사탕입니다, 하트 모양 초콜릿 입니다
class Snack {
protected:
string money;
string name;
string factory_name;
public:
virtual void snack_status() {
}
};
class Candy :public Snack {
string taste;
public:
Candy(string money, string name, string factory_name, string taste) {
this->money = money;
this->name = name;
this->factory_name = factory_name;
this->taste = taste;
}
void snack_status() {
cout << "종류: 사탕" << endl;
cout << "가격: " << money << endl;
cout << "과자명: " << name << endl;
cout << "맛: " << taste << endl;
cout << "제조사: " << factory_name << endl;
}
};
class Chocolate :public Snack {
string design;
public:
Chocolate(string money, string name, string factory_name, string design) {
this->money = money;
this->name = name;
this->factory_name = factory_name;
this->design = design;
}
void snack_status() {
cout << "종류: 초콜릿" << endl;
cout << "가격: " << money << endl;
cout << "과자명: " << name << endl;
cout << "모양: " << design << endl;
cout << "제조사: " << factory_name << endl;
}
};
int main() {
Snack* snackBasket[4] = { new Candy("900", "청포도","짱구 주식회사", "청포도 모양"),
new Candy("800","돌사탕","맹구 주식회사","설탕맛"),
new Chocolate("3500", "석기시대","맹구 주식회사", "돌멩이 모양"),
new Chocolate("2800","초코비","짱구 주식회사","민초맛")
};
for (int i = 0; i < 4; i++) {
snackBasket[i]->snack_status();
cout << "--------------------\n";
}
for (int j = 0; j < 4; j++) {
delete snackBasket[j];
}
}
업캐스팅 & 다운캐스팅
Person* p= new Student();
Student* stu = (Student*)p;
class Person {
protected:
string name;
public:
Person(){//생성자
this->name = "아무개";
}
virtual ~Person() {
cout << "사람의 소멸자입니다." << endl;
}
virtual void info() {
cout << "사람입니다." << endl;
}
void set_name(string name) {
this->name = name;
}
string get_name() {
return name;
}
};
class Student : public Person {
string stu_id;
public:
~Student() {
cout << "학생의 소멸자입니다." << endl;
}
void info() {
cout << "학생입니다." << endl;
}
void test() {
cout << "학생의 test 함수" << endl;
}
};
class Instructor : public Person {
public:
~Instructor() {
cout << "강사의 소멸자입니다." << endl;
}
void info() {
cout << "강사입니다." << endl;
}
};
int main() {
//업캐스팅
Person* p = new Student();
cout << p->get_name() << endl;
//p->test(); 불가능
//다운캐스팅
Student* s = (Student*)p;
s->test(); //가능해짐
p->set_name("길동이");
cout << p->get_name() << " " << s->get_name() << endl;
s->set_name("춘향이");
cout << p->get_name() << " " << s->get_name() << endl;
Student& s1 = *(Student*)p; // 다운캐스팅: Student*이라는 주소로 p를 형변환
s1.set_name("코디");
cout << p->get_name() << " " << s1.get_name() << endl;
Student s2 = *(Student*)p; // 참조X , p의 값을 대입
s2.set_name("코양");
cout << p->get_name() << " " << s2.get_name() << endl;
//s사용까지 하고 나서 반납
delete p;
//포인터변수, 참조변수 예시
int a = 1;
int k = 2;
int& b = a; // 참조변수를 선언할 때는 값 (b가 a의 주소값을 참조, a의 별명은 b이다.)
int* c = &a; // 포인터!!(참조하는 주소를 바꿀 수 있음)
cout << a << " " << b << " " << *c << endl; // 1 1 1
b = k;
cout << a << " " << b << " " << *c << endl; // 2 2 2
a = 3;
cout << a << " " << b << " " << *c << endl; // 3 3 3
c = &k; // c에는 k의 주소값을 넣음, c는 더이상 a를 참조하지 않음
k = 4;
cout << a << " " << b << " " << *c << endl; // 3 3 4
}
Static 멤버
[특징]
1. 객체와 독립적이다 -> 객체를 생성하지 않아도 접근 가능.
2. 정적 메소드 안에서는 일반 멤버에 접근할 수 없음. (static 멤버에만 접근 가능)
[예시]
//멤버: 필드 속성 메소드 등 클래스에 속하는 모든 것
//static변수와 함수가 있다.
//모든 static멤버는 모든 객체가 하나의 공간을 공유한다.
//name은 static멤버 X => 객체별로 공간이 따로 할당됨
//static_var는 static멤버 O => 모든 객체가 같은 공간을 공유
class Person {
protected:
string name;
static int static_var; // 스태틱변수 선언
static int count; // 스태틱변수 선언
public:
static void static_mathod() { //스태틱 메소드 선언, 상속의 개념X, 일반클래스에 대한 개념
//name = "아무개"; name 객체 사용X
cout << "스태틱 메소드입니다." << endl;
}
static int get_count() { // static 게터
return count;
}
Person() {//생성자: 객체 생성시 실행
this->name = "아무개";
count++;
}
~Person() {//생성자: 객체 생성시 실행
this->name = "아무개";
count--;
}
};
int Person::count = 0; // static변수는 할당을 처음에 해줘야함
int main() {
//클래스에는 속하지만, 객체 별로 할당되지 않고 클래스의 모든 객체가 공유하는 멤버
//객체와 독립적 => 객체를 생성하지 않아도 접근 가능
//정적 메소드 안에서는 일반 멤버에 접근할 수 없음! (static 멤버에만 접근 가능)
//static은 두가지 방법 다 가능
Person::static_mathod(); // 클래스를 타고 확인 가능.
Person p;
p.static_mathod(); // 객체를 타고 확인 가능
Person p1;
cout << Person::get_count() << "명 " << endl;
// 2명
Person* p3[5];
for (int i = 0; i < 5; i++) {
p3[i] = new Person();
}
cout << Person::get_count()<< "명 " << endl;
// 7명
for (int i = 0; i < 5; i++) {
delete p3[i];
}
cout << Person::get_count() << "명 " << endl;
// 2명
//vector 활용
vector<Person*> people = { new Person(), new Person()};
cout << Person::get_count() << "명 " << endl;
people.push_back(new Person()); // 한놈 추가
cout << Person::get_count() << "명 " << endl;
int size = people.size();
for (int i = 0; i < size; i++) {
delete people.at(i);
}
for (int i = 0; i < size; i++) {
people.pop_back();
}
cout << Person::get_count() << "명 " << endl;
}
실습
(1)간식 바구니 프로그램을 활용하여 아래 사진과 같은 프로그램 작성
class Snack {
protected:
string name; // 일반 변수는 객체를 생성해야만 할당해줄 수 있다.
static int count; // 객체와 독립적 클래스 자체를 통해 할당할 수 있다.
public:
Snack() {
count++;
}
static int get_count() {
return count;
}
virtual void showInfo() { //동적 바인딩 가능
cout << "error" << endl;
}
};
int Snack::count = 0;
class Candy :public Snack {
string taste;
public:
Candy(string taste) {
this->name = " 사탕";
this->taste = taste;
}
void showInfo() {
cout << taste << "맛" << name << endl;
}
};
class Chocolate :public Snack {
string shape;
public:
Chocolate(string shape) {
this->name = " 초콜릿";
this->shape = shape;
}
void showInfo() {
cout << shape << "모양" << name << endl;
}
};
int main() {
vector<Snack*> snackBasket; // 포인터로 생성
while (1) {
int number;
cout << "과자 바구니에 추가할 간식을 고르시오(1: 사탕, 2: 초콜릿, 0: 종료): " ;
cin >> number;
if (number == 1) {
string taste;
cout << "맛을 입력하세요: ";
cin >> taste;
snackBasket.push_back(new Candy(taste)); //객체의 주소값을 담아줘야 함
}
else if (number == 2) {
string shape;
cout << "모양을 입력하세요: ";
cin >> shape;
snackBasket.push_back(new Chocolate(shape));
}
else if (number == 0) {
break;
}
else {
cout << "0 ~ 2 사이의 숫자를 입력하세요!!" << endl;
}
}
cout << "과자 바구니에 담긴 간식의 개수는 " << Snack::get_count() << "개 입니다." << endl;
cout << endl;
cout << "---------------내가 가진 과자 정보 보기--------------- \n";
for (Snack* snack : snackBasket) { // for-each문, (배열의 원소, 원하는 변수이름 : 배열)
snack->showInfo();
delete snack;
}
snackBasket.clear();
}
마무리
포인터, 다운캐스팅에서 참조변수를 사용 하거나 안 하거나..
정적 바인딩, 동적 바인딩, 동적 메모리 선언..
*s1; // s1 = 주소, *s1 = s1 주소에 담긴 값
힙영역, 스택영역...
스택 영역:
빌드할 때 생성되는 변수에 대한 메모리 저장
관리 필요X
힙 영역:
실행 중(빌드 후 콘솔창이 뜨고 난 뒤)에 할당되는 메모리에 대한 저장
관리 필요O
헷갈리고 머리에 안들어오는게 점점 많아진다. 꾸역꾸역 집어 넣으려고 해도 자꾸 튕겨나가는 느낌~~
특히 다운캐스팅은 잘 모르겠다.. 포인터를 자꾸 사용하는데 포인터의 개념을 제대로 이해하지 못하고 있는 것 같다.
이해를 완벽하게 한 것 같다가도 다시 쳐다보면 응? 이거 왜 또 이해 안가지? 이렇게 되어버린다. 쳇
그럼에도 내가 이해가 안 가는 부분들이 명확하게 어떤 것인지 알게 되고 자각하고 있다는 것 만으로도 많이 성장 했다는 기분이 든다. 아니 확신..
왜냐하면 대학생때 하나도 몰라서 어느걸 공부해야 하는지 모른채로 잡다하게 공부하다가 무너지는 탑을 세웠거던ㅋ
그래서 내가 모르는 것 이해가 안가는 것들이 나와도 기분이 그리 나쁘지 않다. 열심히 공부해서 이해하면 되니까! 어딜 모르는지 아니까! 오히려 좋아~~
오늘도 수고해똬 나 자신!