이번부터는 C++의 객체 포인터, 객체 배열, 그리고 동적 할당에 대해서 알아보자.
C++에서는 객체를 다루기 위해서 객체에 대한 포인터 변수를 선언하고, 이 포인터 변수를 통해서 객체 멤버 변수를 읽고, 값을 쓰거나, 멤버 함수를 호출할 수 있다. 간단한 예시를 통해서 알아보자.
Circle donut;
double d = donut.getArea();
Circle *p; // 객체 포인터를 생성
p = &donut; // 포인터에 객체 주소를 저장하기
d = p->getArea(); // 객체 포인터를 통해서 멤버 함수를 호출하기
객체에 대한 포인터 변수 선언은 일반적인 포인터처럼 선언하면 된다.
Circle *p;
그리고 포인터 변수에 객체 주소를 할당하기 위해서는 & 연산자를 사용해야 한다.
p = &donut;
혹은 포인터 변수를 선언할 때 바로 초기화 할 수 있다.
Circle *p = &donut;
이렇게 선언한 포인터를 통해서 멤버 변수와 멤버 함수에 접근할 수 있다.
보통 객체 이름을 통해서 멤버 변수나 함수에 접근하고 싶다면 . 연산자를 사용하지만, 포인터를 통해서 접근하기 위해서는 -> 연산자를 사용한다.
d = donut.getArea(); // 일반적인 객체 접근
d = p->getArea(); // 포인터 변수를 통한 접근
d = (*p).getArea(); // 이렇게 코딩해도 된다.
참고로 매우 당연한 소리지만, 아무것도 가리키지 않는 포인터로 멤버 함수나 변수에 접근하려고 하면 null pointer 오류가 발생한다.
다음은 객체 포인터를 사용하는 예시 코드이다.
#include <iostream>
using namespace std;
class Circle{
int radius;
public:
Circle(){radius = 1;}
Circle(int r) {radius = r;}
double getArea();
};
double Circle::getArea(){
return 3.13*radius*radius;
}
int main(){
Circle donut;
Circle pizza(300);
cout<<donut.getArea()<<endl; // 객체 이름을 통한 접근
Circle *p;
// 객체 포인터 접근
p = &donut;
cout << p->getArea() <<endl;
cout << (*p).getArea() <<endl;
// 객체 포인터를 통해서 멤버 함수를 접근함
p = &pizza;
cout << p->getArea() <<endl;
cout << (*p).getArea() << endl;
// 객체 포인터를 통해서 멤버 함수 접근
}
C++에서는 객체 배열을 사용할 수 있다. 객체 배열(object array)은 원소(element)가 객체라는 점을 빼면, 기본 자료형을 사용해서 선언하고 사용하는 배열과 차이점이 없다.
예시 코드를 살펴보자.
#include<iostream>
using namespace std;
class Circle {
int radius;
public:
Circle() { radius = 1; }
Circle(int r) { radius = r; }
void setRadius(int r) { radius = r; }
double getArea();
};
double Circle::getArea() {
return 3.14 * radius * radius;
}
int main() {
Circle circleArray[3];
circleArray[0].setRadius(10); // 배열의 index를 활용해서 접근이 가능
circleArray[1].setRadius(20);
circleArray[2].setRadius(30);
for (int i = 0; i < 3; i++) {
cout << "Circle " << i << "의 면적은 " << circleArray[i].getArea() << endl;
}
Circle* p;
p = circleArray; // 배열의 이름은 배열의 첫번째 주소를 가리키는 포인터
for (int i = 0; i < 3; i++) {
cout << "Circle " << i << "의 면적은 " << p->getArea() << endl; // 포인터를 통해서 접근
p++; // 배열 원소 증가
}
}
객체 배열 선언문은 오직 매개 변수 없는 기본 생성자(default constructor)를 사용한다. 따라서 매개변수 있는 생성자가 존재하지만, 기본 생성자가 없는 클래스를 작성한 후 객체 배열을 사용하면 컴파일 오류가 발생하게 된다.
또한 매개변수 있는 생성자를 사용하기 위해서
class_name array_name[index](param);
이런 식으로 선언하는 것 역시 컴파일 오류를 불러일으킨다.
그리고 객체배열을 통해서는 "." 연산자를 통해서 멤버 변수와 함수에 접근함도 유의해야 한다. 포인터와는 다르게 접근한다.
일반적인 배열과 마찬가지로 객체 배열 또한 포인터를 이용해서 다룰 수 있다. 그리고 포인터가 배열의 처음 원소의 주소를 가리킬 경우, 포인터 역시 인덱스를 통해 마치 배열 변수처럼 다룰 수 있다는 점 역시 동일하다. 가령 예를 들어보면 다음과 같다.
p[0].getArea();
(*p).getArea();
p->getArea();
이 3가지 활용법은 모두 동일한 의미를 가지고 있다.
(마지막 p가 배열의 첫번째 원소를 가리킬 경우에만)
객체 배열이 소멸할 경우, 각 객체 원소마다 소멸자가 호출되게 된다. 이 경우에도 당연히 생성된 순서의 역순으로 소멸자가 호출되어 소멸한다.
결론부터 얘기하면 그렇지 않다. 아까 위에서는 생성자를 사용하지 못한다고 했지만, 사실 그것은 선언할 때 얘기이다.
만약 선언과 동시에 객체 원소들을 초기화 한다면, 그 때는 기본 생성자가 아닌 생성자도 얼마든지 사용할 수 있다.
예를 들어보자.
Circle circleArray[3] = {Circle(10), Circle(20), Circle()};
이 경우에는 마지막 원소에만 기본 생성자가 사용되며, 나머지 원소들의 경우 다른 생성자를 통해서 생성된다.
객체 배열의 초기화 예시 코드를 살펴보자.
#include<iostream>
using namespace std;
class Circle {
int radius;
public:
Circle() { radius = 1; }
Circle(int r) { radius = r; }
void setRadius(int r) { radius = r; }
double getArea();
};
double Circle::getArea() {
return 3.14 * radius * radius;
}
int main() {
Circle circleArray[3] = { Circle(10), Circle(20), Circle() };
for (int i = 0; i < 3; i++)
cout << "Circle " << i << "의 면적은 " << circleArray[i].getArea() << endl;
}
다른 프로그래밍 언어와 마찬가지로 C++ 역시 1차원을 넘어선 배열을 만들 수 있다. 객체 배열 역시 마찬가지로 다차원으로 선언 및 생성이 가능하다.
Circle circles[2][3];
단순히 선언만 하면 1차원 객체 배열과 마찬가지로 기본 생성자를 통해서 초기화가 이루어지며, 구현부에서 생성자를 지정할 경우 기본 생성자를 사용하지 않고도 초기화가 가능하다는 것까지 완벽하게 동일하다.
예시 코드를 살펴보자.
#include<iostream>
using namespace std;
class Circle {
int radius;
public:
Circle() { radius = 1; }
Circle(int r) { radius = r; }
void setRadius(int r) { radius = r; }
double getArea();
};
double Circle::getArea() {
return 3.14 * radius * radius;
}
int main() {
Circle circles[2][3];
circles[0][0].setRadius(1);
circles[0][1].setRadius(2);
circles[0][2].setRadius(3);
circles[1][0].setRadius(4);
circles[1][1].setRadius(5);
circles[1][2].setRadius(6);
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
cout << "Circle [" << i << ", " << j << "]의 면적은 ";
cout << circles[i][j].getArea() << endl;
}
}
}