OOP
C++
OOP
Object-Oriented Programming
장점
- 캡슐화 encapsulation : 정보 은닉 information hiding
- 상속 inheritance
- 다형성 polymrphism
- 추상화 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순위 권장
Myclass obj;
힙 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
부모 클래스에서 정의돈 함수를 자식 클래스에서 재정의
상속 관게에서 덮어쓰기
함수 이름, 매개변수, 반환 타입이 동일해야 함
-> 런타임 시점에결정정
구분 | 오버로딩 | 오버라이딩 |
---|---|---|
적용 대상 | 같은 클래스 내에서 사용 | 부모 클래스와 자식 클래스 간 사용 |
관계 | 상속 관계가 필요 없음 | 상속 관계 필수 |
재정의 조건 | 함수 이름 같고 매개변수 타입/개수/순서가 달라야 함 | 함수 이름, 매개변수, 반환 타입이 모두 같아야 함 |
바인딩 시점 | 컴파일 시점 (정적 바인딩) | 런타임 시점 (동적 바인딩, 가상 함수 사용) |
키워드 필요 여부 | 별도의 키워드 필요 없음 | virtual 과 override 키워드 사용 권장 |
예제 용도 | 여러 매개변수 타입/개수에 대응하기 위해 사용 | 부모 클래스의 동작을 자식 클래스에서 재정의하여 변경 |
예시
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;
}