클래스의 특징과 객체 활용

const

const 멤버함수

const 사용 목적은 객체의 멤버변수를 변경시킬 수 없도록 하기 위함이다.
const 멤버함수는 객체의 멤버변수를 변경할 수 없는 읽지 전용함수로, const로 지정되지 않은 다른 함수도 호출할 수 없다.

    int GetX() const
    {
        return this->x;
    }

const 객체

객체를 상수로 취급하여 초기화된 데이터 외에 다른 데이터로 변경 불가능하게 만든다.

int main()
{
    const MousePoint pt1(10, 20);
    MousePoint pt2(100, 200);

    pt1 = pt2;
}

pt1은 상수화 되었기 때문에 수정할 수 없다.

static

전역변수는 현재 클래스뿐 아니라 다른 클래스에서도 접근 가능해 객체지향 철학에 위배된다. static변수는 전역변수와 같은 성질의 멤버 함수이되, 외부 클래스에서는 접근할 수 없는 변수이다.

static 멤버변수

메모리 구조상 전역변수와 같은 공간인 데이터영역에 메모리가 할당. 전역변수와 비슷한 성질을 가지면서도 클래스에 국한되어 사용된다는 차이가 있다.

static으로 선언하면 클래스에 대한 객체가 생성되어도, 단 하나의 변수만 메모리에 할당된다. static변수가 private속성을 갖는다면 같은 클래스의 멤버함수에서만 접근 가능하고 클래스 외부함수에서는 접근이 불가능하다.

전역변수와 같이 프로그램 시작 시 한번만 초기화한다. 또한 접근지정자는 초기화 과정에 전혀 영향을 주지 않으므로, private속성의 static 멤버변수일지라도 클래스 외부에서 초기화 가능하다.

소속클래스명 ::영역결정 연산자 표시해줌으로써 static멤버변수의 소속을 지정해준다

double Deposit::dInterestRate = 0.05;
#include <iostream>
using namespace std;

class Deposit
{
public:
    Deposit() // 디폴트 생성자
    {
    }

    Deposit(char *name, double balance) // 생성자(오버로딩)
    {
        strName = name;
        dBalance = balance;
    }

    void BankBalnce()
    {
        dBalance = dBalance + (dBalance * dInterestRate);
    }

    static void SetInterestRate(double dNewRate)
    {
        dInterestRate = dNewRate;
    }

private:
    char *strName;
    double dBalance;
    static double dInterestRate;
};

double Deposit::dInterestRate = 0.05;

int main()
{
    Deposit custom1;
    Deposit custom2;

    Deposit::SetInterestRate(0.03);
}

static 멤버함수

static 멤버변수의 값을 변경하기 위해 static 멤버 함수를 사용한다.

private 속성의 static 멤버변수 dInterestRate에 접근하기 위해서 사용한다.

#include <iostream>
using namespace std;

class Deposit
{
public:
    Deposit() // 디폴트 생성자
    {
    }

    Deposit(char *name, double balance) // 생성자(오버로딩)
    {
        strName = name;
        dBalance = balance;
    }

    void BankBalnce()
    {
        dBalance = dBalance + (dBalance * dInterestRate);
    }

    static void SetInterestRate(double dNewRate)
    {
        dInterestRate = dNewRate;
    }

    static double GetInterestRate()
    {
        return dInterestRate;
    }

private:
    char *strName;
    double dBalance;
    static double dInterestRate;
};

double Deposit::dInterestRate = 0.05;

int main()
{
    cout << "변경 전 이자율 : " << Deposit::GetInterestRate() << endl;
    Deposit::SetInterestRate(0.03); // 이자율 변경
    cout << "변경된 이자율 : " << Deposit::GetInterestRate() << endl;
}
현재 이자율 : 0.05
변경된 이자율 : 0.03

static 멤버함수는 특정 객체에 영향을 미치지 않으므로 this포인터를 사용하지 못한다.
또한 static 멤버함수 내에서 일반 멤버함수를 호출하지 못하는데, 일반 멤버함수는 객체를 통해서 관리되는 함수이기 때문이다.

객체 상태 정보 관리

static 멤버를 사용하여 객체의 상태 정보 관리에 사용 가능하다.

#include <iostream>
using namespace std;

class Deposit
{
public:
    Deposit() // 디폴트 생성자
    {
        nCount++;
        cout << "객체 개수(생성) : " << nCount << endl;
    }
    ~Deposit()
    {
        nCount--;
        cout << "객체 개수(소멸) : " << nCount << endl;
    }

    Deposit(char *name, double balance) // 생성자(오버로딩)
    {
        strName = name;
        dBalance = balance;
    }

    void BankBalnce()
    {
        dBalance = dBalance + (dBalance * dInterestRate);
    }

    static void SetInterestRate(double dNewRate)
    {
        dInterestRate = dNewRate;
    }

    static double GetInterestRate()
    {
        return dInterestRate;
    }

private:
    char *strName;
    double dBalance;
    static double dInterestRate;
    static int nCount;
};

double Deposit::dInterestRate = 0.05;
int Deposit::nCount = 0;

int main()
{
    Deposit custom1;
    Deposit custom2;
    Deposit custom3;
    Deposit custom4;
}
객체 개수(생성) : 1
객체 개수(생성) : 2
객체 개수(생성) : 3
객체 개수(생성) : 4
객체 개수(소멸) : 3
객체 개수(소멸) : 2
객체 개수(소멸) : 1
객체 개수(소멸) : 0

프렌드(Friend)

클래스의 멤버변수 접근 지정자는 private으로 외부로부터 접근을 차단한다. 오직 멤버함수를 통해서만 멤버변수에 접근이 가능한데, 이 규칙을 깨는 변칙적인 기능이 프렌드이다.(나에게 접근을 허용하겠다)

클래스의 동적 메모리 할당

new/delete

동적 메모리의 필요성

동적메모리는 실행시간에 할당되어 사용되는 메모리 블록을 말한다.(힙)
<->정적 메모리(스택)

동적 메모리는 프로그램 작성 시 얼마만큼의 메모리가 필요한지 알지 못하는 경우에 사용된다. 객체지향 언어에서는 동적 메모리 생성시 new, 소멸시 delete(C++) 연산자를 사용한다.

#include <iostream>
using namespace std;

int main()
{
    int *pBuffer;
    int nLength;
    cout << "힙 영역에 할당할 메모리 수 :";
    cin >> nLength;

    pBuffer = new int[nLength];

    for (int i = 0; i < nLength; i++)
        pBuffer[i] = i + 1;
    for (int i = 0; i < nLength; i++)
        cout << pBuffer[i] << " ";
        
    cout << endl;
    delete[] pBuffer;
}
힙 영역에 할당할 메모리 수 : 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 

힙 영역에 객체 생성

new연산자를 통해 클래스 타입의 메모리를 생성 후 선언한 객체 포인터 변수에 생성 메모리 주소를 넘겨준다

int main()
{
    MousePoint *pt;
    pt = new MousePoint(10, 20);

    cout << "x좌표 : " << pt->GetX() << endl;
    cout << "y좌표 : " << pt->GetY() << endl;

    delete pt;
}
x좌표 : 10
y좌표 : 20

객체 포인터 배열

여러 개의 객체를 관리하는 방법으로 배열을 선언하여 사용할 수 있는데, 이를 객체 포인터 배열이라고 한다.

int main()
{
    MousePoint *pArr[3]; //1
    pArr[0] = new MousePoint(10, 20);
    pArr[1] = new MousePoint(100, 200);
    pArr[2] = new MousePoint(1000, 2000);

    for (int i = 0; i < 3; i++)
        cout << pArr[i]->GetX() << ", " << pArr[i]->GetY() << endl;

    for (int i = 0; i < 3; i++)
        delete pArr[i]; //1

    MousePoint* mp = new MousePoint[3]; //2
    delete[] mp; //2
}
10, 20
100, 200
1000, 2000

//1 객체를 delete
//2 배열을 delete

포인터 멤버변수를 갖는 클래스

클래스 내의 동적 메모리 할당

#include <iostream>
using namespace std;
class String
{
public:
    String(char ch, int nSize);
    ~String();

private:
    int nLength;
    char *pBuffer;
};

String::String(char ch, int nSize)
{
    nLength = nSize;
    pBuffer = new char[nLength + 1];
    memset(pBuffer, ch, nLength);
    pBuffer[nLength] = '\0';
    cout << "pBuffer : " << pBuffer << endl;
    cout << "nLength : " << nLength << endl;
}
String::~String()
{
    delete[] pBuffer;
}
int main()
{
    String str('A', 5);
}
pBuffer : AAAAA
nLength : 5

객체끼리의 대입

int main()
{
    String str1('A', 3);
    String str2('B', 5);
    str1 = str2;
}

객체끼리의 대입할 때 객체끼리의 대입은 멤버 대 멤버의 복사가 일어난다.

str2.nLength = str1.nLength;
str2.pBuffer = str1.pBuffer;
  • 대입 이전

  • 대입 이후

같은 주소값을 갖는 것은 문제되지 않지만, 객체가 소멸할 때 stack메모리 중 str2객체가 먼저 소멸하는데, 소멸할 때 소멸자를 호출하면 str2의 pBuffer멤버변수가 가리키고 있는 힙 영역을 delete하게 된다. 시스템으로 다시 반납

str1도 같은 메모리를 가르키고 있으므로 str1 객체 소멸시 pBuffer가 가리키는 메모리 영역을 다시 delete하려 하는데, 이 메모리는 이미 비워지고 시스템에 반납되어 런타임에서 접근하지 못해 오류가 발생한다.

이 문제를 해결하기 위해 대입 연산자 오버로딩(Assignment Operator Overloading)이라는 방법을 사용한다.

profile
Done is better than perfect

0개의 댓글