• Class란?


C++에서 클래스란 객체를 정의하는 틀 혹은 설계도로서, 클래스에 멤버 변수와 멤버 함수를 선언한다.


흔히들 클래스와 객체의 관계를 붕어빵 틀과 붕어빵에 비유한다.


클래스 - 붕어빵 틀

객체 - 붕어빵


그럼 클래스로 여러 객체를 만들 수 있겠구나??


라는 생각이 든다.


붕어빵 틀은 하나이지만 여러개의 붕어빵을 만들듯이 하나의 클래스로 여러개의 객체를 만들 수 있다.


어? 그럼 객체의 멤버 변수, 멤버 함수는 붕어빵 안의 팥? 슈크림? 이라 생각하면 되는건가??


붕어빵 먹고싶다... 집가는길에 사먹어야지 ㅎ

집가는길에 사먹었다고 한다...ㅎ




클래스에서 객체를 만들어 내면, 객체는 멤버 변수 메모리와 멤버 함수 코드를 실제 가지고 C++프로그램이 실행되는 동안 실존하게된다.


이를 실체 혹은 인스턴스(instance)라고 한다.


붕어빵틀에서 붕어빵을 만들면, 팥앙금 혹은 슈크림을 품은 붕어빵(인스턴스)이 딱 나오게되는 것이다.




클래스는 컴파일이 끝나면 사라지지만, 프로그램은 실행 중에 객체를 생성하여 멤버 변수에 값을 저장하기도 하고 멤버 함수 코드를 실행하기도 한다.


붕어빵이 만들어지면 붕어빵틀은 갖다 치우고, 붕어빵을가지고 크림에 찍어먹을수도 있고, 우유와 함께 먹을 수 있다.




클래스와 객체를 같은 의미로 섞어 부르기도 하지만, 혼란을 피하기위해 정확하게 부르도록 해야한다.


클래스를 객체라고 부르지 않도록 주의하자!


그럼 이제 클래스를 만들어 볼까나?



  • Class 만들기!


원을 추상화 하는 Circle클래스를 만들어 보고, 반지름 r인 멤버변수와 원 넓이를 구하는 멤버 함수를 선언하고 구현해 보자.


class Circle{ //Class 선언부 

public:

int r; //멤버 변수 

double getArea(); //멤버 함수 

};


double Circle::getArea(){         //Class 구현부 

return 3.14 * r * r;

}

<Class 선언부>


1. Circle이라는 클래스를 선언할때, class라는 키워드를 이용해 준다. Circle은 클래스 명이다.


2.  public: 이하의 모든 멤버는 다른 지정자가 선언될 때까지 public으로 접근이 지정된다. (접근 지정은 public, private, protected의 3가지 종류가 있다.) public 접근 지정은 멤버가 아니더라도 외부로부터 접근을 허용한다는 뜻이다. 클래스에서 접근 지정자의 디폴트 값은 private이기 때문에 public으로 지정하고 싶으면 public: 을 설정해 준다. 접근지정을 private로 하기 위해서는 아무것도 쓰지 않거나 private: 를 써 준다. protected 접근 지정은 상속 관계에서 적용되므로 나중에 다루도록 하겠다.


3. 멤버변수인 반지름 r, 멤버함수인 getArea()를 선언한다. 이때 멤버변수 반지름 r은 class 선언시 초기화 할 수 없다. (컴파일에러 발생)


4. 클래스 끝은 반드시 세미콜론을 붙여준다.


<Class 구현부>


5. 멤버함수 getArea()를 구현한다. 이때 return값은 double형 이고, Circle의 멤버함수이므로

double Circle::getArea() 라고 적어준다. 이때 Circle과 getArea() 사이에 있는 :: 를 범위 지정 연산자 라고 하는데, 이것은 같은 이름의 함수가 다른 클래스에 존재할 수 있기 때문에 getArea()라는 함수가 Circle이라는 클래스 안에 있는 것이라고 범위를 지정해 주는 것이다.


6. 면적을 구하는 공식을 return 한다.




클래스를 선언하고 구현하였으니 객체를 한번 생성해 보자!

#include <iostream>


using namespace std; 


class Circle{

public:

int r;

double getArea();

};

double Circle::getArea(){

return 3.14 * r * r;

}


int main(){

Circle donut;        //Circle 클래스의 객체 생성. 객체 이름은 donut

donut.r = 1;

double area = donut.getArea();

cout<< "donut 면적 : "<< area << endl; 

Circle pizza;        //Circle 클래스의 객체 생성. 객체 이름은 pizza

pizza.r = 10;

area = pizza.getArea();

cout<< "donut 면적 : "<< area << endl; 

}

실행 결과

위의 코드를 보자.

donut과 pizza의 코드는 동일하므로 donut만 보겠다.


1. Circle 클래스의 객체를 생성하는데, 객체 명은 donut이다. 이때 객체를 생성하게 되면 클래스 크기의 메모리가 할당된다.


2. 객체의 멤버에 접근하기 위해서는 객체 이름 뒤에 .(점) 을 찍고 뒤에 멤버를 쓴다. 그래서 donut.r 혹은 pizza.r로 Circle 클래스의 멤버변수 r에 접근할 수 있다.


3. 멤버 함수도 동일하게 .(점)으로 접근한다. getArea()의 return형이 double이기 때문에 donut.getArea()를 main함수 area에 초기화 시켜준다.




  • 생성자(Constructor)

생성자란?


예를 들어보자.

공을 생산하는 기계가 있다고 하자. 일반적으로 흰 공을 생산하다가, 빨간색 페인트를 주입하면 빨간공이, 파란색 페인트를 주입하면 파란공이 나온다. 생산 시점에 원하는 색의 페인트를 주입하면 다양한 색의 공을 생산 가능 하다. 


클래스에서 객체를 생성할 때 객체를 초기화 가능하다. 공을 생산할 때 공의 색을 바꿀 수 있다는 말과 동일하다.

클래스는 객체가 생성될 때 자동으로 실행되는 생성자(constructor)라는 특별한 멤버 함수를 통해 객체를 초기화 한다. 한 클래스에 여러 개의 생성자를 둘 수 있으나, 이 중 하나만 실행된다.


class Circle{

....

Circle();                //클래스 이름과 동일한 생성자 함수 선언

Circle(int x);           //리턴 타입 명기하지 않음


void Circle(int x);     //컴파일 에러. 생성자는 리턴타입 없음

   int Circle(int x);      //컴파일 에러. 생성자는 리턴타입 없음

   ....

};


Circle::Circle(){            //매개 변수 없는 생성자 함수 구현

....

  return ;        //생성자 함수를 종료하는 정상적인 리턴문

  return 0;      //컴파일 에러. 생성자 함수는 값을 리턴해서는 안됨

}


Circle::Circle(int x){        //매개 변수를 가진 생성자 함수 구현

....

}


생성자의 특징을 정리해 보자.


1. 생성자의 목적은 객체가 생성될 때 필요한 초기 작업을 위함이다.

예를 들어 멤버 변수의 값을 특정 값으로 설정하거나, 메모리를 동적 할당 받거나, 파일을 열거나, 네트워크를 연결하는 등 객체를 사용하기 전에 필요한 조치를 할 수 있도록 하기 위함이다.


2. 생성자 함수는 오직 한번만 실행된다.

생성자 함수는 각 객체마다 객체가 생성되는 시점에 오직 한 번만 자동으로 실행된다. 공에 페인트를 한번 칠하듯이.


3. 생성자 함수의 이름은 클래스 이름과 동일하게 작성되어야 한다.

이로 인해 생성자는 다른 멤버 함수와 쉽게 구분 할 수 있다.


4. 생성자 함수의 원형에 리턴 타입을 선언하지 않는다.

생성자는 함수이지만 리턴 타입을 선언해서는 안된다. void도 안된다! 


5. 생성자는 중복 가능하다.

위에처럼.. 하지만 중복된 생성자 중 하나만 실행된다.



그럼 이어서 생성자가 실행되는 과정을 살펴보자.

#include <iostream>


using namespace std; 


class Circle{ //Class 선언부 

public:

int r; //멤버 변수 

Circle(); //기본 생성자 

Circle(int x); //매개 변수 있는 생성자 

double getArea(); //멤버 함수 

};


Circle::Circle(){

r = 1; //반지름 값 초기화

cout << "반지름이 : " << r << "인 원 생성" << endl; 

}


Circle::Circle(int x){

r = x; //반지름 값 초기화 

cout << "반지름이 : " << r << "인 원 생성" << endl; 

}


double Circle::getArea(){ //Class 구현부 

return 3.14 * r * r;

}


int main(){

Circle donut; //매개 변수 없는 생성자 호출 

double area = donut.getArea();

cout<< "donut 면적 : "<< area << endl; 

Circle pizza(10); //매개 변수 있는 생성자 호출. 10이 x에 전달됨. 

area = pizza.getArea();

cout<< "pizza 면적 : "<< area << endl; 

}

실행결과


donut이 생성될 때 매개 변수 없는 Circle() 생성자가 호출되고, pizza가 생성될 때 매개 변수 있는 Circle(int x)을 호출하고 매개변수 x 에 10이 전달된다. donut 객체와 pizza 객체는 서로 별도의 공간을 할당받고, r 멤버 변수 역시 각 객체의 공간에 별도로 생성된다. 그러므로 donut 객체의 Circle() 생성자는 donut의 r 멤버변수를 1로 설정하며, pizza 객체의 Circle(int x) 생성자는 pizza 의 r 멤버변수를 10으로 설정한다.



생성자는 꼭 있어야 하는가?


Yes!

어? 그렇다면 생성자를 설정하지 않은 클래스는 어떻게 되는가?

생성자 없는 클래스란 있을 수 없다. 생성자가 없는 클래스에 대해서는, 컴파일러가 기본 생성자(default constructor)를 만들어 삽입하고, 자신이 삽입한 기본 생성자를 호출하도록 컴파일 한다. 그래서 위의 코드 중 처음에 실행한 코드가 에러 없이 진행 될 수 있는 것이다.



기본 생성자(default constructor)


다음과 같이 매개변수 없는 생성자를 말한다.

class Circle{

Circle();    //기본생성자

};


1. default constructor가 자동으로 생성되는 경우.


class Circle{

public:

int r;

double getArea();

};


int main(){

Circle donut;        //정상적으로 컴파일 됨.

}

생성자가 하나도 없는 클래스의 경우 컴파일러는 보이지 않게 기본 생성자를 삽입한다.

자동으로 default constructor가 생성되므로 정상적으로 컴파일이 된다.

그렇다고 해서 작성한 소스코드가 변경되어 저장되는 것은 아니다.



2. default constructor가 자동으로 생성되지 않는 경우.


class Circle{

public:

int r;

double getArea();

Circle(int x);

};


Circle::Circle(int x){

r = x;

}


int main(){

Circle pizza(10);        //Circle(int x) 생성자 호출.

Circle donut;           //컴파일 에러. 기본 생성자가 없기때문.

}


생성자가 하나라도 선언된 클래스의 경우, 컴파일러는 기본생성자를 삽입하지 않는다.

그래서 위의 코드 경우, Circle donut은 기본 생성자가 없기때문에 컴파일 오류가 발생한다.


아무런 생성자를 선언하지 않은 경우라면 기본 생성자가 자동으로 생성되므로 당연히 Circle donut은 정상 컴파일이 되고, Circle pizza에서 컴파일 에러가 발생 할 것이다.




  • 소멸자(Destructor)

소멸자란?


객체 생성시 생성자 함수가 실행되는 것처럼 객체 소멸 시 소멸자 함수가 반드시 실행 된다.

소멸자는 객체가 소멸되는 시점에서 자동으로 호출되는 클래스의 멤버 함수이다.


Circle 클래스에 소멸자를 작성해 보자.

class Circle{

Circle();

Circle(int x);

...

~Circle();        //소멸자 함수 선언

};


Circle::~Circle(){    //소멸자 함수 구현

...

}

소멸자 함수는 리턴타입, 매개변수가 없다.

그리고 생성자와는 다르게 오직 하나만 존재한다.


소멸자의 특징을 살펴보자.


1. 소멸자의 목적은 객체가 사라질 때 필요한 마무리 작업을 위함이다.

객체가 소멸할 때, 동적으로 할당받은 메모리를 운영체제에 돌려주거나, 열어놓은 파일을 저장하고 닫거나, 연결된 네트워크를 해제하는 등 객체가 사라지기 전에 필요한 조치를 하도록 하기 위함이다.


2. 소멸자의 이름은 클래스 이름 앞에 ~를 붙인다.

Circle::~Circle() {...}


3. 소멸자는 리턴 타입이 없으며 어떤 값도 리턴해서도 안된다.

소멸자는 생성자와 같이 리턴 타입 없이 선언되며 어떤 값도 리턴해서는 안 된다.


4. 소멸자는 오직 한 개만 존재하여 매개변수를 가지지 않는다.

소멸자는 생성자와 달리 한 클래스에 한 개만 존재하며 매개변수를 가지지 않는다.


5. 소멸자가 선언되어 있지 않으면 기본 소멸자(default destructor)가 자동으로 생성된다.

생성자와 같이 클래스에 선언하지 않으면 자동으로 기본 소멸자가 생성된다. 

이때 기본 소멸자는 아무 일도 하지 않고 단순 리턴하도록 만들어진다.



소멸자가 어떤식으로 실행되는지 코드를 통해 한번 살펴보자.

위의 Circle 클래스로 생성한 도넛과 피자 객체에다가 소멸자를 추가해보면 다음과 같다.

#include <iostream>


using namespace std; 


class Circle{ //Class 선언부 

public:

int r; //멤버 변수 

Circle(); //기본 생성자 

Circle(int x); //매개 변수 있는 생성자 

~Circle(); //소멸자 선언 

double getArea(); //멤버 함수 

};


Circle::Circle(){

r = 1; //반지름 값 초기화

cout << "반지름이 " << r << "인 원 생성" << endl; 

}


Circle::Circle(int x){

r = x; //반지름 값 초기화 

cout << "반지름이 " << r << "인 원 생성" << endl; 

}  


Circle::~Circle(){    //소멸자 구현

cout<<"반지름이 "<< r <<"인 원 소멸"<<endl; 

}


double Circle::getArea(){ //Class 구현부 

return 3.14 * r * r;

}


int main(){

Circle donut; //매개 변수 없는 생성자 호출 

Circle pizza(10); //매개 변수 있는 생성자 호출. 10이 x에 전달됨. 

}

실행결과

실행 결과와 main 함수를 비교해 보면, 먼저 donut 객체가 생성되고 pizza 객체가 생성된 것을 알 수 있다.

그리고 소멸될때는 나중에 생성된 pizza객체가 먼저 소멸되고,  donut객체가 나중에 소멸된다.


이는 각 객체가 스택에 생성되어 LIFO(Last In First Out) 형태로 구현되기 때문이다.



생성자/소멸자 실행 순서


먼저 지역객체와 전역객체에 대해 살펴보자.


지역 객체(local object) : 함수 내에서 선언된 객체

전역 객체(global object) : 함수 바깥에 선언된 객체

class Circle{

...

};

Circle globalCircle;        //전역 객체

void f(){

Circle localCircle;    //지역 객체

}

전역 객체프로그램이 로딩될 때 생성되어 프로그램이 종료할때(main함수 종료시) 소멸되고,

지역 객체그객체가 포함된 함수가 호출될 때 생성되어 함수 종료시에 반환되어 소멸된다.

전역 객체나 지역 객체 모두 생성된 순서의 반대순으로 소멸 된다. 이는 stack의 특성이다.



다음 페이지에선 접근지정, 인라인 함수, 구조체 그리고 제대로된 전문가의 길로 가기위한 바람직한 C++프로그램 작성법에대해 알아보자.




오류사항이 있다면 알려주시면 감사하겠습니다.

Posted by Jyoel :