동적 메모리 할당,해제를 맡는 new, delete 키워드

C에서 메모리의 동적할당을 할때 malloc 함수를 사용하지요. 동적할당에 대해 모르신다면 저의 동적할당에 대한 포스팅을 보고 오시기 바랍니다.

reakwon.tistory.com/20

 

[C언어] 동적 메모리 할당의 세가지 방법 malloc, calloc, realloc

동적 메모리 할당 우리는 이제껏 메모리를 할당할때 정적으로 할당했습니다. 어떤 것이냐면 int arr[100]; 이렇게 할당을 했었죠. 뭐 문제없습니다. 실행도 잘 되구요. 하지만 이런 상황은 조금 불

reakwon.tistory.com

 

C언어에서 malloc을 사용할때 반환하는 void*를 형변환하여 사용하였습니다. 그리고 동적할당된 메모리를 해제할땐 free를 사용했습니다. C++에서는 이러한 동적할당에 관한 사용이 자주 이루어지기 때문에 아예 키워드로 지정해놓았습니다. malloc을 대체하는 키워드는 new, free를 대체하는 키워드는 delete입니다. 사용되는 방식은 다음과 같습니다.

MyClass* myclass = new MyClass();
delete(myclass);

여기서 malloc과 new는 동적할당하는 것은 같지만 malloc은 함수를 call하는 것이고 new는 C++에서 기본적으로 제공하는 키워드이기 때문에 별도의 라이브러리를 추가할 필요가 없습니다. 또한 new는 생성자를 자동호출하는 특징이 있지요. 이제 C++에서는 객체를 할당할때 malloc으로 하지 않고 new를 이용하여 할당합니다.

오버라이딩(Overriding)

Class의 상속에 대해서 배우셨나요? 그렇다면 이제 다형성을 배워볼 차례입니다. 다형성이라하면 여러 형태를 갖는 객체의 특징을 말하는데요. 아래의 코드를 가지고 다형성과 관련한 클래스의 특징들을 천천히 살펴보도록 합시다.

#include<iostream>
using namespace std;

class Animal {
private:
	int height;
	int weight;
public:
	Animal(int _height, int _weight) :height(_height), weight(_weight) {}
	void printInfo() {
		cout << "==============정보=============" << endl; ;
		cout << "키:" << height << "무게:" << weight << endl;
	}
	int getHeight() {
		return height;
	}
	int getWeight() {
		return weight;
	}
};

class Human :public Animal {
private:
	int race;
public:
	Human(int _height, int _weight, int _race) :Animal(_height, _weight) {
		race = _race;
	}
	void printInfo() {
		cout << "==============정보=============" << endl;
		cout << "키:" << getHeight() << "무게:" << getWeight() << endl;
		cout << "인종:";
		if (race == 0)
			cout << "황인" << endl;
		else if (race == 1)
			cout << "흑인" << endl;
		else if (race == 2)
			cout << "백인" << endl;
		else
			cout << "혼혈" << endl;
	}
};
int main() {
	
	Animal* animal = new Animal(50, 20);
	animal->printInfo();

	Human* human = new Human(150, 80, 3);
	human->printInfo();
	delete(animal);
	delete(human);
}

 

여기서 Animal클래스는 Human클래스의 부모 클래스입니다. main에서는 둘 다 new 키워드로 생성해서 정보를 출력하는 코드네요. Human 클래스에서 printInfo 메소드를 보면 cout 두줄이 정확히 Animal의 printInfo클래스의 메소드와 같은 것을 알 수 있습니다. 

어차피 Human클래스는 Animal클래스를 상속받고 있으니, Animal클래스의 printInfo를 재활용 할 순 없을까요? 그럴때 부모클래스::메소드명 을 호출하여 부모클래스의 메소드를 사용할 수 있습니다. Human 클래스의 메소드를 아래처럼 바꿔봅시다.

void printInfo() {
	Animal::printInfo();
	cout << "인종:";
	if (race == 0)
		cout << "황인" << endl;
	else if (race == 1)
		cout << "흑인" << endl;
	else if (race == 2)
		cout << "백인" << endl;
	else
		cout << "혼혈" << endl;
}

 

이처럼 부모클래스의 함수를 이용하여 자식 클래스에서 같은 메소드 이름으로 새로운 기능을 덧붙이는 방식을 오버라이딩이라고 합니다.

 

 

다형성(Polymorphism)

OOP(Object-Oriented Programming)은 현실 세계를 반영한다는 슬로건을 가지고 있습니다. 위의 코드를 현실세계와 연관지어 생각해봅시다. 우리 사람은 동물의 한 종이죠. 그래서 동물의 속성을 갖고 있습니다. 하지만 동물은 사람이 아니죠. 동물에는 사람외에도 개와 고양이 등 많이 있기 때문인데요. 정리하면 이렇게 되겠네요.

사람은 동물이다.(O)

동물은 사람이다.(X)

위에서 성립이 되는 상황(사람은 동물)을 C++에서는 아래와 같이 표현할 수 있습니다. main함수안의 내용을 아래와 같이 바꿔서 실행해보시기 바랍니다.

Animal* human = new Human(150, 80, 3);
human->printInfo();

 

이렇게 부모클래스를 통해 만들어진 객체가 자신을 상속받는 여러 클래스의 객체로 모양을 띄는 것을 다형성이라고 합니다. OOP에서 매우 중요한 개념입니다.

위의 코드를 수행한 결과에서 한가지 불편한 점이 있는데요. 저희는 human->printInfo()가 당연히 Human클래스의 printInfo를 출력한다는 믿음으로 프로그래밍을 했는데, 막상 결과는 Animal의 printInfo가 호출됩니다. 어떻게 해야지 우리가 원하는 동작을 할 수 있을까요?

virtual 키워드

만약 자신을 상속받는 자식 클래스의 객체가 자신의 메소드를 사용하는데, 이것을 오버라이딩했다면 자식 클래스의 객체 메소드를 호출하라고 지정하는 방법은 메소드 앞에 virtual 키워드를 사용하는 것입니다. 자신의 메소드는 가상으로 만들어져 있으니 자식의 메소드를 호출하라는 의미가 되겠죠. 그래서 우리의 목표를 달성하기 위해서는 Animal 클래스의 printInfo메소드 앞에 virtual 키워드를 추가하면 됩니다. 그렇게 되면 Human의 printInfo 메소드를 호출하게 됩니다.

class Animal {
private:
	int height;
	int weight;
public:
	Animal(int _height, int _weight) :height(_height), weight(_weight) {}
	virtual void printInfo() {
		cout << "==============정보=============" << endl; ;
		cout << "키:" << height << "무게:" << weight << endl;
	}
	int getHeight() {
		return height;
	}
	int getWeight() {
		return weight;
	}
};

 

virual키워드를 사용해야 자식 객체의 메소드를 사용한다고 했죠? 그렇다면 다음의 상황을 예측해봅시다.

class Human :public Animal {
private:
	int race;
public:
	//...생략...//
	void printInfo() {
		//...생략...//
	}
};

class Student :public Human {
private:
	char grade;
public:
	Student(int _height, int _weight, int _race,char _grade) :Human(_height, _weight, _race) {
		grade = _grade;
	}

	void printInfo() {
		Human::printInfo();
		cout << "성적:" << grade << endl;
	}
};

 

Human 클래스는 printInfo에 virtual키워드를 넣지 않았습니다. Student는 Human클래스를 상속받고 있는데, 이때 printInfo는 Human의 printInfo를 호출할까요? 메소드의 virtual이 지정되면 이후 자식은 자동으로 virtual 키워드가 적용이 됩니다. 그래서 결론은 Student의 printInfo가 호출이 되지요. 하지만 명시적으로 virtual을 지정해주는 것이 관례입니다. 코드의 가독성과 이해를 돕기 위해서지요.

자, 이런 다형성은 언제 필요할까요? 예를 들면 함수에서 해당 클래스를 상속하는 모든 객체를 받을 때가 그 예가 됩니다. 아래의 함수에서 변수로 animal를 가리키는 포인터를 받습니다. 이 함수의 목적은 animal인 객체의 printInfo를 출력하는 것인데, Animal클래스를 상속하는 모든 객체를 받을 수 있습니다. 

void printInfo(Animal *animal) {
	animal->printInfo();
}

 

 

this 포인터

객체가 생성될때 자기 자신을 가리키는 포인터가 this 포인터라고 합니다. 만일 아래와 같은 상황에서 this를 사용할 수 있습니다. 저는 생성자에서 객체의 grade를 전달받은 grade의 값으로 입력하고 싶어서 아래와 같은 코드를 사용했습니다.

class Student :public Human {
private:
	char grade;
	Student(int _height, int _weight, int _race,char grade) :Human(_height, _weight, _race) {
		grade = grade;
	}
//...생략...//
};

 

여기서 객체의 grade는 절대 변할 수 없습니다. 객체의 grade보다 매개 변수인 grade가 우선시 되기 때문에 매개변수 grade에 다시 매개변수의 grade의 값을 넣기 때문입니다. 이럴때 사용할 수 있는 것이 바로 this포인터를 사용하는 것이죠.

Student(int _height, int _weight, int _race,char grade) :Human(_height, _weight, _race) {
	this->grade = grade;
}

this포인터는 객체가 생성되고 난 이후에 그 효력을 발휘합니다. 그 객체의 주소를 나타내야하기 때문이죠. 아래와 같이 한번 주소를 찍어봅시다. 정확히 같은 곳을 가리키는 것을 알 수 있습니다.

class Student :public Human {
private:
	char grade;
public:
	Student(int _height, int _weight, int _race,char grade) :Human(_height, _weight, _race) {
		cout <<"this 포인터의 주소"<< this << endl;
		this->grade = grade;
	}
	//...생략...//
};

int main() {
	
	Animal* student = new Student(165, 55, 0, 'A');
	cout <<"student의 주소"<< student << endl;
	student->printInfo();
	delete(student);
}

 

이상 다형성과 관련된 포스팅을 마치도록 하겠습니다. 이해가 되지 않는 부분은 댓글로 남겨주세요.

 

 

반응형
블로그 이미지

REAKWON

와나진짜

,

상속(Inheritance)


클래스라는 것을 배우면서 Object 클래스에 대해서 잠깐 소개한 적이 있었습니다. 그 이야기를 하면서 상속에 대해 언급한 적이 있었습니다. 자바에서는 이 상속의 사전적인 뜻을 그대로 반영합니다.


inheritance

1. 상속받은 재산, 유산; 상속   2. (과거·선대로부터 물려받는) 유산, 유전(되는 것)


내가 500억을 상속받았으면 이 포스팅을 하지 않았다.


사전을 보니 물려받는 것으로 간단하게 정의할 수 있겠군요.

우리는 이제부토 상속이란 클래스 관점으로 보면 됩니다. 클래스에는 변수와 메소드가 있죠. 클래스 입장에서는 이것이 재산이라고 생각하면 됩니다. 그래서 자식이 되는 클래스는 이런 재산을 코드의 추가없이 그대로 사용하거나, 변경해서 사용할 수 있습니다.




여기서 왜 우리는 상속을 해야하는 지 알 수 있습니다. 바로 코드의 재사용성을 더욱 확대하는 것이지요. 그렇다면 그냥 복사, 붙여넣기를 하면 되는 것이 아닌가? 라고 생각할 수도 있지만, 코드의 변경 역시 신경써야합니다. 그리고 관리도 복잡하게 되구요.


가장 중요한 것은 상속을 통해서 다형성을 만족시킬 수 있다는 점입니다. 이 부분에 대해서는 이 다음에 알아보도록 하지요.


extends 

상속을 하는 방법 아래와 같습니다.


class 자식클래스 extends 부모클래스


어떤 클래스를 정의함과 동시에 extends 예약어로 다른 클래스를 포함하게 된다면 extends 이후에 나오는 클래스를 상속하게 되는 것입니다.


어떤 클래스는 자동적으로 Object클래스를 상속했다고 했는데, 그럼 이런 형태일까요?


class 자식클래스 extends 부모클래스 extends Object


아닙니다. 자바에서는 클래스의 다중상속(여러 부모클래스를 두는 것)을 허용하지 않습니다. 이런 경우에는 이미 부모클래스가 Object클래스를 이미 상속받고 있기 때문에 자식클래스는 부모클래스를 상속받음으로써 Object클래스의 메소드를 사용할 수 있는 것입니다.


이렇게 되는 것이죠.


class 부모클래스 extends Object { ... }

class 자식클래스 extends 부모클래스 { ... }





코드를 통해 알아보자.

이쯤되면 코드를 볼 타이밍이겠군요.



class Parent{
	int age=45;
	String name="Parent";
	public Parent(){
		System.out.println("Parent Default Constructor");
	}
	public Parent(int _age,String _name){
		age=_age;
		name=_name;
		System.out.println("Parent Constructor");
	}
	public void showInfo(){
		System.out.println("Name:"+name+", age:"+age);
	}
}

class Child extends Parent{
	public Child(int _age, String _name){
		System.out.println("Child Constructor");
	}
}
public class Main {

	public static void main(String[] args){
		Child a=new Child(25,"REAKWON");
		a.showInfo();
	}
}


우선 Child 클래스는 Parent 클래스를 상속받고 있군요. 


코드에 대해서 간단하게 요약하면 Parent 클래스는 아래와 같은 특징이 있군요.


Default 생성자를 갖고 있다.

매개변수를 갖는 생성자도 있다.

showInfo라는 메소드가 있다.


Child클래스는 그저 매개변수를 받는 생성자가 다에요.


main메소드에서는 Child 객체를 초기화값을 넣어 생성자를 호출합니다. 어떤 결과가 나올까요? 결과를 봅시다.


Parent Default Constructor

Child Constructor

Name:Parent, age:45


어? 부모클래스의 생성자를 우선 호출하는 군요. Default 생성자를 호출하네요.

아아, 우리가 아무 클래스를 상속하게 된다면 부모클래스의 디폴트 생성자를 우선 호출하는 군요.


그리고 우리는 Child클래스에서 showInfo라는 메소드를 만들지 않았음에도 불구하고 main함수에서는 사용하고 있습니다. Parent 클래스를 상속 했기 때문이죠.


근데 좀 부족해요. 뭔가 2%만족이 되지 않는군요.


Child a=new Child(25,"REAKWON");


우리는 생성자를 통해서 age와 name을 바꾸고 싶다는 거죠. 어떻게 해야될까요?




super, this

우리는 위의 코드를 조금 변경해서 age와 name 변수의 데이터를 바꿀 수 있습니다. 바로 부모클래스의 데이터를 직접 변경하는 것이죠.


class Child extends Parent{
	public Child(int _age, String _name){
                age=_age;
                name=_name;
		System.out.println("Child Constructor");
	}
}

우리는 이미 Parent클래스를 상속받은 상태입니다. 그렇기 때문에 Parent클래스의 변수를 접근할 수도 있습니다(물론 부모클래스가 허용한다면).


그런데 우리는 이미 정의된 부모 클래스의 생성자를 다시 활용해보고 싶습니다. 


자식클래스에서 부모클래스를 super클래스라고 부릅니다. 그렇기 때문에 부모클래스의 변수나 메소드, 생성자에 접근할땐 super라는 키워드를 사용합니다.


class Child extends Parent{
	public Child(int _age, String _name){
                super(_age,_name);
		System.out.println("Child Constructor");
	}
}

이런식으로 부모클래스의 생성자(super( args))를 호출함으로써 구현해낼 수도 있습니다.

super. 을 눌러 어떤 변수와 메소드들이 있는지 확인해보세요. 부모클래스가 허락한다면 super클래스의 변수와 메소드를 직접 사용할 수도 있습니다.


이제 코드를 조금 더 변형시켜보도록 해서 실험해 봅시다.


class Child extends Parent{
	public Child(int age, String name){
                age=age;
                name=name;
		System.out.println("Child Constructor");
	}
}


이제 매개변수와 클래스 멤버 변수의 이름이 같습니다. 이런 경우에는 어떤 일이 벌어질까요?


결론은 클래스의 멤버 변수는 변하지 않습니다. 메소드가 호출할때는 매개변수나 그 안의 지역변수가 항상 우선순위를 갖기 때문입니다.


클래스에서 자기 자신을 가리킬 때는 this라는 키워드를 사용합니다. 그렇기 때문에 이런 코딩을 해야합니다.

class Child extends Parent{
	public Child(int age, String name){
                this.age=age;
                this.name=name;
		System.out.println("Child Constructor");
	}
}


this.age는 자기 자신의 변수 age를 명시적으로 나타냅니다. 어? 우리는 Child에는 age와 name이라는 변수가 없는데요?


아까 말했다시피 상속을 받았기 때문에 슈퍼클래스의 변수도 이제 Child 클래스의 것입니다. 그래서 super.age이건 this.age이건 age접근하는 것은 결론적으로 같습니다.




생성자는 어떨까요?

생성자 역시 다르지 않습니다.

this(args)를 통해서 자기 자신의 생성자를 호출 할 수 있습니다.


class Child extends Parent{
        public Child(){
                System.out.println("Child Default Constructor");
        }
	public Child(int age, String name){
                this();
                this.age=age;
                this.name=name;
		System.out.println("Child Constructor");
	}
}


this()를 보세요. 자신의 Default 생성자를 호출하고 있습니다.


다만, 한가지 주의해야할 점은 자기 자신의 생성자나 슈퍼클래스의 생성자를 다른 생성자 메소드에서 호출해야 될 때에는 호출할 생성자 메소드의 최상위에 코딩을 해야합니다. 


overriding

우리는 슈퍼클래스의 메소드를 우리 나름대로 다시 정의해보고 싶습니다. 그것도 역시 가능합니다.

우리는 부모가 정의한 메소드를 우리 입맛에 맞게 바꾸는 것을 Overriding이라고 합니다.




코드와 함께 보도록 합시다.


class Parent{
	int age=45;
	String name="Parent";
	public Parent(){
		System.out.println("Parent Default Constructor");
	}
	public Parent(int _age,String _name){
		age=_age;
		name=_name;
		System.out.println("Parent Constructor");
	}
	public void showInfo(){
		System.out.println("Name:"+name+", age:"+age);
	}
}

class Child extends Parent{
	public Child(){}
	public Child(int _age, String _name){
		super(_age,_name);
		System.out.println("Child Constructor");
	}
	@Override
	public void showInfo(){
		System.out.println("Child showInfo");
		super.showInfo();
	}
}
public class Main {

	public static void main(String[] args){
		Child a=new Child(25,"REAKWON");
		a.showInfo();
	}
}

Child클래스의 showInfo()를 보세요. 부모 클래스의 메소드를 오버라이딩한다는 것을 알리기 위해 @Override annotation으로 명시하고 있습니다. @Overriding이라는 annotation을 써주지 않아도 오류는 없지만, 우리는 컴파일러에게 부모클래스의 메소드를 오버라이딩한다는 것을 알려주어 원치않는 코딩(오타)을 막을 수 있습니다.


또한 Child클래스의 showInfo()는 super클래스의 showInfo() 역시 호출하며 코드를 줄이고 있습니다.


그 결과입니다.


Parent Constructor

Child Constructor

Child showInfo

Name:REAKWON, age:25



여기까지 상속에 대해 주저리 주저리 떠들어봤습니다.

아 힘들었다...

반응형
블로그 이미지

REAKWON

와나진짜

,