정보은닉 


우리는 정보은닉에 대한 이야기를 진행하고자 한다. 


class Point

{

public :

int x ;

int y ;

};


위의 클래스에서 멤버변수 x와 y의 범위는 0이상 100 이하이고, 좌 상단좌표가 [ 0 , 0 ] 

우 하단의 좌표가 [ 100 , 100 ]이라고 가정하자. 그리고 다음의 예를 관찰하자.

이 예제는 Point 클래스의 멤버변수가 public으로 선언되었을때 발생할 수 있는 문제점을 보인다.


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Point  
  5. {  
  6. public:  
  7.     int x;  
  8.     int y;  
  9. };  
  10.   
  11. class Rectangle  
  12. {  
  13. public:  
  14.     Point upLeft;  
  15.     Point lowRight;  
  16.   
  17. public:  
  18.     void ShowRecInfo()  
  19.     {  
  20.         cout << "up Left   : " << '[' << upLeft.x << ", ";  
  21.         cout << upLeft.y << ']' << endl;  
  22.         cout << "low Right : " << '[' << lowRight.x << ", ";  
  23.         cout << lowRight.y << ']' << endl << endl;  
  24.     }  
  25. };  
  26.   
  27. int main()  
  28. {  
  29.     Point pos1 = { -2,4 };  
  30.     Point pos2 = { 5,9 };  
  31.     Rectangle rec = { pos2,pos1 };  
  32.     rec.ShowRecInfo();  
  33.     return 0;  
  34. }  



[ 결과 ]


  • 4행: 점을 표현한 Point 클래스의 멤버변수 x, y는 public으로 선언되어 있어서 어디서든 접근이 가능하다.
  • 11행: Rectangle 클래스는 직사각형을 표현한 것이다. 그런데 직사각형은 두 개의 점으로 표현이 가능하므로, 두 개의 Point 객체를 멤버로 두었다. 이렇듯 클래스의 멤버로 객체를 둘 수 있다.
  • 29~30행: 직사각형을 표현할 두 개의 Point 객체를 생성하였다. 이렇듯 멤버변수가 public으로 선언되면, 구조체 변수를 초기화하듯이 초기화가 가능하다.
  • 31행: 위에서 생성한 Point 객체를 이용해서 Rectangle 객체를 생성 및 초기화하고있다.


위의 예제에서 어떠한 문제가 있는지 지적해보자.

  • 점의 좌표는 0이상 100이하가 되어야 하는데, 그렇지 못한 Point 객체가 있다.
  • 직사각형을 의미하는 Rectangle 객체의 좌 상단 좌표 값이 우 하단 좌표 값보다 크다.

점의 좌표는 0이상 100이하가 되어야 유효하다. 하지만 위의 예제는 이를 지키지 않았다. 이러한 문제는 프로그래머의 실수에서 발생하는 경우가 대부분이다. 뿐만 아니라, 좌 상단의 좌표 값이 우 하단의 좌표 값보다 큰 것으로 보아 두 좌표값이 서로 바뀐듯 하다. 에러가 나지 않은 이유는 문법적으로 문제가 없기 때문에 컴파일러는 위의 코드를 문제삼지 않기 때문이다. 때문에 제한된 방법으로의 접근만 허용을 해서 잘못된 값이 저장되지 않도록 도와야 한다.


다음의 변경된 Point 클래스를 보자.


point.h


  1. #ifndef __POINT_H_  
  2. #define __POINT_H_  
  3.   
  4. class Point  
  5. {  
  6. private:  
  7.     int x;  
  8.     int y;  
  9.   
  10. public:  
  11.     bool InitMembers(int xpos, int ypos);  
  12.     int Getx() const;  
  13.     int Gety() const;  
  14.     bool SetX(int xpos);  
  15.     bool SetY(int ypos);  
  16. };  
  17. #endif  


먼저 멤버변수 x와 y를 private으로 선언하여 임의로 값이 저장되는 것을 막아놓았다.

즉, x와 y라는 정보를 은닉한 상황이다. 대신에 값의 저장 및 참조를 위한 함수를 추가로 정의하였다.

따라서 이 함수 내에서 맴버변수에 저장되는 값을 제한할 수 있다.


point.cpp


  1. #include <iostream>  
  2. #include "Point.h"  
  3. using namespace std;  
  4.   
  5. bool Point::InitMembers(int xpos, int ypos)  
  6. {  
  7.     if (xpos < 0 || ypos < 0)  
  8.     {  
  9.         cout << "Pass out of range values" << endl;  
  10.         return false;  
  11.     }  
  12.   
  13.     x = xpos;  
  14.     y = ypos;  
  15.     return true;  
  16. }  
  17.   
  18. int Point::Getx() const  
  19. {  
  20.     return x;  
  21. }  
  22.   
  23. int Point::Gety() const  
  24. {  
  25.     return y;  
  26. }  
  27.   
  28. bool Point::SetX(int xpos)  
  29. {  
  30.     if (0 > xpos || xpos > 100)  
  31.     {  
  32.         cout << "Pass out of range values" << endl;  
  33.         return false;  
  34.     }  
  35.     x = xpos;  
  36.     return true;  
  37. }  
  38. bool Point::SetY(int ypos)   
  39. {  
  40.     if (0 > ypos || ypos > 100)  
  41.     {  
  42.         cout << "Pass out of range values" << endl;  
  43.         return false;  
  44.     }  
  45.     y = ypos;  
  46.     return true;  
  47. }  


먼저, 멤버변수에 값을 저장하는 함수 InitMembers, SetX, SetY는 0이상 100 이하의 값이 전달되지 않으면, 에러 메세지를 출력하면서 값의 저장을 허용하지 않는 형태로 정의되었다. 따라서 잘못된 값이 저장되지 않을뿐더러, 값이 잘못 전달되는 경우 출력된 메세지를 통해서 문제가 있음을 확인할 수 있다. 


이들을 바탕으로 다음과 같은 결론을 내릴 수 있다.


멤버변수를 private로 선언하고, 해당 변수에 접근하는 함수를 별도로 정의해서, 안전한 형태로 멤버변수의 접근을 유도하는 것이 바로 '정보은닉'이며, 이는 좋은 클래스가 되기 위한 기본조건이다.


위의 코드를 보면 변수의 이름이 XXX 일때

다음과 같이 함수의 이름이 GetXXX, SetXXX로 정의된 함수들을 볼 수 있다.


int GetX( ) const ;

bool SetX( int xpos ) ;


int GetY( ) const ;

bool SetY( int ypos ) ;


이들을 가리켜 '엑세스 함수(access function)'라 하는데, 이들은 멤버변수를 

private으로 선언하면서 클래스 외부에서의 멤버변수 접근을 목적으로 정의되는 함수들이다.


그럼 이어서 정보가 은닉된 Rectangle 클래스를 다음과 같이 제시할 수 있다.


Rectangle.h


  1. #ifndef __RECTANGLE_H_  
  2. #define __RECTANGLE_H_  
  3.   
  4. #include "Point.h"  
  5.   
  6. class Rectangle  
  7. {  
  8. private:  
  9.     Point upLeft;  
  10.     Point lowRight;  
  11.   
  12. public:  
  13.     bool InitMembers(const Point &ul, const Point &lr);  
  14.     void ShowRecInfo() const;  
  15. };  
  16. #endif  


Rectangle클래스도 멤버변수를 private으로 선언하고, 멤버의 초기화를 위한 별도의 함수를 추가하였다. 이 함수 내에서는 좌 상단과 우 하단의 좌표가 뒤바뀌는 것을 검사하는 내용이 담겨있다. 


그럼 어떻게 검사를 진행하는지 직접 확인해보자.


Rectangle.cpp


  1. #include <iostream>  
  2. #include "Rectangle.h"  
  3. using namespace std;  
  4.   
  5. bool Rectangle::InitMembers(const Point &ul, const Point &lr)  
  6. {  
  7.     if (ul.GetX() > lr.GetY || ul.GetY() > lr.GetX())  
  8.     {  
  9.         cout << "Pass out of range values" << endl;  
  10.         return false;  
  11.     }  
  12.     upLeft = ul;  
  13.     lowRight = lr;  
  14.     return true;  
  15. }  
  16.   
  17. void Rectangle::ShowRecInfo() const  
  18. {  
  19.     cout << "up Left   : " << '[' << upLeft.GetX() << ", ";  
  20.     cout << upLeft.GetY() << ']' << endl;  
  21.     cout << "low Right : " << '[' << lowRight.GetX() << ", ";  
  22.     cout << lowRight.GetY() << ']' << endl << endl;  
  23. }  


마지막으로 위의 클래스들을 대상으로 정의된 main 함수를 정의하여 코드를 완성해보자.


  1. #include <iostream>  
  2. #include "Point.h"  
  3. #include "Rectangle.h"  
  4. using namespace std;  
  5.   
  6. bool Point::InitMembers(int xpos, int ypos)  
  7. {  
  8.     if (xpos < 0 || ypos < 0)  
  9.     {  
  10.         cout << "Pass out of range values" << endl;  
  11.         return false;  
  12.     }  
  13.   
  14.     x = xpos;  
  15.     y = ypos;  
  16.     return true;  
  17. }  
  18.   
  19. int Point::GetX() const  
  20. {  
  21.     return x;  
  22. }  
  23.   
  24. int Point::GetY() const  
  25. {  
  26.     return y;  
  27. }  
  28.   
  29. bool Point::SetX(int xpos)  
  30. {  
  31.     if (0 > xpos || xpos > 100)  
  32.     {  
  33.         cout << "Pass out of range values" << endl;  
  34.         return false;  
  35.     }  
  36.     x = xpos;  
  37.     return true;  
  38. }  
  39. bool Point::SetY(int ypos)   
  40. {  
  41.     if (0 > ypos || ypos > 100)  
  42.     {  
  43.         cout << "Pass out of range values" << endl;  
  44.         return false;  
  45.     }  
  46.     y = ypos;  
  47.     return true;  
  48. }  
  49. bool Rectangle::InitMembers(const Point &ul, const Point &lr)  
  50. {  
  51.     if (ul.GetX() > lr.GetX() || ul.GetY() > lr.GetY())  
  52.     {  
  53.         cout << "Pass out of range values" << endl;  
  54.         return false;  
  55.     }  
  56.     upLeft = ul;  
  57.     lowRight = lr;  
  58.     return true;  
  59. }  
  60.   
  61. void Rectangle::ShowRecInfo() const  
  62. {  
  63.     cout << "up Left   : " << '[' << upLeft.GetX() << ", ";  
  64.     cout << upLeft.GetY() << ']' << endl;  
  65.     cout << "low Right : " << '[' << lowRight.GetX() << ", ";  
  66.     cout << lowRight.GetY() << ']' << endl << endl;  
  67. }  
  68.   
  69. int main()  
  70. {  
  71.     Point pos1;  
  72.     if (!pos1.InitMembers(-2, 4))  
  73.         cout << "Init fail" << endl;  
  74.       
  75.     if (!pos1.InitMembers(2, 4))  
  76.         cout << "Init fail" << endl;  
  77.   
  78.     Point pos2;  
  79.     if (!pos2.InitMembers(5, 9))  
  80.         cout << "Init fail" << endl;  
  81.   
  82.     Rectangle rec;  
  83.     if (!rec.InitMembers(pos2, pos1))  
  84.         cout << "Rectangle Init fail" << endl;  
  85.   
  86.     if (!rec.InitMembers(pos1, pos2))  
  87.         cout << "Rectangle Init fail" << endl;  
  88.   
  89.     rec.ShowRecInfo();  
  90.     return 0;  
  91. }  


[ 결과 ]




const 함수


앞서 보인 예제 Point.h와 Rectangle.h에 선언된 다음 함수들에는 const 선언이 추가되어 있다.


  1. int GetX() const;  
  2. int GetY() const;  
  3. void ShowRecInfo() const;  


이 const는 다음 내용을 선언하는 것이다.


"이 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다!"


매개변수도 아니고, 지역변수도 아닌, 멤버변수에 저장된 값을 변경하지 않겠다는 선언이다. 따라서 const 선언이 추가된 멤버함수 내에서 멤버변수의 값을 변경하는 코드가 삽입되면, 컴파일 에러가 발생한다. 이렇게 함수를 const로 선언하면, 실수로 자신의 의도와 다르게 멤버변수의 값을 변경했을 때, 컴파일 에러를 통해서 확인할 수 있다. 따라서 프로그래머의 실수를 최소화 할 수있다. 다음의 예제를 통해 특징들을 알아보자.


  1. class SimpleClass  
  2. {  
  3. private:  
  4.     int numl  
  5.   
  6. public:  
  7.     void InitNum(int n)  
  8.     {  
  9.         num = n;  
  10.     }  
  11.     int GetNum()  
  12.     {  
  13.         return num;  
  14.     }  
  15.     void ShowNum() const  
  16.     {  
  17.         cout << GetNum() << endl;  
  18.     }  
  19. };  


위의 클래스 정의에서 ShowNum 함수는 const 함수로 선언되었다. 그리고 실제로 함수 내에서는 멤버변수 num의 값을 변경하지 않는다. 그럼에도 불구하고 GetNum 함수를 호출하는 문장에서 컴파일 에러가 발생한다. 그 이유는 const 함수 내에서 const가 아닌 함수의 호출이 제한되기 때문이다. const로 선언되지 않은 함수는 아무리 멤버변수에 저장된 값을 변경하지 않더라도 변경할 수 있는 능력을 지닌 함수이다.



'Language > C++' 카테고리의 다른 글

C++ 기본(4) 객체지향 프로그래밍의 이해  (0) 2017.01.03
C++ 기본(3) 클래스(Class)와 객체(Object)  (0) 2016.12.28
C++ 기본(2) - 구조체  (0) 2016.12.23
8. C++ 기본(8)  (0) 2016.12.14
7. C++ 기본(7)  (0) 2016.12.13

객체지향 프로그래밍의 이해


C++은 객체지향 언어이다. 따라서 객체지향에 대한 이해가 필요하다.


객체는 영어로 Object 이다 그리고 이의 사전적인 의미는 "사물 또는 대상" 을 의미한다.


즉 Object는 우리 주변에 존재하는 물건이나 대상 전부를 의미한다. 그렇다면 객체를 지향하는 프로그래밍이라는 것은 무었일까? 예를 들어서 다음 상황을 시뮬레이션 하는 프로그램을 구현한다고 가정해보자.


" 나는 과일장수에게 두개의 사과를 구매했다! "


이 문장에 삽입되어 있는 객체의 종류는 다음과 같다.


나, 과일장수, 사과


그렇다면 '나' 라는 객체는 '과일장수' 라는 객체로부터 '과일' 객체의 구매라는 액션을 취할 수 있어야 한다. 그런대 객체지향 프로그래밍에서는 '나' 그리고 '과일장수'와 같은 객체를 등장시킬 수 있을 뿐만 아니라. '나'라는 객체가 '과일장수' 라는 객체로부터 '과일' 객체를 구매하는 행위도 그대로 표현할 수 있다. 즉, 객체지향 프로그래밍은 현실에 존재하는 사물과 대상, 그리고 그에 따른 해동을 있는 그대로 실체화 시키는 형태의 프로그래밍이다. 이의 확인을 위해서 '나'와 '과일장수'라는 객체를 생성하여 다음의 행동을 실체화시켜 보겠다.


" 나는 과일장수에게 2,000원을 주고 두개의 사과를 구매했다. "


참고로 사과도 객체로 실체화시킬 수 있으나, 

코드의 간결성을 위해서 '나' 와 '과일장수'만 객체화 시키도록 하겠다.



객체를 이루는 것은 데이터와 기능이다.


프로그램상에 과일장수 객체가 존재한다고 가정해보자. 이 객체는 무엇으로 이뤄져야 할까?

프로그램상에서 바라보는 과일장수의 관점은 '과일의 판매'에 있다. 

따라서 프로그램상에서 바라보는 과일장수는 다음과 같은 형태이다,


  • 과일장수는 과일을 판다.
  • 과일장수는 사과 20개 오랜지 10개를 보유하고 있다.
  • 과일장수의 과일판매 수익은 현재까지 50000원이다.

이중 첫 번째는 과일장수의 '행동(behavior)' 을 의미한다. 그리고 두 번째와 세 번째는 과일 장수의 '상태(state)' 를 의미한다. 이처럼 객체는 하나 이상의 상태 정보(Data)와 하나 이상의 행동(기능)으로 구성되며, 상태 정보는 변수를 통해 표현이 되고, 행동은 함수를 통해서 표현이 된다. 먼저 과일장수의 상태 정보를 변수로 표현해보자.


  • 보유하고 있는 사과의 수         -> int numOfApples ;
  • 판매 수익                            -> int myMoney ;
이번에는 과일장수의 행위인 과일의 판매를 함수로 표현해보겠다.

  1. int SaleApples(int money)    // 사과 구매액이 함수의 인자로 전달   
  2. {  
  3.    int num = money/1000;     // 사과가 개당 1000원이라고 가정      
  4.    numOfApples -= num;       // 사과의 수가 줄어들고,  
  5.    myMoney += money;         // 판매 수익이 발생한다.  
  6.    return num;               // 실제 구매가 발생환 사과의 수를 반환.  
  7. }  


이렇게 해서 과일장수 객체를 구성하게 되는 변수와 함수가 마련되었다.

이제 이들을 묶어서 구체화 해보자.



'과일장수'의 정의와 멤버변수의 상수화에 대한 논의


객체를 생성하기에 앞서 객체의 생성을 위한 틀을 먼저 만들어야 한다.이는 현실세계에서 물건을 만들기 위한 틀을 짜는 행위에 비유할 수 있다. '나' 또는 '과일장수' 객체를 생성하기 위해서는 이 둘을 위한 틀을 먼저 만들어야 하는데 위에서 마련해 놓은 함수와 변수를 이용해서 틀을 만들면 다음과 같이 만들 수 있다.


  1. class FruitSeller  
  2. {  
  3. private:  
  4.     int APPLE_PRICE;  
  5.     int numOfApples;  
  6.     int myMoney;  
  7.   
  8. public:  
  9.     int SaleApples(int money)  
  10.     {  
  11.         int num = money / APPLE_PRICE;  
  12.         numOfApples -= num;  
  13.         myMoney += money;  
  14.         return num;  
  15.     }  
  16. };  


그렇다! 클래스의 정의이다.

즉 FruitSeller라는 이름의 클래스가 과일장수의 틀이 되는 것이다.


그런데 우리는 과일장수에게 다음과 같은 질문을 할 수도 있다.


"오늘 과일좀 많이 파셨나요?"


그러면 과일장수는 다음과 같이 대답을 할 것이다.


"2000원 벌었어, 남는 사과는 18개고 말이야!"


그래서 이러한 대화에 사용되는 함수와 변수를 초기화 하는데 사용하는 함수를 추가해서 

과일장수의 틀을 다음과 같이 완성할 수 있다,


  1. class FruitSeller  
  2. {  
  3. private:  
  4.     int APPLE_PRICE;  
  5.     int numOfApples;  
  6.     int myMoney;  
  7.   
  8. public:  
  9.     void InitMembers(int price, int num, int money)  
  10.     {  
  11.         APPLE_PRICE = price;  
  12.         numOfApples = num;  
  13.         myMoney = money;  
  14.     }  
  15.   
  16.     int SaleApples(int money)  
  17.     {  
  18.         int num = money / APPLE_PRICE;  
  19.         numOfApples -= num;  
  20.         myMoney += money;  
  21.         return num;  
  22.     }  
  23.   
  24.     void ShowSalesResult()  
  25.     {  
  26.         cout << "remnantal apple : " << numOfApples << endl;  
  27.         cout << "sales profit    : " << myMoney << endl;  
  28.     }  
  29. };  


'나' 를 표현하는 클래스의 정의


이제 '나'를 표현하기 위한 클래스를 정의할 차례이다. 

그렇다면 나라는 클래스에는 어떠한 변수들과 함수들로 채워져야 할까?


먼저 데이터적인 측면을 바라보자.

구매자에게 있어서 가장 중요한 것은 돈이다. 돈이 있어야 구매가 가능하기 때문이다. 그리고 구매를 했다면 해당 물품을 소유하게 된다. 따라서 두 변수를 클래스의 멤버변수로 생각해 볼 수 있다.


  • 소유하고 있는 현금의 액수       -> int myMoney ;
  • 소유하고 있는 사과의 개수       -> int numOfApples ;

이제 기능적 측면을 생각해보자. 과일 구매자가 지녀야 할 기능은 과일의 구매이다.
따라서 이 기능을 담당할 함수를 클래스에 추가해야 한다.
이 함수의 이름은 BuyApple이라 하면 나의 클래스는 다음과 같이 정의할 수 있다.

  1. class FruitBuyer  
  2. {  
  3.     int myMoney;        // private :  
  4.     int numOfApples;    // private :  
  5.   
  6. public:  
  7.     void InitMembers(int money)  
  8.     {  
  9.         myMoney = money;  
  10.         numOfApples = 0;  
  11.     }  
  12.     void BuyApples(FruitSeller &seller, int money)  
  13.     {  
  14.         numOfApples += seller.SaleApples(money);  
  15.         myMoney -= money;  
  16.     }  
  17.     void ShowBuyResult()  
  18.     {  
  19.         cout << "My Money     : " << myMoney << endl;  
  20.         cout << "num of Apple : " << numOfApples << endl;  
  21.     }  
  22. };  

위의 클래스에 선언된 두 변수 myMoney와 numOfApples에는 private나 public과 같은 선언이 존재하지 않음을 알 수 있다. 그러나 클래스는 아무런 선언이 존재하지 않을 때 private로 간주된다.(구조체는 public) private 선언을 목적으로 접근제어 지시자를 생략하는 경우가 흔히 있으니 기억해두자.


클래스 기반의 두 가지 객체생성 방법

우리는 두개의 클래스를 정의하였다. 그렇다면 객체를 생성하지 않고 이 두 클래스 안에 존재하는 변수에 접근하고 함수를 호출하는 것이 가능할까? 결론부터 말하면 불가능하다, 이들은 실체(객체) 가 아닌 틀이다. 따라서 앞서 정의한 클래스를 실체화 시키는 작업이 필요하다. 즉 객체화 하는 것이다. C++에는 두가지 객체생성 방법이 존재한다. 이는 기본 자료형 변수의 선언방식과 동일함을 보여준다.


  • ClassName objName ;                               // 일반적인 변수의 선언방식
  • ClassName *ptrObj = new ClassName ;         // 동적 할당방식(힙 할당방식)

즉 우리가 정의한 FruitSeller 클래스와 FruitBuyer 클래스의 객체 생성방식은 다음과 같다.

  • FruitSeller seller ;
  • FruitBuyer buyer ;
그리고 이를 동적으로 할당하려면 다음과 같이 생성하면 된다.

  • FruitSeller *objPtr1 = new FruitSeller ; 
  • FruitBuyer *objPtr2 = new FruitBuyer ;


사과장수 시뮬레이션


예제를 완성해보자 이 예제가 의미를 갖는 이유는 두 객체가 서로 대화를 하기 때문이다.

그럼 객체는 어떻게 대화를 주고 받는지 예제를 통해 확인해보자.


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class FruitSeller  
  5. {  
  6.     int APPLE_PRICE;  
  7.     int numOfApples;  
  8.     int myMoney;  
  9.   
  10. public:  
  11.     void InitMembers(int price, int num, int money)  
  12.     {  
  13.         APPLE_PRICE = price;  
  14.         numOfApples = num;  
  15.         myMoney = money;  
  16.     }  
  17.     int SaleApples(int money)  
  18.     {  
  19.         int num = money/APPLE_PRICE;  
  20.         numOfApples -= num;  
  21.         myMoney += money;  
  22.         return num;  
  23.     }  
  24.     void ShowSalesResult()  
  25.     {  
  26.         cout << "remnantal apple : " << numOfApples << endl;  
  27.         cout << "sales profit    : " << myMoney << endl << endl;  
  28.     }  
  29. };  
  30.   
  31. class FruitBuyer  
  32. {  
  33.     int myMoney;  
  34.     int numOfApples;  
  35.   
  36. public:  
  37.     void InitMembers(int money)  
  38.     {  
  39.         myMoney = money;  
  40.         numOfApples = 0;  
  41.     }  
  42.     void BuyApples(FruitSeller &seller, int money)  
  43.     {  
  44.         numOfApples += seller.SaleApples(money);  
  45.         myMoney -= money;  
  46.     }  
  47.     void ShowBuyResult()  
  48.     {  
  49.         cout << "My money     : " << myMoney << endl;  
  50.         cout << "num of Apple : " << numOfApples << endl << endl;  
  51.     }  
  52. };  
  53.   
  54. int main()  
  55. {  
  56.     FruitSeller seller;  
  57.     seller.InitMembers(1000, 20, 0);  
  58.     FruitBuyer buyer;  
  59.     buyer.InitMembers(5000);  
  60.     buyer.BuyApples(seller, 2000);  
  61.   
  62.     cout << "Result of Seller" << endl;  
  63.     seller.ShowSalesResult();  
  64.     cout << "Result of Buyer" << endl;  
  65.     buyer.ShowBuyResult();  
  66.     return 0;  
  67. }  



[ 결과 ]



 

  • 60행 : 이 예제에서 어렵게 느껴질 수 있는 부분이다. BuyApples 은 사과의 구매 기능을 담당하는 함수이다. 즉 이 함수 내에서 사과의 구매가 완성되어야 한다. 그렇다면 생각해 보자. 사과를 구매하는데 있어서 필요한 것 두가지가 무엇인가? 그것은 바로 구매대상과 구매금액이다, 그래서 이 둘의 정보가 인자로 전달되도록 함수가 정의되었다.

위 예제에서 44행과 60행을 특히 주목해서 보자. 이는 코드의 흐름 이상의 것을 담고 있기 때문에 자세히 볼 필요가 있다. 이어서 이 부분에 대한 설명을 추가로 진행하도록 하겠다.


객체간의 대화 방법(Message Passing)


위 예제 44행을 보면 FruitBuyer 객체에 존재하는 함수 내에서 FruitSeller 객체의 함수 SaleApples를 호출하고 있다. 그런데 이 한문장은 현실세계에서 다음과 같이 표현할 수 있다.


" seller 님 사과 2000원어치 주세요 "


'나'라는 객체가 '과일장수'라는 객체로부터 '과일' 객체를 구매하는 행위도 그대로 표현할 수 있다.

즉 44행은 FruitBuyer 객체가 FruitSeller 객체에게 위와 같은 메시지를 전달하는 상황이다.


이처럼 하나의 객체가 다른 하나의 객체에게 메시지를 전달하는 방법은(어떠한 행위의 요구를 위한 메시지 전달) 함수호출을 기반으로 한다. 

그러나 객체지향에서는 이러한 형태의 함수호출을 가리켜 '메시지 전달(Message Passing)' 이라 한다.

'Language > C++' 카테고리의 다른 글

C++ 기본(5) 클래스의 완성  (0) 2017.01.04
C++ 기본(3) 클래스(Class)와 객체(Object)  (0) 2016.12.28
C++ 기본(2) - 구조체  (0) 2016.12.23
8. C++ 기본(8)  (0) 2016.12.14
7. C++ 기본(7)  (0) 2016.12.13


지금까지 설명한 C++의 구조체는 클래스의 일종이다.

그렇다면 클래스와 구조체에는 어떠한 차이점이 있을까?


클래스와 구조체의 유일한 차이점


키워드 struct를 대신해서 class를 사용하면, 구조체가 아닌 클래스가 된다. 아래의 예제를 살펴보자.


class Car

{

char gamerID [ Car_CONST :: ID_LEN ] ;

int fuelGauge ; 

int curSpeed ;


void ShowCarState( ) { . . . . }

void Accel( ) { . . . . }

void Break( ) { . . . . }

} ;


위 처럼 클래스를 사용할 경우 다음의 방식으로 변수를 선언할 수 없다.


Car run99 = { "run99" , 100, 0 } ;      ( x )


그 이유는 클래스 내에 선언된 함수가 아닌, 다른 영역에서 변수를 초기화 하려 했기 때문이다.

클래스는 기본적으로 클래스 내에 선언된 변수는 클래스 내에 선언된 함수에서만 접근이 가능하다.

따라서 다음과 같은 형태로 클래스 변수를 선언해야 한다.


Car run99 ;


클래스를 정의하는 과정에서 각각 변수 및 함수의 접근 허용범위를 별도로 선언해야 한다.

그리고 바로 이것이 키워드 struct 를 이용해서 정의하는 구조체와 

키워드 class를 이용해서 정의하는 클래스의 유일한 차이점이다.


접근제어 지시자(접근제어 레이블)


C++의 접근제어 지시자는 다음과 같이 총 세가지가 존재한다.

  • public         : 어디서든 접근 허용
  • protected    : 상속관계에 놓여있을 때, 유도 클래스에서의 접근 허용
  • private :      : 클래스 내(클래스 내에 정의된 함수)에서만 접근 허용

이중 protected는 '상속' 과 관련이 있으므로 나중에 살펴보도록 하자. 


먼저 public과 private에 대해서 예제를 통해 알아보자.


  1. #include <iostream>  
  2. #include <cstring>  
  3. using namespace std;  
  4.   
  5. namespace CAR_CONST  
  6. {  
  7.     enum  
  8.     {  
  9.         ID_LEN = 20, MAX_SPD = 200, FUEL_STEP = 2,  
  10.         ACC_STEP = 10, BRK_STEP = 10  
  11.     };  
  12. }  
  13.   
  14. class Car  
  15. {  
  16. private:  
  17.     char gamerID[CAR_CONST::ID_LEN];  
  18.     int fuelGauge;  
  19.     int curSpeed;  
  20. public:  
  21.     void InitMembers(char *ID, int fuel);  
  22.     void ShowCarState();  
  23.     void Accel();  
  24.     void Break();  
  25. };  
  26.   
  27. void Car::InitMembers(char *ID, int fuel)   
  28. {  
  29.     strcpy(gamerID, ID);  
  30.     fuelGauge = fuel;  
  31.     curSpeed = 0;  
  32. }  
  33. void Car::ShowCarState()  
  34. {  
  35.     cout << "userID : " << gamerID << endl;  
  36.     cout << "Gauge  : " << fuelGauge << "%" << endl;  
  37.     cout << "Speed  : " << curSpeed << "km/s" << endl << endl;  
  38. }  
  39. void Car::Accel()  
  40. {  
  41.     if (fuelGauge <= 0)  
  42.         return;  
  43.     else  
  44.         fuelGauge -= CAR_CONST::FUEL_STEP;  
  45.   
  46.     if ((curSpeed + CAR_CONST::FUEL_STEP) >= CAR_CONST::MAX_SPD)  
  47.     {  
  48.         curSpeed = CAR_CONST::MAX_SPD;  
  49.         return;  
  50.     }  
  51.     curSpeed += CAR_CONST::MAX_SPD;  
  52.   
  53. }  
  54. void Car::Break()  
  55. {  
  56.     if (curSpeed < CAR_CONST::BRK_STEP)  
  57.     {  
  58.         curSpeed = 0;  
  59.         return;  
  60.     }  
  61.     curSpeed -= CAR_CONST::BRK_STEP;  
  62.   
  63. }  
  64.   
  65. int main()  
  66. {  
  67.     Car run99;  
  68.     run99.InitMembers("run99", 100);  
  69.     run99.Accel();  
  70.     run99.Accel();  
  71.     run99.Accel();  
  72.     run99.ShowCarState();  
  73.     cout << endl;  
  74.     run99.Break();  
  75.     run99.ShowCarState();  
  76.     return 0;  
  77. }  



[ 결과 ]



  • 14행 : struct를 대신하여 class 선언이 삽입되었다. 따라서 이는 클래스 정의에 해당한다.

  • 16행 : 접근제어 지시자중 private이 선언되었으므로, 이어서 등장하는 변수와 함수는 private에 해당하는 범위 내에서(클래스 내에서만) 접근이 가능하다.

  • 20행 : 접근제어 지시자중 public이 선언되었으므로, 이어서 등장하는 변수와 함수는 public에 해당하는 범위 내에서(어디서든) 접근이 가능하다.

  • 21, 27행 : 클래스 안에 선언된 변수의 초기화를 목적으로 정의된 함수이다. 변수가 모두 private으로 선언되어서 main 함수에서 접근이 불가능하다. 하지만 이 함수는 동일 클래스 내에 정의된 함수이므로 접근이 가능하다. 뿐만 아니라, 이 함수는 public으로 선언되어서 main 함수에서 호출이 가능하다. 따라서 main 함수에서는 이 함수의 호출을 통해서 클래스 안에 선언된 변수를 초기화 할 수 있다.

  • 68행 : 초기화를 목적으로 InitMembers 함수를 호출하고 있다. 이 함수는 ID정보와 연료의 게이지 정보를 전달받아서 초기화 하는 형태로 정의되었다. 단 변수 curSpeed는 무조건 0으로 초기화 되도록 정의하였다.

  • 68행~72행 : 함수 호출이 가능한 이유는 함수가 모두 public으로 선언되었기 때문이다.


이제 정리해보자.

1. 접근제어 지시자 A가 선언되면, 그 이후 등장하는 변수나 함수는 
    A에 해당하는 범위 내에서 접근이 가능하다.

2. 그러나 새로운 접근제어 지시자 B가 선언되면, 
   그 이후로 등장하는 변수나 함수는 B에 해당하는 범위 내에서 접근이 가능하다.

3. 함수의 정의를 클래스 밖으로 빼도, 이는 클래스의 일부이기 때문에, 
   함수 내에서는 private으로 선언된 변수에 접근이 가능하다.

4. 키워드 struct를 이용해서 정의한 구조체(클래스)에 선언된 변수와 함수에,
   별도의 접근제어 지시자를 선언하지 않으면, 모든 변수와 함수는 public으로 선언된다.


5. 키워드 class를 이용해서 정의한 클래스에 선언된 변수와 함수에 

   별도의 접근제어 지시자를 선언하지 않으면 모든 변수와 함수는 private으로 선언된다.



문제1

계산기 기능의 Calculator 클래스를 정의해보자. 기본적으로 지니는 기능은 덧셈, 뺄셈, 곱셈, 나눗셈이며, 연산을 할 때 마다 어떠한 연산을 몇 번 수행했는지 기록되어야 한다. 아래의 main 함수와 실행의 예에 부합하는 Calculator 클래스를 정의하면 된다. 단, 멤버변수는 private로 멤버함수는 public으로 선언하자.


  1. int main()  
  2. {  
  3.    Calculator cal;  
  4.    cal.Init();  
  5.    cout << "3.2 + 2.4 = " << cal.Add(3.2, 2.4) << endl;  
  6.    cout << "3.5 / 1.7 = " << cal.Div(3.5, 1.7) << endl;  
  7.    cout << "2.2 - 1.5 = " << cal.Min(2.2, 1.5) << endl;  
  8.    cout << "4.9 / 1.2 = " << cal.Div(4.9, 1.2) << endl;  
  9.    cal.ShowOpCount();  
  10.    cout << endl;  
  11.    return 0;  
  12. }  



문제2

문자열 정보를 내부에 자장하는 Printer라는 이름의 클래스를 디자인하자. 

이 클래스의 두가지 능력은 문자열 저장, 문자열 출력이다.

아래의 main함수와 실행의 예에 부합하는 Printer 클래스를 정의하되

멤버변수는 private로 , 멤버함수는 public으로 선언하자.


  1. int main()  
  2. {  
  3.    Printer pnt;  
  4.    pnt.SetString("Hello world!");  
  5.    pnt.ShowString();  
  6.   
  7.    pnt.SetString("I love C++");  
  8.    pnt.ShowString();  
  9.    return 0;  
  10. }  



문제1 답


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Calculator  
  5. {  
  6. private:  
  7.     int addnum;  
  8.     int minnum;  
  9.     int mulnum;  
  10.     int divnum;  
  11. public:  
  12.     void Init();  
  13.     double Add(double num1, double num2);  
  14.     double Min(double num1, double num2);  
  15.     double Mul(double num1, double num2);  
  16.     double Div(double num1, double num2);  
  17.     void ShowOpCount();  
  18. };  
  19.   
  20. void Calculator::Init()   
  21. {  
  22.     addnum = 0;  
  23.     minnum = 0;  
  24.     mulnum = 0;  
  25.     divnum = 0;  
  26. }  
  27. double Calculator::Add(double num1, double num2)   
  28. {  
  29.     addnum++;  
  30.     return num1 + num2;  
  31. }  
  32. double Calculator::Min(double num1, double num2)  
  33. {  
  34.     minnum++;  
  35.     return num1 - num2;  
  36. }  
  37. double Calculator::Mul(double num1, double num2)  
  38. {  
  39.     mulnum++;  
  40.     return num1*num2;  
  41. }  
  42. double Calculator::Div(double num1, double num2)  
  43. {  
  44.     divnum++;  
  45.     return num1 / num2;  
  46. }  
  47. void Calculator::ShowOpCount()   
  48. {  
  49.     cout << "Add: " << addnum << ' ';  
  50.     cout << "Sub: " << minnum << ' ';  
  51.     cout << "Mul: " << mulnum << ' ';  
  52.     cout << "Div: " << divnum << ' ';  
  53. }  
  54. int main()  
  55. {  
  56.     Calculator cal;  
  57.     cal.Init();  
  58.     cout << "3.2 + 2.4 = " << cal.Add(3.2, 2.4) << endl;  
  59.     cout << "3.5 / 1.7 = " << cal.Div(3.5, 1.7) << endl;  
  60.     cout << "2.2 - 1.5 = " << cal.Min(2.2, 1.5) << endl;  
  61.     cout << "4.9 / 1.2 = " << cal.Div(4.9, 1.2) << endl;  
  62.     cal.ShowOpCount();  
  63.     cout << endl;  
  64.     return 0;  
  65. }  


문제2 답


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. class Printer   
  5. {  
  6. private:  
  7.     char str[100];  
  8. public:  
  9.     void SetString(char *st);  
  10.     void ShowString();  
  11. };  
  12.   
  13. void Printer::SetString(char *st)  
  14. {  
  15.     strcpy(str, st);  
  16. }  
  17. void Printer::ShowString()  
  18. {  
  19.     cout << str << endl;  
  20. }  
  21.   
  22. int main()   
  23. {  
  24.     Printer pnt;  
  25.     pnt.SetString("Hello World!");  
  26.     pnt.ShowString();  
  27.   
  28.     pnt.SetString("I love C++");  
  29.     pnt.ShowString();  
  30.     return 0;  
  31. }  



'Language > C++' 카테고리의 다른 글

C++ 기본(5) 클래스의 완성  (0) 2017.01.04
C++ 기본(4) 객체지향 프로그래밍의 이해  (0) 2017.01.03
C++ 기본(2) - 구조체  (0) 2016.12.23
8. C++ 기본(8)  (0) 2016.12.14
7. C++ 기본(7)  (0) 2016.12.13

C++ 에서 구조체 변수의 선언


C와 C++에서 구조체 변수를 선언하는 방법은 다음과 같다.


 C

C++ 

 

 struct Car basicCar ;

 struct Car simpleCar ;

 

 Car basicCar ;

 Car simple car ; 


C언어에서 앞에 삽입된 struct는 이어서 선언되는 자료형이 구조체를 기반으로 정의된 자료형임을 나타낸다. 그리고 키워드 struct를 생략하려면 별도의 typedef 선언을 추가해야 된다. 


하지만 C++에서는 기본 자료형 변수의 선언방식이나 구조체를 기반으로 정의된  자료형의 변수 선언방식에 큰 차이가 없다. 즉 C++에서는 별도의 typedef 선언 없이도 변수를 선언할 수 있다.


앞서 정의한 구조체를 기반으로 예제를 작성해보자.


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. #define ID_LEN 20  
  5. #define MAX_SPD 200  
  6. #define FUEL_STEP 2  
  7. #define ACC_STEP 10  
  8. #define BRK_STEP 10  
  9.   
  10. struct Car  
  11. {  
  12.     char gamerID[ID_LEN];  
  13.     int fuelGauge;  
  14.     int curspeed;  
  15. };  
  16. void ShowCarState(const Car &car) {  
  17.     cout << "userID: " << car.gamerID << endl;  
  18.     cout << "gauge : " << car.fuelGauge << "%" << endl;  
  19.     cout << "speed : " << car.curspeed << "km/s" << endl;  
  20. }  
  21.   
  22. void Accel(Car &car) {  
  23.     if (car.fuelGauge <= 0)  
  24.         return;  
  25.   
  26.     else  
  27.         car.fuelGauge -= FUEL_STEP;  
  28.   
  29.     if (car.curspeed + ACC_STEP >= MAX_SPD) {  
  30.         car.curspeed = MAX_SPD;  
  31.         return;  
  32.     }  
  33.   
  34.     car.curspeed += ACC_STEP;  
  35. }  
  36.   
  37. void Break(Car &car) {  
  38.     if (car.curspeed < BRK_STEP) {  
  39.         car.curspeed = 0;  
  40.         return;  
  41.     }  
  42.   
  43.     car.curspeed = BRK_STEP;  
  44. }  
  45.   
  46. int main() {  
  47.     Car run99 = { "run99",100,0 };  
  48.     Accel(run99);  
  49.     Accel(run99);  
  50.     ShowCarState(run99);  
  51.     cout << endl;  
  52.     Break(run99);  
  53.     ShowCarState(run99);  
  54.     cout << endl;  
  55.     Car speed77 = { "speed77",100,0 };  
  56.     Accel(speed77);  
  57.     Break(speed77);  
  58.     ShowCarState(speed77);  
  59.       
  60.     return 0;  
  61. }  


[ 결과 ]




  • 4~8행 : 구조체 Car와 관련된 각종 정보를 상수화
  • 16행 : 차의 정보를 출력하는 기능의 함수이다. 단순 정보만 출력하기 때문에 const 참조자를 사용하였다.
  • 22행 : 차의 가속을 위해서 엑셀을 밟은 상황을 표현해 놓은 함수.
  • 37행 : 브레이크를 밟은 상황을 표현한 함수. 단순히 속도가 감속하는것만 표현
  • 47행 : 구조체 변수의 선언 및 초기화가 진행.
  • 48~49행 : 엑셀과 브레이크를 밟은 상황을 연출하고 있다.

위의 선언된 3개의 함수에 대해서 다음과 같이 이야기할 수 있다.

구조체 Car과 함께 부류를 형성하여, Car과 관련된 데이터의 처리를 담당하는 함수


따라서 위의 함수들은 구조체 Car에 종속적인 함수들이라고 할 수 있다. 

하지만 전역함수의 형태를 띠기 때문에, 이 함수들이 구조체 Car에 종속적임을 나타내지 못한다. 

따라서 다른 영역에서 이 함수를 호출하는 실수가 발생할 수 있다.


 

구조체 안에 함수 삽입하기


구조체 속에 종속적인 함수들을 구조체 안에 함께 묶어버리면 관련된 데이터와 함수를 모두 묶을 수 있다.

위의 예제를 이용하여 묶어보자.


  1. struct Car  
  2. {  
  3.     char gamerID[ID_LEN];  
  4.     int fuelGauge;  
  5.     int curspeed;  
  6.   
  7.     void ShowCarState() {  
  8.         cout << "user ID : " << gamerID << endl;  
  9.         cout << "gauge   : " << fuelGauge << "%" << endl;  
  10.         cout << "speed   : " << curspeed << "km/s" << endl;  
  11.     }  
  12.   
  13.     void Accel() {  
  14.         if (fuelGauge <= 0)  
  15.             return;  
  16.         else  
  17.             fuelGauge -= FUEL_STEP;  
  18.   
  19.         if (curspeed + ACC_STEP >= MAX_SPD) {  
  20.             curspeed = MAX_SPD;  
  21.             return;  
  22.         }  
  23.   
  24.         curspeed += ACC_STEP;  
  25.     }  
  26.   
  27.     void Break() {  
  28.         if (curspeed < BRK_STEP) {  
  29.             curspeed = 0;  
  30.             return;  
  31.         }  
  32.   
  33.         curspeed = BRK_STEP;  
  34.     }  
  35. };  


구조체 안에 삽입된 함수의 정의에 어떠한 변화가 생겼는지, ShowCarState 함수를 예로 확인해보자.


  1. void ShowCarState()   
  2. {  
  3.     cout << "user ID : " << gamerID << endl;  
  4.     cout << "gauge   : " << fuelGauge << "%" << endl;  
  5.     cout << "speed   : " << curspeed << "km/s" << endl;  
  6. }  


이렇 듯 연산의 대상에 대한 정보가 불필요한 이유는, 함수가 구조체 내에 삽입되면서 구조체 내에 선언된 변수에 직접 접근이 가능해졌기 때문이다. 따라서 다음과 같이 구조체 변수를 각각 선언하면,


Car run99 = { "run99" , 100 , 0 } ;

Car speed77 = { "speed77" , 100 . 0 } ;


다음의 형태로 구조체 변수가 생성된다.


run99

speed77 

 

 char gamerID[ ID_LEN ] ;

 int fuelGauage ;

 int curspeed ;


 char gamerID[ ID_LEN ] ;

 int fuelGauage ;

 int curspeed ;

 void ShowCarState( )

 {

     . . . . .

 }

 void Accel( )

 {

     . . . . .

 }

 void Break( )

 {

     . . . . .

 }

 void ShowCarState( )

 {

     . . . . .

 }

 void Accel( )

 {

     . . . . .

 }

 void Break( )

 {

     . . . . .

 }


참고로 위 그림에서는 모든 구조체 변수 내에 함수가 각각 별도로 존재하는 것처럼 묘사해 놓았다.

실제로는 모든 Car 구조체 변수가 하나의 함수를 공유한다.

다만, 논리적으로 각각 변수가 자신의 함수를 별도로 지나는 것과 가은 효과 및 결과를 보이기 때문에

C++의 구조체 변수는 위 그림의 형태로 이해하는 것이 좋다. 예제를 통해 확인해보자.


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. #define ID_LEN 20  
  5. #define MAX_SPD 200  
  6. #define FUEL_STEP 2  
  7. #define ACC_STEP 10  
  8. #define BRK_STEP 10  
  9.   
  10. struct Car  
  11. {  
  12.     char gamerID[ID_LEN];  
  13.     int fuelGauge;  
  14.     int curspeed;  
  15.   
  16.     void ShowCarState() {  
  17.         cout << "user ID : " << gamerID << endl;  
  18.         cout << "gauge   : " << fuelGauge << "%" << endl;  
  19.         cout << "speed   : " << curspeed << "km/s" << endl;  
  20.     }  
  21.   
  22.     void Accel() {  
  23.         if (fuelGauge <= 0)  
  24.             return;  
  25.         else  
  26.             fuelGauge -= FUEL_STEP;  
  27.   
  28.         if (curspeed + ACC_STEP >= MAX_SPD) {  
  29.             curspeed = MAX_SPD;  
  30.             return;  
  31.         }  
  32.   
  33.         curspeed += ACC_STEP;  
  34.     }  
  35.   
  36.     void Break() {  
  37.         if (curspeed < BRK_STEP) {  
  38.             curspeed = 0;  
  39.             return;  
  40.         }  
  41.   
  42.         curspeed = BRK_STEP;  
  43.     }  
  44. };  
  45.   
  46. int main() {  
  47.     Car run99 = { "run99",100,0 };  
  48.     run99.Accel();  
  49.     run99.Accel();  
  50.     run99.ShowCarState();  
  51.     cout << endl;  
  52.     run99.Break();  
  53.     run99.ShowCarState();  
  54.     cout << endl;  
  55.     Car speed77 = { "speed77",100,0 };  
  56.     speed77.Accel();  
  57.     speed77.Break();  
  58.     speed77.ShowCarState();  
  59.       
  60.     return 0;  
  61. }  



결과는 위와 동일하다.


문제


2차원 평면상에서의 좌표를 표현할 수 있는 구조체를 다음과 같이 정의하였다.


struct Point

{

int xpos ; 

int ypos ;

} ;


위의 구조체를 기반으로 다음의 함수를 정의하고자 한다 

(자세한 기능은 실행의 예를 통해서 확인하도록 한다.)


void MovePos ( int x, int y ) ;

void AddPoint( const Point &pos ) ;

void ShowPosition( ) ;


단, 위의 함수들은 구조체 안에 정의를 해서 다음의 형태로 main( ) 함수를 구성할 수 있어야 한다.


  1. int main()  
  2. {  
  3.    Point pos1 = {12, 4};  
  4.    point pos2 = {12, 30};  
  5.   
  6.    pos1.MovePos(-7, 10);  
  7.    pos1.ShowPosition();  
  8.   
  9.    pos1.AddPoint(pos2);  
  10.    pos1.showPosition();  
  11.   
  12.    return 0;  
  13. }  



실행 예





  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. struct Point  
  5. {  
  6.     int xpos;  
  7.     int ypos;  
  8.   
  9.     void MovePos(int x, int y) {  
  10.         xpos += x;  
  11.         ypos += y;  
  12.     }  
  13.   
  14.     void AddPoint(const Point &pos) {  
  15.         xpos += pos.xpos;  
  16.         ypos += pos.ypos;  
  17.     }  
  18.     void ShowPosition() {  
  19.         cout << "[" << xpos << ", " << ypos << "]" << endl;  
  20.     }  
  21. };  
  22.   
  23. int main()   
  24. {  
  25.     Point pos1 = { 12,4 };  
  26.     Point pos2 = { 20,30 };  
  27.   
  28.     pos1.MovePos(-7, 10);  
  29.     pos1.ShowPosition();  
  30.   
  31.     pos1.AddPoint(pos2);  
  32.     pos1.ShowPosition();  
  33.   
  34.     return 0;  
  35.   
  36. }  



구조체 안에 enum 상수의 선언


위에서 사용되었던 예제들을 살펴보면 다음의 매크로 상수들이 존재한다.


  1. #define ID_LEN 20  
  2. #define MAX_SPD 200  
  3. #define FUEL_STEP 2  
  4. #define ACC_STEP 10  
  5. #define BRK_STEP 10  


그런데 이들 상수 역시 구조체 Car에게만 의미가 있는 상수들이다. 즉, 다른영역에서 사용하도록 정의된 상수가 아니니 이들 상수도 구조체 내에 포함시키는 것이 좋을 수 있다. 따라서 이러한 경우 열거형 enum을 이용해서 다음과 같이 구조체 내에서만 유효한 상수를 정의하면 된다.


  1. struct Car  
  2. {  
  3.     enum{  
  4.         ID_LEN 20  
  5.         MAX_SPD 200  
  6.         FUEL_STEP 2  
  7.         ACC_STEP 10  
  8.         BRK_STEP 10  
  9.     }  
  10. };  



enum의 선언을 구조체 내부에 삽입하는 것이 부담스럽다면, namespace를 이용해서 상수가 사용되는 영역을 명시하는 것도 또 다른 방법이 될 수 있다. namespace를 사용하면 몇몇 구조체들 사이에서만 사용하는 상수들을 선언할 때 특히 도움이 되며, 위에서 보인 방법보다 기독성도 좋아지는 경향이 있다. 예제를 통해 확인해보자.


  1. #include <iostream>  
  2. using namespace std;  
  3.   
  4. namespace CAR_CONST  
  5. {  
  6.     enum {  
  7.         ID_LEN = 20,  
  8.         MAX_SPD = 200,  
  9.         FUEL_STEP = 2,  
  10.         ACC_STEP = 10,  
  11.         BRK_STEP = 10  
  12.     };  
  13. }  
  14.   
  15. struct Car  
  16. {  
  17.     char gamerID[CAR_CONST::ID_LEN];  
  18.     int fuelGuage;  
  19.     int curSpeed;  
  20.   
  21.     void ShowCarState() {  
  22.         cout << "user ID : " << gamerID << endl;  
  23.         cout << "Gage    : " << fuelGuage << "%" << endl;  
  24.         cout << "speed   : " << curSpeed << "km/s" << endl;  
  25.     }  
  26.     void Accel() {  
  27.         if (fuelGuage == 0)  
  28.             return;  
  29.         else  
  30.             fuelGuage -= CAR_CONST::FUEL_STEP;  
  31.   
  32.         if ((curSpeed + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD) {  
  33.             curSpeed = CAR_CONST::MAX_SPD;  
  34.             return;  
  35.         }  
  36.   
  37.         curSpeed += CAR_CONST::ACC_STEP;  
  38.     }  
  39.     void Break() {  
  40.         if (curSpeed < CAR_CONST::BRK_STEP) {  
  41.             curSpeed = 0;  
  42.             return;  
  43.         }  
  44.         curSpeed -= CAR_CONST::BRK_STEP;  
  45.     }  
  46. };  
  47.   
  48. int main()  
  49. {  
  50.     Car run99 = { "run99",100,0 };  
  51.     run99.Accel();  
  52.     run99.Accel();  
  53.     run99.ShowCarState();  
  54.     cout << endl;  
  55.     run99.Break();  
  56.     run99.ShowCarState();  
  57.     cout << endl;  
  58.   
  59.     Car speed77 = { "speed77",100,0 };  
  60.     speed77.Accel();  
  61.     speed77.Break();  
  62.     speed77.ShowCarState();  
  63.   
  64.     return 0;  
  65. }  



[ 결과 ]



  • 4행~13행 : CAR_CONST 이름공간 안에 구조체 Car에서 사용하는 상수들을 모아놓았다.

  • 17행 : 상수 ID_LEN의 접근을 위해서 이름공간 CAR_CONST를 지정하고있다.

이렇듯 이름공간을 지정해서 코드를 작성했기 때문에, 이 문장만 봐도 이 상수가 어느 영역에서 선언되고 사용되는 상수인지 쉽게 알 수 있다. 그래서 기독성이 좋아졌다고 할 수 있다.


함수 외부로 빼기


함수가 포함되어 있는 C++의 구조체를 보는 순간 

다음의 정보들이 쉽게 눈에 들어와야 코드의 분석이 용이하다.


  • 선언되어 있는 변수 정보

  • 정의되어 있는 함수 정보


보통 프로그램을 분석할 때, 흐름 및 골격 위주로 분석하는 경우가 많다. 그리고 이러한 경우에는 함수의 기능만 파악을 하지, 함수의 세부구현까지 신경을 쓰지 않는다. 따라서 구조체를 보는 순간 정의되어 있는 함수의 종류와 기능이 한눈에 들어오게끔 코드를 작성하는 것이 좋다. 따라서 구조체 내에 정의되어 있는 함수의 수가 많거나 그 길이가 길다면, 다음과 같이 구조체 밖으로 함수를 빼낼 필요가 있다.


struct Car

{

    . . . . . .

    void ShowCarState( );

    void Accel( );

} ;


void Car :: ShowCarState( )

{

    . . . . . .

}


void Car :: Accel( )

{

    . . . . . . 

}


함수의 원형선언을 구조체 안에 두고, 함수의 정의를 구조체 밖으로 빼내는 것이다.

다만 빼낸 다음 해당 함수가 어디에 정의되어 있는지에 대한 정보만 추가해 주면 된다.


예제를 통해 살펴보자.


  • #include <iostream>  
    1. using namespace std;  
    2.   
    3. namespace CAR_CONST  
    4. {  
    5.     enum {  
    6.         ID_LEN = 20,  
    7.         MAX_SPD = 200,  
    8.         FUEL_STEP = 2,  
    9.         ACC_STEP = 10,  
    10.         BRK_STEP = 10  
    11.     };  
    12. }  
    13.   
    14. struct Car  
    15. {  
    16.     char gamerID[CAR_CONST::ID_LEN];  
    17.     int fuelGuage;  
    18.     int curSpeed;  
    19.   
    20.     void ShowCarState();  
    21.     void Accel();  
    22.     void Break();  
    23. };  
    24.   
    25. void Car::ShowCarState() {  
    26.     cout << "userID : " << gamerID << endl;  
    27.     cout << "guage  : " << fuelGuage << endl;  
    28.     cout << "speed  : " << curSpeed << endl;  
    29. }  
    30.   
    31. void Car::Accel() {  
    32.     if (fuelGuage <= 0)  
    33.         return;  
    34.     else  
    35.         fuelGuage -= CAR_CONST::FUEL_STEP;  
    36.   
    37.     if ((curSpeed + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD) {  
    38.         curSpeed = CAR_CONST::MAX_SPD;  
    39.         return;  
    40.     }  
    41.   
    42.     curSpeed += CAR_CONST::ACC_STEP;  
    43. }  
    44.   
    45. void Car::Break() {  
    46.     if (curSpeed < CAR_CONST::BRK_STEP) {  
    47.         curSpeed = 0;  
    48.         return;  
    49.     }  
    50.   
    51.     curSpeed -= CAR_CONST::BRK_STEP;  
    52. }  
    53.   
    54. int main() {  
    55.     Car run99 = { "run99,100,0" };  
    56.     run99.Accel();  
    57.     run99.ShowCarState();  
    58.     cout << endl;  
    59.     run99.Break();  
    60.     run99.ShowCarState();  
    61.     return 0;  
    62. }  



    [ 결과 ]



    • 20~22행 : 구조체 안에 함수의 원형만 남으니, 함수의 종류가 한눈에 들어오고 적절한 주석을 통해 함수의 기능도 쉽게 판단이 가능하다.

    • 25,31,45행 : 원래 속하는 구조체의 이름을 명시하면서 구조체 밖으로 함수의 정의가 빠져나왔다.


    이로써 C++에서의 구조체에 대한 설명이 끝났다.

    그런데 C++에서의 구조체는 클래스의 일종으로 간주된다. 그래서 구조체 안에 함수를 정의할 수 있었던 것이다. 즉, 위에서 정의한 구조체를 가리켜 그냥 '클래스'라고 표현해도 틀리지 않는다.


    다음부터 작성되는 글에는 클래스와 객체에 대해 설명하겠다.

    'Language > C++' 카테고리의 다른 글

    C++ 기본(4) 객체지향 프로그래밍의 이해  (0) 2017.01.03
    C++ 기본(3) 클래스(Class)와 객체(Object)  (0) 2016.12.28
    8. C++ 기본(8)  (0) 2016.12.14
    7. C++ 기본(7)  (0) 2016.12.13
    6. C++ 기본(6)  (0) 2016.12.13

    참조자를 이용한 Call-by-reference와 const 참조자


    아래의 코드를 보고 예상되는 출력결과를 이야기 해보자.

    int num = 24 ;

    func(num) ;

    cout << num << endl ;


    C언어 관점에서는 100% 24가 출력된다. 


    그러나 C++에서는 얼마가 출력될 지 알 수 없다.


    void func(int num) { . . . } 다음과 같이 정의되어 있을 경우 24가 출력되지만


    void func(int &ref) { . . . } 다음과 같이 정의되어 있다면 참조자를 이용해서 num에 저장된 값을 변경할 수 있다.


    여기서 참조자의 단점이 드러난다. 코드를 분석하는 과정에 있다면, 함수의 호출문장만 보고도 함수의 특성을 어느정도 판단할 수 있어야 한다. 그러나 참조자를 사용하는 경우, 함수의 원형을 확인해야 하고, 확인결과 참조자가 매개변수의 선언에 와있다면, 함수의 몸체까지 문장 단위로 확인을 해서 참조자를 통한 값의 변경이 있는지 확인해야 한다.


    이러한 단점을 어느정도 극복하기 위해 const 키워드를 사용한다.


    void func(const int &ref) { . . . }


    다음 코드의 함수 원형을 보면 참조자 ref에 const 선언이 추가되었다. 이는 다음의 의미를 갖는다.


    "함수 func 내에서 참조자 ref를 이용한 값의 변경은 하지 않겠다"


    따라서 참조자를 사용할 경우 다음의 원칙을 정하고 가급적 이 원칙을 지켜주는 것이 좋다.


    함수 내에서 참조자를 통한 값의 변경을 진행하지 않을 경우

    참조자를 const로 선언해서, 함수의 원형만 봐도 값의 변경이 이루어지지 않음을 알 수 있게 한다.


    반환형이 참조형(Referencfe Type)인 경우


    함수의 반환형에도 참조형이 선언될 수 있다. 다음의 예제를 통해 살펴보자


    1. int& reffunc(int &ref){  
    2.    ref++;  
    3.    return ref;  
    4. }  


    위의 함수에서는 매개변수로 참조자가 선언되었는데, 이 참조자를 그대로 반환하고 있다.


    하지만 다음과 같이 참조자를 반환해도 반환형은 참조형이 아닐 수 있다. 이점에 유의하자.


    1. int reffunc(int &ref){  
    2.    ref++;  
    3.    return ref;  
    4. }  


    이 둘의 차이점을 생각해 보면서 다음의 예제를 통해 살펴보자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. int &reffunc(int &ref) {  
    5.     ref++;  
    6.     return ref;  
    7. }  
    8.   
    9. int main() {  
    10.     int num1 = 1;  
    11.     int &num2 = reffunc(num1);  
    12.   
    13.     num1++;  
    14.     num2++;  
    15.     cout << "num1: " << num1 << endl;  
    16.     cout << "num2: " << num2 << endl;  
    17.     return 0;  
    18. }  



    [ 결과 ]



    • 11행 : reffunc 함수가 참조자를 반환했고, 이를 다시 참조자에 저장하고 있다.

    • 13~14행 : 변수 num1과 참조자 num2의 값을 1씩 증가시키고 있다.

    • 15~16행 : 변수 num1과 참조자 num2의 관계를 확인하기 위한 출력이다.


    참조형으로 반환된 값을 참조자에 저장하면, 참조의 관계가 하나 더 추가된다. 즉 위의 예제는 다음과 동일하다.


    int num1 = 1 ;

    int &ref = num1 ;

    int &num2 = ref ; 


    그런데 함수 reffunc의 매개변수로 선언된 참조자 ref는 지역변수와 동일한 성격을 갖는다. 즉 reffunc이 반환하면 참조자ref는 소멸된다. 그러나 참조자는 참조자일뿐, 그 자체로 변수는 아니다


    따라서 참조자가 소멸하더라도 참조자가 참조하는 변수는 소멸되지 않는다.


    그럼 이번에는 reffunc의 11행을 다음과 같이 변경해보자.


    int num2 = reffunc(num1) ;


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. int &reffunc(int &ref) {  
    5.     ref++;  
    6.     return ref;  
    7. }  
    8.   
    9. int main() {  
    10.     int num1 = 1;  
    11.     int num2 = reffunc(num1);  
    12.   
    13.     num1++;  
    14.     num2+=100;  
    15.     cout << "num1: " << num1 << endl;  
    16.     cout << "num2: " << num2 << endl;  
    17.     return 0;  
    18. }  



    [ 결과 ]




    • 11행 : 참조형으로 반환이 되지만, 일반 변수를 선언해서 반환 값을 저장할 수 있다.

    • 13~14행 : num1과 num2가 다른 변수임을 확인하기 위해서 서로 다른 연산을 진행하였다.

    위 예제에서 보이는 상황은 다음과 동일하다. 다만 함수의 호출을 통해서 이 과정이 진행되었을 뿐이다.

    int num1 = 1 ;
    int &ref = num1 ;
    int num2 = ref ;

    이렇듯 반환형이 참조형인 경우, 반환 값을 무엇으로 저장하느냐에 따라서 그 결과에 차이가 있으므로 적절한 선택을 해야 한다. 마지막으로 참조자를 반환하되, 반환형은 기본자료형인 경우를 알아보자.

    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. int reffunc(int &ref) {  
    5.     ref++;  
    6.     return ref;  
    7. }  
    8.   
    9. int main() {  
    10.     int num1 = 1;  
    11.     int num2 = reffunc(num1);  
    12.   
    13.     num1 += 1;  
    14.     num2 += 100;  
    15.     cout << "num1: " << num1 << endl;  
    16.     cout << "num2: " << num2 << endl;  
    17.       
    18.     return 0;  
    19. }  


    [ 결과 ]



    • 6행 : 참조자를 반환하지만, 반환형이 int이기 때문에 참조자가 참조하는 변수 값이 반환된다.

    • 13~14행 : num1과 num2가 다른 변수임을 확인하기 위해 서로 다른 연산을 진행하였다.


    실행 결과를 보면 이전의 예제 결과와 차이가 없다. 하지만 다음의 차이가 있다.

    반환형이 참조형인 경우 반환 값을 다음과 같이 두가지 형태로 저장할 수 있다.


    int num2 = reffunc(num1) ;            (O)

    int &num2 = reffunc(num1) ;          (O)


    하지만 반환형이 기본 자료형으로 선언된 함수의 반환 값은 반드시 변수에 저장해야 한다.


    int num2 = reffunc(num1) ;             (O)

    int &num2 = reffunc(num1)            (O)

    'Language > C++' 카테고리의 다른 글

    C++ 기본(3) 클래스(Class)와 객체(Object)  (0) 2016.12.28
    C++ 기본(2) - 구조체  (0) 2016.12.23
    7. C++ 기본(7)  (0) 2016.12.13
    6. C++ 기본(6)  (0) 2016.12.13
    5. C++ 기본(5) - OOP 단계별 프로젝트 01  (0) 2016.12.12

    참조자(Reference)의 이해


    참조자는 자신이 참조하는 변수를 대신할 수 있는 또 하나의 이름이다.


    예제를 통해 살펴보자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. int main() {  
    5.     int num1 = 1020;  
    6.     int &num2 = num1;  
    7.   
    8.     num2 = 3047;  
    9.     cout << "val : " << num1 << endl;  
    10.     cout << "ref : " << num2 << endl;  
    11.   
    12.     cout << "val : " << &num1 << endl;  
    13.     cout << "ref : " << &num2 << endl;  
    14.   
    15.     return 0;  
    16. }  


    [ 결과 ]



    • 6행 : num1에 대한 참조자 num2를 선언하였다. 이후로는 num1과 num2가 동일한 메모리 공간을 참조하게 된다.

    • 9~10행 : 동일한 값이 출력되면, num1과 num2가 동일한 메모리 공간을 참조함을 증명한다.

    • num1과 num2의 주소값을 출력하게 하였다. 


    참조자는 변수를 대상으로만 선언이 가능하다. 하지만 일단 선언되고 나면 변수와 차이가 없다. &연산자를 이용해서 주소값을 반환 받을 수 있고, 함수 내에서 선언된 local 참조자는 지역변수와 마찬가지로 함수를 빠져나가면 소멸된다.


    즉 다음의 선언에서,


    int &num2 = num1 ;

     

    num1이 변수의 이름이라면, num2는 num1의 별칭이라는 뜻이다. 그런데 이 역시 참조자를 이해하기 위한 좋은 비유가 된다. 예를 들어 "송경식"이라는 사람의 별명이 "불곰"이라면 다음 두문장이 의미하는 바와 이 문장을 처리했을 때의 결과는 모두 동일하다.


    "송경식에게 전화 해서 당장 오라고 해!"

    "불곰한테 전화해서 당장 오라고 해!"


    참조자의 특징

    • 참조자의 수에는 제한이 없으며, 참조자를 대상으로도 참조자를 선언할 수 있다.

    • 참조자는 선언과 동시에 누군가를 참조해야만 한다.

    • 참조자는 본디 변수에 또다른 이름을 붙이는 것이기 때문에 상수를 대상으로 참조자를 선언할 수 없다.


    참조자는 무조건 선언과 동시에 변수를 참조하도록 해야 한다. 여기서 말하는 변수의 범위는 배열 요소도 포함되며 이와 관련해서는 예제를 통해 알아보자.

    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. int main() {  
    5.     int arr[3] = { 1,2,3 };  
    6.     int &ref1 = arr[0];  
    7.     int &ref2 = arr[1];  
    8.     int &ref3 = arr[2];  
    9.   
    10.     cout << ref1 << endl;  
    11.     cout << ref2 << endl;  
    12.     cout << ref3 << endl;  
    13.   
    14.     return 0;  
    15. }  

    [ 결과 ]


    예제와 실행결과에서 보이듯이 배열 요소는 변수로 간주되어 참조자의 선언이 가능하다.

    그리고 포인터 변수 또한 변수이기 때문에 참조자의 선언이 가능하다.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. int main() {  
    5.     int num = 12;  
    6.     int *ptr = &num;  
    7.     int **dptr = &ptr;  
    8.   
    9.     int &ref = num;  
    10.     int *(&pref) = ptr;  
    11.     int **(&dpref) = dptr;  
    12.   
    13.     cout << ref << endl;  
    14.     cout << *pref << endl;  
    15.     cout << **dpref << endl;  
    16.   
    17.     return 0;  
    18. }  


    [ 결과 ]




    • 10~11행 : 포인터 변수의 참조자 선언도 &연산자를 하나 더 추가하는 형태로 진행이 된다. (9행과 비교)

    • 14행 : pref는 포인터 변수 ptr의 참조자이므로, 변수 num에 저장된 값이 출력된다.

    • 15행 : dpref는 포인터 변수 dptr의 참조자이므로, 변수 num에 저장된 값이 출력된다.


    참조자(reference)와 함수


    C언어를 공부하며 배운 함수의 두가지 호출 방식은 다음과 같다.

    • Call-by-value        ->  값을 인자로 전달하는 함수의 호출방식
    • Call-by-reference   ->  주소 값을 인자로 전달하는 함수의 호출방식

    이중에서, Call-by-value 기반의 함수는 다음과 같이 정의된 함수를 의미한다.

    1. int func(int a, int b){  
    2.     return a+b;  
    3. }  

    위 함수는 두개의 정수를 인자로 요구한다. 따라서 Call-by-value 기반의 함수이다. Call-by-value 형태로 정의된 함수의 내부에서는, 함수외부에 선언된 변수에 접근이 불가능하다. 따라서 두 변수에 저장된 값을 서로 바꿔서 저장할 목적으로 다음과 같이 함수를 정의하면 원하는 결과를 얻을 수 있다.

    1. void swapp(int a, int b){  
    2.    int tmp = a;  
    3.    a = b;  
    4.    b = tmp;  
    5. }  


    위의 함수를 대상으로 다음의 main 함수를 실행해보자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. void swapp(int a, int b);  
    5.   
    6. int main() {  
    7.     int a = 10;  
    8.     int b = 20;  
    9.     swapp(a, b);  
    10.       
    11.     cout << "a : " << a << endl;  
    12.     cout << "b : " << b << endl;  
    13.   
    14.     return 0;  
    15. }  
    16.   
    17. void swapp(int a, int b) {  
    18.     int tmp = a;  
    19.     a = b;  
    20.     b = tmp;  
    21. }  



    [ 결과 ]




    a와 b에 저장된 값이 서로 바뀌지 않았음을 의미한다. 그래서 필요한 것이 Call-by-reference 기반의 함수이다.


    1. void swapp(int *a, int *b) {  
    2.     int tmp = *a;  
    3.     *a = *b;  
    4.     *b = tmp;  
    5. }  

     

    위의 함수에서는 두 개의 주소 값을 받아서, 그 주소 값이 참조하는 영역에 저장된 값을 직접 변경하고 있다.

    따라서 위 함수를 대상으로 다음의 main함수를 실행해보자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. void swapp(int *a, int *b);  
    5.   
    6. int main() {  
    7.     int a = 10;  
    8.     int b = 20;  
    9.     swapp(&a, &b);  
    10.       
    11.     cout << "a : " << a << endl;  
    12.     cout << "b : " << b << endl;  
    13.   
    14.     return 0;  
    15. }  
    16.   
    17. void swapp(int *a, int *b) {  
    18.     int tmp = *a;  
    19.     *a = *b;  
    20.     *b = tmp;  
    21. }  


    [ 결과 ]




    참조자를 이용한 Call-by-reference


    Call-by-reference의 가장 큰 핵심은 함수 내에서 함수외부에 선언된 변수에 접근할 수 있다는 것이다.

    참조자를 이용해서 함수를 정의해도 이러한 일이 가능하다. 다음의 함수를 살펴보자.


    1. void swpbyref(int &ref1, int &ref2){  
    2.    int tmp=ref1;  
    3.    ref1=ref2;  
    4.    ref2=tmp;  
    5. }  


    매개 변수의 선언 위치에 참조자가 와서 햇갈릴 수 있다.


    매개변수는 함수가 호출되어야 초기화가 진행되는 변수들이다. 즉 위의 매개변수 선언은 초기화가 이루어 지지 않은 것이 아니라, 함수 호출 시 전달되는 인자로 초기화를 하겠다는 의미의 선언이다. 그럼 위의 함수를 대상으로 다음과 같이 함수를 호출하면 어떻게 될까?


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. void swpbyref(int &ref1, int &ref2);  
    5.   
    6. int main() {  
    7.     int a = 10;  
    8.     int b = 20;  
    9.   
    10.     swpbyref(a, b);  
    11.     cout << "a: " << a << endl;  
    12.     cout << "b: " << b << endl;  
    13.       
    14.     return 0;  
    15. }  
    16.   
    17. void swpbyref(int &ref1, int &ref2) {  
    18.     int tmp = ref1;  
    19.     ref1 = ref2;  
    20.     ref2 = tmp;  
    21. }  



    [ 결과 ]



    • 18~20행 : 두 참조자 ref1, ref2에 저장된 값의 교환 과정이다. 이 과정은 main함수에 선언된 변수 a, b의 교환으로 이루어 진다.

    • 10행 : 매개변수로 참조자가 선언되었으니, 참조의 대상이 될 변수를 인자로 전달하면 된다.


    문제


    1. 참조자를 이용해서 다음 요구사항에 부합하는 함수를 각각 정의하여라.

    ->인자로 전달된 int형 변수의 값을 1씩 증가시키는 함수

    ->인자로 전달된 int형 변수의 부호를 바꾸는 함수


    그리고 위의 각 함수를 호출하여 그 결과를 확인하는 main함수까지 작성하여라.


    2. 다음의 코드를 살펴보자.


    1. int main(){  
    2.    int num1=5;  
    3.    int *ptr1=&num1;  
    4.    int num2=10;  
    5.    int *ptr2=&num2;  
    6. }  

     

    위의 코드를 보면 ptr1과 ptr2가 각각 num1과 num2를 가리키고 있다. 이때 ptr1과 ptr2를 대상으로


    swptr (ptr1, ptr2) ;


    다음과 같이 함수를 호출하고 나면, ptr1과 ptr2가 가리키는 대상이 서로 바뀌도록 swptr함수를 정의해보자.



    문제 1.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. int pluss(int a);  
    5. int mul(int a);  
    6.   
    7. int main() {  
    8.     int num1=10;  
    9.     int plusv, mulv;  
    10.     plusv = pluss(num1);  
    11.     mulv = mul(num1);  
    12.       
    13.     cout << plusv << endl;  
    14.     cout << mulv << endl;  
    15.   
    16.     return 0;  
    17. }  
    18.   
    19. int pluss(int a) {  
    20.     int &ref = a;  
    21.     ref = ref + 1;  
    22.     return ref;  
    23. }  
    24. int mul(int a) {  
    25.     int &ref = a;  
    26.     ref = ref*(-1);  
    27.     return ref;  
    28. }  


    문제 2.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. void swptr(int *(&pref1), int *(&pref));  
    5.   
    6. int main() {  
    7.     int num1 = 5;  
    8.     int *ptr1 = &num1;  
    9.   
    10.     int num2 = 10;  
    11.     int *ptr2 = &num2;  
    12.   
    13.     cout << *ptr1 << endl;  
    14.     cout << *ptr2 << endl;  
    15.   
    16.     swptr(ptr1, ptr2);  
    17.   
    18.     cout << ptr1 << endl;  
    19.     cout << ptr2 << endl;  
    20. }  
    21.   
    22. void swptr(int *(&pref1), int *(&pref2)) {  
    23.     int *ptr = pref1;  
    24.     pref1 = pref2;  
    25.     pref2 = ptr;  
    26. }  


    ※ 위의 답은 제가 임의로 작성한 답입니다. 여러분이 생각하고 직접 작성해보세요!!


    'Language > C++' 카테고리의 다른 글

    C++ 기본(2) - 구조체  (0) 2016.12.23
    8. C++ 기본(8)  (0) 2016.12.14
    6. C++ 기본(6)  (0) 2016.12.13
    5. C++ 기본(5) - OOP 단계별 프로젝트 01  (0) 2016.12.12
    4. C++ 기본(4)  (0) 2016.12.11

    bool 자료형


    bool자료형은 C++에만 존재하는 자료형이었다. 그러나 C의 최근 표준에서는 bool을 기본자료형에 추가하였다. 하지만 여전히 bool은 C++의 기본 자료형으로 인식을 많이한다. 그 이유는 상당수의 C 컴파일러가 bool을 지원하지 않기 때문이다. bool자료형을 공부하기에 앞서 true와 false를 이해하는 것이 우선이다.


    예제를 통해 살펴보자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. int main() {  
    5.     int num = 10;  
    6.     int i = 0;  
    7.   
    8.     cout << "true : " << true << endl;  
    9.     cout << "false : " << false << endl;  
    10.   
    11.     while (true) {  
    12.         cout << i++ << ' ';  
    13.         if (i > num)  
    14.             break;  
    15.     }  
    16.     cout << endl;  
    17.   
    18.     cout << "sizeof 1: " << sizeof(1) << endl;  
    19.     cout << "sizeof 0: " << sizeof(0) << endl;  
    20.     cout << "sizeof true: " << sizeof(true) << endl;  
    21.     cout << "sizeof false: " << sizeof(false) << endl;  
    22.   
    23.     return 0;  
    24. }  



    [ 결과 ]




    • 8~9행 : 키워드 true와 false를 콘솔에 출력했을 때의 출력내용을 확인하기 위한 문장.
    • 11행 : C++에서 while( )함수로 무한루프를 돌릴 때 true를 이용할 수 있다.
    • 18~19행 : 상수 1과 0의 데이터 크기를 확인하기 위한 문장이다.
    • 20~21행 : true와 false의 크기를 확인하기 위한 문장이다.

    여기서 많은 사람들이 true는 1이고 false는 0이라는 오해를 많이 한다.

    실제로 true는 1이 아니며 false는 0이 아니다. 이 둘은 '참'과 '거짓'을 표현하기 위한 1byte 크기의 데이터일 뿐이다. 다만 true와 false가 정의되기 이전에는 참을 표현하기 위해 숫자 1을, 거짓을 표현하기 위해 숫자 0을 사용했기 때문에 이 둘을 출력하거나 정수의 형태로 형 변환하는 경우 각각 1과 0으로 변환하도록 정의되어 있을 뿐이다.


    따라서 true와 false를 굳이 숫자에 연결시켜서 이해하면 안된다.
    true는 '참'을 false는 '거짓'을 나타내는 목적으로 정의된 데이터로 인식하는 것이 바람직하다.


    자료형 bool


    true는 '참'을 false는 '거짓'을 나타내는 목적으로 정의된 데이터이기 때문에, 이들 데이터의 저장을 위한 자료형이 별도로 정의되어 있는 것은 당연하다. true와 false를 가리켜 bool형 데이터라 한다. 그리고 bool은 int, double과 마찬가지로 기본자료형의 하나이기 때문에 다음과 같이 bool형 변수를 선언하는 것이 가능하다.


    bool trueone = true ;

    bool truetwo = false ; 


    예제를 통해서 bool형이 어떻게 사용되는지 알아보자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. bool positive(int num) {  
    5.     if (num <= 0)  
    6.         return false;  
    7.     else  
    8.         return true;  
    9. }  
    10.   
    11. int main(void){  
    12.   
    13.     bool pos;  
    14.     int num;  
    15.     cout << "Input number: "; cin >> num;  
    16.   
    17.     pos = positive(num);  
    18.       
    19.     if (pos)  
    20.         cout << "positive number" << endl;  
    21.   
    22.     else  
    23.         cout << "negative number" << endl;  
    24.   
    25.     return 0;  
    26. }  



    [ 결과 ]



    • 4행 : bool도 기본 자료형이기 때문에 함수의 반환형으로도 선언이 가능하다.

    • 13, 17행 :  bool형 변수를 선언해서 함수 positive가 반환하는 bool형 데이터를 저장하고 있다.


    'Language > C++' 카테고리의 다른 글

    8. C++ 기본(8)  (0) 2016.12.14
    7. C++ 기본(7)  (0) 2016.12.13
    5. C++ 기본(5) - OOP 단계별 프로젝트 01  (0) 2016.12.12
    4. C++ 기본(4)  (0) 2016.12.11
    3. C++ 기본(3)  (0) 2016.12.09

    프로그램 구현 연습


    프로그램명 : 은행계좌 관리 프로그램


    기능

    1. 계좌개설

    2. 입      금

    3. 출      금

    4. 전체 고객 잔액 조회


    • 통장의 계좌번호는 중복되지 아니한다 (중복검사 안하겠다는 뜻).
    • 입금 및 출금액은 무조건 0보다 크다 (오류 검사 안하겠다는 뜻).
    • 고객의 계좌정보는 계좌번호, 고객이름, 고객의 잔액, 세가지만 저장한다.
    • 둘 이상의 고객 정보 저장을 위해서 배열을 사용한다.
    • 계좌번호는 정수의 형태이다.

    실행 예시

    ----------------Menu----------------

    1.  계좌 개설

    2. 입금

    3. 출금

    4. 계좌정보 전체 출력

    5. 프로그램 종료

    선택 : 1


    [계좌 개설]

    계좌 ID : 115

    이    름 : secretpack

    입금액  : 15000


    위와 같이 계좌 개설이 진행된 다음에도 계속해서 메뉴가 출력되어 추가 메뉴 선택이 가능해진다.

    다음은 앞서 입력한 정보를 대상으로 입금을 진행한 다음에 전체 정보를 출력하는 예이다.


    ----------------Menu----------------

    1.  계좌 개설

    2. 입금

    3. 출금

    4. 계좌정보 전체 출력

    5. 프로그램 종료

    선택 : 2


    [입    금]

    계좌 ID : 115

    입금액  : 70

    입금 완료


    ----------------Menu----------------

    1.  계좌 개설

    2. 입금

    3. 출금

    4. 계좌정보 전체 출력

    5. 프로그램 종료

    선택 : 4

    계좌 ID : 115

    이    름 : secretpack

    잔    액 : 15070



    메뉴에서 계좌정보 전체 출력을 선택하면 모든 계좌의 ID, 이름, 잔액정보가 출력되어야 한다.

    위의 경우에는 저장된 계좌 정보가 하나이기 때문에 secretpack 한사람의 계좌 정보만 출력된 것이다.


    프로그램 구현


    1. #include <iostream>  
    2. using namespace std;  
    3. const int name_len = 20;  
    4.   
    5. void show_menu(void); // menu  
    6. void make_bank(void); // 1. 계좌 개설  
    7. void input_money(void); // 2. 입금  
    8. void output_money(void); // 3. 출금  
    9. void info_bank(void); // 4. 계좌 정보  
    10.   
    11. typedef struct {  
    12.     int accID;  
    13.     int balance;  
    14.     char cusName[name_len];  
    15. }Accout;  
    16.   
    17. Accout accArr[100];  
    18. int accNum = 0;  
    19.   
    20. int main() {  
    21.     int menu;  
    22.   
    23.     while (1) {  
    24.   
    25.         show_menu();  
    26.         cout << "choice Number : "; cin >> menu;  
    27.   
    28.         if (menu == 5) {  
    29.             cout << "Program exit" << endl;  
    30.             break;  
    31.         }  
    32.   
    33.         else if (menu == 1)  
    34.             make_bank();  
    35.   
    36.         else if (menu == 2)  
    37.             input_money();  
    38.   
    39.         else if (menu == 3)  
    40.             output_money();  
    41.   
    42.         else if (menu == 4)  
    43.             info_bank();  
    44.   
    45.         else  
    46.             cout << "Wrong Number" << endl;  
    47.     }  
    48.     return 0;  
    49.   
    50. }  
    51.   
    52. void show_menu(void) {  
    53.     cout << "---------MENU---------" << endl;  
    54.     cout << "1. Make Account" << endl;  
    55.     cout << "2. Deposit Money" << endl;  
    56.     cout << "3. Withdraw Money" << endl;  
    57.     cout << "4. Show all info" << endl;  
    58.     cout << "5. Exit program" << endl;  
    59. }  
    60.   
    61. void make_bank(void) {  
    62.     int id;  
    63.     char name[name_len];  
    64.     int balance;  
    65.   
    66.     cout << endl;  
    67.     cout << "----------------------" << endl;  
    68.     cout << "[ Make Account ]" << endl;  
    69.     cout << "Account ID : "; cin >> id;  
    70.     cout << "Name: "; cin >> name;  
    71.     cout << "input money : "; cin >> balance;  
    72.     cout << "----------------------" << endl;  
    73.     cout << endl;  
    74.   
    75.     accArr[accNum].accID = id;  
    76.     accArr[accNum].balance = balance;  
    77.     strcpy(accArr[accNum].cusName, name);  
    78.     accNum++;  
    79. }  
    80.   
    81. void input_money(void) {  
    82.     int money;  
    83.     int id;  
    84.     cout << endl;  
    85.     cout << "----------------------" << endl;  
    86.     cout << "[ Deposit Money ]" << endl;  
    87.     cout << "Account ID : "; cin >> id;  
    88.     cout << "Input money : "; cin >> money;  
    89.     cout << "----------------------" << endl;  
    90.     cout << endl;  
    91.   
    92.     for (int i = 0; i < accNum; i++) {  
    93.         if (accArr[i].accID == id) {  
    94.             accArr[i].balance += money;  
    95.             cout << "Complete!!" << endl << endl;  
    96.             return;  
    97.         }  
    98.     }  
    99.     cout << "ID not found!!" << endl << endl;  
    100. }  
    101.   
    102. void output_money(void) {  
    103.     int money;  
    104.     int id;  
    105.     cout << endl;  
    106.     cout << "----------------------" << endl;  
    107.     cout << "[ With draw Money ]" << endl;  
    108.     cout << "Account ID : "; cin >> id;  
    109.     cout << "Output Money : "; cin >> money;  
    110.     cout << "----------------------" << endl;  
    111.     cout << endl;  
    112.   
    113.     for (int i = 0; i < accNum; i++) {  
    114.         if (accArr[i].accID == id) {  
    115.             if (accArr[i].balance < money) {  
    116.                 cout << "you haven't enough money" << endl << endl;  
    117.                 return;  
    118.             }  
    119.             accArr[i].balance -= money;  
    120.             cout << "Complete!!" << endl << endl;  
    121.             return;  
    122.         }  
    123.     }  
    124.     cout << "ID not found!!" << endl << endl;  
    125. }  
    126.   
    127. void info_bank(void) {  
    128.     for (int i = 0; i < accNum; i++) {  
    129.         cout << endl;  
    130.         cout << "-----INFORMATION------" << endl;  
    131.         cout << "Account ID : " << accArr[i].accID << endl;  
    132.         cout << "Name : " << accArr[i].cusName << endl;  
    133.         cout << "Your money  : " << accArr[i].balance << endl;  
    134.         cout << "----------------------" << endl;  
    135.         cout << endl;  
    136.     }  
    137. }  


    'Language > C++' 카테고리의 다른 글

    7. C++ 기본(7)  (0) 2016.12.13
    6. C++ 기본(6)  (0) 2016.12.13
    4. C++ 기본(4)  (0) 2016.12.11
    3. C++ 기본(3)  (0) 2016.12.09
    2. C++ 기본(2)  (0) 2016.12.09

    이름공간 (namespace)


    이름공간은 말 그대로 특정 영역에 이름을 붙여주기 위한 문법적 요소이다.


    예제를 통해 namespace를 이해하도록 하자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. namespace bestcom {  
    5.     void simplefunc(void) {  
    6.         cout << "bestcom func" << endl;  
    7.     }  
    8. }  
    9.   
    10. namespace progcom {  
    11.     void simplefunc(void) {  
    12.         cout << "progcom func" << endl;  
    13.     }  
    14. }  
    15.   
    16. int main() {  
    17.     bestcom::simplefunc();  
    18.     progcom::simplefunc();  
    19.       
    20.     return 0;  
    21. }  


    • 4 ~ 8행  : bestcom이라는 이름의 공간을 마련하였다. 그리고 그 안에 simplefunc( )을 정의하였다.
    • 10 ~ 14행 : progcom이라는 이름의 공간을 마련하였다. 그리고 그 안에 simplefunc( )을 정의하였다.
    • 21행 : 이름공간 bestcom 내에 정의된 함수 bestcom :: simplefunc( ) 호출
    • 22행 : 이름공간 progcom 내에 정의된 함수 progcom :: simplefunc( ) 호출


    [ 결과 ]




    위 예제에서 사용된  :: 연산자를 가리켜 범위지정 연산자 라 하며 이름 공간을 지정할 때 사용하는 연산자이다.


    콘솔 입출력을 진행할 때 원형은 std :: cout , std :: cin, std :: endl 과 같이 사용하였다.


    이는 <iostream> 헤더에 선언되어 있는 cout, cin, endl은 namespace std에 선언되어 있다는 것을 알 수 있다.

    이름 충돌을 막기 위해 C++표준에서 제공하는 다양한 요소들은 이름공간 std에 선언 되어 있다.


    using을 이용한 이름공간의 명시


    이 예제를 통해 using namespace 를 선언하는 이유를 살펴보자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. namespace hybrid {  
    5.     void hbridfunc() {  
    6.         cout << "So simple function" << endl;  
    7.         cout << "in namespace hybrid" << endl;  
    8.     }  
    9. }  
    10.   
    11. int main() {  
    12.     using namespace hybrid;  
    13.     hbridfunc();  
    14.   
    15.     return 0;  
    16. }  



    [ 결과 ]



    • 13행 : 키워드 using을 이용해서 이름공간에 정의된 hbridfunc( )을 호출할 때에는 namespace를 지정하지 않고 호출한다는 것을 명시 (선언) 하고 있다.
    • 14행의 using 선언을 통해서 이름공간의 지정 없이 hbridfunc( )함수를 호출하고 있다.

    using namespace hybrid ;

    다음의 선언은 'hbridfunc( ) 를 namespace hybrid에서 찾으라는 일종의 선언이다.


    using namespace std ;

    마찬가지로 다음의 선언은 <iostream> 내에 있는 함수들을 namespace std에서 찾으라는 선언이다.


    이제는 using namespace std ; 선언하는 이유를 알았으니 앞으로도 계속 사용할 것이다.



    범위지정연산자의 또다른 기능


    지역변수의 이름이 전역변수의 이름과 같을 경우, 전역변수는 지역변수에 의해 가려진다는 특징이 있다.


    다음의 예제를 통해 살펴보자.


    1. #include <iostream>  
    2.   
    3. int val = 100;  
    4.   
    5. int simfunc(void) {  
    6.     int val = 20;  
    7.     val += 3;  
    8. }  


    위의 코드에서 보이듯이 simfunc( ) 내에서 전역변수와 동일한 이름의 지역 변수 val이 선언되었기 때문에 이어서 등장하는 문장에서는 지역변수 val의 값을 3 증가시킨다. 그렇다면 simfunc( )에서 전역변수 val에 접근하려면 어떻게 하면 좋을까? 이 때에도 범위지정연산자를 사용하면 된다.


    1. int val=100;  
    2.   
    3. int simpfunc(){  
    4.     int val=20;  
    5.     val+=3 // 지역변수 val의 값 3 증가  
    6.     ::val+=7; // 전역변수 val의 값 7 증가   
    7. }  



    'Language > C++' 카테고리의 다른 글

    6. C++ 기본(6)  (0) 2016.12.13
    5. C++ 기본(5) - OOP 단계별 프로젝트 01  (0) 2016.12.12
    3. C++ 기본(3)  (0) 2016.12.09
    2. C++ 기본(2)  (0) 2016.12.09
    1. C++ 기본  (0) 2016.12.08

    인라인 (inline) 함수


    인라인 함수를 공부하기 전에 매크로 함수에 대해서 먼저 살펴보자.


    매크로 장점

    매크로 단점 

     실행 속도의 이점이 있다.

    정의하기 어렵다.

    복잡한 함수를 매크로의 형태로 정의하는데 한계가 있다. 


    예제를 통해 살펴보자


    1. #include <iostream>  
    2. #define square(x) ((x)*(x))  
    3. using namespace std;  
    4.   
    5. int main() {  
    6.     cout << square(5) << endl;  
    7.     return 0;  
    8. }  



    위의 코드는 전처리 과정을 거치면 다음과 같이 변경된다.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. int main() {  
    5.     cout << ((5)*(5)) << endl;  
    6.     return 0;  
    7. }  



    위 예제와 같이 함수의 몸체부분이 함수호출 문장을 완전히 대체하였을때 "함수가 인라인화 되었다" 라고 표현한다.


    다음 예제를 통해 인라인 함수를 이해해보자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. inline int square(int x) {  
    5.     return x*x;  
    6. }  
    7.   
    8. int main() {  
    9.     cout << square(5) << endl;  
    10.     cout << square(12) << endl;  
    11.   
    12.     return 0;  
    13. }  



    예제 결과




    ※ 매크로를 이용한 함수 인라인화는 전처리기에 의해서 처리되지만 inline을 이용한 함수는 컴파일러에서 처리한다.


    자료형을 정의하기 때문에 데이터 손실이 발생할 수 있다. => 템플릿 이용 (나중에 언급)



    이름공간(namespace)


    이름공간이란 말 그대로 특정 영역에 이름을 붙여주기 위한 문법적 요소이다.


    다음의 예제를 통해 이해해보자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. namespace bobgood{  
    5.     void func1() {  
    6.         cout << "bobgood func" << endl;  
    7.     }  
    8. }  
    9. namespace comp {  
    10.     void func1() {  
    11.         cout << "comp func" << endl;  
    12.     }  
    13. }  
    14.   
    15. int main() {  
    16.     bobgood::func1();  
    17.     comp::func1();  
    18.   
    19.     return 0;  
    20. }  



    예제 결과




    bobgood 이라는 공간을 마련하였다. 그리고 그 안에 func1 함수를 정의하였다.

    따라서 이 함수는 bobgood :: func1이라고 지칭하게 된다.


    comp 이라는 공간을 마련하였다. 그리고 그 그 안에 func1 함수를 정의하였다.

    따라서 이 함수는 comp :: func1 이라고 지칭하게 된다.


    그리고 각각의 공간에서 func1 함수를 호출하였다.


    위 예제에서 사용한 연산자 ( :: )를 가리켜 범위지정 연산자 라 하며, 이름공간을 지정할 때 사용하는 연산자이다.


    다음 예제를 계속해서 살펴보자.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. namespace bobgood {  
    5.     void func1();  
    6. }  
    7.   
    8. namespace comp {  
    9.     void func1();  
    10. }  
    11.   
    12. int main() {  
    13.     bobgood::func1();  
    14.     comp::func1();  
    15.   
    16.     return 0;  
    17. }  
    18.   
    19. void bobgood::func1() {  
    20.     cout << "bestcom func1" << endl;  
    21. }  
    22.   
    23. void comp::func1() {  
    24.     cout << "comp func1" << endl;  
    25. }  


    결과는 위의 소스코드 의 결과와 같다.



    4 ~ 6행  : 이름공간 안에 함수의 선언만 삽입되었다.

    8 ~ 10행 : 마찬가지로 이름공간 안에 함수의 선언만 삽입되었다.

    19행 ~ 21행 : 이름공간 bobgood에 선언된 함수 func1을 정의한다.

    23 ~ 25행 : 마찬가지로 comp에 선언된 함수 func1을 정의한다.



    동일한 이름공간에 정의된 함수를 호출할 때에는 이름공간을 명시할 필요가 없다.


    1. #include <iostream>  
    2. using namespace std;  
    3.   
    4. namespace bobgood {  
    5.     void func1();  
    6. }  
    7.   
    8. namespace bobgood {  
    9.     void func2();  
    10. }  
    11.   
    12. namespace comp {  
    13.     void func3();  
    14. }  
    15.   
    16. int main() {  
    17.     bobgood::func1();  
    18.     return 0;  
    19. }  
    20.   
    21. void bobgood::func1() {  
    22.     cout << "bobgood func1" << endl;  
    23.     func2();  
    24.     comp::func3();  
    25. }  
    26.   
    27. void bobgood::func2() {  
    28.     cout << "so pretty func2" << endl;  
    29. }  
    30.   
    31. void comp::func3() {  
    32.     cout << "comp func3" << endl;  
    33. }  



    'Language > C++' 카테고리의 다른 글

    6. C++ 기본(6)  (0) 2016.12.13
    5. C++ 기본(5) - OOP 단계별 프로젝트 01  (0) 2016.12.12
    4. C++ 기본(4)  (0) 2016.12.11
    2. C++ 기본(2)  (0) 2016.12.09
    1. C++ 기본  (0) 2016.12.08

    + Recent posts