Company
교육 철학

LV12 string 클래스 구현해보기

이번 시간에 배울 내용

이번 강의에서는 C++의 std::string 클래스를 직접 만들어 봅니다. 지금까지 배운 클래스, 생성자, 연산자 오버로딩, 동적 메모리 할당 등 모든 개념을 종합적으로 활용하는 실전 프로젝트입니다.
왜 string 클래스를 만들어 볼까요?
C 스타일 문자열(char*)의 불편함을 해결하기 위해
실제 std::string이 어떻게 동작하는지 이해하기 위해
객체지향 프로그래밍의 실전 감각을 익히기 위해

사전 지식: C 스타일 문자열의 문제점

C 스타일 문자열이란?

char str[] = "Hello"; // 문자 배열로 문자열 표현 char* ptr = "World"; // 포인터로 문자열 표현
C++
복사

왜 불편할까?

1. 크기가 고정되어 있어요

char str[10] = "Hello"; // "Hello"는 5글자인데 10칸을 미리 할당해야 함 // 나중에 더 긴 문자열을 넣으려면? 불가능!
C++
복사

2. 문자열 연결이 복잡해요

char str1[20] = "Hello"; char str2[] = " World"; strcat(str1, str2); // 복잡한 함수 사용 필요
C++
복사

3. 길이 계산도 직접 해야 해요

int len = strlen(str); // 함수 호출 필요
C++
복사

std::string은 이런 문제를 해결합니다

std::string str = "Hello"; str += " World"; // 간단한 연결! int len = str.length(); // 길이도 간단하게!
C++
복사

string 클래스의 설계도

필요한 재료 (멤버 변수)

우리가 만들 string 클래스는 세 가지 정보를 저장해야 합니다:
private: char* mStr; // 1️⃣ 실제 문자열 데이터 int mSize; // 2️⃣ 현재 문자열 길이 int mCapacity; // 3️⃣ 할당된 메모리 크기
C++
복사

왜 이 세 가지가 필요할까요?

mStr (실제 문자열)

문자열 데이터를 저장하는 곳입니다. 동적 할당으로 만들어서 크기를 자유롭게 조절할 수 있습니다.
// 예시 mStr = new char[15]; // 15칸짜리 공간 확보 // mStr이 가리키는 메모리: [H][e][l][l][o][\0][ ][ ][ ]...
C++
복사

mSize (현재 길이)

지금 문자열이 몇 글자인지 기억합니다. 매번 strlen()을 호출하지 않아도 됩니다!
// "Hello"를 저장했다면 mSize = 5; // 5글자
C++
복사

mCapacity (용량)

실제로 할당한 메모리가 몇 칸인지 기억합니다. 여유 공간을 미리 확보해서 나중에 문자열을 추가할 때 매번 새로 할당하지 않아도 됩니다.
// "Hello"(5글자)를 위해 15칸을 할당했다면 mCapacity = 15; // 나머지 10칸은 여유 공간
C++
복사

왜 여유 공간이 필요할까요?

비유: 가방을 살 때 딱 맞는 크기보다 여유있게 사는 이유와 같습니다.
// 여유 공간이 없다면: string str = "Hello"; // 5칸 할당 str += " "; // 1칸 더 필요 → 새로 6칸 할당 str += "W"; // 1칸 더 필요 → 새로 7칸 할당 str += "o"; // 1칸 더 필요 → 새로 8칸 할당 // 매번 새로 할당하면 느려요! // 여유 공간이 있다면: string str = "Hello"; // 15칸 할당 (여유 10칸) str += " World"; // 여유 공간 사용 → 새로 할당 안 함!
C++
복사

1단계: 생성자 만들기

생성자가 하는 일

생성자는 객체가 처음 만들어질 때 초기화 작업을 합니다.
ya::string str("Hello"); // 이 순간 생성자 호출!
C++
복사

생성자 코드

string(const char* str) { mSize = strlen(str); // 1️⃣ 길이 계산 mCapacity = (mSize * 2) + (mSize / 2); // 2️⃣ 여유 공간 계산 mStr = new char[mCapacity]; // 3️⃣ 메모리 할당 memset(mStr, 0, mCapacity); // 4️⃣ 메모리 초기화 memcpy(mStr, str, mSize + 1); // 5️⃣ 문자열 복사 }
C++
복사

단계별 자세한 설명

길이 계산

mSize = strlen(str);
C++
복사
strlen() 함수는 \0(널 문자) 전까지의 길이를 셉니다
"Hello"라면 → mSize = 5

여유 공간 계산

mCapacity = (mSize * 2) + (mSize / 2);
C++
복사
왜 이렇게 계산할까요?
현재 크기의 2.5배를 할당합니다. 충분한 여유 공간을 확보하기 위해서입니다.
// 예시: "Hello" (5글자) mCapacity = (5 * 2) + (5 / 2) = 10 + 2 = 12// 예시: "Hello World" (11글자) mCapacity = (11 * 2) + (11 / 2) = 22 + 5 = 27
C++
복사

메모리 할당

mStr = new char[mCapacity];
C++
복사
new로 동적 메모리를 할당합니다
mCapacity 크기만큼 공간을 확보합니다
메모리 그림:

메모리 초기화

memset(mStr, 0, mCapacity);
C++
복사
할당한 메모리를 모두 0으로 채웁니다
왜 필요한가요? 새로 할당한 메모리에는 쓰레기 값이 들어있을 수 있어서 깨끗하게 정리합니다

문자열 복사

memcpy(mStr, str, mSize + 1);
C++
복사
매개변수로 받은 문자열을 우리 메모리에 복사합니다
mSize + 1일까요? 널 문자(\0)까지 복사하기 위해서입니다!
// "Hello" 복사 시 str : [H][e][l][l][o][\0] ↓ ↓ ↓ ↓ ↓ ↓ mStr : [H][e][l][l][o][\0][0][0][0][0][0][0]
C++
복사

생성자 전체 동작 예시

ya::string str("Hello");
C++
복사

2단계: 소멸자 만들기

소멸자가 하는 일

객체가 사라질 때 메모리 정리를 합니다. 생성자에서 new로 할당한 메모리를 delete로 해제해야 메모리 누수를 방지할 수 있습니다.

소멸자 코드

~string() { delete[] mStr; // 1️⃣ 메모리 해제 mStr = nullptr; // 2️⃣ 포인터 초기화 }
C++
복사

왜 필요한가요?

{ ya::string str("Hello"); // 생성자: new로 메모리 할당 // str 사용... } // 이 중괄호를 벗어나면 str 객체 소멸 // 소멸자: delete로 메모리 해제 필요!
C++
복사
메모리 누수 방지:
// 소멸자가 없다면: void test() { ya::string str1("Hello"); // 메모리 할당 ya::string str2("World"); // 메모리 할당 ya::string str3("!!!!"); // 메모리 할당 } // 함수 종료 → 메모리가 해제 안 됨 → 메모리 누수! // 소멸자가 있다면: void test() { ya::string str1("Hello"); // 메모리 할당 ya::string str2("World"); // 메모리 할당 ya::string str3("!!!!"); // 메모리 할당 } // 함수 종료 → 소멸자 자동 호출 → 메모리 해제 완료!
C++
복사

delete[] vs delete

delete[] mStr; // ✅ 배열 할당(new[])에는 delete[] 사용 delete mStr; // ❌ 잘못된 사용! 일부만 해제됨
C++
복사

3단계: += 연산자 만들기

+= 연산자가 하는 일

기존 문자열 뒤에 새로운 문자열을 추가합니다.
ya::string str("Hello"); str += " World"; // "Hello World"가 됨
C++
복사

+= 연산자 코드

void operator+= (const char* str) { int len = strlen(str); // 1️⃣ 추가할 문자열 길이 int newSize = mSize + len; // 2️⃣ 새로운 전체 길이 // 3️⃣ 용량 체크 및 재할당 if (newSize >= mCapacity) { mCapacity = (newSize * 2) + (newSize / 2); char* newStr = new char[mCapacity]; memset(newStr, 0, mCapacity); memcpy(newStr, mStr, mSize); delete[] mStr; mStr = nullptr; mStr = newStr; } // 4️⃣ 문자열 추가 memcpy(mStr + mSize, str, len + 1); mSize = newSize; }
C++
복사

단계별 자세한 설명

추가할 문자열 길이 계산

int len = strlen(str);
C++
복사
// 예시 str += " World"; // 추가할 문자열 len = strlen(" World") = 6
C++
복사

새로운 전체 길이 계산

int newSize = mSize + len;
C++
복사
// 예시: "Hello"에 " World" 추가 mSize = 5 // 기존 "Hello" len = 6 // 추가할 " World" newSize = 5 + 6 = 11
C++
복사

용량 체크 및 재할당 (중요!)

용량이 충분한 경우:
현재 상태: mStr: [H][e][l][l][o][\0][0][0][0][0][0][0] mSize: 5 mCapacity: 12 " World" (6글자) 추가 시: newSize = 11 11 < 12 (용량 충분!) → 재할당 없이 바로 추가
C++
복사
용량이 부족한 경우:
현재 상태: mStr: [H][e][l][l][o][\0][0][0] mSize: 5 mCapacity: 8 " World !!!!" (12글자) 추가 시: newSize = 17 17 >= 8 (용량 부족!) → 재할당 필요!
C++
복사

재할당 과정 상세 설명

// 3-1. 새로운 용량 계산 mCapacity = (newSize * 2) + (newSize / 2); // newSize = 17이면 // mCapacity = (17 * 2) + (17 / 2) = 34 + 8 = 42 // 3-2. 새 메모리 할당 char* newStr = new char[mCapacity]; // newStr → [0][0][0]...[0] (42칸) // 3-3. 새 메모리 초기화 memset(newStr, 0, mCapacity); // 3-4. 기존 데이터 복사 memcpy(newStr, mStr, mSize); // mStr: [H][e][l][l][o][\0][0][0] // ↓ ↓ ↓ ↓ ↓ ↓ // newStr: [H][e][l][l][o][0][0]...[0] (42칸) // 3-5. 기존 메모리 해제 delete[] mStr; mStr = nullptr; // 3-6. 새 메모리로 교체 mStr = newStr;
C++
복사
그림으로 이해하기:

문자열 추가

memcpy(mStr + mSize, str, len + 1); mSize = newSize;
C++
복사
포인터 연산 이해하기:
mStr + mSize // 기존 문자열의 끝 위치를 가리킴 // 예시: "Hello"의 경우 mStr → [H][e][l][l][o][\0][0]...[0] ↑ ↑ mStr mStr + mSize (5번 위치, \0가 있는 곳)
C++
복사
복사 과정:
// " World" 추가 시 memcpy(mStr + mSize, " World", len + 1); // ↑ 복사 시작 위치 (5번 인덱스) // ↑ 6글자 + \0 = 7바이트 복사 전: mStr: [H][e][l][l][o][\0][0][0][0][0][0][0] ↑ 여기부터 복사 복사 후: mStr: [H][e][l][l][o][ ][W][o][r][l][d][\0] mSize: 11
C++
복사

전체 동작 예시

ya::string str("Hello"); // 초기 상태 str += " World"; // 문자열 추가
C++
복사

4단계: [] 연산자 만들기

[] 연산자가 하는 일

배열처럼 인덱스로 문자에 접근할 수 있게 합니다.
ya::string str("Hello"); cout << str[0]; // 'H' 출력 (읽기) str[1] = 'a'; // "Hallo"로 변경 (쓰기)
C++
복사

[] 연산자 코드

char& operator[] (int index) { return mStr[index]; }
C++
복사

왜 참조(&)를 반환할까요?

참조 반환의 의미:
char& operator[] (int index) // ✅ 참조 반환 { return mStr[index]; } // 이렇게 동작: str[0] = 'h'; // ↓ 실제로는 mStr[0] = 'h'; // 직접 수정 가능!
C++
복사
참조가 아니면 어떻게 될까요?
char operator[] (int index) // ❌ 값 반환 { return mStr[index]; } // 이렇게 동작: str[0] = 'h'; // ↓ 실제로는 char temp = mStr[0]; // 복사본 생성 temp = 'h'; // 복사본만 수정됨 // mStr[0]은 그대로! 원본 수정 안 됨!
C++
복사

읽기와 쓰기 모두 가능

ya::string str("Hello"); // 읽기: 값을 가져옴 cout << str[0]; // 'H' 출력 char c = str[1]; // c = 'e' // 쓰기: 값을 변경 str[0] = 'h'; // "hello"로 변경 str[4] = 'p'; // "hellp"로 변경
C++
복사

작동 원리 상세 설명

ya::string str("Hello"); str[1] = 'a';
C++
복사

5단계: 유틸리티 함수들

Size() 함수

현재 문자열의 길이를 반환합니다.
int Size() const { return mSize; }
C++
복사

const가 붙은 이유

int Size() const // ← 이 함수는 멤버 변수를 수정하지 않음을 보장
C++
복사
const 함수는 읽기 전용 함수입니다
멤버 변수를 변경할 수 없습니다
안전성을 보장합니다
ya::string str("Hello"); int len = str.Size(); // ✅ 안전하게 길이만 읽어옴
C++
복사

c_str() 함수

C 스타일 문자열 포인터를 반환합니다. cout 같은 C 함수와 호환되기 위해 필요합니다.
const char* c_str() const { return mStr; }
C++
복사

왜 필요한가요?

ya::string str("Hello"); // cout은 char* 타입을 출력할 수 있음 cout << str.c_str(); // ✅ "Hello" 출력 // 직접 출력하면? cout << str; // ❌ 에러! (연산자가 없음)
C++
복사

const가 두 번 붙은 이유

const char* c_str() const // ↑ ↑ // | 함수가 멤버를 수정 안 함 // 반환된 문자열을 수정하면 안 됨
C++
복사
ya::string str("Hello"); const char* ptr = str.c_str(); ptr[0] = 'h'; // ❌ 에러! const char*는 수정 불가
C++
복사

전체 코드와 사용 예시

완성된 ya::string 클래스

#include <iostream> #include <cstring> // strlen, memcpy, memset namespace ya { class string { private: char* mStr; int mSize; int mCapacity; public: // 생성자 string(const char* str) { mSize = strlen(str); // 넉넉하게 할당 (기본 크기보다 조금 더 크게) mCapacity = (mSize * 2) + (mSize / 2); if (mCapacity == 0) mCapacity = 1; // 빈 문자열 대비 mStr = new char[mCapacity]; memset(mStr, 0, mCapacity); memcpy(mStr, str, mSize + 1); // NULL 문자 포함 복사 } // 소멸자 ~string() { if (mStr != nullptr) { delete[] mStr; mStr = nullptr; } } // += 연산자 void operator+= (const char* str) { int len = strlen(str); int newSize = mSize + len; // 용량이 부족할 경우 재할당 if (newSize >= mCapacity) { // 용량 늘리기 (기존 로직 유지) mCapacity = (newSize * 2) + (newSize / 2); char* newStr = new char[mCapacity]; memset(newStr, 0, mCapacity); // 1. 기존 데이터 복사 (새 공간으로) memcpy(newStr, mStr, mSize); // 2. 기존 메모리 해제 delete[] mStr; // 3. 포인터 교체 (중요!) mStr = newStr; } // 4. 문자열 이어 붙이기 (기존 끝부분부터, NULL 문자 포함) memcpy(mStr + mSize, str, len + 1); // 5. 사이즈 갱신 mSize = newSize; } // [] 연산자: 인덱스로 문자 접근 char& operator[] (int index) { return mStr[index]; } // Size 함수 int Size() const { return mSize; } // c_str 함수 (문자열 주소 반환) const char* c_str() const { return mStr; } }; } int main() { // 테스트 코드 ya::string s("Hello"); std::cout << "처음: " << s.c_str() << std::endl; s += " World!"; std::cout << "변경 후: " << s.c_str() << std::endl; std::cout << "길이: " << s.Size() << std::endl; std::cout << "인덱스 [1]: " << s[1] << std::endl; return 0; }
C++
복사

실습 코드

int main() { // std::string 사용법 비교 std::string stdStr("Hello"); stdStr += " World"; cout << "std::string: " << stdStr << endl; cout << "4번째 문자: " << stdStr[4] << endl; cout << "━━━━━━━━━━━━━━━━━━━━" << endl; // 우리가 만든 ya::string 사용 ya::string myStr("Hello"); cout << "초기 문자열: " << myStr.c_str() << endl; cout << "길이: " << myStr.Size() << endl; // 문자열 추가 myStr += " World"; cout << "\n문자열 추가 후: " << myStr.c_str() << endl; cout << "길이: " << myStr.Size() << endl; // 개별 문자 변경 myStr[0] = 'h'; // 'H' → 'h' cout << "\n첫 글자 변경 후: " << myStr.c_str() << endl; // 인덱스 접근 cout << "4번째 문자: " << myStr[4] << endl; return 0; }
C++
복사

실행 결과

문자열 추가 후: Hello World
길이: 11
첫 글자 변경 후: hello World
4번째 문자: o

핵심 개념 정리

1. 동적 메모리 관리

왜 동적 할당을 사용하나요?

// ❌ 정적 할당: 크기가 고정됨 char str[10]; // 최대 9글자만 저장 가능
C++
복사
mCapacity: [H][e][l][l][o][\0][ ][ ][ ][ ][ ][ ] (12칸 할당) mSize: 5 ↑─────────────────↑ (실제 사용 5칸) mStr: 포인터가 첫 번째 위치를 가리킴 // ✅ 동적 할당: 필요한 만큼 확장 가능 char* str = new char[size]; // 필요한 크기만큼 할당
Plain Text
복사

메모리 관리 3단계

// 1단계: 할당 mStr = new char[mCapacity]; // 2단계: 사용 memcpy(mStr, "Hello", 6); // 3단계: 해제 (소멸자에서) delete[] mStr;
C++
복사

2. 연산자 오버로딩

왜 연산자를 오버로딩하나요?

직관적인 코드 작성:
// ❌ 연산자 오버로딩 없이 str.append(" World"); char c = str.at(0); // ✅ 연산자 오버로딩으로 str += " World"; // 더 직관적! char c = str[0]; // 더 간단!
C++
복사

구현한 연산자들

+=: 문자열 추가
[]: 인덱스 접근

3. 참조 반환의 중요성

// ✅ 참조 반환: 원본 수정 가능 char& operator[] (int index) { return mStr[index]; } str[0] = 'h'; // 원본이 바뀜 // ❌ 값 반환: 복사본만 수정됨 char operator[] (int index) { return mStr[index]; } str[0] = 'h'; // 원본은 그대로
C++
복사

4. const의 의미

int Size() const // 이 함수는 멤버를 수정하지 않음
C++
복사
읽기 전용 함수임을 보장
실수로 값을 변경하는 것을 방지
코드의 안전성 향상

5. 메모리 효율성

Capacity 전략

mCapacity = (mSize * 2) + (mSize / 2); // 2.5배 할당
C++
복사
왜 여유 공간을 남기나요?

실습 과제

기본 과제: string 클래스 마스터하기

1단계: 따라 치기 (3회)

2단계: 완전히 이해했는지 확인

다음 질문에 답할 수 있나요?
1.
mCapacity를 왜 mSize보다 크게 할당하나요?
2.
소멸자에서 delete[]를 왜 꼭 해야 하나요?
3.
[] 연산자에서 왜 참조(&)를 반환하나요?
4.
+= 연산자에서 언제 재할당이 일어나나요?

응용 과제: 기능 추가하기

난이도 : 간단한 함수 추가

// 1. 문자열이 비어있는지 확인 bool IsEmpty() const { return mSize == 0; } // 2. 문자열 비우기 void Clear() { memset(mStr, 0, mCapacity); mSize = 0; } // 사용 예시 ya::string str("Hello"); if (!str.IsEmpty()) str.Clear();
C++
복사

난이도 : 비교 연산자

// == 연산자 구현 bool operator== (const char* str) { return strcmp(mStr, str) == 0; } // 사용 예시 ya::string str("Hello"); if (str == "Hello") cout << "같습니다!" << endl;
C++
복사

난이도 : 복사 생성자

// 다른 string 객체로부터 복사 string(const string& other) { mSize = other.mSize; mCapacity = other.mCapacity; mStr = new char[mCapacity]; memset(mStr, 0, mCapacity); memcpy(mStr, other.mStr, mSize + 1); } // 사용 예시 ya::string str1("Hello"); ya::string str2(str1); // str1을 복사
C++
복사

도전 과제: std::vector 구현하기

std::vector란?

크기가 자동으로 늘어나는 배열입니다.
#include <vector> std::vector<int> vec; vec.resize(100); vec.push_back(1); // 요소 추가 vec.push_back(2); vec.push_back(3); cout << vec[0]; // 1 출력 cout << vec.size(); // 3 출력
C++
복사

구현할 기능들

template<typename T> class vector { public: vector(); // 생성자 ~vector(); // 소멸자 void push_back(T value); // 요소 추가 void clear(); // 모든 요소 제거 void resize(int newSize); // 크기 변경(사용 가능 공간) void reserve(int size); // 실제 메모리 할당 크기 T& operator[](int index); // 인덱스 접근 int size() const; // 크기 반환 private: T* mData; // 실제 데이터 배열 int mSize; // 현재 요소 개수 int mCapacity; // 할당된 용량 };
C++
복사

힌트

string 클래스와 비슷하지만 다른 점:
char가 아닌 템플릿(T) 사용
문자열 복사(memcpy) 대신 반복문으로 복사
strlen 대신 size 변수로 관리
// push_back 구현 힌트 void push_back(T value) { // 1. 용량 체크 if (mSize >= mCapacity) { // 재할당 (string과 비슷) } // 2. 요소 추가 mData[mSize] = value; mSize++; }
C++
복사

이번 강의를 통해 배운 것들

1. 객체지향의 실전 적용

캡슐화: 내부 구현(mStr, mSize, mCapacity)을 private으로 숨김
인터페이스: 사용하기 쉬운 함수들(+=, [], Size()) 제공
생성자/소멸자: 자동 초기화와 정리

2. 메모리 관리의 중요성

동적 할당: 필요한 만큼만 메모리 사용
메모리 누수 방지: 소멸자에서 확실하게 해제
재할당 전략: 여유 공간으로 효율성 향상

3. 연산자 오버로딩의 활용

직관적인 코드: str += "abc"처럼 자연스러운 표현
타입 안전성: C 스타일보다 안전한 사용

4. STL 라이브러리의 이해

std::string어떻게 동작하는지 이해
다른 STL 컨테이너(vector, list 등)도 비슷한 원리로 동작

다음 단계

더 배워야 할 것들

1.
복사 생성자대입 연산자 (Deep Copy)
2.
Move 문법 (C++11)
3.
템플릿을 활용한 제네릭 프로그래밍
4.
예외 처리 (메모리 할당 실패 등)

실전 적용

기존 문제들을 std::string, std::vector로 다시 풀기
직접 만든 클래스로 실제 프로그램 작성해보기
성능 비교: 직접 구현 vs STL

자주 묻는 질문 (FAQ)

Q1. 왜 mCapacity를 mSize보다 크게 만드나요?

A. 매번 문자열을 추가할 때마다 재할당하면 느리기 때문입니다.
// 여유 공간 없이 (비효율적) "H" → 재할당 "He" → 재할당 "Hel" → 재할당 // 매번 재할당! // 여유 공간 있음 (효율적) "H" → 재할당 (12칸 확보) "He" → 여유 공간 사용 "Hel" → 여유 공간 사용 // 한 번만 재할당!
C++
복사

Q2. memcpy와 strcpy의 차이는?

A.
memcpy: 정확히 N바이트를 복사
strcpy: \0을 만날 때까지 복사
char src[] = "Hello"; // memcpy: 정확히 6바이트 복사 memcpy(dest, src, 6); // strcpy: \0까지 자동 복사 strcpy(dest, src);
C++
복사

Q3. delete와 delete[]의 차이는?

A.
delete: 단일 객체 해제
delete[]: 배열 해제
int* a = new int; // 단일 할당 delete a; // 단일 해제 int* arr = new int[10]; // 배열 할당 delete[] arr; // 배열 해제
C++
복사

Q4. const char* c_str() const에서 const가 2개인 이유는?

A.
const char* c_str() const // ↑ ↑ // | 2번째: 함수가 멤버를 수정 안 함 // 1번째: 반환된 포인터로 문자열 수정 안 함
C++
복사

Q5. 참조 반환이 위험하지 않나요?

A. 멤버 변수를 참조 반환하는 것은 안전합니다. 객체가 살아있는 동안 멤버도 유효하기 때문입니다.
char& operator[](int index) { return mStr[index]; // ✅ 안전 (객체가 살아있음) } // 위험한 경우: char& GetChar() { char c = 'A'; return c; // ❌ 위험! 지역 변수 반환 }
C++
복사
수고하셨습니다!
string 클래스를 직접 구현하면서 객체지향 프로그래밍의 핵심을 모두 경험해보았습니다. 이제 STL을 사용할 때 내부에서 어떤 일이 일어나는지 이해할 수 있을 것입니다!
다음 강의에서는 템플릿을 더 깊이 다루고, 제네릭 프로그래밍을 배워보겠습니다.