1. 객체 배열(Object Array)

변수 배열, 구조체 배열은 들어보셨어도 객체 배열에 대해서는 들어보셨나요? 객체 배열도 다른 배열과 마찬가지로 비슷한 형식으로 선언됩니다. (데이터 타입 대신 클래스명이 위치합니다.) 객체 배열은 아래와 같은 방법으로 쉽게 만들 수 있습니다.

클래스명 객체명[크기];

아래는 Student 객체를 배열로 만들어 각각의 요소들의 이름, 나이, 학번등을 초기화하는 예제입니다.

#include <iostream>

using namespace std;

class Student
{
private:
	char name[10];
	int age;
	int studentID;
public:
	Student() { cout << "생성자 호출!" << endl; }
	void SetInfo(char * _name, int _age, int _studentID)
	{
		strcpy(name, _name);
		age = _age;
		studentID = _studentID;
	}
	void GetInfo()
	{
		cout << "이름: " << name << endl;
		cout << "나이: " << age << endl;
		cout << "학번: " << studentID << endl;
	}
	~Student() { cout << "소멸자 호출!" << endl; }
};

int main()
{
	Student student[5];
	char name[10];
	int age, studentID;

	for(int i=0; i<5; i++)
	{
		cin >> name >> age >> studentID;
		student[i].SetInfo(name, age, studentID);
	}
	for(int i=0; i<5; i++)
		student[i].GetInfo();
}

결과:

생성자 호출! 생성자 호출! 생성자 호출! 생성자 호출! 생성자 호출!

김철수 14 1

김영희 14 2

이영수 15 3

이영철 15 4

이영희 15 5

이름: 김철수 나이: 14 학번: 1

이름: 김영희 나이: 14 학번: 2

이름: 이영수 나이: 15 학번: 3

이름: 이영철 나이: 15 학번: 4

이름: 이영희 나이: 15 학번: 5

소멸자 호출! 소멸자 호출! 소멸자 호출! 소멸자 호출! 소멸자 호출!

계속하려면 아무 키나 누르십시오 . . .

(결과가 하도 길어서 몇몇 부분은 한줄로 압축시켰습니다.)


코드를 보시면 5~26행에 Student란 클래스가 정의되었고, SetInfo란 함수로 초기화가 이루어지며, 디폴트 생성자와 소멸자가 존재합니다. 주목하셔야 할부분은 바로 30행입니다. 길이 5인 student의 객체 배열이 선언되었으며, 36행에서 사용자로부터 입력을 받아 37행처럼 SetInfo 함수로 넘겨 초기화시키고 있습니다. 40행에선 멤버 변수의 값들을 모두 출력시키는 역할을 합니다. 객체 배열의 선언과 구조체 배열의 선언과 비교해보면 차이가 없죠?


이번에는 객체 배열을 어떻게 초기화하는지 알아보도록 합시다. 아래는 위의 예제에서 SetInfo를 이용한 초기화 방식과는 달리, 생성자로 인자를 넘겨 멤버 이니셜라이저로 멤버 변수를 초기화하는 예제입니다.

#include <iostream>

using namespace std;

class Student
{
private:
	char name[10];
	int age;
	int studentID;
public:
	Student(char * _name, int _age, int _studentID) : age(_age), studentID(_studentID) { strcpy(name, _name); }
	void GetInfo()
	{
		cout << "이름: " << name << endl;
		cout << "나이: " << age << endl;
		cout << "학번: " << studentID << endl;
	}
};

int main()
{
	Student student[3]={Student("김철수", 14, 1), Student("김영희", 14, 2), Student("이영수", 15, 3)};

	for(int i=0; i<3; i++)
		student[i].GetInfo();
}

결과:

이름: 김철수

나이: 14

학번: 1

이름: 김영희

나이: 14

학번: 2

이름: 이영수

나이: 15

학번: 3

계속하려면 아무 키나 누르십시오 . . 


코드를 보시면, 12행에서 멤버 이니셜라이저를 통한 초기화로 바뀌었고, SetInfo 함수를 없앴습니다. 23행을 보시면 객체 배열의 초기화 방법을 알 수 있습니다. 이번에는 객체 포인터 배열을 한번 보도록 할까요?


2. 객체 포인터 배열(Object Pointer Array)

C언어에서 잠깐 봤던 포인터 배열과 비슷합니다. 단지 다른게 있다면, 객체의 포인터 배열이라는 점이죠. 즉, 객체의 주소값들의 모임이라고 할 수 있습니다. 아래는 객체 포인터 배열의 선언 형식입니다.

클래스명 * 객체명[크기];

아래 예제는 객체 배열의 예제에서 객체 배열 대신 객체 포인터 배열로 변경한 방식입니다.

#include <iostream>

using namespace std;

class Student
{
private:
	char name[10];
	int age;
	int studentID;
public:
	Student(char * _name, int _age, int _studentID) : age(_age), studentID(_studentID) { strcpy(name, _name); }
	void GetInfo()
	{
		cout << "이름: " << name << endl;
		cout << "나이: " << age << endl;
		cout << "학번: " << studentID << endl;
	}
};

int main()
{
	Student * student[3];
	char name[10];
	int age, studentID;

	for(int i=0; i<3; i++) {
		cin >> name >> age >> studentID;
		student[i] = new Student(name, age, studentID);
	}
	for(int i=0; i<3; i++)
		student[i]->GetInfo();
	delete student[0];
	delete student[1];
	delete student[2];
}

결과:

김철수 14 1

김영희 14 3

이영수 15 3

이름: 김철수

나이: 14

학번: 1

이름: 김영희

나이: 14

학번: 3

이름: 이영수

나이: 15

학번: 3

계속하려면 아무 키나 누르십시오 . . .


코드를 보시면, Student 클래스는 달라진게 없습니다. 달라진 부분이 있다면, 역시 main 함수 안이겠죠? 먼저 23행을 봅시다. 포인터 배열이 선언되어 있는것이 보이시죠? 그다음 29행을 보시면 new 연산자를 통해 객체를 만듭니다. 객체의 주소값이 배열에 들어간 셈입니다. 그리고 나서 33~35행에서, delete 연산을 통해 해제해주어야 합니다. delete 연산을 통해 해제해주어야 한다는것, 잊지마세요.


3. this 포인터

this 포인터가 무엇일까요? this 포인터는 어떤 곳에 쓰일까요? this 포인터에 대해 알아보기 전에, 잠깐 예제를 하나 보도록 합시다. 다음 예제는 생성자의 매개변수로 클래스 내의 멤버 변수를 초기화 하는 예제입니다.

class Student
{
private:
   int age;
public:
   Student(int age) {
      age = age; // ?
   }
   // ..
}

위 코드의 6~8행을 한번 보도록 합시다. Student 생성자의 매개변수를 보시면 age란 매개변수가 있고, 7행을 보시면 멤버 변수에 매개 변수의 값이 저장되는 것 같으나, 사실은 매개 변수에 매개 변수의 값이 저장됩니다. 이 상황을 해결하려면 멤버 변수의 이름을 바꾸던가, 매개 변수의 이름을 바꾸던가, 둘 중 하나는 변수의 이름을 바꿔야 하는 불편함이 있습니다. 또다른 방법으로는, 우리가 이제 배우게 될 "this 포인터"를 이용하면 간단히 해결할 수 있습니다.


이 this 포인터(자기 참조 포인터)란, 객체를 참조하는 포인터이며, 이 포인터를 가지고 클래스 내의 멤버 변수를 지칭할 수도 있습니다. 쉽게 말해서, 자기 자신을 가르키는 녀석이라고 생각하시면 됩니다. 아래 예제를 한번 보실까요?

#include <iostream>

using namespace std;

class MyClass
{
private:
	int num1;
	int num2;
public:
	MyClass(int num1, int num2)
	{
		this->num1 = num1;
		this->num2 = num2;
	}
	void GetInfo()
	{
		cout << "num1: " << num1 << endl;
		cout << "num2: " << num2 << endl;
	}
};

int main()
{
	MyClass mc(10, 20);

	mc.GetInfo();
	return 0;
}

결과:

num1: 10

num2: 20

계속하려면 아무 키나 누르십시오 . . .


코드의 11~15행을 보시면, 매개변수를 갖는 생성자가 정의되었는데, 13행에서 this 포인터를 이용해서 객체 내의 멤버 변수에 접근하고 있습니다. this->num1, this->num2는 MyClass 객체 내의 멤버 변수 num1과 num2를 가르킵니다. 물론, 그 뒤에 등장하는 num1과 num2은 매개변수임을 알 수 있습니다. 주의하셔야 할 점은, 멤버 이니셜라이저에서 this 포인터를 쓰실 수 없습니다. 그렇지만 this 포인터 없이도, 멤버변수(매개변수)로 인식하므로 아래와 같은 형태의 구성이 가능합니다.

MyClass(int num1, int num2) : num1(num1), num2(num2)

이번 강좌는 여기서 그만 마치도록 하겠습니다. 수고하셨습니다. 다음 강좌에서는 상속 오버라이딩과 가상, 그리고 다중 상속에 대해 알아보도록 하겠습니다.