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 함수가 호출될 수도 있기 때문