객체 레퍼런스  
 : 객체 레퍼런스란 객체를 참조 할 수 있는 레퍼런스로 클래스 포인터(객체 포인터)의 특성과 일치 한다. 
  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.     ScholarStd p;   
  31.     Student& ref1=p;   
  32.     Person& ref2=p;   
  33.   
  34.     p.Sleep();   
  35.     ref1.Sleep();   
  36.     ref2.Sleep();   
  37.   
  38.     return 0;   
  39. }  

 앞서 배웠듯이 is-a 관계에서 어떤 클래스의 포인터는 자신 객체 뿐만 아니라, 자신을 상속하고 있는 클래스의 객체도 가리킬수가 있다. Person 클래스의 포인터를 가지고, 위의 코드에서의 세개 클래스 객체를 다 가리킬 수 있다. Person 클래스의 참조도 마찬가지로, 여기 세개의 클래스를 다 참조 할 수 있다. 저번에 포스팅했던 객체 포인터의 특성과 일치하는 것을 알 수 있다. 

 객체 레퍼런스 권한  
  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 PartTimeStd : public Student   
  21. {   
  22. public:   
  23.     void Work(){   
  24.         cout<<"Work"<<endl;   
  25.     }   
  26. };   
  27.   
  28. int main(void)   
  29. {   
  30.     PartTimeStd p;   
  31.     p.Sleep();   
  32.     p.Study();   
  33.     p.Work();   
  34.   
  35.     Person& ref=p;   
  36.     ref.Sleep();   
  37. //  ref.Study(); // Error의 원인   
  38. //  ref.Work();  // Error의 원인   
  39.   
  40.     return 0;   
  41. }  

 객체의 레퍼런스 권한도 객체 포인터 권한과 마찬가지이다. 객체 포인터때도 지겹도록 반복해서 언급을 했지만, A클래스의 참조는 B객체도 C객체도 참조 할 수 있는데, 접근할 수 있는 영역은 A클래스내에 선언되어 있거나, A 클래스가 상속하고 있는 멤버로서 제한되어 진다. 

Posted by 모과이IT
,
일단 const에 대해서는 이미 언급을 해 놓았죠.  [C/C++] 콘스트(const)란? (←링크 참조) 그럼 C++에서 클래스라는 곳에서는 어떻게 쓰이는지 한번 알아 보도록 하겠습니다. 

 const 멤버 변수  
 : 우선 예제를 먼저 보도록 하겠습니다.
  1. #include<iostream>   
  2. using namespace std;   
  3.   
  4. class Student   
  5. {   
  6.     const int id;   
  7.     int age;   
  8.     char name[20];   
  9.     char major[30];   
  10. public:   
  11.     Student(int _id, int _age, char* _name, char* _major)    
  12.     {   
  13.         id=_id;  //에러   
  14.         age=_age;   
  15.         strcpy(name, _name);   
  16.         strcpy(major, _major);   
  17.     }   
  18.   
  19.     void ShowData()   
  20.     {   
  21.         cout<<"이름: "<<name<<endl;   
  22.         cout<<"나이: "<<age<<endl;   
  23.         cout<<"학번: "<<id<<endl;   
  24.         cout<<"학과: "<<major<<endl;   
  25.     }   
  26. };   
  27.   
  28. int main()   
  29. {   
  30.     Student Kim(200577065, 20, "Kim Gil Dong""Computer Eng.");   
  31.     Student Hong(200512065, 19, "Hong Gil Dong""Electronics Eng.");   
  32.   
  33.     Kim.ShowData();   
  34.     cout<<endl;   
  35.     Hong.ShowData();   
  36.   
  37.     return 0;   
  38. }  
 Student라는 클래스의 id라는 멤버 변수를 const로 선언을 했습니다. 우리 일상생활에서도 보듯이 id라는 것은 고유 숫자로 부여를 받거나 하죠. 하지만 여기 Student 생성자 부분에서 에러를 발생하게 됩니다. 일반적으로 클래스에서의 객체 생성 순서에 제일 처음으로 하는 것은 바로 "메모리 공간의 할당"입니다. 
  생성자가 호출되지 않는 상태에서, 메모리 할당되는 순간 name, age,  id 뭐 이런 것들이 초기화 됩니다. (일단 우리가 원하는 값이 아닌 쓰레기 값으로 채워지게 되죠) 하지만 이렇게 id에 쓰레기 값으로 채워 진다면, const 선언을 했기 때문에 id는 아예 이 쓰레기 값으로 초기화 되어서 두번 다시 바꾸지 못하게 되고, 결국 에러가 납니다. 
 우리는 위 예제를 통해서  const 멤버 변수는 생성자 내에서 초기화 시킬 수 없음을 알았습니다. 그럼 어떻게 해야 할까요? 이 문제를 해결하기 위한 방법으로 등장한 문법적 방법이 바로 initializer입니다. 
  바로 생성자의 꺽쇠 안의 몸체 부분이 아니라, 그 사이에  : id(_id) 이런식으로 선언하는 방법이다. (이 부분은 생성자 호출 되기 이전에 완료가 되게 됩니다.) 멤버 변수 id를 Student 생성자 호출될때 인자로 전달되었던 _id로 초기화 해라 라는 의미이다. 

 const 멤버 함수  
 : 그럼 클래스의 멤버 함수에 const를 붙인다는것은 어떤 의미를 가질까요? 바로 아래와 같은 의미를 가집니다. 
멤버 함수에 const를 붙인다는 것은 멤버 변수의 값의 변경을 허용하지 않음은 물론이고, 멤버 변수값의 변경에 대한 기회도 제공하지 않겠다
 const를 쓴 멤버 함수의 간단한 예제를 보겠습니다 .
  1. #include<iostream>   
  2. using namespace std;   
  3.   
  4. class Student   
  5. {   
  6.     const int id;   
  7.     int age;   
  8.     char name[20];   
  9.     char major[30];   
  10. public:   
  11.     Student(int _id, int _age, char* _name, char* _major):id(_id), age(_age)   
  12.     {   
  13.         strcpy(name, _name);   
  14.         strcpy(major, _major);   
  15.     }   
  16.   
  17.     void ShowData() const  
  18.     {   
  19.         //name = "바보";  //에러 발생   
  20.         cout<<"이름: "<<name<<endl;   
  21.         cout<<"나이: "<<age<<endl;   
  22.         cout<<"학번: "<<id<<endl;   
  23.         cout<<"학과: "<<major<<endl;   
  24.     }   
  25. };  
 여기서는 Showdata()라는 함수를 const 시켰습니다. 그래서 함수 내에서 만약 주석부분과 같이 값의 변경을 한다고 하면, 에러를 발생하게 됩니다. 더욱 자세한 것은 다음 예제를 통해서 알아 보겠습니다. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Count   
  5. {   
  6.     int cnt;   
  7. public :   
  8.     Count() : cnt(0){}   
  9.     int* GetPtr() const{   
  10.         return &cnt;  // Compile Error   
  11.     }   
  12.   
  13.     void Increment(){   
  14.         cnt++;   
  15.     }   
  16.   
  17.     void ShowData() const {   
  18.         ShowIntro();  // Compile Error   
  19.         cout<<cnt<<endl;           
  20.     }   
  21.     void ShowIntro() {   
  22.         cout<<"현재 count의 값 : "<<endl;   
  23.     }   
  24. };   
  25.   
  26. int main()   
  27. {   
  28.     Count count;   
  29.     count.Increment();   
  30.     count.ShowData();   
  31.   
  32.     return 0;   
  33. }  
 에러 첫번째, 위의 10번째줄 Compile Error 부분은 멤버 변수 조작은 안하지만, 멤버 변수를 조작할수 있는 기회제공을 하고 있습니다. 여기에서 GetPtr() 함수는 cnt를 포인터를 리턴해 주고 있는데, 만약 누군가가 Getptr을 호출한후 ,cnt의 포인터를 얻어낸다고 한다면, cnt 포인터를 얻어낸 사람은 포인터를 통해 얻은 cnt의 주소값을 통해서 cnt의 값을 조작할 수 있다는 것입니다.  
  결국, getptr()함수는 간접적으로 멤버 변수의 조작 동기를 제공한 것입니다. 그래서 compile error가 발생합니다. 이 컴파일 에러를 피하기 위해서는 const를 더 추가해주면 됩니다. 바로 아래와 같이 말이죠. 
  1. const int* GetPtr() cosnt   
  2. {   
  3.         ............   
  4. }  
  int* 앞에 const가 붙으면 포인터가 가리키는 곳은 상수화 시키는 데이터 상수화를 하겠다는 거죠. 그래서, 이 포인터를 얻은 영역에서는 cnt라는 변수에 접근은 가능하지만, 변경을 불가능하게 만드는 것입니다. 

 다음 에러 두번째, 바로 Showdata()함수 안에 Showintro()함수 호출 부분입니다. 
  1. void ShowData() const {   
  2.         ShowIntro();  // Compile Error   
  3.         cout<<cnt<<endl;           
  4.     }   
  5.     void ShowIntro() const {   
  6.         cout<<"현재 count의 값 : ";   
  7.     }  
 프로그래밍에서 런타임, 컴파일 타임에 결정되는 요소들이 있는데, 값이 변경되느냐는 사항은 런타임에 결정되는 요소이고, 컴파일러는파일 타임에 Showintro 라는 함수를 어디까지 이해하느냐 하면은, 이 함수가 상수화 되지 않는 일반적인 함수라고 인식을 하게 됩니다. 
 그래서 Showdata() 함수 내부에서 Showintro() 함수 부분을 호출 할때, 컴파일러는 Showintro 함수의 내부를 검사하는 것이 아니라, showintro가 상수화 된 함수인지 아닌 함수인지만 분석해서, 이 함수가 상수화 되어 있으면 멤버 조작할 가능성이 없다고 인식하겠지만, 지금 예제에서는 컴파일러는 showintro가 상수화가 되지 않앗다는 것만 인식하기 때문에 컴파일 에러가 나는 것입니다. 
  const를 빼는 것도 한 방법이겠지만, 굳이 const를 뺄 필요는 없겠죠? 다음과 같이 Showintro 함수도 const를 붙여 줍니다. 
  1. void Showintro() const  
  2. {   
  3.       ..................   
  4. }  
 그러면 컴파일러가 Showintro가 상수화 되었다고 인식을 하게 되어서 컴파일단에서 에러가 나지 않게 되는 것이죠. 자 완성된 예제입니다.
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Count   
  5. {   
  6.     int cnt;   
  7. public :   
  8.     Count() : cnt(0){}   
  9.     const int* GetPtr() const{   
  10.         return &cnt;  // Compile Error   
  11.     }   
  12.   
  13.     void Increment(){   
  14.         cnt++;   
  15.     }   
  16.   
  17.     void ShowData() const {   
  18.         ShowIntro();  // Compile Error   
  19.         cout<<cnt<<endl;           
  20.     }   
  21.     void ShowIntro() const {   
  22.         cout<<"현재 count의 값 : ";   
  23.     }   
  24. };   
  25.   
  26. int main()   
  27. {   
  28.     Count count;   
  29.     count.Increment();   
  30.     count.Increment();   
  31.     count.ShowData();   
  32.   
  33.     return 0;   
  34. }  

Posted by 모과이IT
,
This 포인터란?  
 : This Pointer란 객체가 멤버함수를 호출할 때 자동으로 멤버함수에게 전달되는 포인터이며, 호출한 객체를 가리키는 포인터를 말한다. 흔히 자기 참조 포인터(자기 자신을 가리킬 수 있는 포인터)라고 불리우며, 멤버 함수에게만 this pointer가 전달된다.(Friend 함수는 멤버 함수가 아니므로 this pointer가 전달되지 않는다. Static 함수도 마찬가지이다.) 아래의 예제를 보면 금방 감이 올것이다. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Person    
  5. {   
  6. public:   
  7.     Person* GetThis(){         
  8.         return this//this 포인터를 리턴.   
  9.     }   
  10. };   
  11.   
  12. int main()   
  13. {   
  14.     Person *p1 = new Person();   
  15.     cout<<"포인터 p1: "<<p1<<endl;   
  16.     cout<<"p1의 this: "<<p1->GetThis()<<endl<<endl;   
  17.   
  18.     Person *p2 = new Person();   
  19.     cout<<"포인터 p2: "<<p2<<endl;   
  20.     cout<<"p2의 this: "<<p2->GetThis()<<endl;   
  21.   
  22.     return 0;   
  23. }  

 
  결과를 보면 알 수 있듯이, This 포인터는 멤버 함수를 소유한 객체를 가리키고 있다. 다시 말해서 멤버 함수를 호출할 때 사용한 객체를 가리키고 있다. 그럼 이런 this pointer의 타입은 어떻게 되는것일까? 일단 객체를 가리키는 것이므로 Person 클래스의 포인터 타입이 여기서 this의 리턴 타입이라고 할 수 있다. (Person * 타입)

Friend  
 :  일반적으로 클래스의 비공개멤버(private)는 외부함수에서 접근할 수 없다. 해 갈 수 없다. private 변수, 함수는 멤버 함수에서만 접근이 가능하기 때문이다. 그런데 멤버함수가 아닌 외부함수에서 접근해야 할 경우가 발생될 수 있다. 그래서 나타난게 friend 라는 문법이다. 그런 경우 클래스에서 프렌드함수로 선언해 주면 외부함수이면서 클래스의 비공개 멤버에 접근할수 있는 권한을 갖게 된다.
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Counter   
  5. {   
  6. private:   
  7.     int val;   
  8.   
  9. public:   
  10.     Counter() {    
  11.         val=0;   
  12.     }   
  13.     void Print() const {   
  14.         cout<<val<<endl;   
  15.     }   
  16.            
  17.     friend void SetX(Counter& c, int val);  //friend 선언.   
  18. };   
  19.   
  20. void SetX(Counter& c, int val) // 전역함수.   
  21. {   
  22.     c.val=val;   
  23. }   
  24.   
  25. int main()   
  26. {   
  27.     Counter cnt;   
  28.     cnt.Print();   
  29.   
  30.     SetX(cnt, 2002);   
  31.     cnt.Print();   
  32.   
  33.     return 0;   
  34. }  
  1.   
  2. #include <iostream>   
  3. using std::cout;   
  4. using std::endl;   
  5.   
  6. class AAA   
  7. {   
  8. private:   
  9.     int data;   
  10.     friend class BBB;  // class BBB를 friend로 선언함!   
  11. };   
  12.   
  13. class BBB   
  14. {   
  15. public:   
  16.     void SetData(AAA& aaa, int val){   
  17.         aaa.data=val; //class AAA의 private 영역 접근!   
  18.     }   
  19. };   
  20.   
  21. int main()   
  22. {   
  23.     AAA aaa;   
  24.     BBB bbb;   
  25.   
  26.     bbb.SetData(aaa, 10);   
  27.   
  28.     return 0;   
  29. }  

 위 두 예제를 보면 쓰는 용법에 대해서는 간단히 한눈에 알아 볼 수 있다. friend는 함수, 클래스에 적용해서 쓸 수 있다. friend가 가지는 특징을 살펴 보면 아래 4가지 정도로 요약 할 수 있다. 
 1. 클래스에 friend 키워드와 함께 원형을 명시 해야 한다. 
 2. 상속 되지 않는다. 
 3. friend 함수는 클래스의 멤버가 아니라 일반함수이다. (멤버 접근 연산자로 접근하지 않는다.)
 4. 보통 사용되는 곳은 연산자 중복 사용시, 하나의 함수에서 다른 클래스들의 비공개 멤버로 접근시 사용된다. (전방참조)

※ 전방참조(Forward Reference)  : 두개 이상의 클래스를 동시에 선언하는 것은 논리적으로 불가능하므로 컴파일러에게 미리 이 클래스가 있음을 알려주는 것

 보통 friend가 OOP(Object Oriented Programming)의 캡슐화와 정보은닉을 해친다는 이야기가 있지만, 대다수의 경우 friend는 멤버의 개수를 줄여줌으로서 캡슐화에 더 이로운 영향을 끼친다. (public 멤버가 적을수록 캡슐화가 좋아진다)  friend를 피하기 위해 인위적으로 public 멤버를 추가한다면 그것이 오히여 OOP에 악역향을 끼친다. 
Posted by 모과이IT
,
저번 포스팅에 이어 두번째로 이번에는 소멸자에 대해서 얘기해 보겠습니다. 우선 소멸자를 이야기 하기 전에 객체가 소멸되는 시점에 관해서 언급 해보도록 하죠. 

* 객체가 소멸되는 시점 : 함수 내에 지역적으로 변수가 선언되면 함수 호출이 끝남과 동시에 소멸이 된다. 이와 마찬가지로 객체도 함수내에서 선언된다고 하면, 함수 호출이 끝나면 소멸되게 된다. 

* 전역적으로 선언된 객체 
: 전역변수는 프로그램 시작과 동시에 메모리에 올라갔다가, 프로그램이 종료될때 소멸된다. 객체도 마찬가지로 똑같은 동작을 하지만, 객체는 이렇게 생성할 일은 거의 없다고 보면 된다. 

* 전역 : 일반적인 객체지향에 전역이란 개념은 존재 하지 않는다. 이를 대신하기 위한 static 멤버 변수, 멤버 함수가 존재 한다. (이거 대해서는 나중에...)

소멸자 (Destructor)  
 : 객체의 메모리 반환을 위해서 객체 소멸시 자동 호출되는 함수로 클래스의 이름 앞에는 ~가 붙은 형태를 띄우며, 리턴하지 않고, 리턴 타입도 없다. 전달인자는 항상 Void 형으로 오버로딩, 디폴트 매개변수의 선언이 불가능하다는 특징을 가지고 있다. 
  그럼 아래의 동적 할당 예제를 통해서 소멸자가 어떻게 동작하고 있는지 한번 자세히 알아 보도록 하자. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class DynamicArray{   
  5. public:   
  6.     int *arr;   
  7.   
  8.     DynamicArray(int arraySize)   
  9.     {   
  10.         arr = new int [arraySize]; //인자로 받는 크기만큼 메모리를 할당한다.   
  11.     }   
  12.        
  13.     //소멸자, 메모리를 해제한다.    
  14.     ~DynamicArray()   
  15.     {   
  16.         delete[] arr;   
  17.         arr = NULL;   
  18.     }   
  19. };   
  20.   
  21. void main()   
  22. {   
  23.     int size;   
  24.     int i;   
  25.     cout<< "몇개의 정수를 입력하겠는가? ";   
  26.     cin>>size;   
  27.   
  28.     DynamicArray da(size);   
  29.   
  30.     cout<< size <<"개의 정수를 입력 하시오. "<<endl;   
  31.     for(i=0; i< size; i++)   
  32.         cin>>da.arr[i];   
  33.   
  34.     for(i = size -1 ; i>=0; --i)   
  35.         cout<< da.arr[i]  << " ";   
  36.   
  37.     cout<<endl;      
  38. }  
- 결과값 

  DynamicArray 클래스는 내부적으로 동적 메모리 할당을 사용한다. 28번째 줄에서 DynamicArray 타입의 객체 da를 생성하면서 필요한 메모리의 크기를 인자로 전달하는데, 10번째 줄처럼 메모리를 동적으로 할당하고 그 주소를 arr 멤버 변수에 보관해준다. 아마 DynamicArray를 생성하면 아래와 같은 모습을 띄고 있을 것이다. 

 결국 main() 함수가 끝나면 da 객체가 자동적으로 소멸하게 된다. 함수 안에서 정의한 변수는 함수가 종료와 동시에 소멸되므로, 객체가 소멸하면서 자동적으로 소멸자가 호출되고, 16번째 줄처럼 소멸자 안에서 arr이 가리키는 메모리를 해제 한다. 
  만약 소멸자가 없었다 라고 한다면, 우리는 클래스를 만들고 프로그램을 짤때마다 일일히 다 해제를 해줘야 한다. 만약 프로그램의 크기가 커진다고 생각하면, 참 큰일이 아닐수 없다. 

객체의 소멸 순서  
 : 객체의 소멸 순서는 소멸자 호출 -> 메모리 반환 순서로 객체가 소멸되고, 소멸자의 호출을 먼저 해주는 이유는 소멸자의 호출을 먼저 해줌으로서, 메모리가 반환되어질때, 반환되어지지 않은 메모리 공간을 명시적으로 반환해주기 위해서이다. 

 기본적으로 소멸자를 명시해주지 않아도 디폴트 소멸자가 사용되고, 디폴트 소멸자는 디폴트 생성자와 같은 특징을 가지고 있다. 소멸자의 명시적 제공은 첫번째, 생성자에서 메모리를 동적으로 할당하는 경우나 디버깅시 사용자가 객체의 소멸되는 시점을 알고 싶을때 사용을 한다. 
Posted by 모과이IT
,
1. 생성자 (Constructor)   
 :  생성자는 객체를 생성할 때 자동적으로 호출되는 함수로 클래스와 같은 이름의 형태를 띤 함수이며, 리턴형이 없으며, 리턴 하지도 않는 특징을 가지고 있다. 그럼 왜 생성자가 필요할까? 바로 객체를 생성과 동시에 초기화를 해주기 위해 필요하다.  
  객체를 생성과 동시에 초기화 주는 것이 클래스의 좋은 구조이기 때문에, C++는 정보은닉과 더불어 생성과 동시에 초기화라는 안전성을 제공하는 생성자라는 문법을 제공해 주는 것이다. 

1-1. 생성자의 사용?  
 : 생성자의 형태를 보면, 일단 세가지 형태를 가지고 있다. 첫번째로 디폴트 생성자 (Default Constructor), 인자가 있는 생성자, 마지막으로는 복사 생성자 (Copy Constructor) 이 세가지 형태가 있다. 

- 디폴트 생성자  
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Person{   
  5. public:   
  6.     int number;   
  7.     char *name;   
  8.        
  9.     //생성자   
  10.     Person()   
  11.     {   
  12.         number =0; name = "Noname";    
  13.     }      
  14.     void print()   
  15.     {   
  16.         cout<< "Number : "<<number <<" Name :" <<name <<endl;   
  17.     }   
  18. };   
  19.   
  20. int main()   
  21. {   
  22.     Person P;   
  23.     P.print();   
  24.   
  25.     return 0;   
  26. }  
 위에서 생성자 부분을 보면, 생성자의 원형만 적어 주었다. 생성자는 클래스와 동일한 이름을 가진 멤버 함수로, 다른 멤버 함수와 같이 클래스 안이나 바깥쪽에서 정의하는 것이 가능하다.  객체를 생성하면 기본 적으로 생성자가 호출 되는데, 이렇게 인자가 없는 생성자를 디폴트 생성자라고 부른다. 
  굳이 생성자를 저렇게 명시적으로 적어 주지 않더라도 클래스에서는 디폴트 생성자를 호출한다. (하지만 초기화에 대한 기대는 버려야 할 것이다.)

- 인자가 있는 생성자
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Person{   
  5. public:   
  6.     int number;   
  7.     char *name;   
  8.        
  9.     //인자가 있는 생성자   
  10.     Person(int _number, char* _name)   
  11.     {   
  12.         number =_number; name = _name;   
  13.     }      
  14.     void print()   
  15.     {   
  16.         cout<< "Number : "<<number << endl<<"Name :" <<name <<endl;   
  17.     }   
  18. };   
  19.   
  20. int main()   
  21. {   
  22.     Person P(1, "myname");   
  23.     P.print();   
  24.   
  25.     return 0;   
  26. }  
 인자가 있는 생성자는 별도로 정의되어 있는 용어는 아니지만, 말그대로 생성자에 인자값을 가진것이 바로 인자가 있는 생성자이다. 디폴트 생성자와 크게 다를바가 없지만, 인자가 있기 때문에 어떻게 인자를 넘겨줄 것인지가 문제가 된다.. 
  생성자는 객체를 생성할 때, 호출되기 때문에 인자를 넘겨주는 것도 객체를 생성할때 위와 보는 것과 같이 main 함수에서 해주어야 한다.  이렇게 인자가 있는 생성자를 사용하면 객체의 생성과 동시에 초기화 하는 것이 편해진다. 

- 복사 생성자
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. class Person{   
  5. private:   
  6.     int number;   
  7.     char *name;   
  8. public:   
  9.        
  10.     Person()   
  11.     {   
  12.         number = 0; name = "noname";   
  13.     }   
  14.     Person(int _number, char* _name)   
  15.     {   
  16.         number =_number; name = _name;   
  17.     }   
  18.        
  19.     //복사 생성자   
  20.     Person(const Person& p)   
  21.     {   
  22.         cout<<"Copy Constructor Call"<<endl<<endl;   
  23.         number = p.number;   
  24.         name = p.name;   
  25.     }   
  26.     void print()   
  27.     {   
  28.         cout<< "Number : "<<number << endl<<"Name :" <<name <<endl;   
  29.     }   
  30. };   
  31.   
  32. int main()   
  33. {   
  34.     //객체 생성   
  35.     Person P1(1, "myname"), P2(2, "your name");   
  36.     Person P3 = P1;   
  37.   
  38.     P3.print();   
  39.     P3 = P2;   
  40.        
  41.     cout<<endl;   
  42.     P3.print();   
  43.   
  44.     return 0;   
  45. }  
 복사 생성자는 다른 객체로부터 값을 복사해서 초기화하는데 사용하며, 자신과 동일한 타입의 객체에 대한 레퍼런스를 인자로 받는 생성자이다. 인자값에 Const를 써준 이유에 대해서는 아래의 링크를 참조 하면 도움이 될 것이다. 


  복사 생성자는 36번째 줄처럼 객체를 사용해서 초기화하는 경우에 호출이 된다 .디폴트 생성자를 호출하고 나서 복사 생성자를 또 호출하는게 아니라, 오직 복사 생성자만 호출한다. 
  우리가 복사 생성자를 만들지 않아도, 36번째 줄처럼 초기화는 잘 작동을 한다. 그럼 왜 복사 생성자를 만들어야 하나? 그 이유는 1:1 복사 하는 것 말고 다른 방식으로 복사하고 싶은 경우가 있기 때문에 그런 경우에 복사 생성자를 재정의 해서 사용하면 될것이다. 

쓰다 보니 길어져서, 소멸자에 대해서는 다음 포스팅에서 이야기 해 보도록 하겠습니다.
Posted by 모과이IT
,
1. 정보은닉 (Information Hiding)  

 : 프로그램을을 사용하는 사용자가 알아야 하는 것은 프로그램 사용법이지 프로그램의 내부 동작이나 상세 구조가 아니다. 사용자가 굳이 알 필요가 없는 불필요한 정보는 숨김으로써 사용자는 최소한의 정보만으로 프로그램을 쉽게 사용할 수 있어야 한다. 

  C++에서는 클래스의 정보 은폐 기능을 지원하기 위해 private, public, protected 등의 접근제어 키워드를 통해 선언된 클래스 외부에서 직접적인 접근을 허용하지 않는것을 정보은닉이라 할 수 있다. 
  하지만 간접적 접근 경로를 제공해줘야 한다. 숨길 멤버와 공개할 멤버의 블록을 구성하도록 해서, 공개된 멤버는 외부에서 자유롭게 읽을 수 있지만 숨겨진 멤버를 참조하려고 시도하면 컴파일 과정에서 접근할 수 없다는 에러로 처리를 하면 된다.  (아래는 정보은닉의 예)
  1. #include<iostream>   
  2. using namespace std;   
  3.   
  4. class Point   
  5. {   
  6.     int x;   // x좌표의 범위 : 0 ~ 100   
  7.     int y;   // y좌표의 점위 : 0 ~ 100   
  8. public:   
  9.     int GetX(){ return x; }   
  10.     int GetY(){ return y; }   
  11.   
  12.     void SetX(int _x){ x=_x; }   
  13.     void SetY(int _y){ y=_y; }   
  14. };   
  15.   
  16. int main()   
  17. {   
  18.     int x, y;   
  19.     cout<<"좌표입력 : ";   
  20.     cin>>x>>y;   
  21.   
  22.     Point p;   
  23.     p.SetX(x);   
  24.     p.SetY(y);   
  25.   
  26.     cout<<"입력 된 데이터를 이용해서 그림을 그림"<<endl;   
  27.     return 0;   
  28. }  
2. 정보 은닉의 필요성  

 : 캡슐화된 코드를 제공할때 함부로 건드려서는 안되는 코드들을 보호하기 위해, 프로그램의 안정적 구현을 위해서는 정보은닉이 필요하다.


3. 캡슐화 (Encapsulation)  

  : 캡슐화란 클래스를 정의하는데 있어서 관련있는 데이터 및 함수를 하나로 묶는것이다. 캡슐화를 함으로써 정보 은닉도 함께 가져오는 효과를 가져오기도 한다. 아래의 캡슐화가 된 예제와 캡슐화가 안된 예제를 한번 보시죠. 


3-1)  캡슐화가 안된 예제
  1. #include<iostream>   
  2. using namespace std;   
  3.   
  4. class Point   
  5. {   
  6.     int x;   // x좌표의 범위 : 0 ~ 100   
  7.     int y;   // y좌표의 범위 : 0 ~ 100   
  8. public:   
  9.     int GetX(){ return x; }   
  10.     int GetY(){ return y; }   
  11.   
  12.     void SetX(int _x);   
  13.     void SetY(int _y);   
  14. };   
  15.   
  16. void Point::SetX(int _x)   
  17. {   
  18.     if(_x<0 || _x>100) {   
  19.         cout<<"X좌표 입력 오류, 확인 요망"<<endl;   
  20.         return;   
  21.     }   
  22.     x=_x;   
  23. }   
  24. void Point::SetY(int _y)   
  25. {   
  26.     if(_y<0 || _y>100)   
  27.     {   
  28.         cout<<"Y좌표 입력 오류, 확인 요망"<<endl;   
  29.         return;   
  30.     }   
  31.     y=_y;   
  32. }   
  33.   
  34. class PointShow   
  35. {   
  36. public:   
  37.     void ShowData(Point p)   
  38.     {   
  39.         cout<<"x좌표: "<<p.GetX()<<endl;   
  40.         cout<<"y좌표: "<<p.GetY()<<endl;   
  41.     }   
  42.   
  43. };   
  44.   
  45.   
  46. int main()   
  47. {   
  48.     int x, y;   
  49.     cout<<"좌표입력 : ";   
  50.     cin>>x>>y;   
  51.   
  52.     Point p;   
  53.     p.SetX(x);   
  54.     p.SetY(y);   
  55.   
  56.     PointShow show;   
  57.     show.ShowData(p);   
  58.   
  59.     return 0;   
  60. }  
  예를 들어 클래스를 정의하고 나서 프로젝트가 진행된 상황에서 문제를 발견, 포인트 클래스에다가 자기가 지니고 있는 변수 x,y에 대해 출력하는 기능을 넣어 줘야 되겠구나 생각을 하게 된다고 가정을 해봅시다. 하지만 이미 point라는 클래스는 이미 정해져 있고, 프로젝트가 이미 진행이 되어 있기 때문에, 기존의 클래스를 변경한는 것은 어려운 일 일것이다. 
  그래서 일반적으로 출력이란 기능이 없으므로, 이에 대한 기능을 클래스로 정의 하기에 이를 것입니다. 포인트 클래스의 객체를 인자로 받아서 리턴되는 데이터를 출력하는 형태로 클래스를 정의하는 pointshow 라는 클래스를 만들게 되는 것이죠. 
  메인함수에서 포인트 객체도 생성하고, 출력을 위해 show라는 객체도 생성하게 되고, 결과는 무리 없이 출력 되겠지만, 이것은 캡슐화가 무너졌다 라고 볼 수 있습니다.
  showdata라는 함수는 내가 정의한 포인트 함수의 x,y를 출력하기 위한 함수 이므로, 이것은 포인트에 대한 기능을 가진 것이라고 볼 수 있으므로  showdata라는 함수는 포인트 클래스 안에 존재 하는것이 맞는 것이죠. 

3-2) 캡슐화된 예제 :  위 상황에서 캡슐화가 제대로 됐다고 하면, 아래와 같은 예제가 나올 것입니다. 
  1. #include<iostream>   
  2. using namespace std;   
  3.   
  4. class Point   
  5. {   
  6.     int x;   // x좌표의 범위 : 0~100   
  7.     int y;   // y좌표의 범위 : 0~100   
  8. public:   
  9.     int GetX(){ return x; }   
  10.     int GetY(){ return y; }   
  11.   
  12.     void SetX(int _x);   
  13.     void SetY(int _y);   
  14.   
  15.     void ShowData();  //캡슐화를 위해 추가된 함수.   
  16. };   
  17.   
  18. void Point::SetX(int _x)   
  19. {   
  20.     if(_x<0 || _x>100) {   
  21.         cout<<"X좌표 입력 오류, 확인 요망"<<endl;   
  22.         return;   
  23.     }   
  24.     x=_x;   
  25. }   
  26. void Point::SetY(int _y)   
  27. {   
  28.     if(_y<0 || _y>100)   
  29.     {   
  30.         cout<<"Y좌표 입력 오류, 확인 요망"<<endl;   
  31.         return;   
  32.     }   
  33.     y=_y;   
  34. }   
  35.   
  36. void Point::ShowData()   
  37. {   
  38.     cout<<"x좌표: "<<x<<endl;   
  39.     cout<<"y좌표: "<<y<<endl;   
  40. }   
  41.   
  42. int main()   
  43. {   
  44.     int x, y;   
  45.     cout<<"좌표입력 : ";   
  46.     cin>>x>>y;   
  47.   
  48.     Point p;   
  49.     p.SetX(x);   
  50.     p.SetY(y);   
  51.     p.ShowData();   
  52.   
  53.     return 0;   
  54. }  

4. 캡슐화를 하는 이유?  

 : 코드의 재활용성을 높이고 에러발생을 최소화하며 다이나믹한 속성을 높이기 위해서 캡슐화가 필요 하다.

Posted by 모과이IT
,
1. 값에의한 호출  

  : 값에 의한 호출은 Call By Value라고 불리우며, 인자로 넘기는 값을 복사해서 새로운 함수에 넘겨주는 방식으로 값의 복사에 의한 함수 호출을 의미합니다. 

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

 위의 예제는 Call by Value 예제와 결과값 입니다. Call by Value는 값을 복사해서 전달하기 때문에 원본의 값이 변경될 가능성이 없다는 특징을 가지고 있지만, 값을 넘겨 줄때마다 고비용 , 복사손실 문제가 발생하는 단점도 있습니다. 
 ※ Call by Value는 Pass by Value 라고 불리기도 합니다. 

2. 참조에 의한 호출   

  : 참조에 의한 호출은 Call by Reference라고 부르며, 주소 값을 인자로 전달하는 함수 호출을 말합니다. 아래 예제를 보시죠. (위와 거의 유사합니다.)

  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. void swap(int *a, int *b);   
  5.   
  6. int main()   
  7. {   
  8.     int val1 = 10;   
  9.     int val2 = 20;   
  10.   
  11.     cout<< "Before the swap function" <<endl;   
  12.     cout<<"val1 : "<<val1<<endl;   
  13.     cout<<"val2 : "<<val2<<endl;   
  14.   
  15.     swap(&val1, &val2);   
  16.   
  17.     cout<<endl<<"After Swap function " <<endl;   
  18.     cout<<"val1 : "<<val1<<endl;   
  19.     cout<<"val2 : "<<val2<<endl;   
  20.   
  21.     return 0;   
  22. }   
  23.   
  24. void swap (int *a, int *b)   
  25. {   
  26.     int temp = *a;   
  27.     *a = *b;   
  28.     *b = temp;   
  29. }  

  위의 예제에서 알 수 있듯이 Call by Reference는 복사손실과 고비용 문제에서는 벗어났지만, 원본의 값의 변경이 일어 날 수 있다는 중대한 문제점을 안고 있는걸 볼 수 있습니다. 

 Call by Value 나 Call by Reference 둘다 문제점을 가지고 있는데요, Effective C++에서는 이거에 대한 개선점을 언급한 바가 있습니다. 바로 항목 20 에서 인데 그에 대한 설명은 아래 포스팅을 참조 해 주십시오. 

Posted by 모과이IT
,
우선 레퍼런스를 알아 보기 전에 변수에 대해서 간단히 알아 봅시다. 
1. 변수 (Variable)  

   :  변수란 메모리 공간에 붙은 이름 이라고 할 수 있습니다. C에서는 하나의 메모리 공간에 하나의 이름만을 가지고 있었는데, C++에서는 하나의 메모리 공간에 둘이상의 이름을 붙여 줄 수 있죠. 그것이 바로 레퍼런스입니다. 


2. 레퍼런스  

 : 이름을 지니는 대상에 별명을 붙여주는 행위. 아래와 같이 사용합니다. 

  1. int main()   
  2. {   
  3.     int val =10;   
  4.     int *pVal = &val; //주소 값을 얻기 위해 & 연산자 사용의 예   
  5.     int &Val = val; //레퍼런스 선언을 위한 &연산자 사용   
  6.   
  7.     return 0;   
  8. }  

 '&' 연산자를 이용해 마음대로 변수에다가 별명을 붙여 줄 수 있는데요. 아래 예제를 보시죠.
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. int main()   
  5. {   
  6.     int val =10;   
  7.     int &AAA = val; //레퍼런스 선언을 위한 &연산자 사용   
  8.     AAA = 20;   
  9.   
  10.     cout<< "val value is  : "<<val <<endl;   
  11.     cout<< "AAA value is  : "<<AAA <<endl;   
  12.   
  13.     val = 40;   
  14.   
  15.     cout<< "val value is  : "<<val <<endl;   
  16.     cout<< "AAA value is  : "<<AAA <<endl;       
  17.   
  18.     return 0;   
  19. }  
 아래 예제를 실행한 결과 입니다. 

 레퍼런스를 이용한 변수의 값을 변경하여도 어차피 AAA나 val은 같은 메모리 공간을 가지고 있기 때문에 위와 같은 출력 결과를 출력하는 것을 알 수 있습니다. 
Posted by 모과이IT
,
우리들이 프로그램을 실행하면, 운영체제는 우리들이 실행한 프로그램을 위해 메모리 공간을 할당해줍니다. 그냥 할당해 주느냐? 그것이 아니라 용도별로 메모리 공간을 할당해 운영체제가 메모리 영역을 구분해 메모리 공간의 효율적 사용을 하게 만들어 줍니다. 

1. 메모리 영역의 종류  
* 데이터영역 : 전역 변수, Static변수를 위한 메모리 공간
 - 전역 변수 : 프로그램이 시작하자마자 메모리 공간에 올라가서 종료 될때까지 남아있는다.
 - Static 변수 : 프로그램 시작과 동시에 메모리 공간에 올라가서 종료될때까지 남아 있긴 한다. 
* 힙영역 : 런타임에 크기가 결정될수 있는 요소들을 올리기 위한 공간
* Stack : 컴파일 타임에 크기가 결정될 수 있는 요소들을 메모리 공간에 올리기 위한 영역

 각각의 영역에 대한 설명을 들었습니다. 아래 예제를 한번 보시죠. 
  1. #include <iostream>   
  2. usng namespace std;   
  3.   
  4. void function (int);   
  5.   
  6. int main()   
  7. {   
  8.     int size;   
  9.     cin>>size;   
  10.     function(size);   
  11.     return 0;    
  12.     //메인함수에서 요구되어 지는 메모리 공간의 크기는 컴파일 타임에 크기가 결정 가능하다.    
  13.         //이런 것을 올리기 위한 영역이 Stack이다.   
  14. }   
  15.   
  16. void function(int i)   
  17. {   
  18.     static int i = 10; //funtion이라는 함수가 처음 호출될때 i라는 변수가 초기화 된다.    
  19.     int array[i];    
  20. }  
  위의 예제는 에러를 일으키는데 위에서  function이라는 함수는 컴파일 타임에 요구되어 지는 메모리 크기를 계산하기는 불가능합니다. array라는 배열을 선언하기에 있어서 그 크기를 전달되는 인자값에 의해서 결정되어 지는데, 이 i 값은 고정이 아니라, 프로그램을 실행하는 사람에 의해서 값 변동이 심할 것입니다. 
  그러면 컴파일 되는 동안에 funtion이라는 함수내에서 요구되어지는 메모리 크기를 컴파일 타임에 결정할 수 없게 되고, 이 크기는 입력되는 size값이 결정되어야만 결정이 되죠. (이 사이즈는 누군가 프로그램을 실행하여야 결정되기 때문에 Array배열의 사이즈 크기는  런타임에 결정이 된다.)

 - 일반적으로 int array[i]; 이렇게 선언하면, 스택에 올리라는 의미가 된다. (이것은 스택에 못올린다.) 그래서 우리가 힙이라는 메모리 공간에 이런 배열을 선언하기 위해 malloc과 free라는 함수를 사용하게 되는 것이죠. 

2. Malloc & Free  
- Malloc : 힙에다가 메모리 공간을 할당하기 위한 함수 , free는 malloc으로 할당된 메모리 공간을 해제 하기 위한 함수이다. 
- free 함수가 필요한 이유 : 힙이라는 메모리 공간은 프로그래머가 관리 하는 메모리 공간이기 때문에 malloc을 쓰면 free로 꼭 해제를 해줘야 한다. 
Posted by 모과이IT
,
1. Const?  
 : Const는 변수를 상수화 하기 위해 사용하는데, 쓰이는 위치에 따라서 용도가 조금 달라진다. 

- 첫번째, const int* n 
 :  위와 같이 const가 자료형 뒤에 붙은 경우를 데이터 상수화 라고 하고 n이라는 포인터가 가리키는 대상을 상수화 하겠다는 의미가 된다. 
  1. #include <iostream>   
  2. using namespace std;   
  3.   
  4. const int*n;   
  5.   
  6. void main()   
  7. {   
  8.     int b =10;   
  9.     n = &b;   
  10.        
  11.     b = 20;   
  12.     cout<< b<<endl; //허용   
  13.   
  14.     *n = 30; //error (you cannot assign to a variable that is const)       
  15. }  
 위의 예제를 한번 보자 . n이라는 포인터가 가리키는 곳 B로 지정하고, B의 값은 변경하면 무리 없이 변경되는 것을 볼 수 있지만, *n =30; 이 부분에서는 직접 실행해 보면, 에러가 나는것을 알 수 있다. n이 지닌 값은 변경이 안된다는 것이다. 즉, 실제로 메모리 공간 자체가 상수화 된것이 아니고, n이라는 포인터를 이용해서 데이터 변경을 막겠다는 의미가 된다. (하지만 변수 b를 이용해 변경은 가능하다)

- 두번째, int* const n
 :  위와 같이 cosnt가 붙은 경우를 포인터 상수화 라고 한다. 예제를 통해 한번 알아 보자.
  1. #include <iostream>   
  2.   
  3. void main()   
  4. {   
  5.     int a =20;   
  6.     int b =10;   
  7.     intconst n = &b;   
  8.        
  9.     n = &a; //you cannot assign to a variable that is const    
  10. }  
 위에서 같이 선언한 n이라는 포인터가 b를 가르키고 있을 시에, 이런 상황에 변수 a를 또 가리키도록 하려는 것을 허용 하지 않겠다는 의미가 된다.

- 세번째, const int* const n;        :    데이터, 포인터 둘다 상수화 하겠다는 의미이다. 
Posted by 모과이IT
,