[VEDA] 12일차

나우히즈·2025년 4월 2일

VEDA

목록 보기
11/16

9장. 객체지향 설계 원칙

✅ SOLID란?

S Single Responsibility Principle (단일 책임 원칙)
: 클래스는 하나의 책임만 가져야 한다
O Open/Closed Principle (개방-폐쇄 원칙)
: 확장에는 열려 있고, 변경에는 닫혀 있어야 한다
L Liskov Substitution Principle (리스코프 치환 원칙)
: 자식 클래스는 언제나 부모 클래스 대신 사용 가능해야 한다
I Interface Segregation Principle (인터페이스 분리 원칙)
: 클라이언트는 자신이 사용하지 않는 인터페이스에 의존하면 안 된다
D Dependency Inversion Principle (의존 역전 원칙)
: 추상에 의존해야지, 구체에 의존하면 안 된다

🔶 1. S — 단일 책임 원칙 (SRP)

클래스는 오직 하나의 변경 이유만 가져야 한다.

✳️ 의미

  • 한 클래스는 하나의 역할만 담당해야 함
  • 한 클래스가 여러 책임을 가지면 변경 이유가 많아지고, 변경 시 다른 기능에 영향을 줄 수 있음

✅ 예

class ReportPrinter {
public:
    void printReport();
};

class ReportSaver {
public:
    void saveReport();
};

✔️ 출력과 저장을 각각 다른 클래스에 분리!

🔶 2. O — 개방-폐쇄 원칙 (OCP)

확장에 열려 있고, 변경에는 닫혀 있어야 한다.

✳️ 의미

  • 기존 코드를 수정하지 않고도, 기능을 추가(확장)할 수 있어야 한다.
  • 다형성과 추상화를 통해 새로운 기능을 덧붙이는 식으로 설계

✅ 예

class Shape {
public:
    virtual void draw() = 0;
};

class Circle : public Shape {
    void draw() override { std::cout << "Draw Circle\n"; }
};

class Renderer {
public:
    void render(Shape* shape) { shape->draw(); }
};

✔️ 새로운 도형을 추가해도 Renderer는 수정할 필요 없음 → 확장에는 열려 있고, 기존에는 닫힘

🔶 3. L — 리스코프 치환 원칙 (LSP)

자식 클래스는 부모 클래스로 대체할 수 있어야 한다.

✳️ 의미

  • 상속받은 자식 클래스가 부모 클래스의 계약(기능, 의미)을 위반하지 않아야 함
  • 자식 클래스가 부모처럼 행동해야 하고, 부모 타입을 기대하는 곳에 자식 객체를 넣어도 문제 없이 동작해야 함

❌ 나쁜 예

class Bird {
public:
    virtual void fly();
};

class Ostrich : public Bird {
    void fly() override {
        throw "타조는 못 날아요!";
    }
};

✔️ Ostrich는 Bird이지만 fly() 기능을 위반함 → LSP 위배

🔶 4. I — 인터페이스 분리 원칙 (ISP)

인터페이스는 작게 나누어야 하고, 클라이언트는 자신이 쓰지 않는 함수에 의존하면 안 된다.

✳️ 의미

  • 불필요하게 큰 인터페이스를 만들지 말고, 각 클라이언트가 필요로 하는 만큼만 인터페이스를 제공해야 함
  • 역할별 인터페이스 분리

❌ 나쁜 예

class IMachine {
public:
    virtual void print() = 0;
    virtual void scan() = 0;
    virtual void fax() = 0;
};

class OldPrinter : public IMachine {
    void print() override {}
    void scan() override {}   // ❌ 필요 없음
    void fax() override {}    // ❌ 필요 없음
};

✔️ 하나의 인터페이스에 너무 많은 기능을 담음 → 사용하지 않는 기능도 구현 강요

🔶 5. D — 의존 역전 원칙 (DIP)

고수준 모듈은 저수준 모듈에 의존하면 안 된다. 둘 다 추상에 의존해야 한다.

✳️ 의미

  • 구체 클래스가 아닌 추상(인터페이스, 추상 클래스)에 의존하도록 설계
  • 객체 생성은 외부에서 주입받고, 기능은 추상에 맞춰 사용

✅ 예

class IMessage {
public:
    virtual void send(const std::string& msg) = 0;
};

class Email : public IMessage {
    void send(const std::string& msg) override {
        std::cout << "Email: " << msg << '\n';
    }
};

class Messenger {
    IMessage* service;
public:
    Messenger(IMessage* s) : service(s) {}
    void deliver() {
        service->send("Hello");
    }
};

✔️ Messenger는 Email에 직접 의존하지 않고 IMessage라는 추상에만 의존


10장. 템플릿

✅ C++ 템플릿이란?

**템플릿(template)**은 자료형에 관계없이 일반화된 코드를 작성하기 위한 문법입니다.

✔️ 즉, 같은 로직을 여러 타입에 대해 반복적으로 작성하지 않고도
하나의 코드로 여러 자료형을 처리할 수 있습니다.

✅ 1. 함수 템플릿

📌 기본 문법

template <typename T>
T add(T a, T b) {
    return a + b;
}
  • T는 템플릿 타입 매개변수
  • typename 또는 class 둘 다 사용 가능 (template도 됨)

📌 사용 예

std::cout << add<int>(3, 4);      // 7
std::cout << add<double>(1.5, 2.3); // 3.8

또는 자동 타입 추론도 가능:

std::cout << add(3, 4);         // T는 int로 추론
std::cout << add(1.5, 2.3);     // T는 double로 추론

✅ 2. 클래스 템플릿

📌 기본 문법

template <typename T>
class Box {
private:
    T value;

public:
    Box(T v) : value(v) {}
    T get() const { return value; }
};

📌 사용 예

Box<int> intBox(10);
Box<std::string> strBox("Hello");

std::cout << intBox.get();    // 10
std::cout << strBox.get();    // Hello

✅ 3. 템플릿 인자 여러 개

template <typename T1, typename T2>
class Pair {
public:
    T1 first;
    T2 second;

    Pair(T1 a, T2 b) : first(a), second(b) {}
};

Pair<int, std::string> p(1, "hi");

✅ 4. 템플릿 특수화 (Specialization)

  • 특정 타입에 대해 다르게 동작하도록 별도로 구현하는 방법
    함수 오버로딩 방식과 유사하게 별도로 정리해주면 된다.
template <typename T>
class Printer {
public:
    void print(T value) {
        std::cout << value << "\n";
    }
};

// 문자열 특수화
template <>
class Printer<const char*> {
public:
    void print(const char* value) {
        std::cout << "문자열: " << value << "\n";
    }
};

✅ 5. 템플릿 사용 시 장점

  • 코드 재사용성 ↑ : 같은 코드로 다양한 자료형 처리 가능
  • 타입 안전 : 컴파일 타임에 타입 체크 진행
  • 일반 함수보다 빠를 수도 있음 (인라인 최적화 가능)

⚠️ 단점 및 주의점

  • 코드 부풀림 (code bloat) : 자료형마다 별도로 인스턴스화됨 → 바이너리 커질 수 있음
  • 디버깅 어려움 : 템플릿 오류 메시지가 복잡하고 길다
  • 컴파일 시간 증가 : 많은 템플릿 인스턴스화 시 컴파일 느려짐

✅ 템플릿과 STL의 관계

  • C++ STL (Standard Template Library)은 거의 전부가 템플릿으로 구성되어 있습니다.
  • std::vector, std::stack, std::map<Key, Value> 등
  • 자료형에 구애받지 않는 자료구조와 알고리즘을 제공하는 핵심 기반이 바로 템플릿이에요.

11장. C++ 표준 라이브러리

std::string 라이브러리

✅ std::string 이란?

std::string은 C++ 표준 라이브러리에서 제공하는 동적 문자열 클래스.

✔ 내부적으로 std::vector처럼 동적 메모리를 관리하며
✔ 길이, 복사, 비교, 연결, 검색, 수정 등 다양한 문자열 연산을 지원합니다.

✅ 기본 사용법

#include <iostream>
#include <string>

int main() {
    std::string str = "Hello";
    std::cout << str << "\n";
}

✅ 주요 생성자

std::string s1;                   // 빈 문자열
std::string s2("hello");          // 문자열 리터럴로 초기화
std::string s3(5, 'A');           // "AAAAA"
std::string s4 = s2;              // 복사 생성

✅ 주요 멤버 함수 & 연산

  1. 길이
    str.length() 또는 str.size() 문자열 길이 반환(length는 문자열 길이, size는 스트링이 사용할 수 있는 메모리공간을 의미)
  2. 연결
    str1 + str2 문자열 연결, append() 함수 통해 연결 가능
  3. 비교
    str1 == str2, !=, < 등 사전순 비교
  4. 부분 문자열
    str.substr(pos, len) 부분 문자열 추출
  5. 삽입
    str.insert(pos, "text") 문자열 삽입
  6. 삭제
    str.erase(pos, len) 문자열 삭제
  7. 검색
    str.find("text"), rfind() 부분 문자열 찾기
  8. 대체
    str.replace(pos, len, "new") 부분 문자열 치환
  9. 문자 접근
    str[i], at(i) 문자 접근 (at()은 범위 체크)
  10. 비우기
    str.clear() 문자열 비우기
  11. empty 확인
    str.empty() 비었는지 확인

✅ 문자열 반복

std::string s = "Hello";

for (char c : s)
    std::cout << c << " ";

for (size_t i = 0; i < s.size(); ++i)
    std::cout << s[i];

✅ 문자열 입력

std::string name;
std::cin >> name;         // 공백 전까지 읽음

std::getline(std::cin, name);  // 한 줄 전체 읽음

✅ C 스타일 문자열과 호환

const char* cstr = str.c_str();  // std::string → const char*

std::string str2 = std::string(cstr);  // const char* → std::string

11-3 파일시스템

  #include <iostream>
#include <string>
#include <filesystem>
#include <fstream>

using namespace std;
namespace fs = std::filesystem;

int main()
{
	fs::create_directories("MyDirectory");

	ofstream outFile("MyDirectory/myFile.txt");
	outFile << "Hello, FileSystem Library!" << endl;
	outFile.close();

	cout << "Files in MyDirectory:\n";
	for (const fs::directory_entry& entry : fs::directory_iterator("MyDirectory"))
	{
		if (entry.is_regular_file())
		{
			std::cout << entry.path().filename() << endl;
		}
	}

	ifstream inFile("MyDirectory/myFile.txt");
	string line;
	while (getline(inFile, line))
	{
		cout << line << endl;
	}

	inFile.close();

	// fs::remove_all("MyDirectory");
	return 0;
}

✅ 1. #include , namespace fs = std::filesystem;

📌 설명

  • C++17부터 추가된 은 디렉토리/파일 조작을 할 수 있는 라이브러리입니다.
  • 파일 생성, 디렉토리 생성, 파일 정보 확인, 삭제 등 운영체제 수준 파일 작업이 가능해요.
  • fs::로 줄여서 사용하기 위해 namespace alias를 사용.

✅ 2. 디렉토리 생성

fs::create_directories("MyDirectory");

📌 설명

  • 지정한 경로에 디렉토리 생성.
  • 중간 디렉토리도 없으면 재귀적으로 전부 생성해줍니다.
  • 이미 존재해도 에러 없이 그냥 넘어감 (true/false 반환).

✅ 3. 파일 쓰기 – std::ofstream

ofstream outFile("MyDirectory/myFile.txt");
outFile << "Hello, FileSystem Library!" << endl;
outFile.close();

📌 설명

  • #include <fstream> 필요
  • std::ofstream (output file stream)은 파일 출력용 스트림입니다.
  • ofstream filename(path) 으로 파일을 생성하거나 열 수 있어요.
  • 경로에 파일이 없으면 새로 만듦
  • 있으면 덮어씌움
  • 스트림이 열려 있으면 << 연산자로 문자열을 쓸 수 있음.
  • 파일 스트림은 사용 후 반드시 close() 해주는 게 좋습니다.

✅ 4. 디렉토리 내 파일 목록 출력

for (const fs::directory_entry& entry : fs::directory_iterator("MyDirectory"))
{
	if (entry.is_regular_file())
	{
		std::cout << entry.path().filename() << endl;
	}
}

📌 설명

  • fs::directory_iterator(path)로 지정 디렉토리 내의 모든 파일 및 서브디렉토리 탐색 가능.
  • fs::directory_entry는 각각의 엔트리(파일/디렉토리/심볼릭링크 등)를 나타냄.
  • entry.is_regular_file() → 일반 파일인지 확인
  • entry.path().filename() → 파일명만 추출

✅ 5. 파일 읽기 – std::ifstream

ifstream inFile("MyDirectory/myFile.txt");
string line;
while (getline(inFile, line))
{
	cout << line << endl;
}
inFile.close();

📌 설명

  • std::ifstream (input file stream)은 파일 입력용 스트림입니다.
  • getline(inFile, line)을 통해 한 줄씩 읽음 (개행 전까지)
  • 스트림은 .close()로 닫아주는 게 좋습니다.
  • 읽을 때는 반드시 파일이 열려 있는지 확인하는 습관도 좋습니다.
if (!inFile.is_open()) {
    cerr << "파일 열기에 실패했습니다!\n";
}

✅ 6. 파일/디렉토리 삭제

fs::remove_all("MyDirectory");

📌 설명
• 디렉토리 및 내부 모든 파일을 재귀적으로 삭제
• fs::remove_all(path)은 디렉토리 전체 삭제를 안전하게 수행
• fs::remove(path)은 파일이나 빈 디렉토리만 삭제 가능

혹은 바이너리 파일 입출력, 예외 처리 예시도 더 보여드릴게요! 😊

0개의 댓글