1. 매크로 함수 (Macro Function)  
 : 전처리기에 의해 처리 되면서, 매우 빠른 실행의 이점을 가지게 되는 함수이다.  아래와 같이 선언해 쓰고, 매크로 함수는 데이터형에 영향을 받지 않는 특징을 가지고 있다. 
  1. #define Square ((x) * (x))  

하지만 왜 C++에 와서 그대로 매크로 함수를 쓰지 않고 인라인 함수라는 것을 새로 만들어서 사용하는 것일까? 그 이유를 한번 알아 보자.

2. 인라인 함수 (Inline Function)  
 : C와의 매크로 함수와 같지만, 인라인 함수는 컴파일러단에서 처리되면서, 구현이 용이 하며 (이전의 매크로는 구현이 까다롭다), 컴파일러에게 최적화 기회를 제공한다. 

- 컴파일러에 의해 처리가 되면 무엇이 좋을까?
 : 바로 정의가 잘못될 시에 오류 메세지를 찍어 주는 역할을 해줄 수 있다는 것이다. 전처리기에 의해 처리 되는 매크로 함수는 만약 사용자의 실수로 정의가 잘못되었다고 하면, 이런 오류를 잡아내지 못하는 단점을 가지고 있다. 
- 구현의 용이 ?
 : 매크로 함수는 전처리 단계에서 기계적으로 인수를 치환하기 때문에 괄호를 잘 써줘야 하는데요, 반면 인라인 함수는 그냥 우리가 함수를 선언하고 사용하는것 처럼 아주 간단하게 사용할 수 있다는 점이 구현에 용이 하다고 할 수 있습니다. (아래는 사용의 예입니다. 앞에 간단히 inline만 붙여주면 돼죠) 
  1. inline int Square(int x)    
  2. {    
  3. return x * x;   
  4. }  

3. 네임스페이스 (Namespace)  
: 네임 스페이스는 이름 충돌을 막기 위해 공간에 이름을 주는 행위라고 할 수 있겠는데, 한 아파트에 철수가 두명 사는데 그냥 부르기는 헷갈리니 각 사는곳을 덧붙여 이름을 주는 것이죠. 101호에 사는 철수야 , 202호에 사는 철수야 하고 말이죠. 아래와 같이 간단히 쓸 수가 있습니다. 
  1. #include <iostream>   
  2.   
  3. namespace A_COM   
  4. {   
  5.     void function(void)   
  6.     {   
  7.         std::cout<<"A.com에서 정의한 함수"<<std::endl;   
  8.     }   
  9. }   
  10.   
  11. namespace B_COM   
  12. {   
  13.     void function(void)   
  14.     {   
  15.         std::cout<<"B.com에서 정의한 함수"<<std::endl;   
  16.     }   
  17. }   
  18.   
  19. int main(void)   
  20. {   
  21.     A_COM::function();   
  22.     B_COM::function();   
  23.     return 0;   
  24. }  
Posted by 모과이IT
,
1. 함수 오버로딩   
 : 함수 오버로딩(Function Overloading)이란 간단히 말하면, 동일한 이름의 함수를 중복해서 정의하는 것을 말합니다. (단, 매개변수의 정보가 달라야 한다는 가정하에 말입니다. 갯수나 타입이이 일치하지 않는 한도 내에서 말이죠)

C언어에서는 함수이름의 중복을 허용하지 않았는데, 그 이유는 C언어에서 함수를 호출할 때, 함수의 이름정보만을 가지고 호출할 함수를 찾기 때문입니다. 하지만, C++ 에서는 호출할 함수를 찾는데 있어서, 이름뿐만 아니라 매개변수의 정보도 포함하고 있기 때문에 이런 기능이 가능한 것입니다. 

아래와 같은 형태의 함수들은 C에서는 불가능했지만, C++에서는 이것이 가능하다는 얘기 입니다.
  1. int function (int n){}   
  2. int function (char c){}   
  3. int function (int n , char c){}  


2. 디폴트 매개변수  
: C++의 함수가 가진 또 하나의 특징은 바로 디폴트 매개변수(Default Parameter) 라는 것인데, 디폴트 매개변수란 전달되지 않는 인자를 대신하기 위한 기본값이 설정되어 있는 변수입니다. 아래와 같은 함수에서 (  ) 이 곳이 디폴트 매개변수를 선언할수 있는 부분입니다.
  1. int function (int a=0){ return a+1;}  

 - 사용법 : 만약 아래같이 분리 해서 사용할 경우 함수 정의 선언부에 한번만 설정 할 수 있습니다.
  1. #include<iostream>   
  2.   
  3. int BoxVolume(int length, int width=1, int height=1);   
  4.   
  5. int main()   
  6. {   
  7.     std::cout<<"[3, 3, 3]       : "<<BoxVolume(3, 3, 3)<<std::endl;   
  8.     std::cout<<"[5, 5, def]     : "<<BoxVolume(5, 5)<<std::endl;   
  9.     std::cout<<"[7, def, def]   : "<<BoxVolume(7)<<std::endl;   
  10.     return 0;   
  11. }   
  12.   
  13. int BoxVolume(int length, int width, int height)   
  14. {   
  15.     return length*width*height;   
  16. }  
Posted by 모과이IT
,
 C 에서의 static  
 :  C언어에서 static을 사용했던 이유는 전역변수는 프로그램어느 곳에서 접근 가능하지만, 지역변수에다가 static을 붙여주면, 지역변수가 전역변수의 특성을 지니게 하면서 접근할 수 있는 범위에 제한을 둔다는 특징이 있기 때문이었다. 

 Class, Static 멤버의 등장  
 : C++에서는 전역이라는 개념이 존재하지 않는다. 그럼 왜 C++은 왜 전역을 쓰지 않나? 기본적으로 전역변수는 접근 범위가 제한되어 있지 않기 때문에 프로그램 관리가 어려운 점도 있지만, 근본적으로 OOP에서는 전역이라는 개념이 존재하지 않기 때문이다. 그래서 전역 변수와 전역 함수를 일부 대체하기 위해서 static 멤버 라는 개념이 등장한 것이다. 
  1. #include<iostream>   
  2. using namespace std;   
  3.   
  4. int count=1;   
  5.   
  6. class Person   
  7. {   
  8.     char name[20];   
  9.     int age;   
  10. public:   
  11.     Person(char* _name, int _age)   
  12.     {   
  13.         strcpy(name, _name);   
  14.         age=_age;   
  15.         cout<<count++<<"번째 Person 객체 생성"<<endl;   
  16.     }   
  17.     void ShowData(){   
  18.         cout<<"이름: "<<name;   
  19.         cout<<"나이: "<<age;   
  20.     }   
  21. };   
  22.   
  23. int main(void)   
  24. {   
  25.     Person p1("Lee", 13);   
  26.     Person p2("Hong", 22);   
  27.   
  28.     return 0;   
  29. }  
 위의 예제는 전역 변수 count가 있고, person이 생성 될때마다 count 값을 한번씩 증가 시키고 출력시키는 프로그램이다. 생성되는 person 객체는 전역변수 count를 참조해서 값이 일정하게 증가될 것이다. 하지만 C++로 넘어 오면서 우리는, OOP, 객체지향 프로그래밍을 위하여 전역 변수, 전역 함수를 쓰지 않도록 해야 한다!!

 static 멤버의 특징  
 1. static 멤버(변수 or 함수)는 C++에서 일반적으로 클래스 변수, 클래스 함수라 불리운다. 그럼 왜 클래스 변수,함수 라 불리우는가?  객체를 생성했을 때, 객체의 멤버로 존재하는것이 아니라, 클래스 내에 선언이 되어 클래스를 기반으로 생성되는 모든 객체는 다 공유 하기 때문에(즉, 객체 단위가 아니라 클래스 단위로 사용되기 때문에) 이렇게 불리운다. 
 2. main 함수 호출 이전에 메모리 공간에 올라가서 초기화 한다. (전역변수와 동일)
 3. 선언된 클래스의 객체 내에 직접 접근을 허용한다.
 4. static 멤버 초기화문으로 초기화해야 한다. 왜 초기화문이 필요한가? 예제를 통해 알아 봅시다.
  1. #include<iostream>   
  2. using namespace std;   
  3.   
  4. class Person   
  5. {   
  6.     char name[20];   
  7.     int age;   
  8.     static int count;   
  9. public:   
  10.     Person(char* _name, int _age)   
  11.     {   
  12.         strcpy(name, _name);   
  13.         age=_age;   
  14.         cout<<count++<<"번째 Person 객체 생성"<<endl;   
  15.     }   
  16.     void ShowData(){   
  17.         cout<<"이름: "<<name;   
  18.         cout<<"나이: "<<age;   
  19.     }   
  20. };   
  21.   
  22. int Person::count=1; // static 멤버 초기화   
  23.   
  24. int main(void)   
  25. {   
  26.     Person p1("Lee", 13);   
  27.     Person p2("Hong", 22);   
  28.   
  29.     return 0;   
  30. }  

 static 멤버는 메인 함수 호출 되기 이전에 초기화가 이루어진다. 하지만 이 count를 생성자 내에서 초기화 시킬 경우에 메모리 공간에 올라감과 동시에 초기화가 이루어 지는게 아니라, 객체가 생성될때마다 값이 리셋될것이다. 그래서 항상 같은 결과만을 출력하게 되기 때문에 초기화를 위해 static 멤버 초기화문이 등장했다. 
Posted by 모과이IT
,
우선 여기서는 상속과 다형성에 대한 간단한 의미만 정리 하고 넘어갈 것입니다. 다음에 상속과 다형성에 대해서 더 자세한 포스팅을 할 것입니다. 
 상속 (Inheritance)  
 : 상속은 기존의 클래스를 토대로 해서 새로운 클래스를 만드는 방법이라고 할 수 있다. 객체지향 프로그래밍을 사용해서 워드 프로세서를 만드는 경우를 생각해보자. 고객으로부터 웹페이지 형식으로 문서를 저장할 수 있게 요구 받았다고 하면, 이때는 문서 저장과 관련된 기존의 클래스를 조금 개조해서 웹 페이지 형식으로 저장하게 만들 수 있다. 
  이렇게 기존의 클래스를 조금 고쳐서 새로운 클래스를 만들고 싶을 때 상속을 사용할 수 있다. 일상생활에서의 상속은 부모의 재산이나 부채를 그대로 이전하는 것을 의미하는데, 객체지향 프로그래밍에서의 상속도 이와 비슷하다. 클래스 A가 클래스 B를 상속받게 만들면, 클래스 B는 "부모 클래스" (or Base Class , Super Class)가 되고, 클래스 A는 "자식 클래스" (or Derived Class)가 된다. 부모 클래스는 자식 클래스에게 자신의 모든 멤버 변수와 함수를 물려준다. 물론 OOP에서는 물질적인 것 뿐만 아니라, 어떤 기본적인 사람의 특성들도 상속의 특성이 될 수 있다. (아래는 상속을 사용한 클래스의 간단한 예제)
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Person   
  5. {   
  6.     int age;   
  7.     char name[20];   
  8. public:   
  9.   
  10.     int GetAge() const {   
  11.         return age;   
  12.     }   
  13.     const char* GetName() const {   
  14.         return name;   
  15.     }   
  16.   
  17.     Person(int _age=1, char* _name="noname"){   
  18.         age=_age;   
  19.         strcpy(name, _name);   
  20.     }   
  21. };   
  22.   
  23. class Student: public Person   
  24. {   
  25.     char major[20]; //전공   
  26. public:   
  27.     Student(char* _major){   
  28.         strcpy(major, _major);   
  29.     }   
  30.     const char* GetMajor() const {   
  31.         return major;   
  32.     }   
  33.     void ShowData() const {   
  34.         cout<<"이름: "<<GetName()<<endl;   
  35.         cout<<"나이: "<<GetAge()<<endl;   
  36.         cout<<"전공: "<<GetMajor()<<endl;   
  37.     }   
  38. };   
  39.   
  40. int main(void)   
  41. {   
  42.     Student Kim("computer");   
  43.     Kim.ShowData();   
  44.   
  45.     return 0;   
  46. };  
 상속받는 클래스를 만들기 위해서 class Student : public [상속할 클래스 이름] 이런식으로 상속이라는 문법을 사용할 수 있다. 상속을 사용한 프로그램은 메모리 공간 할당 -> 부모 클래스의 생성자 실행 -> 자식 클래스의 생성자 실행 이라는 순서를 가지게 된다.

 다형성 (Polymorphism)  
 : 다형성(Polymorphism)은 OOP(Object Oriented Programming)의 개념을 설명할 때 추상화(Abstraction)과 더불어 가장 중요하게 등장하는 용어이다. 다형성을 이해하기 위해 간단한 예를 들어 보도록 하자. 예를 들어 돈을 생각해보자. 돈 만원은 만원짜리 한장, 천원짜리 열장, 백원짜리 동전 100개로도 구성 할 수 있다. 즉 만원을 구성하는 형태는 다 틀리지만, 모두 동일한 가치인 만원을 의미하게 된다. 이렇게 만원을 다양한 형태로 나타낼 수 있는 것이 다형성이다. 
  객체지향 언어에서 의미하는 다형성은 다음과 같은 의미로 정의할 수 있다. 
서로 다른 객체가 동일한 메시지에 대하여 서로 다른 방법으로 응답할 수 있는 기능
여기서 "서로 다른 객체"는 서로 다른 클래스를 의미한다. 물론 상속의 경우에도 해당이 된다. 다음의 "동일한 메시지"라는 의미는 서로 다른 객체에게 같은 메세지를 보낸다는 의미가 되겠다. 마지막으로 "서로 다른 방법"으로 응답한다는 의미는 무엇일까? 
 예를 들어 보자. 우선 어떤 도형 모형의 클래스와 이 클래스를 상속 받는 삼각형, 사각형, 원형... 등의 이런 형태를 갖는 클래스가 있다고 하자. "서로 다른 객체"는 삼각형, 사각형에서 각자 생성한 객체가 될것이고,  "동일한 메시지"는 도형을 그려라 라는 의미를 가지는 메소드인 Draw 함수가 될것이다. 그렇다면 "서로 다른 방법"은 draw라는 같은 형태의 메소를 받아 삼각형, 사각형 객체는 삼각형, 사각형을 그릴텐데, 삼각형을 그리기 위한 방법과 사각형을 그리기 위한 방법. 이것들은 서로 다를 것이다. 즉, 같은 메소드 호출에 대해 서로 다른 방법으로 응답을 하게 되는 것이다. 
 위와 같은 개념을 객체지향에서 다형성이라고 한다. 위와 같은 계층 구조를 그림으로 나타내면 아래와 같다. 

Figure 클래스는 하위 클래스에서 모형을 그리는데 사용될 수 있는 draw 함수를 가지고 있다. 그러나 Figure 클래스에는 실제 도형을 그리는 함수 구현 부분을 정의할 수가 없다. 그 이유는 Figure의 하위 클래스인 Triangle, Square, Circle 클래스마다 그리는 방법이 다르기 때문이다. 객체지향에서는 이러한 경우 Figure Class를 추상 클래스(Abstract class)로 정의 하고 draw 함수 역시 추상 함수(메소드)로 정의한다. 
 추상 메소드(함수)란 함수의 선언부분만 있고 구현 부분이 업스는 함수를 말한다. 이렇게 선언된 추상 클래스는 하위 클래스에서 구현되어 사용된다. 즉, 각각의 하위 클래스에서 상속받은 추상 메소드를 서로 다른 방법으로 구현하게 되는 것이다. 이를 코드로 정리 해보면 아래와 같은 코드가 나올 것이다. 
  1. #include <iostream>   
  2. #include <string>   
  3. using namespace std;   
  4.   
  5. class Figure   
  6. {   
  7. public:   
  8.     virtual string draw() = 0;     
  9. };   
  10.   
  11. class Triangle : public Figure   
  12. {   
  13. public:   
  14.     virtual string draw() { return "Draw Triangle"; }   
  15. };   
  16.   
  17. class Square : public Figure   
  18. {   
  19. public:   
  20.     virtual string draw() { return "Draw Square"; }   
  21. };   
  22.   
  23. class Circle : public Figure   
  24. {   
  25. public:   
  26.     virtual string draw() { return "Draw Circle"; }   
  27. };   
  28.   
  29. int main()   
  30. {   
  31.     Figure* F1 = new Triangle;   
  32.     Figure* F2 = new Square;   
  33.     Figure* F3 = new Circle;   
  34.   
  35.     cout << F1->draw() <<endl;   
  36.     cout << F2->draw() <<endl;   
  37.     cout << F3->draw() <<endl;   
  38.        
  39.     delete F1;   
  40.     delete F2;   
  41.     delete F3;   
  42.            
  43.     return 0;   
  44. }  

Posted by 모과이IT
,
우선 다음의 상속이 구현된 소스 코드를 보고 문제점이 무엇인지 알아 보자. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Person   
  5. {   
  6.     int age;   
  7.     char name[20];   
  8. public:   
  9.   
  10.     int GetAge() const {   
  11.         return age;   
  12.     }   
  13.     const char* GetName() const {   
  14.         return name;   
  15.     }   
  16.   
  17.     Person(int _age=1, char* _name="noname"){   
  18.         age=_age;   
  19.         strcpy(name, _name);   
  20.     }   
  21. };   
  22.   
  23. class Student: public Person   
  24. {   
  25.     char major[20]; //전공   
  26. public:   
  27.     Student(char* _major){   
  28.         strcpy(major, _major);   
  29.     }   
  30.     const char* GetMajor() const {   
  31.         return major;   
  32.     }   
  33.     void ShowData() const {   
  34.         cout<<"이름: "<<GetName()<<endl;   
  35.         cout<<"나이: "<<GetAge()<<endl;   
  36.         cout<<"전공: "<<GetMajor()<<endl;   
  37.     }   
  38. };   
  39.   
  40. int main(void)   
  41. {   
  42.     Student Kim("computer");   
  43.     Kim.ShowData();   
  44.   
  45.     return 0;   
  46. };  
 위의 소스 코드 문제점을 보자면 main에서 객체 생성을 하는 Student Kim 객체선언 하는것을 보아 이름이 Kim으로 예상할 수 있겠다. 하지만 여기서 나이와 전공은 우리가 원하는 형태로 초기화 하지 못한다는 점이 바로 문제점이다. 즉, Student 클래스 객체가 생성될때, 자신의 멤버는 생성자내에서 초기화 하고 있지만, Person 클래스의 멤버는 default 값으로 초기화 되고 있는게 문제라고 할 수 있다. 
 잠깐 소스를 살펴 보자면, showdata 함수 내에서 GetName이라는 함수를 호출 하고 있는데, Student 클래스내에서는 GetName이라는 함수가 없지만, 이것을 호출 할 수 있는 이유는 Student 클래스가 Person 클래스를 상속하고 있기 때문에 Person 에 GetName 이라는 함수가 있어서 이런 호출이 가능한 것이다. (상속의 특성이라 할 수 있다.)

 문제로 돌아와서, Student 클래스에 Person의 멤버 변수들도 Student 클래스의 멤버로 상속되어 지니까 아예 Student 클래스를 정의 할때, 인자값으로 나이와 이름을 초기화 해서 Default로 초기화 되는것을 개선해 보자는 것이다.  
  1. Student (int _age, char* _name , char * _major)   
  2. {   
  3.     age = _age;   
  4.     strcpy(name, _name);   
  5.     strcpy(major, _major);   
  6. }  
 위와 같이 Student 생성자에서 age와 name을 초기화 해주면 될것이다. 하지만 Person 멤버들이 private 으로 선언되어 있으니 위와 같이 코딩을 하면 컴파일 에러가 날것이다. 왜 이런 에러가 나는 것일까? 비록 Person 클래스의 멤버는 Student 클래스에 의해 상속 되어 지지만, Person 클래스의 멤버들이 private이기 때문에 Person 클래스 내에서만 접근 가능 하기 때문이다. 
 그렇다고 이 문제를 해결하기 위해 public으로 멤버 변수들을 선언하면 문제는 해결되겠지만, 객체지향의 정보은닉이 무너지는 결과를 낳을 것이다. 그래서 필요한게 바로 멤버 이니셜라이저(member initializer)이다. (또는 초기화 리스트)

 멤버 이니셜라이저  
  1. Student(int _age, char* _name, char* _major)   
  2.     : Person(_age, _name)   
  3. {   
  4.     strcpy(major, _major);   
  5. }  
 보통 멤버 이니셜라이저에서는 멤버 변수의 초기화를 이루기 위해 멤버변수 이름이 오지만, 여기에서는 클래스 이름이 나왔다. 이 의미는 _age와 _name 이 두개의 인자값을 받을 수 있는 생성자를 호출하라는 의미로 바뀐다. 이제 완성된 코드로 실행해보자. 이제 원하는 대로 출력이 되는 것을 알 수 있다. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Person   
  5. {   
  6.     int age;   
  7.     char name[20];   
  8. public:   
  9.   
  10.     int GetAge() const {   
  11.         return age;   
  12.     }   
  13.     const char* GetName() const {   
  14.         return name;   
  15.     }   
  16.   
  17.     Person(int _age=1, char* _name="noname"){   
  18.         age=_age;   
  19.         strcpy(name, _name);   
  20.     }   
  21. };   
  22.   
  23. class Student: public Person   
  24. {   
  25.     char major[20]; //전공   
  26. public:   
  27.     Student(int _age, char* _name, char* _major)   
  28.         : Person(_age, _name)   
  29.     {   
  30.         strcpy(major, _major);   
  31.     }   
  32.     const char* GetMajor() const {   
  33.         return major;   
  34.     }   
  35.     void ShowData() const {   
  36.         cout<<"이름: "<<GetName()<<endl;   
  37.         cout<<"나이: "<<GetAge()<<endl;   
  38.         cout<<"전공: "<<GetMajor()<<endl;   
  39.     }   
  40. };   
  41.   
  42. int main(void)   
  43. {   
  44.     Student Pac(19, "Pacs.tistory.com""computer");   
  45.     Pac.ShowData();   
  46.   
  47.     return 0;   
  48. };  


 protected   
 : protected는 접근제어 키워드 중 하나로, 정보은닉 포스팅에서 이미 언급한 바가 있는데, protected는 private과 기능이 완전히 동일 하지만, 이것이 상속관계에서 쓰일 때는 파생 클래스가 기본 클래스로의 접근이 가능해진다. 
  1. class Person   
  2. {   
  3. protected:   
  4.     int age;   
  5.     char name[20];   
  6. public:   
  7.       .................   
  8. };  
 사용할 때는 위와 같이 private 대신에 protected를 써주면 되겠다. 하지만 protected를 쓴다고 해도 멤버 이니셜라이저를 쓰는것이 좋은 구조이다. 만약 멤버 변수의 이름이 변경되어 지는 경우가 생겼다고 한다면, 이 바뀐 이름들을 자신 클래스는 물론이고, 상속을 하고 있는 클래스에서도 이름을 바꿔야 하는 문제가 생긴다.  단순히 이름을 바꾸면 된다고 생각하기 쉽지만, 한클래스의 변경은 다른 클래스의 변경을 유발 시켰기 때문에 심각한 문제라고 볼 수 있다. 
  
 ※ Derived class 생성자 내에서는 Derived class의 멤버 변수만 직접적으로 초기화 시켜 주고 Base class 멤버는 Base class의 생성자를 통한 초기화가 가장 좋은 방법이라고 말할 수 있다. 
Posted by 모과이IT
,
1. C언어에서의 구조체  

  : 구조체란 다른 타입을 가지는 변수들을 하나의 이름으로 묶어둔 것이라고 할 수 있다. 그럼 구조체를 왜 사용할까? 구조체는 관련있는 데이터를 하나의 자료형으로 묶어서, 프로그램의 구현 및 관리가 용이 해진다는 장점을 가지고 있기 때문이다.

  C언어에서 구조체에 대한 불만이라고 한다면, 구조체를 기본 자료형으로 인식해 주지 않는다는 점이다. 아래 예제를 보자. 
  1. //Person이라는 사용자 정의 자료형 (Structure)   
  2. struct Person{   
  3.     int age;   
  4.     char name[10];   
  5. };    
  6.   
  7. int main()   
  8. {   
  9.     int a =10;   
  10.     Person P //C에서는 에러가 난다.    
  11.        
  12.     return 0;   
  13. }  
2. C++에 와서 달라진점?  

   C++로 넘어오면서 구조체 사용에 대해 달라진점이 있다고 하면, C에서는 구조체에 함수를 넣을 수 없었지만 C++에서는 넣을 수 있다는이고, 위의 예제와 같이 C에서는 구조체 사용을 위해서는  Person P 라고 사용하면 안된다.struct Person P 이렇게 써야 한다. C++은 기본 자료형이나 사용자 정의 자료형이나 다 동일한 자료형으로 인식함으로써 이와 같은 문법 사용이 이루어진 것이다.  또한, C에서는 사용자 정의 자료형을 가지고 +, -, * 이런 연산이 허용안되지만, C++에서 연산자 오버로딩을 통해 이런것까지도 이런것도 가능하다. 


3. 기본자료형과 사용자 정의 자료형  

 - C에서의 기본 자료형 : int , char, float.... 

               사용자 정의 자료형 : 구조체, 공용체(Union), enum

- C++ 에서의 기본 자료형 : C와 동일
                   사용자 정의 자료형 : C와 동일 하고 여기에 Class가 추가 되었다. 

4. 클래스  

  :  변수와 함수를 하나로 묶어서 자료형을 정의 할 수 있는 문법적 요소 (변수 + 함수) C++에서는 구조체도 클래스의 범주에 들어가며, 클래스의 변수는 변수라고 하지 않고 객체라고 불리운다. 그럼 왜 클래스란 개념이 생겨났는가?


5. 왜 클래스란 개념이 등장했는가?  

   클래스를 설명 하기 앞서서 잠깐 객체지향을 언급해야 겠는데 객체지향이란 프로그램을 구현하는데 있어서 현실세계를 그대로 모델링을 해서 이것을 그대로 프로그래밍으로 옮겼으면 좋겠다는 생각을 모티브로 나온 것이다. 바로 현실세계에 존재하는 대상을 가지고, 자료형으로 정의 하는것이다. 하지만 현실세계의 사물들을 자료형으로 정의 하기 위해서는 데이터 추상화가 필요 한데, 데이터 추상화란 현실세계의 사물을 데이터적인 측면과 기능적인 측면을 통해서 정의 한것이라 할 수 있다. 

  이런 데이터 추상화를 거쳐서 클래스화 되고 인스턴스화(객체를 만들어내는 행위 : 일명 객체화)가 일어 난다. 

* 그럼 왜 클래스는 함수와 변수로 표현해야 될까?
 : 현실세계에 존재하는 대상을 추상화 하다 보면, 데이터적 특성도 있지만, 기능적인 측면이 있기 때문에  함수 변수 이 두개를 하나의 자료형으로 묶는 것이다. 이것이 바로 클래스이다.
Posted by 모과이IT
,
다음과 같이 상속(Inheritance)의 경우를 보자 
  1. class A : public B   
  2. {   
  3. };  
 B클래스를 public으로 상속하겠다는 의미로 우리들이 일반적으로 많이 사용하는 class A : [상속형태] B 형태를 가지고 있다. 기본적으로 기본 클래스는 파생 클래스에 의해 상속되어지는 과정에서 접근 권한이 변경이 된다. 그 상속 형태에 따라 멤버들을 상속하는데 있어서 접근권한을 무엇으로 변경할 것이냐 하는 얘기이다. 아래는 기본 클래스와 상속의 형태에 따른 파생클래스에서의 기본클래스로의 접근범위를 표로 나타낸것이다. 일반적으로 public을 가장 많이 쓰이므로 그 부분에 초점을 맞춰서 보는 것이 좋다.

그냥 도표로는 언뜻 이해 하기가 힘드니, 이것을 코드와 분석을 통해서 더 자세히 알아 보자.(상속의 형태는 매번 볼때마다 헷갈리니 잘 알아 두도록 하자)
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Base{   
  5. private:   
  6.     int a;   
  7. protected:   
  8.     int b;   
  9. public:   
  10.     int c;   
  11. };   
  12.   
  13. class Derived : public Base //or protected Base or private Base   
  14. {   
  15.   
  16. };   
  17.   
  18. void main()   
  19. {   
  20.     Derived obj;       
  21. }  
 protected 상속의 경우  
: protected Base' 로 선언 했다고 가정하자. 상속의 형태가 protected이다. 이 의미는 protected보다 접근 권한이 넓은 것이 있다면, 그냥 protected에 맞춰서 내가 상속을 하겠다는 의미를 가지고 있다. 
 - int c : 상속되어지는 과정에서 int c는 지금 public 인데 protected보다 접근할 수 있는 범위가 넓기 때문에 상속이 되면, protected로 맞춰지게 된다. 
 - int b : 여전히 protected가 된다. 
 - int a : private인 int a는 private는 protected 보다 접근 권한이 작으니까 그대로 private로 상속되겠다 생각하기 쉽지만, 파생 클래스에서 int a는 private int a로 선언이 된다. (상속되면 결과적으로) int a는 private이기 때문에 파생 클래스에서 접근 가능한것으로 생각하기 쉽지만, 접근가능하지 않다 그 이유는 private 멤버의 특성상 자기 클래스 내에서만 접근 가능하지 상속됐다고 파생클래스에서 접근 하지는 못하기 때문이다. 그래서 우리는 헷갈리지 않게 Base 클래스에서 private로 선언된 멤버는 상속이 될때 접근불가 라는 키워드가 붙는다고 생각하는 것이 좋다.

 private 상속의 경우  

 - int c , int b: 이 둘은 private보다 범위가 넓으니 private로 맞춰 지겠다.
 - int a : 위에서 언급한 Base 클래스에서 private로 선언된 멤버는 접근불가라는 것을 기억하자.

 public 상속의 경우  

 - int c : 범위가 같으므로 같은 public 으로
 - int b : public 보다 범위가 작으니 자신 그대로의 접근범위를 가져온다. protected
 - int a :  위에서 언급한 Base 클래스에서 private로 선언된 멤버는 접근불가라는 것을 기억하자.
Posted by 모과이IT
,
is-a 관계  
 : 상속에 있어서 우리는 주로 public 상속에 대해서 이야기 할 것이고, 사용할 것이다. 그럼 우리가 주로 사용할 puiblic 상속을 사용할때는 is-a관계가 성립되도록 만들어야 한다. 그럼 is-a 관계는 무슨 관계이냐? 말그대로 is a : ~은 ~이다 라는 관계를 성립하자는 이야기이다.
 예를 들어 사람과 학생의 관계를 생각해보자 (Person - Student) 
 - Person is a student  : 사람은 학생이다. 이것은 성립이 안된다. 모든 사람이 학생일수는 없는 노릇 아닌가?
 - Student is a person  : 학생은 사람이다. 이것은 성립된다. 그래서 우리가 상속 클래스를 만들때, Student 클래스는 파생클래스로 Person 클래스를 상속할 수 있는것이다. 

* 왜 is-a 관계여야 하나? 

 위와 같은 상속 관계에서 클래스는 모든 사람의 공통적인 분모를 가지고 추상화 시켜서 클래스를 만들것이다. 각각의 클래스의 범위는 모든 사람, 모든 학생, 모든 장학생으로 범위가 잡혀질 것이다. 일반적으로 사람이 10명있을 때보다, 5명 있을때(즉, 인원수가 적을수록) 우리는 더많은 공통적인 특성을 더 많이 뽑아 낼 수 있다. 
 위의 그림을 보면, 아래로 내려 갈수록 범위는 좁아지겠지만, 기능은 많아질 것이다. 범위가 좁아지기 위해서는 is-a 관계로 형성이 되어야 한다. 기능이 많아지면 멤버 함수가 늘어 난다는 의미인데 그것이 C++의 상속적 특성과 완전히 일치한다. 그래서 is-a관계를 성립시키는 것이 상속을 하는 조건에 있어서 반드시 필요하다. 
상속관계가 있을 때, 아래로 갈수록 구체화 또는 특수화(specialization) 되어 지고, 

                                                                                  올라갈수록 보다 일반화(generalization) 되어 진다. 
 정리 하자면, is-a관계가 성립될 때, 아래로 갈수록 특성이 많아 지는데 이러한 특성들은 멤버 함수와 멤버 변수로서 반영이 되고, 그렇기 때문에 상속으로서 관계를 맺어 줄 수 있는 것이다. 상속 관계에 있을 때 파생클래스는 기본클래스의 모든 특성들을 포함하기 때문에(특성들이 많아 지기 때문에) is-a 관계로 표현한다.

 is-a 관계의 잘못된 상속 예  


 아까 위에서 성립이 안된다던 Person is a student의 is-a 관계를 예로 한번 들어 보자. 만약 Student 클래스에서 공부하는 기능인 study함수가 있고 Person 클래스가 Student 클래스를 상속 했다고 하자. main 에서 Person 객체를 여러개 생성했다고 하면, Person은 상속받은 함수의 기능을 가지고 있으므로, 모두 study 기능을 가지고 있을 것이다. 하지만, 모든 사람이 공부를 하는 것이 아닐것이다. 
  그럼에도 불구하고 스터디 기능을 다 가지고 있을 것이다. 왜냐하면 우리가 프로그램에서 상속관계를 이렇게 묶어 냈기 때문이다. 그래서 문제가 된다. 프로그램도 현실세계에 있는 기능을 그대로 프로그래밍언어로 표현한 것이기 때문에 현실세계와 동떨어지면 그 프로그램은 난해한 프로그램이 되는 것이다. 따라서 is-a 관계를 성립 되도록 만들어야 한다. 

 Has-a 관계  
 : is-a 관계과 마찬가지로 ~ has a ~ : ~가 ~을 소유한다. 이런 의미를 가지는 관계를 말한다. 예를 들어 보면 이런거다.  
 - Policeman have a cudgel  : 경찰은 몽둥이를 소유한다. 
  1.   
  2. #include <iostream>   
  3. using namespace std;   
  4.   
  5. class Cudgel  //몽둥이   
  6. {    
  7. public:   
  8.     void Swing(){ cout<<"Swing a cudgel!"<<endl; }   
  9. };   
  10.   
  11. class Police : public Cudgel  //몽둥이를 소유하는 경찰   
  12. {   
  13. public:   
  14.     void UseWeapon(){ Swing(); }   
  15. };   
  16.   
  17. int main()   
  18. {   
  19.     Police pol;   
  20.     pol.UseWeapon();   
  21.   
  22.     return 0;   
  23. }  
 위에서 몽둥이 클래스는 휘두르는 기능인 Swing 함수를 가지고 있다. 경찰 클래스는 몽둥이 클래스를 상속했기에 몽둥이 클래스의 기능을 사용할 수 있다.(즉, 소유한 것이다) 이것으로 "Police is cudgel"의 is-a 관계는 성립이 안되지만, has-a 관계가 성립되어도 상속이 된다는것을 알 수 있다. 

 Has-a 관계에의한 상속 & 포함  
 : has-a 관계는 상속뿐만이 아니라, 다른 방법에 의해서도 표현가능하다. 그것이 바로 포함이라는 개념이다. 그 첫번째는 "객체 멤버에 의한 포함관계" 이다. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Cudgel  //몽둥이   
  5. {    
  6. public:   
  7.     void Swing(){ cout<<"Swing a cudgel!"<<endl; }   
  8. };   
  9.   
  10.   
  11. class Police //몽둥이를 소유하는 경찰   
  12. {   
  13.     Cudgel cud;   
  14. public:   
  15.     void UseWeapon(){ cud.Swing(); }   
  16. };   
  17.   
  18. int main()   
  19. {   
  20.     Police Pol;   
  21.     Pol.UseWeapon();   
  22.   
  23.     return 0;   
  24. }  
 Police Class의 멤버 변수로 Cudgel class의 객체가 왔다. (C에서의 구조체 멤버로 구조체 변수가 올수 있는것과 같은 이치) C++은 기본 자료형이나 사용자 정의 자료형이나 동일시 하고 있는 특성을 가지고 있다. 그러기에 이런 선언이 가능한 것이다. 

 일단 메인함수에서 클래스의 객체로 만들때 어떻게 만들어 지는지 알아 보자.
 처음에 pol이라는 이름으로 메모리 공간을 할당하고, cudgel 객체를 위한 메모리 공간도 할당을 해줄 것이다. 이때의 순서를 정리 하자면  Police 메모리 공간 할당 -> cudgel 객체 메모리 공간할당 & 생성자 호출 -> Police 생성자 호출 이 이뤄질 것이다. 

 13번째 줄인 Cudgel cud; 는 명시적으로 어떤 생성자를 호출 하라고 명시 되어 있지 않으므로, Cudgel 클래스의 인자값 받지 않는 void 생성자를 호출 할 것이다.  이러한 형태로 클래스의 객체가 멤버로 포함되기 위해서는 반드시 인자값 받지 않는 void 생성자를 지녀야 한다. 

 여기서 13번째 줄에 Cudgel cud(10); 이라는 문장도 쓸수 있지 않을까? 라는 의구심을 가지게 된다. "cud 생성과정에서 10이라는 인자값을 받는 생성자를 호출 하겠다"라는 의미이다. 이런 문장이 올 수 있을 거 같지만, 오지 못한다. 이 문장은 객체를 생성과 동시에 초기화하는 효과를 가지는데, C에서 구조체 멤버 변수 선언과 동시에 초기화 불가능한 것과 마찬가지로 C++에서도 클래스의 멤버는 생성과 동시에 초기화가 불가능하다. (C/C++ 이외에 java 와 C#은 가능하다) 따라서, C++은 클래스의 객체가 멤버로 존재하기 위해서는 그 객체를 기반으로 하는 클래스는 반드시 void 생성자를 지녀야 한다. 

 결과적으로 생성된 police 객체에는 cud라는 객체가 존재하는 것이다. police는 cud를 포함하고 있는 것이다. 그렇기 때문에 UseWeapon 함수를 호출하면서, 자기가 가지고 있는 Swing 함수를 호출 할 수 있는 것이다. 이것을 포함관계라고 하고, UML에서는 아래와 같이 표현한다. 

 객체 포인터 멤버에 의한 포함관계  
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Cudgel  //몽둥이   
  5. {    
  6. public:   
  7.     void Swing(){ cout<<"Swing a cudgel!"<<endl; }   
  8. };   
  9.   
  10.   
  11. class Police //몽둥이를 소유하는 경찰   
  12. {   
  13.     Cudgel* cud;   
  14. public:   
  15.     Police(){   
  16.         cud=new Cudgel;  //객체 동적 생성 (void 생성자 호출) 인자값받는 객체 동적 생성도 가능   
  17.     }   
  18.     ~Police(){   
  19.         delete cud;   
  20.     }   
  21.     void UseWeapon(){ cud->Swing(); }   
  22. };   
  23.   
  24. int main()   
  25. {   
  26.     Police pol;   
  27.     pol.UseWeapon();   
  28.   
  29.     return 0;   
  30. }  
 위 예제는, 객체 포인터 멤버에 의한 포함관계의 예제이다. 일단 그림을 한번 보자.

 소스코드 13번째 줄에서, cudgel 객체가 생성되고,그 포인터를 cud라는 멤버에 대입을 하니까 police 객체는 cud 객체를 위와 같이 가리키는 형태를 띄게 될것이다. 그렇기 때문에, UseWepon 함수에서는 cud라는 포인터가 가리키는 객체의 swing 함수를 호출 할 수가 있다.  
  객체는 동적 생성했으므로, cudgel 객체는 힙(Heap)에 있을 것이고 메인함수에서 생성된 지역적인 특성을 지니는 객체 이므로 cud는 스택(Stack)에 존재할 것이다. 
 물론 존재하는 위치 / 메모리 구조가 틀리지만, 결과적으로 이것도 포함 관계라고 할 수 있다. 여기서 말하는 포함은 물리적인 특성이 아니라, 논리적, 기능적인 특성을 같이 하고 있는 것도 포함이라고 할 수 있다.
 보시다시피 논리적으로 Police 클래스 Cudgel 객체의 포인터를 가지고 있으므로 그것을 소유 하고 있는것이다. (언제든 참조 가능) 이것도 역시 포함 관계에 해당된다. 위의 형태를 가진것이 바로 객체 포인터 멤버에 의한 포함관계이다. (주로 이것을 많이 쓴다)

 상속으로 묶인 두 개의 클래스는 강한 연관성(결합도(coupling))을 띄게 된다. 만약 소유관계의 상속에서 경찰이 몽둥이를 가지지 않은 경찰을 표현하기 위해서는 클래스의 구조 자체가 바뀌어야 한다. 하지만 이를 포함관계로 구성하면 간단히 표현이 가능해진다. 따라서, 상속은 Is-a관계의 표현에 매우 적절하다. 그리고 경우에 따라서는 Has-A 관계의 표현에도 사용될 수 있으나, 이는 프로그램의 변경에 많은 제약을 가져다 줄 수 있다.
Posted by 모과이IT
,
객체 포인터  
 : 객체 포인터란 객체를 가리킬 수 있는 포인터를 의미한다. (객체의 주소 값을 저장할 수 있는 포인터)
  - 예를 들어 A 클래스의 포인터는 A 객체뿐만 아니라, A클래스를 상속하는 파생 클래스 객체의 주소 값도 저장이 가능하다

 다음 상속 관계를 한번 살펴 보도록 하자.
 is-a관계에 의해서 ScholarStd 객체는 Student 객체이자 Person 객체도 동시에 된다.  ("ScholarStd 는 Person 객체이다." 이런 말도 틀린말은 아니다) 그러면 "Student 객체는 ScholarStd  객체이다" 는 성립이 안된다. is-a 관계는 아래쪽으로 성립이 안된다. 우리가 프로그램상에서 서로의 객체를 생성했다고 해보자. 아래의 예제는 is-a 관계에서 위의 그림은 소스코드로 나타낸 것이다. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Person   
  5. {   
  6. public:   
  7.     void Sleep(){    
  8.         cout<<"Sleep"<<endl;   
  9.     }   
  10. };   
  11.   
  12. class Student : public Person   
  13. {   
  14. public:   
  15.     void Study(){   
  16.         cout<<"Study"<<endl;   
  17.     }   
  18. };   
  19.   
  20. class ScholarStd  : public Student   
  21. {   
  22. public:   
  23.     void Receive_Scholar(){   
  24.         cout<<"Work"<<endl;   
  25.     }   
  26. };   
  27.   
  28. int main(void)   
  29. {   
  30.     Person* p1=new Person;   
  31.     Person* p2=new Student;   
  32.     Person* p3=new ScholarStd ;   
  33.   
  34.     p1->Sleep();   
  35.     p2->Sleep();   
  36.     p3->Sleep();   
  37.   
  38.     return 0;   
  39. }  
여기서 p1,2,3 포인터는 Person 클래스의 포인터니까 Person클래스의 객체를 가리킬 수 있는 포인터가 되는것이다. (*p1는 Person 클래스의 객체를 가리킬 수 있는 포인터) 
- p2라는 포인터는 Student 클래스의 객체를 가리키는 것도 문제가 없을 것이다. 
- p3라는 포인터는 ScholarStd 클래스의 객체를 가리키는 것도 문제가 없을것이다. 

그럼 "Student *s;" 라고 선언할시를 생각해 보자. 이 선언은 ScholarStd 클래스의 객체를 가리키는 것은 문제 없다. (ScholarStd 객체는 Student 객체이자 Person 객체이므로) 하지만 포인터 s는 Person 클래스의 객체를 가리키는 것은 문제가 된다. 왜냐하면 모든 Person 객체들이 다 Student가 아니기 때문이다. 그리고 클래스의 객체를 가리킨다는 의미는 역으로 is-a관계가 성립해야 된다는 걸 의미한다.

 객체 포인터 권한  
  지금까지는 객체가 가리키는 것이 어떤 경우가 합당한지를 알아 봤는데, 이 객체를 가지고 그 안의 함수를 사용하는 경우는 어떨까? 객체 포인터 권한에 대해 알아보자. 
  예를 들어 여기 A라는 클래스가 있다 A는 B에 의해 상속되어 지고, C는 B클래스에 의해 상속되어 진다고 가정하고 각각의 클래스에는 클래스 이름에 맞는 a,b,c() 함수가 존재한다고 해보자. 
 그 후, C 클래스의 객체를 생성한다고 하자. 그러면 C클래스의 객체 안에는 아마도, A클래스내에 선언되어 있는 a()함수도 있을것도 b()도 있고 자신의 클래스의 함수에 c()도 있을것이다. 아래의 그림 처럼 말이다. (아래의 그림처럼 함수 경계선이 있는것은 아니다.) C 클래스의 객체는 B객체이자 A 객체가 되므로 A,B,C 클래스의 포인터를 가지고 이 것을 가리킬수 있을 것이다. 
  이렇게 포인트 변수들이 가리키는 대상이 같을 경우, 만약 포인터 A (a*)가 가리키는 C 객체의 주소값이 0x10번지 라고 하면, 포인터 b,c의 주소값은 0x10번지로 으로 다 똑같다.  
  A클래스의 포인터를 가지고 가리키는 대상이 비록 C객체 이지만, A클래스의 포인터를 가지고 호출 할 수 있는 함수는 A클래스 내에 선언되어 있는 멤버 변수나 멤버함수로 제한적이다.  "A클래스 포인터(A* a) 가 가리키는 대상이 C 객체 일지라도, A 클래스의 포인터로 접근 할 수 있는 영역은 A 클래스내로 제한된다." 라는 이야기 이다.  


 위의 이야기를 염두에 두고 소스코드를 보자.  C클래스의 포인터 c는 C클래스 객체를 가리키고 있다. 그러므로 C클래스의 포인터로 접근할 수 있는 영역은 C클래스내로 제환된다는 이야기 이다. is-a관계에 의해 C는 모든 함수에 접근 권한을 가지게 되므로 아래의 코드는 문제가 없다. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class A{   
  5. public:   
  6.     void a(){ cout<<"call a function"<<endl; }   
  7. };   
  8.   
  9. class B : public A   
  10. {   
  11. public:   
  12.     void b(){ cout<<"call b function"<<endl; }   
  13. };   
  14.   
  15. class C : public B   
  16. {   
  17. public:   
  18.     void c() { cout<<"call c function"<<endl; }   
  19. };   
  20.   
  21. void main()   
  22. {   
  23.     C* c = new C;   
  24.     c->a();   
  25.     c->b();   
  26.     c->c();   
  27. }  

다음의 코드를 한번 살펴보자. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class A{   
  5. public:   
  6.     void a(){ cout<<"call a function"<<endl; }   
  7. };   
  8.   
  9. class B : public A   
  10. {   
  11. public:   
  12.     void b(){ cout<<"call b function"<<endl; }   
  13. };   
  14.   
  15. class C : public B   
  16. {   
  17. public:   
  18.     void c() { cout<<"call c function"<<endl; }   
  19. };   
  20.   
  21. void main()   
  22. {   
  23.     C* c = new C;   
  24.     B* b = c;    
  25.     A* a = b;   
  26.   
  27.     cout<<c <<endl;   
  28.     cout<<b <<endl;   
  29.     cout<<a <<endl;   
  30. }  


 우선 24번째 줄에 있는 B* b = c;  문법을 보자. 여기서 우리는 포인터의 타입이 일치 하지 않는데 어떻게 이런 문법이 가능한가 라는 의구심을 가지게 될것이다. 하지만 앞서 배웠듯이, C클래스의 포인터는 C클래스의 포인터 이자, A,B 클래스의 포인터도 되므로 이런 문법은 성립이 되는 것이다. 
 문제 이야기로 넘어와 보자. 위 예제는 객체 포인터 권한 도입부분에 설명한 부분을 코드로 풀어 놓은 것이다. c,b,a를 출력해보면 같은 주소값을 가짐을 확인할 수 있다. 
  그리고 또 확인해야 할 것이 있다. B클래스의 포인터를 이용해 각각 함수를 호출해 보자. 메인함수에 아래와 같은 코드를 넣어 컴파일해 보자.
  1. b->a();   
  2. b->b();   
  3. b->c(); //에러  

위와 같이 컴파일 에러를 확인해 볼수 있다. 왜그런 것일까? 우리는 b라는 포인터가 가리키는 객체가 C클래스의 객체라는 것을 알고 있지만, 컴파일러는 b라는 포인터가 가리키는 대상이 C클래스의 객체라는것을 모른다. (그게 C건 어떤 것이건 아예 모른다.) b라는 포인터가 어느 클래스의 객체를 가리키는지는 runtime에 결정 되기 때문에 컴파일러는 이 대상이 어느 객체를 가리키는지 결정을 못내리는 것이다.

 이와 마찬가지로 A의 클래스 포인터 a 가 가리키는 것이 우리는 C클래스의 객체라는것을 알고 있지만, 컴파일러는 무엇을 가리키는지 알지 못한다. 그래서 컴파일러는 포인터 a가 가리키는 대상이 무엇이든지 a함수의 호출은 전혀 문제가 안될것이라는 판단하에 a함수 호출만을 보장하는 것이다. 
Posted by 모과이IT
,