이번 장에서는 본격적으로 '클래스'에 대해서 설명하겠습니다. 저번 장에서는 C언어의 구조체 관점(?)에서 살펴보았죠. 클래스의 아주 중요한 정보은닉, 캡슐화에 대한 개념을 설명하고 생성자, 소멸자 그리고 기타 필요한 설명을 이어나가도록 하겠습니다~

 정보 은닉(Information Hiding)

정보 은닉은 객체 내부의 존재하는 데이터를 숨긴다고 생각하시면 됩니다. 결론부터 말씀드리자면 객체의 외부에서 객체 내에 존재하는 맴버 변수에 직접 접근하는 권한을 허용하면 안됩니다. 즉, 객체 내에 존재하는 맴버 변수에 직접 접근하는 것은 정보 은닉에 위배됩니다.

 class point
{
   public:
          int x;
          int y;
 };

 int main(void)
{
  point p;
  p.x = 10;
  p.y = 10;
  return 0;
 }

 point 라는 클래스는 x좌표와 y좌표값을 맴버 변수로 정의하고 있습니다. 메인 함수내에서 p라는 point형 객체를 만들어준 뒤에 p.x = 10; p.y = 10; 라는 코드를 통해 클래스 맴버 변수의 값을 바꾸어 줍니다. 즉, 이처럼 외부에서 맴버 변수로의 직접 접근이 가능해집니다. 
 
 그렇다면 어떻게 정보 은닉을 할 수 있을까요? 모든 맴버 변수를 private로 선언해주어야 합니다. 그러나 private 선언 후에는 직접 접근을 할 수 없으므로 간접 접근을 위한 특별한 '경로'를 만들어 주어야 합니다. 맴버 함수로 말이죠~

 class point
{
   private:
          int x;
          int y;
   public:
        void change_point(int _x, int _y)
        {
             x = _x;
             y = _y;
         }
 };

 int main(void)
{
   point p;
   p.change_point(10,10);
   return 0;
 }

 클래스의 선언에서 private나 public 키워드를 사용하지 않고 그냥 선언해준다면 기본적으로 private로 선언이 됩니다. change_point함수는 public 맴버이므로 외부에서 접근이 가능하고, private로 선언된 맴버 변수에도 접근이 가능합니다. 즉, 클래스 외부에서 이 함수를 이용해서 맴버 변수 x, y에 간접적인 접근이 가능해지죠. 보통 이런 함수를 엑세스 함수(메소드)라고 부릅니다.

 이처럼 정보은닉은 어려운 개념은 아닙니다~ 직관적으로 쉽게 이해할 수 있는 부분이죠~ 또 다른 개념인 캡슐화에 대해서 알아보겠습니다. 

 캡슐화

캡슐화란? 관련 있는 데이터와 함수를 하나의 단위로 묶는 것 입니다. 즉, 관련 있는 데이터와 함수를 클래스라는 하나의 캡슐 내에 모두 정의하는 것 입니다. 

 class point
{
   private:
          int x;
          int y;
   public:
        void change_point(int _x, int _y)
        {
             x = _x;
             y = _y;
         }
        int getx() {return x;}
        int gety() {return y;}
 };

 class pointview
{
  public:
       void showdata(point p)
       {
           cout << "x 값 : " << p.getx() <<endl;
           cout << "y 값 : " << p.gety() <<endl;
        }
  };

 int main()
{
    point p;
    p.change_point(10, 10);
   
    pointview view;
    view.showdata(p);
    return 0;
 }

 감이 오시나요~? 사실상 pointview 클래스는 상당히 불필요한 존재입니다. point클래스에 좌표 값을 출력할 함수가 없으므로 새로운 클래스를 정의하여 출력시키고 있죠. 그것도 view.showdata(p)라는 call-by-value를 통해서 아주 무겁게(?) 출력하고 있습니다. 무겁게라는 말이 이해 안가시면 레퍼런스 쪽을 다시 공부하고 오세요~!!! 무튼 캡슐화란 관련 있는 데이터와 함수를 하나의 클래스로 정의하는 것이라고 하였습니다. 그런데 위의 예제에서 보면 x, y좌표에 관련된 데이터와 함수가 두 개의 클래스로 양분되어 있습니다. 이는 캡슐화의 원칙에 어긋나버리게 되는거죠~ 그러므로 캡슐화에 맞도록 수정해보겠습니다~

 class point
{
   private:
          int x;
          int y;
   public:
        void change_point(int _x, int _y)
        {
             x = _x;
             y = _y;
         }
        int getx() {return x;}
        int gety() {return y;}
   
        void showdata();
 };

void point::showdata()
{
           cout << "x 값 : " << x <<endl;
           cout << "y 값 : " << y <<endl;
}
     

 int main()
{
    point p;
    p.change_point(10, 10);
  
    p.showdata();
    
     return 0;
 }

 캡슐화는 생각보다 상당히 중요한 개념입니다. 실제로도 이 부분을 무시하고 프로그래밍하면 추 후에 문제가 발생했을때 상당한...뻘..짓(?)으로 이어지게 되죠~

 생성자(constructor) & 소멸자(destructor)

바로 예제를 보면서 설명해보겠습니다.

class person
{
  private:
     char name[20];
     char phone[20];
     int age;

  public:
     void show();
 };

void person::show()
{
   cout<<name<<phone<<age<<endl;
 }

int main()
{
   person p = {"kim", "010-1111-2222", 10};
   p.show();
   return 0;
 }

 진한 부분을 보시면 객체를 생성과 동시에 원하는 값으로 초기화 하려고 하고있습니다. 그러나 중요한 점은 맴버 변수가 private로 선언되어 있다는 점이죠. 즉, 위와 같은코드의 접근은 허용되지 않습니다. 그렇다고 엑세스 함수를 사용해서 접근을 하면 말처럼 "생성과 동시에 초기화" 가 아니지 않습니까~ 액세스 함수를 사용하면 "생성 후 초기화" 가 될 뿐이죠.. 이러한 문제를 해결해 주는 것이 생성자입니다.

 class person
{
   char name[20] = "boxbop";
   char phone[20] = "010-1111-2222";
   int age = 10;
 }

 그렇다고 이러한 초기화 방법이 가능할까요?! 분명히 클래스나 구조체에서는 선언하는데 있어서 맴버를 초기화 할 수 없습니다~ 무조건 맴버 변수를 선언!만 할 수 있습니다. 그러나 C#, java에서는 위와 같은 초기화가 가능하긴 합니다만 C나 C++에서는 절대 불가능합니다~ 참고하시구요~

 그렇다면 생성자는 어떤모양일까요~? 그전에 객체의 생성과정을 잠깐 살펴보겠습니다. 객체는 메모리를 할당한 후에 -> 생성자를 호출합니다. 생성자는 무엇이냐면 함수이고, 클래스의 이름과 같은 이름을 가지고 있고, 리턴하지도 않고 리턴 타입도 선언되지 않습니다. 다음 예제를 살펴봅시다~

class person
{
  private:
     char name[20];
     char phone[20];
     int age;

  public:
     void show();
   
     person()
     {
          char name[20] = "boxbop";
          char phone[20] = "010-1111-2222";
          int age = 10;
      }
      
 };

볼드 처리된 부분이 생성자 입니다. 리턴 타입도 없고 리턴도 없습니다. 클래스의 이름과도 같죠~? 딱 보시고 아! 생성자구나! 라고 생각하면 됩니다~ 따라서

int main(void)
{
   person p;
   p.show();
  
   return 0;
 }

 위와 같은 코드의 출력은 boxbop, 010-1111-2222, 10 을 출력하게 됩니다. 생성자 때문에 단지 객체를 생성만 해주었을 뿐인대 초기화가 되어있죠~ 물론 생성자가 없는 상태에서 객체를 생성해주면 쓰레기 값이 들어있습니다. 생성자를 좀 더 바꾸어 봅시다.

     person(char* _name, char* _phone, int _age)
     {
         strcpy(name, _name);
         strcpy(phone, _phone);
         age = _age;        
      }
      

 이렇게 바꾸어 주었습니다. 설마 name = _name; 이렇게 코딩하시는 분들 계시죠~? 그렇게 하면 안되는거 아시겠죠~? 모르신다면 포인트쪽을 다시 한번 공부해오시길 부탁드리겠습니다~ 힌트는 메모리 주소!!! 여기까지~ㅋㅋㅋ

 자 그렇다면 위와 같은 생성자를 어떻게 사용할까요~? 예제를 통해 보여드리겠습니다.

 int main()
{
   person p (boxbop,010-1111-2222,10);
   p.show();
   return 0;
 }

 객체를 생성과 동시에 초기화 한 것이 보이시죠~? 이렇게 생성자는 객체 생성 시 원하는 값으로 초기화하기 위한 용도로 사용됩니다~

 디폴트(Default) 생성자

생성자에는 몇 가지 특징이 있습니다.
1. 생성자를 정의하지 않으면 디폴트 생성자가 자동 삽입됩니다.
2. 생성자도 함수이므로 오버로딩이 가능합니다.
3. 생성자도 함수이므로 디폴트 매개 변수의 설정이 가능합니다.

class point
{
   int x,y;
   public:
   point() {}
 }

볼드 처리 된 부분이 디폴트 생성자의 형태입니다. 그러나 디폴트 생성자같은 경우에는 프로그래머가 정의해 놓은 생성자가 하나라도 존재하면 디폴트 생성자가 자동으로 삽입되지 않습니다.

class point
{
     int x,y;
   public:
     point(int _x, int _y) { x = _x, y = _y; }
 }

int main(void)
{
   point p1(10, 20); // 가능
   point p2; // 불가능!!!

   return 0;
 }

위와 같은 예제는 컴파일 에러를 발생시키죠~ 그러나 생성자도 오버로딩이 된다고 하지 않았습니까~? 다음과 같이 수정해주면 됩니다.

class point
{
     int x,y;
   public:
     point(int _x, int _y) { x = _x, y = _y; }
     point() {}
 }

int main(void)
{
   point p1(10, 20); // 가능
   point p2; // 가능 (디폴트 생성자)

   return 0;
 }

 위 예제는 디폴트 생성자를 따로 삽입했습니다. 때문에 p2의 선언이 가능해진 것 입니다. 그리고 생성자도 함수라고 하지 않았습니까~? 때문에 디폴트 매개변수를 설정할 수 있습니다. 다음과 같이 말이죠.

class point
{
     int x,y;
   public:
     point(int _x=0, int _y=0) { x = _x, y = _y; }
     
 }

int main(void)
{
   point p1(10, 20); // 가능
   point p2; // 가능 (디폴트 매개 변수)

   return 0;
 }

디폴트 매개 변수 때문에 디폴트 생성자가 없어도 p2의 선언이 가능합니다.

 생성자와 동적할당

이번에는  생성자 내에서 메로리 공간을 동적 할당하는 경우에 대해서 살펴보겠습니다. 예제는 조금 복잡하겠네요~

#include <iostream>
using std::cout;
using std::endl;

class person
{
           char *name, *phone;
           int age;
     public:
           person(char* _name, char* _phone, int _age);
           void show();
 };

 person::person(char* _name, char* _phone, int _age)
{
      name = new char[strlen(_name)+1];
      strcpy(name, _name);
      phone = new char[strlen(_phone)+1];
      strcpy(phone, _phoen);
      age = _age;
 }
 void person::show()
{
     cout<<name<<phoen<<age<<endl;
 }

int main()
{
     person p("kim", "010-1111-2222",10);
     p.show();
     return 0;
 }

 p라는 객체를 생성하고 있죠? 제일 먼저 메모리 공간이 할당되고, p라는 이름이 부여됩니다. 그다음으로는 생성자를 호출하면서 선언되어 있던 문자열과 정수가 인자로 전달되죠. 생성자 내에서는 전달된 문자열의 길이를 계산해서 메모리 공간을 할당하고 문자열을 복사합니다.
 결과적으로 객체 p는 main 함수 내에서 생성되었으므로 스택 영역에 할당이 됩니다만 맴버 변수 name과 phone이 가리키는 메모리 공간은 힙영역이 됩니다. 그러나 위 코드에서는 생성자 내에서 동적 할당한 메모리 공간을 해제해 주지 않고 있습니다. 따라서 다음과 같이 해결해주어야 합니다.
    
#include <iostream>
using std::cout;
using std::endl;

class person
{
           char *name, *phone;
           int age;
     public:
           person(char* _name, char* _phone, int _age);
           void show();
           void delmemory();
 };

 person::person(char* _name, char* _phone, int _age)
{
      name = new char[strlen(_name)+1];
      strcpy(name, _name);
      phone = new char[strlen(_phone)+1];
      strcpy(phone, _phoen);
      age = _age;
 }
 void person::show()
{
     cout<<name<<phoen<<age<<endl;
 }
 void person::delmemory()
{
   delete []name;
   delete []phone;
 }

int main()
{
     person p("kim", "010-1111-2222",10);
     p.show();
     p.delmemory();
     return 0;
 }

 이렇게 보면 문제가 해결된 듯 합니다. 그러나 사실은 그렇지 않죠. 만약 이런식의 클래스가 여러개가 있다고 한다면 상당히 골치아파집니다. 귀찮죠.... 그리고 메모리 해제를 놓칠 수 있습니다. 사람은 완벽하지 않기 때문이죠. 그래서 등장한 것이 소멸자(destructor)입니다.

 소멸자(destructor)

 객체의 소멸 과정도 객체의 생성 과정과 마찬가지로 소멸자 호출 -> 메모리 반환이라는 비슷한 과정을 거치게 됩니다. 소멸자의 특징은 다음과 같습니다.

1. 함수입니다.
2. 클래스의 이름 앞에 '~'가 붙습니다.
3. 리턴, 리턴 타입이 존재하지 않습니다.
4. 매개 변수를 받지 않고, 오버로딩, 디폴트 매개 변수의 선언도 불가능합니다.

 바로 다음과 같은 예제를 살펴보겠습니다.

#include <iostream>
using std::cout;
using std::endl;

class person
{
           char *name, *phone;
           int age;
     public:
           person(char* _name, char* _phone, int _age);
           void show();
           ~person();
 };

 person::person(char* _name, char* _phone, int _age)
{
      name = new char[strlen(_name)+1];
      strcpy(name, _name);
      phone = new char[strlen(_phone)+1];
      strcpy(phone, _phoen);
      age = _age;
 }
 person::~person()
{
      delete []name;
      delete []phone;
 }
 void person::show()
{
     cout<<name<<phoen<<age<<endl;
 }


int main()
{
     person p("kim", "010-1111-2222",10);
     p.show();
     return 0; /*리턴과 동시에 p객체 소멸
                   이 부분에서 소멸자 호출*/
 }

 볼드 처리된 부분을 보시면 리턴타입도, 인자도 받지 않습니다. 그리고 소멸자는 객체 소멸시 자동적으로 호출됩니다. 즉 소멸자의 가장 중요한 특징은 객체 소멸 시 반드시 한번 호출된다는 것입니다.

 정리하자면 생성자 내에서 메모리를 동적 할당하는 경우, 이를 해제하기 위해서 반드시 소멸자를 정의해야 합니다!

 디폴트(default) 소멸자

디폴트 생성자를 살펴보았으니 어렵지 않게 이해할 수 있습니다. 디폴트 생성자 처럼 아무것도 정의해주지 않으면 자동으로 삽입이 됩니다.

class point                                     class point
{                                                   {
   int x, y;                                          int x, y;
 public:                                           public: 
   void show();                                   point() {}
 }                                                    ~point() {}
                                                       void show()
                                                      }

 왼쪽과 오른쪽의 코드는 완전하게 동일합니다. 대충 이해가 가셨죠~?

 클래스 그리고 배열

C++에서는 이러한 객체 포인터 배열도 선언이 가능합니다. C언어에서 구조체 배열기억나시죠~? 동일한 개념입니다. 다만 객체 별이 생성되기 위해서는 void생성자의 호출이 요구됩니다. 일단 보시죠

class point
{
         int x, y;
    public:
         point()
         {
             cout<<"void생성자 호출"<<endl;
             x = y = 0;
          }
         point(int _x, int _y)
         {
             x = _x, y = _y;
          }
         void change(int _x, int_y) { x = _x, y = _y; }
 };

 int main()
 {
      point array[3]; //void 생성자의 호출이 요구됨

      array[0].change(1,1);
      array[1].change(2,2);
      array[2].change(3,3):

      return 0;
  }

 출력결과를 보시면 "void생성자 호출" 이라는 메시지가 총 3번 출력됩니다. 즉, 객체 배열이 생성되기 위해서는 void 생성자의 호출이 요구된다는 이야기죠. 일단 아시다시피 배열 안에 객체가 존재합니다. 바로 이어서 객체 포인터 배열로 넘어가겠습니다. 객체 포인터 배열이란 객체를 가리킬 수 있는 포인터로 구성이 되어 있는 배열을 의미합니다. 추가적으로 다음 예제를 통해서 어떻게 객체를 동적으로 생성 및 소멸하는지도 알아보겠습니다.

class point
{
         int x, y;
    public:
         point()
         {
             cout<<"void생성자 호출"<<endl;
             x = y = 0;
          }
         point(int _x, int _y)
         {
             x = _x, y = _y;
          }
         void change(int _x, int_y) { x = _x, y = _y; }
 };

 int main()
 {
      point *array[3];
  
      array[0] = new point(1,1);
      array[1] = new point(2,2);
      array[2] = new point(3,3);

      array[1]->change(4,4); //포인터에 접근은 ' -> ' 연산자 사용

      delete array[0];
      delete array[1];
      delete array[2];

      return 0;
  }

 메인함수 첫 번째 줄에서는 포인터 배열을 선언하고 있습니다. 따라서 point객체 3개를 가리킬 수 있는 배열이 생성됩니다. 즉 point객체의 주소값을 저장할 수 있는 배열이 생성되죠. new point(1,1)은 1, 1을 인자로 받을 수 있는 point 클래스의 생성자를 호출하면서 point객체를 생성합니다. 물론 힘영역에 생성이 될 것이고, 생성된 객체의 주소 값이 point* 형으로 반환될 것입니다. 단, 여기서는 객체의 배열이 생성되는 것이 아니므로 void생성자의 호출이 요구되지는 않습니다. 동적할당 과정에서 다르게 정의되어 있는 생성자를 호출하기 때문이기도 하구요~ 이해가 충분히 가셨을꺼라 믿습니다~

 this 포인터

맴버 함수 내에서는 this라는 이름의 포인터를 사용할 수 있습니다. this 포인터는 포인터의 개념을 잘 잡고 있다면 예제만으로도 아주 쉽게 이해할 수 있을겁니다.

class point
{
   public:
      person*  GetThis()
      {
          return this; //this 포인터를 리턴
       }
  };

int main()
{
    point *ptr = new person();
    cout<<"포인터 ptr의 값 :"<< ptr <<endl;
    cout<<"ptr의 this 값 :" << ptr->GetThis()<<endl;
    return 0;
 }

출력값을 살펴보시면 알겠지만 포인터 ptr의 값과 ptr의 this값이 동일합니다. this는 자기 자신을 가리키는 용도로 사용되는 포인터 입니다. 자기 참조 포인터라고 하죠. this 포인터의 유용함을 한번 더 살펴보겠습니다.

 class data
{
    int aaa;
   public:
    data(int aaa)
    {
        aaa = aaa;
     }
  };

위와 같은 클래스가 있다고 봅시다. 볼드처리된 부분을 보시면 의아해 하실겁니다. 왼쪽의 aaa와 오른쪽의 aaa가 서로 어떤 aaa인지 구분이 안가죠. 맴버 변수인 aaa인지 매개변수로 받는 aaa인지 말입니다. 우리의 의도는 맴버변수 aaa에 매개변수 aaa를 대입하는 겁니다. 때문에 왼쪽이 맴버변수, 오른쪽이 매개변수가 되어야 우리가 의도한 코드가되는거죠. this포인터를 이용하여 다음과 같이 변경해줍니다.
 class data
{
    int aaa;
   public:
    data(int aaa)
    {
        this->aaa = aaa;
     }
  };

만약 이러한 클래스를 메인함수에서 data a(111);으로 생성해주었다고 해봅시다. 이 객체는 주소값 0x11번지에 할당되어 있습니다. 이 상황에서 클래스 내부에 있는 this는 0x11번지를 가리키는 포인터가 되는겁니다. 때문에 this->aaa는 0x11번지에 할당된 객체의 aaa라고 인식을 하게됩니다. 신기하지않나요~? 객체의 주소 값을 가지고 지역 변수(매개변수)에 접근을 하다니 말입니다!!! 그러나 이러한 문제는 변수의 이름을 바꾸어주는 것으로 상당히 간단하게 해결할 수 있겠죠~?

 friend 선언

private으로 선언된 맴버 변수는 외부 접근이 허용되지 않는다고 설명했습니다. 그러나 frined 선언을 통해서 private으로 선언된 맴버 변수의 접근을 허용할 수 있습니다.

class number
{
         int val;
      public:
         number()
         {
              val = 0;
          }
          friend void setting (number& c, int val);
 };

 void setting(number& c, int val) // 이건 전역함수입니다!!!
{
     c.val = val;
 }

 분명히 클래스 외부로 나온 setting함수는 전역 함수 입니다. 그럼에도 불구하고 c.val 이라는 코드로 number 객체의 private 맴버인 val에 접근을 하고 있지요. 이것이 가능한 이유는 클래스 내에서 setting 함수를 friend 키워드를 통해 정의해주었기 때문입니다. 즉, 전역함수 void setting(number& c, int val)을 friend로 선언하고 있습니다.
 다시 말하자면 클래내에서 함수를 정의할때 friend 키워드를 붙여주게 되면 이는 전역함수라고 취급을 해버리면서 선언되는 전역함수는 해당 클래스의 private 맴버 변수에도 접근을 허용하게 해줍니다.

 클래스에 대한 friend 선언

friend 선언은 클래스에도 사용이 가능합니다. 그러나 여기서는 방향성에 대해서 주의하셔야 합니다.

class A
{
   private:
        int number;
        friend class B; //B에게 내 모든걸 다줄꺼야!
 };

 class B
{
   private:
        void setting(A& a, int value)
        {
              a.number = value;
         }
 };

 클래스 A에서 보시면 B 클래스를 friend로 선언하고 있습니다. 이것은 A클래스는 B클래스에게 private 영역의 접근을 허용하겠다는 의미가 됩니다. "B 클래스는 나의 private 맴버 변수에 접근을 해도 좋다!!" 라고 알려줍니다. 그러나 B클래스는 A클래스를 friend로 선언하지 않았기 때문에 A클래스는 B클래스의 private 맴버 변수에 접근할 수 없게 됩니다. 이것이 friend 선언의 단방향성 입니다.

 이번 장은 여기서 마치도록 하겠습니다.... 생각보다 많이 길어졌네요 ㅠㅠㅠ 사실 어제 포스팅하다가 졸려서 백업해놓고 오늘 마무리했습니다. 그래도 나름 오래 걸렸어요...ㅠㅠㅠ 오늘은 토요일인대... 불토인대...ㅠㅠㅠㅠㅠ흑흑 열심히 해야죠!

 


 드디어 클래스를 공부하는군요. 클래스와 우리가 기존에 알고 있던 구조체를 같이 언급하면서 설명하도록 하겠습니다. 프로그래밍을 할 때, 관련 있는 데이터를 하나로 묶습니다. 관리하기도, 프로그래밍 하기에도 편하기 때문이죠. 그래서 구조체를 사용했습니다. 즉, 부류를 형성하는 데이터들을 하나의 자료형으로 정의해서, 관리 및 프로그램 구현에 도움을 주고있습니다.

 지금까지 우리는 구조체를 공부하면서 구조체 내부에 오직 변수만 정의하여 사용해왔습니다. 그러나 클래스는 함수까지도 포함을해서 정의합니다. 어떠한 데이터들이 부류를 형성한다고 가정해 봅시다. 예를들어 은행업무에서의 고객이름, 비밀번호, 계좌번호, 잔액은 모두 고객에 대한 정보로 부류를 이루고 있습니다. 출금기능과 입금기능은 함수로 구현을 했고 이러한 기능도 고객 정보에서 구현이 됩니다. 결론적으로 클래스의 개념은 구조체의 개념을 포함하고 있습니다. 클래스는 변수와 함수를 포함하기 때문이죠

 이제부터 클래스 내부에 정의되어 있는 변수를 맴버변수, 함수를 맴버 함수로 이야기 하겠습니다. 그리고 클래스의 변수는 객체라고 부르겠습니다. 클래스를 정의하는 방법은 구조체와 아주 비슷하니까 예제보시고 아 이렇구나~라고 생각만 하세요~

 클래스 맴버의 접근제어 방식에 대하여 알아보겠습니다. 무슨말이냐~ 하면요 클래스 안에 선언되어 있는 맴버의 접근 허용 범위를 이야기하는 것 입니다. 

  public, protected, private

이렇게 3개의 키워드가 존재합니다. 클래스 내에서 변수를 가지고 노는 것이 내부 접근, 클래스를 벗어나 다른 곳에서 클래스의 변수(맴버변수)를 가지고 노는 것이 외부 접근이라고 생각하시면 됩니다. 이러한 접근을 제어하는 역할을 하는데요 한번 살펴보도록 하겠습니다. 아! protected 키워드는 나중에 상속을 공부할때 언급하기로 하고 일단은 public과 private만 설명하겠습니다.

 class person
{
   private:
          int height;
   public:
          int age;
          void hchange();
          void achange();
          void show()
          {
                 cout<<"hi boxbop"<<endl;
           }
 };

 int main(void)
{
   person p;
   p.height = 10; //에러
   p.age = 10;  //가능
   p.show();  //가능
 }

 height 라는 변수는 private, 그외에는 public을 지정해주었습니다.
메인 함수에서 이러한 맴버 변수들에 접근하고 있습니다 요런걸 외부 접근이라고 하죠~ 여기서 height의 값을 10으로 바꾸는 부분에서는 에러를 발생시킵니다. 그외에는 public으로 설정해주었으니까 가능하구요. private는 맴버가 선언이 되면 클래스의 내부 접근만 허용하겠다는 이야기 입니다. 반면에 public은 클래스 외부 접근도 허용하겠다는 이야기가 되죠~

 클래스 내부의 함수를 외부에 정의하는 법을 배워보겠습니다. 이 방법을 왜 공부해보냐면 위의 클래스 같은 경우에는 함수의 구현부분이 상당히 짧습니다. 그러나 실제로는 짧지가 않죠! 때문에 클래스가 상당히 조잡(?)해지는 경우가 있을 수 있는데요 이를 방지하기 위해서 살펴볼거랍니다~ 위 예제에 이어서 설명하도록 하겠습니다.

 void p::hchange()
{
   heigh = 10;
 }
 void p::achange()
{
   age = 10;
 }

요렇게 작성해주면 됩니다. 물론 클래스 정의 부분과 메인함수의 사이에요~ 만약 ' p:: '라는 선언이 존재하지 않는다면 이는 전역함수가 되버리지만 저런 선언때문에 클래스 외부에서도 클래스의 함수를 구현할 수 있는겁니다. 즉, 맴버 함수의 선언만 클래스 내부에 두고, 정의(구현)는 클래스 밖으로 빼낼 수 있습니다.
 
 사실 클래스의 맴버 함수를 내부에 정의한다는 것은 외부에 정의하는 것과 달리 인라인(in-line)으로 처리할 것을 요구합니다. 

  inline void p::hchange()
{
   heigh = 10;
 }
 inline void p::achange()
{
   age = 10;
 }

 이렇게 앞에 inline키워드를 붙여주면 비록 함수의 정의가 클래스 외부에 있다고 하더라도 인라인화가 가능합니다. 

 오늘은 여기까지입니다. 피곤해서... 다음 장은 클래스에 대하여 마저 알아보도록 하겠습니다.


 

 


 사실 이번장은 1강에 이어 설명하고자 하는 내용을 담았습니다. 레퍼런스에 대한 내용을 주로 설명할 것 입니다~

 C++에는 C언에서 존재하지 않았던 새로운 자료형이 등장합니다. bool형이 그것이죠. bool형의 변수는 true와 false둘 중 하나가 될 수 있습니다. 사실 이 값을 int형으로 변환해주면 true는 1을, false는 0을 나타내지만 그냥 bool형 데이터로서 인정을 해주는 편이 더 좋습니다.

 #include <iostream>
 using std::cin;
 using std::cout;
 using std::endl;

bool trueOrfalse(int i)
{
    if(i<0)
       return false;
    esle
       return ture;
 }

int main(void)
{
    int num;
    bool result;
    cin>>num;

    result = trueOrfalse(num);
    if(resutl == true)
    {
         cout<<"0보다 크거나 같은 수"<<enld;
     }
     else
     {
         cout<<"0보다 작은 수"<<enld;
      }
    return 0;
  }

 레퍼런스(reference)
: 레퍼런스는 이름을 지니고 있는 대상에게 지어주는 별명을 예로들면 쉽게 이해할 수 있습니다. 다음을 예로 들어보겠습니다.

  int &ref = value;

위의 문장은 vla이라는 int형 변수의 이름에 ref라는 별명을 붙인 것 입니다. &연산자는 C에서 주소 값을 얻기위해 사용했습니다만 사용하는 위치에 따라서 주소 값을 얻는데 사용될 수 있고, 레퍼런스를 선언하는데 사용될 수 있습니다.

 int *ptr = &value;  // 주소 값을 얻기 위한 &연산자
 int &ref = vla;       // 레퍼런스 선언을 위한 &연산자

 차이점 충분히 이해가 가셨나요? 그렇다면 ref와 value는 동일한 취급을 받습니다. 즉, 레퍼런스를 가지고 하는 연산은 레퍼런스가 참조하는 변수의 이름을 가지고 하는 연산과 같은 효과를 지니게 됩니다.

 메모리 관점에서 레퍼런스를 좀 더 살펴볼까요? 변수를 선언하게되면 메모리 공간 할당이 이루어지고 이름을 부여하게 됩니다. 사실 C언어에서는 하나의 메모리 공간에 하나의 이름만 부여할 수 있었지만 C++에서는 그렇지 않습니다. 하나의 메모리 공간에도 여러가지의 이름을 부여할 수 있는 것이죠~ 이름이 존재 하는 공간에 하나의 이름을 더 부여하는 행위가 레퍼런스 선언이 되는겁니다. 중요한것은 레퍼런스와 변수는 생성되는 방법에 있어서만 차이를 보일 뿐, 일단 만들어지고 나면 완전히 같은 것입니다.

 그러나 변수와 레퍼런스는 만들어지는 과정에서 차이점이 분명 존재합니다. 변수는 새로운 메모리 공간에 이름을 부여하지만 레퍼런스는 이미 이름을 지니고 있는 메모리 공간에 하나의 이름을 더 부여하는 것이기 때문이죠.

 int &ref;
 int &ref = 10;

 위와 같은 문장은 잘못된 문장입니다. 레퍼런스는 선언과 동시에 반드시 초기화 되어야하기 때문입니다.

 레퍼런스를 이용한 Call By Reference

void swap (int &a, int &b)
{
    int temp = a;
    a = b;
    b = temp;
 }

int main(void)
{
    int val1=10;
    int val2=20;

    swap(val1, val2);
    return 0;
 }

 swap이라는 함수에서 val1과 val2를 인자로 전달하는 과정을 유심히 보세요~ swap함수는 전달되는 인자를 레퍼런스로 받고 있습니다. 즉, val1은 a, val2는 b라는 별명이 주어지게 되고 각각의 변수는 동일하게 취급됩니다. 즉, main함수 내에서 선언한 변수 val1, val2라는 이름이 붙어 있는 메모리 공간에 a와 b라는 이름이 하나씩 더 붙게 된 것입니다. 때문에 swap함수 내에서는 a와 b라는 이름으로 main함수 내에 선언된 변수 val1, val2에 직접 접근이 가능하게 된 것입니다.

 레퍼런스의 다른 장점을 Call By Value에 적용시켜 예를 들어보겠습니다.

struct _Person
{
    int age;
    char name[20];
    char personalID[20];
 };
 typedef struct _Person Person;

 void ShowData(Person p)
 {
    cout<<"이 름 : <<p.name<<endl;
    cout<<"주민번호 : <<p.personalID<<enld;
    cout<<"나 이 : <<p.age<<endl;
 }

 int main(void)
{
    Person man;
   
    cin >> man.name;
    cin >> man.age;
    cin >> man.personalID;
 
    ShowData(man);
    return 0;
 }

 갈 수록 예제가 길어지네요...ㅠㅠㅠ헉헉헉.... 구조체 변수를 출력하는 함수에서 전달되는 인자를 주의 깊게 살펴 보길 바랍니다~ ShowData(man)에서 구조체 변수 man을 인자로 전달하고 있습니다. 전달하는 방식은 call by value입니다. 따라서 변수 man을 매개 변수 p에 복사하게됩니다. 이 과정에서 복사되는 바이트의 수는 int + char*20 + char*20 = 총 44바이트가 됩니다. 단지 출력하기 위해서 이만큼의 바이트를 복사합니다. 때문에 인자로 전달하는 변수의 크기가 클 수록 함수의 호출이 부담스러울 수밖에 없는거죠...

 그러나 레퍼런스를 이용해서 이러한 문제를 해결할 수 있습니다.

 void ShowData(Person &p)
 {
    cout<<"이 름 : <<p.name<<endl;
    cout<<"주민번호 : <<p.personalID<<enld;
    cout<<"나 이 : <<p.age<<endl;
 }

 인자가 &p로 바뀌었죠? 위의 함수는 전달되는 인자를 레퍼런스 형태로 받고 있습니다. 이름만 하나 더 추가하는 것이니까 44바이트나 되는 크기의 복사는 발생하지 않습니다. 때문에 성능은 향상이되죠.

 자 이제 우리는 좀 더 숙달된 프로그래머니까 프로그램의 안전성을 고려해봅시다. ShowData함수는 구조체 변수의 데이터를 출력만 하는 함수입니다. 때문에 레퍼런스를 이용한 값의 변형을 불가능 해야됩니다. 즉 참조만 하여 출력하기만 가능하고 값의 변경을 불가능해야 된다는 이야기입니다. const라는 키워드를 이용해봅시다.

 void ShowData(const Person &p)
 {
    cout<<"이 름 : <<p.name<<endl;
    cout<<"주민번호 : <<p.personalID<<enld;
    cout<<"나 이 : <<p.age<<endl;
 }

 레퍼런스의 선언 앞에 키워드 const를 붙여주었습니다. 이는 레퍼런스 p 자체를 상수화 하겠다는 의미입니다. 즉, 레퍼런스 p를 통한 데이터의 조작을 혀용하지 않겠다라고 선언해주는 것이지요~

 int& function(int &val)
{
    val++;
    return val;
 }

int main(void)
{
    int n = 10;
   
int &ref = increment(n);
   
    return 0;
 }

 int &ref = increment(n) 에서는 n을 인자로 전달하면서 함수를 호출하고 있습니다. 이 함수는 전달인자를 레퍼런스 val로 받고 있지요. function 함수의 구현 부분을 보시면 반환형이 int& 으로 레퍼런스를 반환형으로 받고 있습니다. return값도 val을 반환하기때문에 레퍼런스를 반환하구 있구요. 때문에 ref와 n은 동일하게 됩니다. 다만 function 함수의 매개 변수로 선언된 레퍼런스 val은 지역 변수와 마찬가지로 함수의 호출이 완료되면 사라져버립니다. 때문에 지역 변수를 레퍼런스로 리턴하는 일은 없어야 합니다~ 이정도만 하고 넘어가도록 하겠습니다.

 new 연산자와 delete 연산자

메모리를 동적으로 관리하기 위해서 malloc과 free함수를 사용해왔습니다. C++에서는 이를 대신하는 키워드 new, delete 가 등장합니다.

 int main(void)
{
    int size;
    cin >> size;

    int* arr = (int *)malloc(sizeof(int)*size); //배열을 동적할당합니다.

     ...... //생략

    free(arr); //할당된 메모리를 소멸시킵니다.
    
    return 0;
 }

 malloc 함수와 free함수를 이용하시려면 stdlib.h파일을 포함해주셔야되는거 잊지 마시구요~ malloc함수를 호출하여 인자로 전달된 크기만큼 단순히 메모리 공간을 할당만 하기 때문에 byte단위로 할당하고자 하는 메모리 공간의 크기를 전달해야 하고, void 포인터형으로 반환되는 포인터를 적절히 형 변환해서 사용해야 합니다. 조금은 복잡하죠~? 위 예제는 new, delete를 사용하면 간단해집니다.

 int *val = new int;

이 문장은 int형 데이터 1개를 저장하기 위해 메모리를 할당합니다.

 int *arr = new int[size];

 그렇다면 이 문장은 어떨까요? 길이가 size인 int형 배열을 위한 메모리를 할당해줍니다. malloc에 비하면 훨씬 직관적이고 쉽지않나요? malloc함수는 주소 값을 void 포인터형으로 반환하기 때문에 형 변환을 해야했지만 이제는 그럴필요가 없다는 겁니다. new연산자는 용도에 맞게 포인터를 반환해주기 때문이죠.

 위에서 할당된 메모리 공간을 반환하는 방법은 다음과 같습니다.

 delete val;
 delete []arr;

 주의할 것은 할당된 메모리 공간이 배열일 경우입니다. 모양이 조금 특이하죠?
malloc과 free를 사용한 예제를 new 와 delete를 사용하여 나타내보겠습니다.

 int main(void)
{
    int size;
    cin >> size;

    
int *arr = new int[size]; // 배열을 동적할당합니다.

     ...... //생략

    
delete []arr; //할당된 메모리를 소멸시킵니다.
    
    return 0;
 }

 즉, new 연산자를 사용하면 malloc과 달리, 할당하고자 하는 메모리 공간의 크기를 계산해야 할 필요도 없고, 적절한 형태로 포인터를 형 변환해 줄 필요도 없습니다~!

 여기서도 안전성을 고려해봅시다. new 연산자는 메모리를 동적으로 할당하지만 만약에 메모리의 공간이 부족하여 메모리 할당이 실패했다고 해봅시다. 그렇다면 new연산자는 NULL포인터를 리턴합니다. 그렇다면 위 예제를 다음과 같이 작성해볼 수 있겠네요

 int main(void)
{
    int size;
    cin >> size;

    int *arr = new int[size]; // 배열을 동적할당합니다.

    if(arr == NULL)
    {
         cout<<"메모리 할당 시패"<<endl;
         return -1; //프로그램 종료
     }

     ...... //생략

    delete []arr; //할당된 메모리를 소멸시킵니다.
    
    return 0;
 }

 
 그러나 일반적으로 프로그램을 작성할때는 위와 같은 코드를 삽입하지 않습니다. 운영체제의 메모리 관리 능력을 믿기 때문이죠. 때문에 메모리 검사 코드같은경우는 필요없다고 판단하고 오히려 조건검사 문장이 오기 때문에 성능만 저하시킨다고 생각합니다. 때문에 프로그램을 테스트 하는 과정에서만 위와 같은 오류 검사 코드를 넣고 최종 버전에서는 오류 검사 코드를 포함시키지 않는 방법을 선택하기도 한다더군요...ㅎㅎ 매크로를 이용하면 좀 더 편하게 구현할 수 있다는 겁니다. 걍 한번 보기만 하고넘어가세요~

#include <iostream>

#define DEBUG 1 //테스트 버전
//#define DEBUG 2 //최종 버젼

using std::cout;
using std::cin;
using std::endl;

 int main(void)
{
    int size;
    cin >> size;

    int *arr = new int[size]; // 배열을 동적할당합니다.


#if DEBUG == 1

    if(arr == NULL)
    {
         cout<<"메모리 할당 시패"<<endl;
         return -1; //프로그램 종료
     }

#endif

     ...... //생략

    delete []arr; //할당된 메모리를 소멸시킵니다.
    
    return 0;
 }

 이번 장은 여기까지 하도록 하겠습니다. 레퍼런스와 메모리를 동적할당하는 새로운 연산자에 대해서 공부했구요~ 중요한 부분이니까 반드시 숙지하시구요~ 요즘 날씨가 장난아니게 추운대 조심들하세요~ 다음 장에서는 클래스에 관한 내용을 다루도록 하겠습니다~ (--)(__)(--)!!
   

 


 

 C언어에 바로 이어서 C++언어로 넘어왔습니다. C언어 학습 후에 공부하게 되는 C++이므로 C언어에서 다루지 않았던 추가적인 내용들, 또는 C언어와 C++언어의 차이점 등에 중점을 두고 공부할 생각입니다. C++언어는 C언어를 포함하고 있습니다. 즉, C++은 C언어가 지니지 않는 문법적 특성도 많이 가지고 있다는 이야기입니다. 참고로 C++언어의 확장자는 .c 가 아닌 .cpp 로, 헤더파일은 #include<stdio.h>가 아닌 #include<iostream>으로 지정해 줘야 되는거 잊지마세요~~

 
cout << 출력대상
 cout<<1<<'a<<"string"<<endl;

: C언어의 printf 함수는 출력하고자 하는 대상의 출력 포맷을 정의해줘야 했지만 C++의 이러한 출력 방식은 우리가 원하는 형태로 적절히 출력을 해줍니다. endl을 출력하라는 의미는 다음 줄로 넘어가라는 의미를 가지고 있습니다.

 #include <iostream>

 int main(void)
 {
    std::cout<< "hello boxbop!!"<<std::endl;
    return 0;
  }

 std:: 나 << 연산자는 곧 설명할테니 조금만 기다려주시구요~

 
cin >> 입력 변수
 cin >> value;
 cin >> value1 >> value2;

:cout을 이용한 출력이 자료형에 맞는 적절한 출력을 보장했던 것처럼, cin을 이용한 데이터의 입력은 자료형에 맞는 적절한 입력을 보장해주고 있습니다~또한!!!!!! 중요한 것중에 하나는 C++는 원하는 위치어디서나 변수를 선언하는 것이 가능합니다!! C언어에서는 함수를 정의함에 있어서 지역 변수의 선언이 항상!! 제일 먼저 등장해야 했습니다!! 이에비하면 엄청나게 편한거죠~
그리고 첫 번째 데이터와 두 번째 데이터의 경계는 탭, 스페이스바, 엔터키의 입력에 의해 나눠집니다. C언어의 scanf함수와 동일하죠~

 #include <iostream>

 int main(void)
{
   int value1, value2;

   std::cin>>value1>>value2;
   std::cout<<"입력 값: "<<value1<<','<<value2<<endl;
   return 0;
 }

 
함수의 오버로딩?!

C컴파일러는 호출하고자 하는 함수를 찾을 때 오로지 함수의 이름 정보만을 가지고 찾기 때문에 정의된 매개 변수의 형태가 달라도 동일한 이름의 함수 정의는 허용되지 않습니다. 그러나 C++는 가능합니다. 왜냐하면 함수의 이름뿐 아니라 매개 변수의 정보까지도 참조를 하기 때문이죠~ C++은 이름이 같고 매개 변수의 타입 혹은 개수가 다른 함수들의 정의를 허용합니다. 이를 함수 오버로딩(function overloading)이라고 합니다.

 int function(int n)
 int function(char c)
 int function(int v1, int v2)

위 의 3개의 함수는 모두 다른 함수입니다. 함수의 이름은 같지만 매개 변수의 타입 및 개수가 다르기 때문입니다. 다만 함수의 리턴 타입만 다르다고 해서는 함수가 오버로딩되지는 않습니다^-^

 디폴트 매개 변수

디폴트(Default) 즉, 기본적인 매개 변수라는 것이 C++에서는 존재합니다.

int function(int a=0)
{
    return a;
 }

 현재 매개 변수 a는 0이라는 디폴트 매개 변수 값이 설정되어 있습니다. 이 값이 의미하는 것은 function이라는 이름의 함수를 호출하면서 인자를 전달하지 않으면 0으로 간주하겠다!!입니다. 말 그대로 기본 설정 매개 변수입니다~
 즉, function()function(0)은 동일합니다. 여기서 한 가지 주의할 것은 함수의 선언이 함수의 정의 이전에 존재하는 경우 디폴트 매개 변수는 선언 부분에 놓여져야 합니다.

 인라인(in-line)함수
C언어에서 배웠던 매크로가 기억나시나요? 함수를 매크로로 정의하면 전처리기에 의해서 함수 호출 문장이 함수의 몸체 부분으로 완전히 대치돼 버립니다. 함수의 호출 과정이 사라졌기 때문에 성능적인 면에서도 뛰어나죠. 함수 호출 문장이 함수의 몸체 부분으로 완전히 대치되 버리는 현상을 가리켜 함수가 inline화 되었다고 표현합니다.

 #include <iostream>
 inline  int  function(int x)
 {
    return x*x;
 }

 int main(void)
{
   std::cout<<function(5)<<std::endl;
}
 
 함수 function을 inline화 하게 됩니다. function(5)는 (5 * 5)로 단순히 치환됩니다. C++에서는 이와 같이 성능 향상을 목적으로 함수를 inline화하기 위해서 매크로를 사용할 필요가 없습니다.

 이름공간(namespace)
이름공간은 말 그대로 특정 영역에 이름을 붙여주기 위한 문법적 요소입니다. 예를들어 A회사와 B회사가 공동으로 프로젝트를 진행한다고 합시다. 몇 달후에 각각의 회사가 구현한 모듈을 하나로 묶기 위해서 모였습니다. 그러나 A회사와 B회사가 각각 구현한 함수들의 이름이 같은 경우가 일어났습니다. 이는 상당히 골치아픈 문제입니다. 만약 이런 경우라면 어떻게 해야 될 까요? 한 아파트에 민식이라는 친구가 2명 살고 있습니다. 202호 민식이와 702호 민식이죠 아파트 방송에서 민식이라는 친구를 부를 때 그냥 민식이라고 하면 202호 민식이 인지 704호 민식이인지를 모를겁니다. 때문에 202호 민식이나, 702호 민식이라고 불러야되는거죠. 즉, 같은 이름을 가졌더라도 존재하는 공간을 다르게해서 구분짓도록 합니다.

 #include <iostream>

 namespace A_company
 {
    void function(void)
    {
        std::cout<<"A회사에서 정의한 함수"<<enld;
     }
  }

 namespace B_company
 {
    void function(void)
    {
        std::cout<<"B회사에서 정의한 함수"<<enld;
     }
  }

int main(void)
{
    A_company::function();
    B_company::function();
 }

 namespace라는 키워드로 이름 공간을 구성하고 내부에 필요한 함수들을 정의 합니다. A회사의 이름공간에도 function함수가, B회사의 이름공간에도 function함수라는 똑같은 이름을 가진 함수가 존재합니다. 이름공간이 다르다면 같은 이름의 변수나 함수의 선언이 허용되기때문에 가능한 일입니다. 중요한 것은 이름공간 내에 선언되어 있는 변수나 함수에 접근하는 방법입니다.
:: 연산자가 보이시나요? 이 연산자를 범위 지정 연산자라고 합니다. 
 A_company::function() 에서 의미하는 것은 A_company라는 이름공간 안에 선언되어 있는 function함수를 호출하라는 의미입니다. 

 함수의 선언과 정의를 분리하고자 하는 경우에는 다음과 같이 하셔야됩니다.

#include <iostream>
namespace A_company 
//함수의 선언
{
   void function(void)
 }
namespace B_company
{
   void function(void)
 }

int main(void)
{
    ......
//메인함수의구현
 }

namespace A_company
//함수의 구현
{
      void function(void)
      {
           std::cout<<"A회사 함수"<<endl;
       }
 }
namespace B_company
{
      void function(void)
      {
           std::cout<<"B회사 함수"<<endl;
       }
 }

 자 그렇다면 우리가 지금까지 사용해온 std::cout 나 std::endl 또는 std::cin을 이해할 수 있겠죠? std라는 이름공간안에 존재하는 함수들을 사용하겠다는 의미가 됩니다.

   using 이라는 키워드
 cout, cin, endl을 참조할 때마다 std:: 를 앞에다 붙여줘야 하는 이유는 설명했습니다. 그렇다면 항상 이렇게 붙여서 사용해야 되는 것일까요? 이를 해결하기위한 방법이 있습니다.

#include <iostream>

 namespace A
{
    void funtion(void)
    {
        std::cout<<"hi! boxbop"<<endl;
     }
 }

 
using A::function;

int main(void)
{
    function();
    return 0;


 using A::function 와 같은 선언은 앞으로 function이라는 이름을참조하는 코드가 나오면 A라는 이름공간 안에 선언된 function이라는 이름을 참조하라는 의미입니다. 

 using std::cout;
 using std::cin;
 using std::endl;

 이렇게 코드를 작성하면 메인함수에서는 일일히 std::를 안붙여주고 cout, cin, endl을 사용할 수 있습니다.

 using namespace std;

라고 작성하면 어떻게 될까요? 이것은 이름공간 std안에 존재하는 이름들은 그냥 참조하겠다고 선언되어 있는 것 입니다. 따라서 이후부터는 범위지정 연산 없이 접근이 가능하게 되는 것이죠. 그러나 이와 같은 선언은 나중에 충돌을 일으킬 수 있는 원인이 될 수 있어서 using std::cout 와 같이 조금은 번거로워도 일일이 작성해주는 편이 좀 더 좋습니다.(일일히는 일일이의 옛말이더군요..... 갑자기 헷갈려서 찾아봤습니다. 참고~)

 int value = 100; // 전연변수

int main(void)
{
   int value = 100;  //지역변수
   ::value += 1;  //전역변수
   return 0;
 }

::value += 1; 에서 범위 지정 연산자를 사용해서 전역변수에 접근을 하고 있습니다. 그러나 이름공간이 따로 존재하지 않는데요 이는 전역 변수로 선언되어있는 변수 value에 접근하라는 뜻이 됩니다. 때문에 범위 지정 연선자는 전역 변수에 접근하기 위한 용도로도 사용이 가능합니다.

 오늘은 여기까지 하도록 하겠습니다. 다음 장에서는 본격적으로 C++에 들어가기에 앞서서 레퍼런스에 대한 내용을 좀 더 살펴보도록 하겠습니다~
 

+ Recent posts