클래스 선언 방법

class Book
{
private:
  int current_page;         // 멤버 변수
public:
  void set_percent();       // 멤버 함수
  int total_page;
  . . .
};

Book my_book;     //    클래스 Book 객체 my_book 선언

 

 

외부클래스 정의 방법

클래스 선언 밖에서 멤버함수를 정의할 때 주의사항

  • 외부에서 멤버함수를 정의하려면 클래스 안에 정의할 멤버함수가 이미 있어야 한다. ( 오버라이딩? 같은 느낌 )
  • 외부에서 정의하려면 함수의 몸체, 즉 { } 가 클래스 내에 있으면 안 된다.

 

사용법

class Member{
public:
	Member();
	void print();
};

Member::Member(){
	printf("생성자 외부클래스로 정의~");
}

void Member::print(){
	printf("멤버함수 외부클래스로 정의~");
}

 

 

this 포인터

  • c++에서는 모든 멤버 함수가 자신만의 this포인터를 가지고 있다.
  • this 포인터는 해당 멤버 함수를 호출한 객체를 가리킨다.
  • this는 현재 객체의 주소를 저장한 포인터 변수이므로 반환할 때 참조 연산자(*)를 사용해 객체를 반환한다.
    ( *를 사용하지 않으면 this는 포인터이므로 주소값만 반환한다. )
  • 정적 멤버함수는 this 포인터가 없다.

 

 

생성자

생성자는 3가지 타입이 있다.            ➡️            기본 생성자, 디폴트 생성자, 복사 생성자

 

1. 기본 생성자

#include<iostream>
using namespace std;

class Test
{
private:
  int num1;
  double num2;
public:
  Test(int x, double y){
    num1 = x;
    num2 = y;
  }
  
  void printNum() const {
    cout << num1 << " " << num2 << endl;
  }
};

int main(){
  Test test(10, 10.1);            //          암시적 호출로 객체 생성
  test.printNum();
  
  Test test = Test(10, 10.1);     //          명시적 호출로 객체 생성
  test.printNum();          
  
  Test test;                      //          기본 생성자로 들어감
  test.printNum();
  
  Test(10, 10.1);        // 임시객체 생성 ( 실행도중 잠깐만 사용되는 객체 ) ➡️ 다음줄로 넘어가면 사라진다.
  return 0;
}

 

 

2. 디폴트 생성자

  • 객체를 생성할 때 아무런 인자를 넘겨주지 않을 때 멤버 변수를 초기화하는 생성자
  • 생성자를 만들지 않으면 컴파일러는 자동으로 텅 빈 생성자를 만든다.

예시1)

class Test{
private:
  int num1;
  double num2;
public:
  Test(){}   
  void printNum() const{}
};

int main(){
  Test test;
}

 

예시2)

class Test{
private:
  int num1;
  double num2;
public:
  Test(int a=10, double b = 10.1){
	cout << "디폴트 호출" << endl;
	num1 = a;
	num2 = b;
  }
  
  void printNum() const{}
};

int main(){
  Test test; // a = 10. b = 10.1 
  Test test2(15,15.1) // a = 15, b = 15.1 ➡️ 여기도 디폴트 생성자로 들어가는데 a와 b값이 지정해준 값들로 들어감
}

 

 

3. 복사 생성자

 

c++은 아래와 같이 묵시적 변환을 해준다.

int num1 = 5;              ➡️        int num1(5); 와 동일

int num2 = num1;      ➡️        int num2(num1); 와 동일

 

객체 선언도 마찬가지로 묵시적 변환을 해준다.

A a;

A b = a;                      ➡️        A b(a); 와 동일하다. 즉, 복사이다!!

 

 

복사는 얕은 복사와 깊은 복사가 있다.

 

얕은 복사

  • 객체를 복사할 때 기존 객체의 메모리 주소만 복사
  • 디폴트 복사 생성자이다. 즉, 연산자 오버로딩으로 재정의하지 않아도 이미 존재하는 대입연산자이다.
  • 대충 복사하는 느낌

얕은 복사의 문제점

#include<iostream>
#include<string>
using namespace std;

class A
{
pvivate:
  char* name;
  int age;
public:
  A(const char* myname, int myage) : age(myage)
  {
    name = new char[strlen(myname) + 1];
    strcpy(name, myname);
  }
  
  void ShowInfo() const {
    cout << "이름 : " << name << endl;
    cout << "나이 : " << age << endl;
  }
  
  ~A(){
    delete []name;
    cout << "소멸자 호출" << endl;
  }
};

int main(){
  A a("이름", 20);
  A b = a;   //   shallow copy로 주소값만 복사해온다. ( shallow copy가 디폴트 복사생성자임 )
  a.ShowInfo();
  b.ShowInfo();   //  객체 a의 name을 가리키는 주소값을 b도 할당받아 두 객체가 같은 문자열을 delete해서 에러 발생

  return 0;
}

얕은 복사가 이루어지면서 a와 b가 둘 다 같은 주소를 delete해서 중복 소멸하는 문제가 발생한다.
그리고 b의 원래 주소는 다시는 접근할 방법이 없게 되면서 delete할 수가 없게 되어 메모리 누수가 발생한다.

 

깊은 복사

  • 객체가 가진 멤버의 값과 형식 자체를 복사하여 "객체 자체"가 복사되는 것
  • 복사 생성자를 직접 정의(연산자 오버로딩을 통해 재정의)함으로써 객체간의 같은 메모리 공간 참조를 막아준다.
#include<stdio.h>

class Sample
{
private:
  int num1;
  int num2;
public:
  Sample(const Sample& Sp);
};


// 참조전달 방법으로 객체 자체를 가져와서 복사한다. ( 대입연산자를 직접 재정의한 것 )
Sample::Sample(const Sample& Sp){
  printf("복사 생성자\n");
  num1 = Sp.num1;
  num2 = Sp.num2;
}

int main(){
  Sample a;
  Sample b(a);  
  //  Sample b = a;와 같은 문장이다.
  //  c++에서 묵시적 변환한 것 ( Sample b = a;를 Sample b(a)로 알아서 변환해줌 ) 
  //  묵시적 변환을 막아주고 싶으면 재정의한 부분 앞에 explicit을 써주면 된다.
  //  ➡️ 그러면 Sample b = a;는 에러가 난다.
  //  묵시적 변환을 최소로 만들어주는게 좋은 코드이다. 
  //  묵시적 변환이 많이 발생할수록 결과를 예측하기 어렵다.
}

// 재정의한 복사(대입연산자)로 인해 깊은 복사가 이루어졌다.

'C,C++' 카테고리의 다른 글

C++ 상속  (0) 2022.04.13
C++ 접근 제어자  (0) 2022.04.10
C++ enum ( 열거형 자료형 )  (0) 2022.04.10
C++ 스마트 포인터 ( shared_ptr, make_shared )  (0) 2022.04.10
C++ 범위 지정 연산자(::)와 namespace  (0) 2022.04.10

+ Recent posts