C++ 복습(3) - 객체의 생성과 사용
객체의 생성과 사용
- 포인터로 객체의 멤버를 접근할 때
- 객체포인터 ->로 멤버 변수 및 함수에 접근
예를 들어 아래와 같이 사용할 수 있다.
#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.14 * radius * radius;
}
클래스가 위와 같다면, 아래와 같이 객체 이름 및 포인터로 멤버 접근이 가능하다.
int main() {
Circle donut;
// 객체 이름으로 멤버 접근
cout << dounut.getArea() << endl;
// 객체 포인터로 멤버 접근
Circle *p;
p = &donut;
cout << p->getArea() << endl;
cout << (*p).getArea() << endl;
}
위와 같이, 객체 이름과 ‘,’ 연산자로 직접 멤버에 접근할 수도 있고, 포인터 변수를 만들어서 그 객체 포인터에직접 ‘->’를 사용하여 접근하거나, (*p)와 ‘.’을 사용해 접근할 수도 있다.
객체 배열
위의 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;
}
객체 배열을 생성하게 되면, 그 초기화는 아래와 같이 이루어질 수 있다.
// 배열의 각 원소 객체의 멤버 접근
Circle circleArray[3]; // 기본 생성자 사용, 기본생성자가 없다면 오류 발생
// 멤버 변수 업데이터
circleArray[0].setRadius(10);
circleArray[1].setRadius(20);
circleArray[2].setRadius(30);
// 배열의 각 원소 객체당 생성자를 지정하는 방법
Circle circleArray[3] = {Circle(10), Circle(20), Circle()};
클래스의 2차원 배열의 경우 아래와 같다.
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);
객체의 동적 할당
C에서 malloc(), free()를 이용해 메모리를 동적할당 받았다면, C++에서는 new(), delete()연산자로 메모리를 동적 할당 및 반환한다.
- new 연산자
- 기본 타입 메모리 할당, 배열/객체/객체 배열 할당
- 힙 메모리로부터 객체를 위한 메모리 할당 요청
- 객체 할당 생성 시 생성자 호출
- delete 연산자
- new로 할당받은 메모리 반환
- 소멸자 호출 뒤 객체를 힙에 반환
예시는 아래와 같다.
데이터타입 *포인터변수 = new 데이터타입;
delete 포인터변수;
int *pInt = new int;
char *pChar = new char;
Circle *pCircle = new Circle();
delete pInt;
delete pChar;
delete pCircle;
동적 할당 시 메모리를 초기화할 수 있다.
데이터타입 *포인터변수 = new 데이터타입(초기값);
char *pChar = new char('a');
// 배열은 동적 할당 시 초기화 불가능
int *pArray = new int[10](20); // 컴파일 오류 발생
배열은 delete시 아래와 같이 ’[]’를 꼭 써줘야 한다.
int *p = new int[10];
delete [] p;
동적 메모리 할당 시, 메모리 누수를 조심해야 한다. 아래 경우를 통해 확인해보자.
char n = 'a';
char *p = new char[1024];
p = &n // p가 n을 가리키면, 기존에 할당받은 1024 바이트의 메모리 누수 발생
char *p;
for(int i = 0; i < 100000; i++){
p = new char[1024];
}
// for문이 돌 때마다, 이전 iteration에서 할당받은 1024 바이트의 메모리 누수 발생
프로그램이 종료되면, 운영체제는 누수 메모리를 모두 힙에 반환하긴한다. 하지만, 프로그램을 최적으로 동작시키려면 당연히 실행 중 누수가 발생하지 않도록 해야한다.
this 포인터
- 클래스의 객체를 가리키는 포인터
- 클래스 멤버 함수 내에서만 사용
- 실제 클래스 내의 멤버함수의 경우, 컴파일러가 자동으로 ‘this->’를 앞에 붙여준다.
class Circle{
int radius;
public:
Circle() {this->radius=1;}
Circle(int radius) {this->radius = radius;}
void setRadius(int radius) {this->radius = radius;}
...
};
멤버 변수와 매개변수의 이름이 같은 경우 이를 this로 반드시 구분해서 사용해야 한다.
Circle(int radius){
this->radius = radius;
}
또한 멤버 함수가 객체 자신의 주소를 리턴할 때도 반드시 this가 필요하며, 이는 연산자 중복 시에 매우 필요하다.
class Sample {
public:
Sample* f() {
...
return this;
}
}
this는 사용시 주의할 점이 있다.
- 멤버 함수가 아닌 함수에서 this의 사용이 불가하다.
- static 멤버 함수에서는 this를 사용할 수 없다.
- 객체가 생기기 전에 static 함수가 호출될 수도 있기 때문