Company
교육 철학

LV12 string 클래스 구현해보기

커스텀 String 클래스 구현

#include <iostream> #include <string> #include <cstring> // strlen, memset, memcpy를 위해 필요 namespace ya { class string { public: // 🏗️ 생성자: C 스타일 문자열로부터 초기화 string(const char* str) { mSize = strlen(str); // 문자열 길이 계산 mCapacity = (mSize * 2) + (mSize / 2); // 여유 공간 2.5배 할당 mStr = new char[mCapacity]; // 동적 메모리 할당 memset(mStr, 0, mCapacity); // 메모리 0으로 초기화 memcpy(mStr, str, mSize + 1); // 문자열 복사 (\0 포함) } // 🗑️ 소멸자: 메모리 해제 ~string() { 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); memcpy(newStr, mStr, mSize); // 기존 데이터 복사 delete[] mStr; // 기존 메모리 해제 mStr = nullptr; mStr = newStr; // 새 메모리로 교체 } // 📝 문자열 추가 (기존 끝부분부터) memcpy(mStr + mSize, str, len + 1); // \0까지 포함해서 복사 mSize = newSize; // 크기 업데이트 } // 🎯 [] 연산자: 인덱스로 문자 접근 char& operator[] (int index) { return mStr[index]; // 참조 반환으로 읽기/쓰기 가능 } // 📏 크기 반환 함수 int Size() const { return mSize; } // 🖨️ 출력을 위한 C 스타일 문자열 반환 const char* c_str() const { return mStr; } private: char* mStr; // 실제 문자열 데이터 포인터 int mSize; // 현재 문자열 길이 int mCapacity; // 할당된 메모리 용량 }; } int main() { // 🌟 std::string 사용 예시 std::string strA("Hello"); int len = strA.length(); strA += " World"; std::cout << "std::string: " << strA << std::endl; std::cout << "4번째 문자: " << strA[4] << std::endl; // 🎯 우리가 만든 ya::string 사용 예시 ya::string strB("Hello"); std::cout << "\nya::string 초기값: " << strB.c_str() << std::endl; std::cout << "크기: " << strB.Size() << std::endl; strB += " World~~~~~~~~~"; std::cout << "문자열 추가 후: " << strB.c_str() << std::endl; std::cout << "크기: " << strB.Size() << std::endl; strB[4] = 'a'; // 'o'를 'a'로 변경 std::cout << "4번째 문자 변경 후: " << strB.c_str() << std::endl; std::cout << "4번째 문자: " << strB[4] << std::endl; return 0; }
C++
복사

학습 목표

지금까지 배운 객체지향 지식과 클래스를 활용하여 std::string 클래스를 비슷하게 설계해보기
실제 구현: 매우 복잡하지만 핵심 기능만 간단히 구현
주요 구현 요소: 생성자, += 연산자, [] 연산자

ya::string 클래스 구조

핵심 멤버 변수

private: char* mStr; // 📝 실제 문자열 데이터를 저장하는 포인터 int mSize; // 📏 현재 문자열의 길이 int mCapacity; // 📦 할당된 메모리 용량 (여유 공간 포함)
C++
복사
메모리 구조 개념:
mCapacity: [H][e][l][l][o][\0][ ][ ][ ][ ][ ][ ] (12칸 할당) mSize: 5 ↑─────────────────↑ (실제 사용 5칸) mStr: 포인터가 첫 번째 위치를 가리킴
Plain Text
복사

생성자 구현

string(const char* str) { mSize = strlen(str); // 🔢 문자열 길이 계산 mCapacity = (mSize * 2) + mSize; // 🏗️ 여유 공간 확보 (3배 할당) mStr = new char[mCapacity]; // 🆕 동적 메모리 할당 memset(mStr, 0, mCapacity); // 🧹 메모리 초기화 (0으로) memcpy(mStr, str, mSize + 1); // 📋 문자열 복사 (\0 포함) }
C++
복사
단계별 동작:
ya::string strB("Hello"); // 호출 시 1. strlen("Hello") = 5 → mSize = 5 2. (5 * 2) + 5 = 15 → mCapacity = 15 3. new char[15]15칸 메모리 할당 4. memset으로 모두 0[0][0][0]...[0] 5. memcpy로 "Hello" 복사 → [H][e][l][l][o][\0][0]...[0]
C++
복사

+= 연산자 오버로딩

void operator+= (const char* str) { int len = strlen(str); // 🔢 추가할 문자열 길이 int newSize = mSize + len; // 📏 새로운 전체 크기 if (newSize >= mCapacity) // 🚨 용량 초과 체크 { // 📦 용량 재할당 mCapacity = (newSize * 2) + newSize; char* newStr = new char[mCapacity]; memset(newStr, 0, mCapacity); memcpy(newStr, mStr, mSize); // 🔄 기존 데이터 복사 // 🗑️ 기존 메모리 해제 delete[] mStr; mStr = nullptr; mStr = newStr; // 🔗 새 메모리로 교체 } // ➕ 문자열 추가 (기존 끝부분부터) memcpy(mStr + mSize, str, len + 1); mSize = newSize; // 📏 크기 업데이트 }
C++
복사
동작 과정 예시:
ya::string strB("Hello"); // mSize=5, mCapacity=15 strB += " World~~~~~~~~~"; // 13글자 추가 → 총 18글자 1. newSize = 5 + 13 = 18 2. 18 >= 15 → 용량 부족! 3. 새 용량 = (18 * 2) + 18 = 54 4. 새 메모리 할당 후 "Hello" 복사 5. 기존 메모리 해제 6. " World~~~~~~~~~" 추가
C++
복사

[] 연산자 오버로딩

char& operator[] (int index) { return mStr[index]; // 📍 참조 반환으로 읽기/쓰기 가능 }
C++
복사
참조 반환의 장점:
strB[4] = 'a'; // 🖊️ 쓰기 가능 cout << strB[4]; // 📖 읽기 가능
C++
복사

Size() 함수

int Size() const // 🔒 const 함수 (값 변경 안함) { return mSize; }
C++
복사

전체 구현 코드## 핵심 개념 정리

동적 메모리 관리

할당: new char[capacity]로 필요한 크기만큼 할당
해제: delete[] mStr로 메모리 누수 방지 (소멸자에서)
재할당: 용량 부족 시 더 큰 공간으로 확장

메모리 효율성

Capacity 전략: 현재 크기의 3배 할당으로 빈번한 재할당 방지
여유 공간: 문자열 추가 시 매번 재할당하지 않도록 버퍼 확보

연산자 오버로딩 활용

+= 연산자: 직관적인 문자열 추가 인터페이스
[] 연산자: 배열처럼 인덱스로 접근 가능
참조 반환: 읽기와 쓰기 모두 지원

숙제 안내

기본 숙제

string 클래스 반복 연습
// 3번 디버깅하며 따라서 치기 (마지막 1번은 안보고 구현) // 그 다음 코드 보지 않고 main만 참고하여 직접 구현
C++
복사

표준 라이브러리 활용

기존 문제들을 std::list, std::string으로 다시 풀기
동적할당 링크드리스트 문제 → std::list 사용
문자열 처리 문제들 → std::string 사용

고급 숙제 - std::vector 구현

추가 도전 과제: std::vector를 직접 구현해보기
#include <iostream> #include <vector> int main() { std::vector<int> vec; vec.push_back(1); // 요소 추가 vec.push_back(2); vec.push_back(3); vec.push_back(4); vec.push_back(5); // 🔄 반복문으로 출력 for (size_t i = 0; i < vec.size(); i++) { std::cout << vec[i]; } vec.clear(); // 모든 요소 제거 vec.resize(10); // 크기를 10으로 조정 // 📝 값 할당 for (size_t i = 0; i < vec.size(); i++) { vec[i] = i; } return 0; }
C++
복사
vector 구현 시 고려사항:
템플릿: template<typename T> 사용
동적 배열: 크기 변경 가능한 배열 구조
주요 함수들:
push_back(): 요소 추가
size(): 현재 크기 반환
clear(): 모든 요소 삭제
resize(): 크기 변경
operator[]: 인덱스 접근

학습 포인트

이번 학습을 통해 얻을 수 있는 것:
메모리 관리: 동적 할당과 해제의 실제 활용
연산자 오버로딩: 직관적인 클래스 인터페이스 설계
STL 이해: 표준 라이브러리가 어떻게 구현되는지 체험
객체지향 설계: 캡슐화와 정보 은닉의 실제 적용
다음 단계: 이 경험을 바탕으로 더 복잡한 자료구조들(vector, list 등)을 직접 구현할 수 있는 기반 완성!