포스트

OOP

C++

OOP

OOP

Object-Oriented Programming


장점

  1. 캡슐화 encapsulation : 정보 은닉 information hiding
  2. 상속 inheritance
  3. 다형성 polymrphism
  4. 추상화 abstraction


캡슐화

접근 지정자

항목간단 설명내용
public공개 멤버클래스 외부에서 자유롭게 접근 가능
protected보호된 멤버클래스 외부에서 접근 불가능
파생 클래스(상속받은 클래스) 에서는 가능
private비공개 멤버클래스 외부, 파생 클래스에서도 접근 불가
클래스 내부에서만 접근 가능


기타

연산자 오버로딩 strcat 대신 “aa” + “bb” = “aabb”
RAII : 스마트 포인터 : 객체의 라이프 사이클과 리소스 자동 관리, 메모루 누수 해결? 예외 처리 네임스페이스 : 이름 충돌 해결 RTTI 런타임 타입 정보


클래스

객체를 생성하기 위한 설계도 객체는 클래스의 인스턴스, 클래스에 정의된 속성과 동작을 실제로 구현

데이터(멤버 변수)와 동작(멤버 함수)를 하나로 묶는 사용자 정의 자료형


구조체와의 차이 : 기본 접근 수준 | | | | ————– | ——- | | 구조체(struct) | public | | 클래스(class) | private |


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
using namespace std;

struct StructExample {
    int x; // 기본적으로 public
};

class ClassExample {
    int x; // 기본적으로 private
};

int main() {
    StructExample s;
    s.x = 10; // 접근 가능

    // ClassExample c;
    // c.x = 10; // 오류: private 멤버는 접근 불가능
    return 0;
}


멤버 변수 & 멤버 함수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>

using namespace::std;

class foo {
	//멤버 변수
	int a;	
	int b;

	//멤버 함수
	void func1() {	
		;
	}	
	void func2() {
		;
	}
};

int main() {

}


static

static 정적 멤버 변수는 클래스의 모든 객체가 공유
일반 멤버 변수는 객체별 독립 멤버 함수는 공유


클래스 생성 위치

스택이 좋음 메모리 관리 알아서 해주기 때문


객체 생성 방법

  1. 스택 : 1순위 권장
    Myclass obj;

  2. 힙 1) new, delete : 힙에 생성한다면 권장 : 생성자 호출이 가능해서?
    Myclass* obj = new Myclass(); 2) malloc, free


free 이후에

생성자 주소에 nullptr 넣어서 해제된 객체 사용 막기


초기화

1
2
int x = 0;  //암묵적 형변환 -> 복사 초기화
int x{0};   //명시적 형변환 -> 타입에 맞지 않을 경우 컴파일 에러 오류 -> 더 안전


this

현재 객체를 가리키는 포인터
자기 자신의 주소

인스턴스 메서드 안에서만 사용 가능


namespace

이름 공간 정의
이름 충돌 방지

::가 C++에서 우선순위 가장 높음

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>

namespace our {
	void init(){
		printf("our");
	}
}

namespace your {
	void init() {
		printf("your");
	}
}
int main() {

	our::init();

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>

using namespace std;
namespace  A {
	void init() {
		cout << "A namespace" << endl;
	}
}
namespace  B {
	void init() {
		cout << "B namespace" << endl;
	}
}
void init() {
	cout << "global namespace" << endl;
}
int main() {
	A::init();
	B::init();
	init();
}


using namespace

하나의 함수명만 사용할 경우
그냥 함수명으로 호출 가능
-> 충돌 가능성 있음


cin, cout

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>

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

int main() {

	int a;
	cin >> a;
	cout << a;

}


using, typedef

C++ 가급적 using 사용, typedef 대신

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<iostream>

using namespace std;

typedef int i;
using ii = int;

int main() {
	i a = 1;
	ii b = 2;

	cout << a << endl;
	cout << b << endl;
}


auto

복잡한 형태 auto로 단순하게 선언

decletype

표현식의 타입을 추론
변수나 함수 반환 값의 타입을 정확하게 알 수 없을 때 유용

1
2
3
4
5
6
7
8
9
#include <iostream>

int main() {
    int x = 5;
    decltype(x) y = 2;  // 'x'와 동일한 타입인 'int'로 선언

    std::cout << y << std::endl;  // 출력: 2
    return 0;
}


균일한 초기화 (uniform initialization)

중괄호 {}를 사용하여 모든 타입의 변수 초기화 가능
축소 변환(narrowing conversion) 방지 -> 데이터 손실 발생할 수 있는 초기화 허용 x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string>

class Person {
public:
    std::string name;
    int age;

    Person(std::string n, int a) : name{n}, age{a} {} // 균일한 초기화 사용
};

int main() {
    int a{10};       // 정수 초기화
    double b{3.14};  // 실수 초기화
    char c{'A'};     // 문자 초기화
    std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;


    Person p{"Alice", 25}; // 객체 초기화
    std::cout << "Name: " << p.name << ", Age: " << p.age << std::endl;

    return 0;
}


string 관한

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <string>
// include <csting.h> string.h의 cpp 버전

int main(void) {
	char s1[5] = "abcd";
	char s2[5] = { 0 };

	// s1을 s2로 복사
	strcpy_s(s2, s1);
	std::cout << s1 << std::endl;
	std::cout << s2 << std::endl;

	// 두문자열 같은지
	if (strcmp(s2, s1) == 0) { std::cout << "same" << std::endl; }
	else { std::cout << "not same" << std::endl; }


	std::string s3 = "xyzw";
	std::string s4 = "";

	// = 연산자
	s4 = s3;
	std::cout << s3 << std::endl;
	std::cout << s4 << std::endl;

	// == 연산자
	if (s3 == s4) { std::cout << "same" << std::endl; }
	else { std::cout << "not same" << std::endl; }

	// + 연산자
	std::string s5 = s3 + s4;
	std::cout << s5 << std::endl;

	return (0);
}


오버로딩 Overloading

같은 이름 함수 여러 개 정의
매개변수가 다르면 같은 이름으로 여러 함수 정의
-> 컴파일 시점에 결정, 변환 타입은 오버로딩x


오버라이딩 Overriding

부모 클래스에서 정의돈 함수를 자식 클래스에서 재정의
상속 관게에서 덮어쓰기
함수 이름, 매개변수, 반환 타입이 동일해야 함
-> 런타임 시점에결정정


구분오버로딩오버라이딩
적용 대상같은 클래스 내에서 사용부모 클래스와 자식 클래스 간 사용
관계상속 관계가 필요 없음상속 관계 필수
재정의 조건함수 이름 같고 매개변수 타입/개수/순서가 달라야 함함수 이름, 매개변수, 반환 타입이 모두 같아야 함
바인딩 시점컴파일 시점 (정적 바인딩)런타임 시점 (동적 바인딩, 가상 함수 사용)
키워드 필요 여부별도의 키워드 필요 없음virtualoverride 키워드 사용 권장
예제 용도여러 매개변수 타입/개수에 대응하기 위해 사용부모 클래스의 동작을 자식 클래스에서 재정의하여 변경


예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

class Animal {
public:
    virtual void sound() {
        cout << "Animal makes a sound." << endl;
    }
};

class Dog : public Animal {
public:
    void sound() override {
        cout << "Dog barks." << endl;
    }

    void callParentSound() {
        Animal::sound();  // 부모 클래스의 sound() 호출
    }
};

int main() {
    Dog dog;
    dog.sound();           // 자식 클래스의 sound() 호출
    dog.callParentSound(); // 부모 클래스의 sound() 호출
    return 0;
}


네임 맹글링

C++ 함수 오버로딩 위해서
같은 이름의 함수나 변수를 링커 단계에서 고유하게 식별할 수 있도록 이름을 변환

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
using namespace std;

void foo(int x) {
    cout << "foo(int)" << endl;
}

void foo(double x) {
    cout << "foo(double)" << endl;
}

int main() {
    foo(10);    // 호출: foo(int)
    foo(3.14);  // 호출: foo(double)
    return 0;
}

컴파일 후 변환된 이름
foo(int) -> _Z3fooi foo(double) -> _Z3food


함수 템플릿

teplate <typename T>
타입 매개변수 T는 호출 시점에 컴파일러가 전달된 타입에 따라 자동으로 대체
template <class T> 도 가능

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
using namespace std;

// 함수 템플릿 정의
template <typename T>
T square(T x) {
    return x * x;
}

int main() {
    // 템플릿 인수 명시적으로 지정
    double d = square<double>(3.2);  // T를 double로 명시
    cout << "결과: " << d << endl;  // 출력: 10.24

    // 템플릿 인수를 생략 (컴파일러가 자동 추론)
    int i = square(5);  // T가 int로 자동 추론
    cout << "결과: " << i << endl;  // 출력: 25

    return 0;
}


다형성

OCP (Open/Closed Principle) 개방 폐쇄 원칙
확장에는 열려있고, 변경에는 닫혀있도록
-> C는 콜백(함수 포인터)
-> CPP(OOP)는 인터페이스

부모 클래스의 virtual 키워드를 자식 클래스에서 오버라이딩 해서 사용

코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream>
using namespace std;

class Base {
public:
    virtual ~Base() {	//가상 소멸자
        std::cout << "Base destructor called" << std::endl;
    }
    virtual void show() {  // 가상 함수
        std::cout << "Base class show() called" << std::endl;
    }
};

class Derived : public Base {
public:
    ~Derived() {	//가상 소멸자
        std::cout << "Derived destructor called" << std::endl;
    }
    void show() override {  // 자식 클래스에서 재정의된 메서드
        std::cout << "Derived class show() called" << std::endl;
    }
};

int main() {
    Base* basePtr = new Derived();
    basePtr->show();  // 동적 바인딩, Derived 클래스의 show() 호출
    delete basePtr;
    return 0;
}
Derived class show() called
Derived destructor called
Base destructor called


인터페이스

특정 클래스를 정의하면서 그 클래스가 제공해야 하는 메서드를 선언하는 추상적인 개념
순수 가상 함수(Pure Virtual Function)을 사용하여 구현 -> override 강제
virtual키워드와 함께 = 0으로 정의

1
virtual void draw() const = 0;  // const : 읽기 전용

추상 클래스(abstract class) : 순수 가상 함수를 포함한 클래스
객체로 만들지 못하고 상속으로써만 사용
상속받은 자식 클래스는 순수 가상 함수를 오버라이딩(override) 해야함

1
2
3
4
5
6
7
// 자식 클래스가 인터페이스를 구현
class Rectangle : public IShape {
public:
    void draw() const override {  // 인터페이스의 순수 가상 함수를 재정의
        std::cout << "Drawing Rectangle" << std::endl;
    }
};
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
using namespace std;

// 추상 클래스
class Shape {
public:
    virtual void draw() const = 0;  // 순수 가상 함수
    virtual ~Shape() {}
};

// 구체적인 형태의 클래스
class Circle : public Shape {
public:
    void draw() const override {
        cout << "Drawing a Circle" << endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() const override {
        cout << "Drawing a Rectangle" << endl;
    }
};

// 새로운 형태를 추가할 때 코드 변경 없이 확장 가능
class Triangle : public Shape {
public:
    void draw() const override {
        cout << "Drawing a Triangle" << endl;
    }
};

int main() {
    Shape* shape;

    // 기존 코드 변경 없이 새로운 형태 추가
    shape = new Circle();
    shape->draw();

    delete shape;

    shape = new Rectangle();
    shape->draw();

    delete shape;

    shape = new Triangle();
    shape->draw();

    delete shape;

    return 0;
}


상속

생성 & 소멸
하위 객체 생성시 -> 상위 객체 먼저 생성
하위 객체 소멸시 -> 하위 객체 먼저 소멸


구성 (composition)

클래스가 다른 클래스를 멤버 변수로 포함하는 방식으로 기능을 확장
상속 x, 직접 객체를 포함

코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>

class Engine {
public:
    void start() const {
        std::cout << "Engine starts" << std::endl;
    }
};

class Car {
private:
    Engine engine;  // Engine 객체를 멤버 변수로 포함
public:
    void start() const {
        engine.start();  // Engine 객체의 메서드 호출
    }
};

int main() {
    Car myCar;
    myCar.start();  // Car가 Engine을 구성하여 메서드를 호출
    return 0;
}


멤버 함수 대체 (Member Function Delegation)

부모 클래스에서 제공하는 특정 기능을 자식 클래스에서 사용하기 위해 부모 클래스의 멤버 함수를 호출하는 방식

코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

class Shape {
public:
    virtual void draw() const {
        std::cout << "Drawing Shape" << std::endl;
    }
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing Circle" << std::endl;
    }

    void drawBorder() const {
        Shape::draw();  // 부모 클래스의 draw() 메서드를 호출
        std::cout << "Drawing Circle Border" << std::endl;
    }
};

int main() {
    Circle circle;
    circle.draw();       // 자식 클래스가 재정의한 draw()
    circle.drawBorder(); // 자식 클래스가 부모의 draw()를 대체 호출
    return 0;
}



구성(Composition)멤버 함수 대체(Member Function Delegation)
다른 클래스를 멤버 변수로 포함하여 기능을 추가부모 클래스의 메서드를 자식 클래스에서 호출하여 기능 확장
코드의 결합도를 낮추고 유연성 증가부모 클래스의 메서드를 사용하여 새로운 동작 추가
클래스 간 독립적인 객체 관계 유지자식 클래스가 부모 클래스의 기능을 호출하여 확장


생성자

코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

// 클래스 정의
class Car {
private:
    string brand;
    int year;

public:
    // 생성자 정의
    Car(string b, int y) {
        brand = b;
        year = y;
    }

    void display() {
        cout << "Brand: " << brand << ", Year: " << year << endl;
    }
};

int main() {
    Car car1("Toyota", 2020);
    car1.display();  // Output: Brand: Toyota, Year: 2020

    return 0;
}


부모랑 자식 객체 간 멤버 변수 다르면 부모 클래스에 기본 생성자 필요
-> 빈 멤버 변수가 생겨서 별로 안좋음
-> 구성을 통해

소멸자


업 캐스팅 (Up casting)

상위 객체 자리에 하위 객체가 대신

1
2


다운 캐스팅 (Down casting)

하위 객체 자리에 상위 객체가 대신

1
2


예제 : 다형성 : 개선 전, 후 비교

개선 전 코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>

class LandLinePhone {
public:
	void landlineCall() const {
		std::cout << "유선 전화를 걸다." << std::endl;
	}
};

class CellPhone {
public:
	void cellPhoneCall() const {
		std::cout << "무선 전화를 걸다." << std::endl;
	}
};

class SmartPhone {
public:
	void smartPhoneCall() const {
		std::cout << "영상 전화를 걸다." << std::endl;
	}
};


class Person {
public:
	void makeCall(const LandLinePhone* phone) const {
		phone->landlineCall();
	}

	void makeCall(const CellPhone* phone) const { 
		phone->cellPhoneCall();
	}

	void makeCall(const SmartPhone* phone) const {
		phone->smartPhoneCall();
	}
};

int main() {
	Person person;

	LandLinePhone landLinePhone;
	person.makeCall(&landLinePhone);

	CellPhone cellPhone;
	person.makeCall(&cellPhone);

	SmartPhone smartPhone;
	person.makeCall(&smartPhone);

	return 0;
}


개선 후 코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <iostream>

class Phone {
public:
	virtual void Call() const = 0;
};

class LandLinePhone : public Phone {
public:
	void Call() const override {
		std::cout << "유선 전화를 걸다." << std::endl;
	}
};

class CellPhone : public Phone {
public:
	void Call() const override {
		std::cout << "무선 전화를 걸다." << std::endl;
	}
};

class SmartPhone : public Phone {
public:
	void Call() const override {
		std::cout << "영상 전화를 걸다." << std::endl;
	}
};

class Person {
public:
	void makeCall(const Phone* phone) const {
		phone->Call();
	}
};

int main() {
	Person person;

	LandLinePhone landLinePhone;
	person.makeCall(&landLinePhone);

	CellPhone cellPhone;
	person.makeCall(&cellPhone);

	SmartPhone smartPhone;
	person.makeCall(&smartPhone);

	return 0;
}


연산자 오버로딩

연산자 오버로딩이 되지 않는 연산자도 있음

연산자 오버로딩 : 멤버함수로

기본 코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>

class Point {
private:
	int x;
	int y;

public:
	Point() : x{ 0 }, y{ 0 } {} // 기본 생성자

	Point(int x, int y) : x{ x }, y{ y } {// 매개 변수 생성자
	}

	void print() const {
		std::cout << "(" << x << ", " << y << ")" << std::endl;
	}
};

int main(void) {
	Point pt1 = { 11,22 };
	Point pt2 = { 22,33 };

	pt1.print();
	pt2.print();

	return 0;
}



그냥 연산자 오버로딩 시도 : 에러

1
2
3
4
5
6
7
8
9
10
11
12
13
int main(void) {

	Point pt1 = { 11,22 };
	Point pt2 = { 22,33 };	

	pt1.print();
	pt2.print();

	Point pt3 = pt1 + pt2; // 에러
	pt2.print();

	return 0;
}


올바른 연산자 오버로딩 추가

1
2
3
4
5
6
7
8
9
class Point {
    ...

    //연산자 오버로딩
	Point operator+(const Point& other) const {
		return Point(this->x + other.x, this->y + other.y);
	}

}


최종 코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <iostream>

class Point {
private:
	double x;
	double y;
public:
	//기본 생성자 : 객체를 초기화시 인자가 없는 경우 호출
	Point() : x{ 0 }, y{ 0 } {}

	//매개 변수 생성자 :  객체 초기화시 인자 x,y로 초기화
	Point(double x, double y) : x{ x }, y{ y } {
	}

	void print() const {
		std::cout << "(" << x << ", " << y << ")" << std::endl;
	}

	Point operator+(const Point& other) const {
		return Point(this->x + other.x, this->y + other.y);
	}
	Point operator-(const Point& other) const {
		return Point(this->x - other.x, this->y - other.y);
	}
	Point operator*(const Point& other) const {
		return Point(this->x * other.x, this->y * other.y);
	}
	Point operator/(const Point& other) const {
		return Point(this->x / other.x, this->y / other.y);
	}
};

int main(void) {

	Point pt1 = { 11,22 };
	Point pt2 = { 22,33 };

	pt1.print();
	pt2.print();

	Point pt3;
	
	pt3 = pt1 + pt2;
	pt3.print();
	pt3 = pt1 - pt2;
	pt3.print();
	pt3 = pt1 * pt2;
	pt3.print();
	pt3 = pt1 / pt2;
	pt3.print();

	return 0;
}


연산자 오버로딩 : 비멤버함수로

변경 방법 :
멤버변수 참조 위해 private -> public
const Point& other -> const Point& a, const Point& b

코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <iostream>

class Point {
public:
	double x;
	double y;
public:
	//기본 생성자 : 객체를 초기화시 인자가 없는 경우 호출
	Point() : x{ 0 }, y{ 0 } {}

	//매개 변수 생성자 :  객체 초기화시 인자 x,y로 초기화
	Point(double x, double y) : x{ x }, y{ y } {
	}

	void print() const {
		std::cout << "(" << x << ", " << y << ")" << std::endl;
	}


};

Point operator+(const Point& a, const Point& b)  {
	return Point(a.x + b.x, a.y + b.y);
}
Point operator-(const Point& a, const Point& b)  {
	return Point(a.x - b.x, a.y - b.y);
}
Point operator*(const Point& a, const Point& b)  {
	return Point(a.x * b.x, a.y * b.y);
}
Point operator/(const Point& a, const Point& b)  {
	return Point(a.x / b.x, a.y / b.y);
}

int main(void) {

	Point pt1 = { 11,22 };
	Point pt2 = { 22,33 };

	pt1.print();
	pt2.print();

	Point pt3;
	
	pt3 = pt1 + pt2;
	pt3.print();
	pt3 = pt1 - pt2;
	pt3.print();
	pt3 = pt1 * pt2;
	pt3.print();
	pt3 = pt1 / pt2;
	pt3.print();

	return 0;
}


예제 : 연산자 오버로딩 + 로 strcat 만들기

코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
#include <iostream>
#include<cstring>

class Mystring {
private:
	char* str;
	int len;
public:
	//기본 생성자 : 빈 문자열로 초기화
	Mystring() : str{ nullptr }, len{ 0 } {}

	//매개 변수 생성자 : 입력 문자열로 초기화
	Mystring(const char* inputstr) {
		if (inputstr) {
			len = std::strlen(inputstr);
			str = new char[len + 1];
			std::strcpy(str, inputstr);
		}
		else {
			str = nullptr;
			len = 0;
		}
	}

	//복사 생성자
	Mystring(const Mystring& other) {
		len = other.len;
		if (other.str) {
			str = new char[len + 1];
			std::strcpy(str, other.str);
		}
		else {
			str = nullptr;
		}
	}

	//소멸자 : 동적 메모리 해제
	~Mystring() {
		std::cout << str << "소멸" << std::endl;
		delete[] str;
		//delete[] :
		//배열의 각 요소를 개별적으로 소멸 처리
		//배열에 할당된 메모리 해제
	}

	//+연산자 오버로딩 : 문자열 합치기
	Mystring operator+(const Mystring& other) const {
		int newlen = len + other.len;
		char* newstr = new char[newlen + 1];

		if (str)std::strcpy(newstr, str);				//복사하기
		if (other.str)std::strcat(newstr, other.str);	//뒤에 붙이기

		return Mystring(newstr);
	}

	void print() const {
		if (str) {
			std::cout << str << std::endl;
		}
		else {
			std::cout << "Empty" << std::endl;
		}
	}
};

int main(void) {

	Mystring str1("hi");
	Mystring str2(", nice  to meet you");
	Mystring str3 = str1 + str2;

	str1.print();
	str2.print();
	str3.print();

	std::cout << std::endl;

	return 0;
}