Iterator
string()
객체지향적 코드 작성법
컨테이너의 내부 구조는 서로 다르지만, Iterator를 통해 동일한 코드로 알고리즘 사용 가능
반복자는 컨테이너의 요소에 대한 일관된 접근 방법을 제공하므로, 알고리즘이 특정 컨테이너의 내부 구현과 무관하게 동작할 수 있다
반복자는 포인터처럼 동작하지만, 실제 타입은 컨테이너마다 별도로 정의된 "클래스(객체)"
컨테이너 내부의 직접적인 주소(메모리 위치)가 아니라, 그 컨테이너의 특정 원소를 가리키는 "포인터 유사 객체"
객체이므로 생성자와 같은 멤버함수도 존재
*it으로 값을 읽고, ++it으로 다음 요소의 위치로 이동
std::vector<int>::iterator 같이 자료형이 좀 기므로, 보통 auto it = ~~ 으로 사용
컴파일러에게 변수의 타입을 자동으로 추론하게 해주는 키워드
컴파일러가 추론해야하므로 반드시 초기화해서 사용해야 함
초기화 값을 바꾸어주면 그에 맞게 알아서 자료형도 바뀌는 편리함
auto x = 10; // int
auto y = 3.14; // double
auto z = "hello"; // const char*
auto flag = true; // bool
std::vector v = {1,2,3};
// it은 std::vector<int>::iterator의 자료형을 가지게 됨
for (auto it = v.begin(); it != v.end(); ++it) {
// *it으로 값을 읽고, ++it으로 다음위치로 이동 (포인터와 비슷)
std::cout << *it << std::endl;
}

해당 컨테이너의 순방향 반복자(iterator)를 반환
.begin(): 첫 번째 원소를 가리키는 반복자를 반환
.end(): 마지막 원소 "다음 위치"를 가리키는 반복자를 반환
map<string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 88}};
// map의 iterator는 map<key자료형, value자료형>::iterator
// map<string, int>::iterator it
for (auto it = scores.begin(); it != scores.end(); ++it) {
cout << it->first << ": " << it->second << endl;
}
// Alice: 90
// Bob: 85
// Charlie: 88

역방향 반복자
.rbegin(): 컨테이너의 마지막 원소를 가리키는 역방향 반복자
.rend(): 컨테이너의 첫 번째 원소 이전을 가리키는 역방향 반복자
반복문에 사용 예시
map<string, int> scores = {{"Alice", 90}, {"Bob", 85}, {"Charlie", 88}};
for (auto it = scores.rbegin(); it != scores.rend(); ++it) { // 역방향이지만 ++it 사용
// code
}
vector<string> words = {"apple", "banana", "cherry", "date"};
string target = "banana";
auto it = find(words.rbegin(), words.rend(), target);
if (it != words.rend()) {
cout << "역방향 인덱스: " << distance(words.rbegin(), it) << endl;
// 순방향 반복자와 역방향 반복자끼리 계산 불가능
cout << "정방향 인덱스: " << distance(words.begin(), it.base()) - 1 << endl;
}
// 역방향 인덱스: 2
// 정방향 인덱스: 1
컨테이너의 첫 원소부터 마지막 원소까지 앞에서 뒤로 순회할 때 사용하는 기본 반복자
++로 앞에서 뒤로 이동, *로 현재 가리키는 원소에 접근(--는 안 됨)
std::vector::iterator가 대표적인 순방향 반복자
컨테이너를 뒤에서 앞으로 순회할 때 사용하는 반복자 (순방향 반복자와 자료형이 다름)
++를 통해 컨테이너 끝에서 앞으로 이동(반대방향), *로 원소에 접근
순방향 반복자와 다르게-- 가능. 양방향반복자임
역방향 반복자와 순방향 반복자는 타입이 다르기때문에, 직접 교환하거나 대입할 수 없으며, 변환이 필요
변환 없이 사용하면 컴파일 에러
base() vector<int> a = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto r_it = find(a.rbegin(), a.rend(), 5);
cout << *r_it << *r_it.base(); //56
역방향 반복자를 순방향 반복자로 변환시켜줌
반복자가 가리키는 원소의 “바로 다음”(forward 방향 기준) 위치의 반복자를 반환
그래서 distance(words.begin(), it.base())에서 1을 빼주는 것
순방향 반복자를 역방향 반복자로 변환
<iterator> 헤더에 정의
근데 얘도 반복자가 가리키는 원소의 "바로 다음"(reverse 방향 기준) 위치의 반복자를 반환
vector<int> a = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
auto it = find(a.begin(), a.end(), 5);
cout << *it << *make_reverse_iterator(it); // 54
string()은 객체 string의 생성자using namespace std;
// 아래의 예시들은 다 string객체의 생성자 (string())
string s1; // 빈 문자열 "" 생성
string s2(s1); // s1문자열 복사해 생성
string s3("hello"); // const char*형태의 C문자열을 받아 생성
string s4(s3, pos, count); // s3의 pos 위치부터 count 개수까지 복사해 s4 생성
string s5(vec.begin(), vec.end()); // 반복자 범위의 문자들을 복사해 생성
string s6(vec.rbegin(), vec.rend()); // 반복자 거꾸로 복사해 생성
string s7(5, 'A'); // "AAAAA"
string s1; // ""
string s2("hello"); // "hello"
string s3(s2); // "hello"
string s4(s2, 1, 3); // "ell"
vector v = {'w', 'o', 'r', 'l', 'd'};
string s5(v.begin(), v.end()); // "world"
string s6(v.rbegin(), v.rend()); // "dlrow"
string s7(4, '!'); // "!!!!"
관련성이 있는 모듈끼리 같은 클래스에 놓기
계산하는 모듈은 계산 클래스에, 출력모듈은 출력 클래스에 몰아 놓기
모듈 또는 클래스 간의 의존성을 나타내는 것으로, 결합도가 낮아야 좋은 코드
결합도가 높으면 각 모듈 간 의존성이 강해져, 하나의 모듈이 변경될 때, 다른 모듈도 영향을 받게 됨
자동차 클래스에서 엔진 클래스를 포함하는 경우(결합도 높음), 아래와 같이 직접할 수 있지만, 새로운 다양한 종류의 엔진 클래스를 추가하게 된다면, 자동차 클래스도 함께 수정해야 함
변경이 잦은 경우 수정 범위가 커지고 유지 보수가 어려워짐

결합도를 낮추기 위해 엔진 인터페이스를 새로 추가하여 활용
자동차 클래스는 엔진 인터페이스에만 의존하므로, 새로운 엔진을 추가해도 자동차 코드를 수정할 필요가 없다
인터페이스는 A로 표시되며, 꼭 가상함수를 써 override 되도록 해야한다

// 엔진 인터페이스
class Engine {
public:
virtual void start() = 0; // 가상함수로 override 해야 하게끔 설정
};
class DieselEngine : public Engine {
public:
void start() {}
};
class ElectricEngine : public Engine {
public:
void start() {}
};
// Car 클래스는 엔진 인터페이스에만 의존
class Car {
private:
unique_ptr<Engine> engine;
public:
Car(unique_ptr<Engine> eng) : engine(move(eng)) {}
void startCar() { engine->start(); }
};
int main() {
// DieselEngine을 사용하는 경우
auto dieselEngine = make_unique<DieselEngine>();
Car dieselCar(move(dieselEngine));
dieselCar.startCar();
// ElectricEngine을 사용하는 경우
auto electricEngine = make_unique<ElectricEngine>();
Car electricCar(move(electricEngine));
electricCar.startCar();
}


확장에는 열려있고, 수정에는 닫혀있도록 코드 짜기
기능이 변하거나 확장되는 것은 가능하지만 그 과정에서 기존의 코드가 수정되지 않아야 함
class ShapeManager {
public:
void drawShape(int shapeType) {
if (shapeType == 1) { /*원 그리기*/ }
else if (shapeType == 2) { /*사각형 그리기*/ }
}
};

class Shape {
public:
virtual void draw() = 0; // 순수 가상 함수
};
class Circle : public Shape {
public:
void draw() {/*원 그리기*/}
};
class Square : public Shape {
public:
void draw() {/*사각형 그리기*/}
};
class Triangle : public Shape {
public:
void draw() {/*삼각형 그리기*/}
};
class ShapeManager {
public:
void drawShape(Shape& shape) { shape.draw(); }
};
자식 클래스는 부모 클래스에서 기대되는 행동을 보장해야 함
즉, 부모 클래스를 사용하는 코드가 자식 클래스로 대체되더라도 정상적으로, 일관적으로 동작해야 함
예를 들어, 정사각형이 직사각형의 자식클래스일 때, 어차피 정사각형은 높이와 너비가 같다고 정사각형의 setWidth함수에서 heigt까지 설정하면 안 됨.
원래 부모클래스에서의 setWidth함수는 너비만 설정하는 함수였으므로.

자신이 사용하지 않는 기능은 따로 인터페이스로 분리한 후 인터페이스로 연결해 사용
프린터기와 스캐너는 별개이기도 하고, 이들의 기능만 사용할 것이므로 분리해야 한다

아래와 같이 인터페이스를 분리하여 사용하는 것이 좋다

상위 모듈이 하위 모듈에 의존해서는 안 됨
아래 예시처럼, 상위 모듈인 Computer에서 하위 모듈인 Keyboard, Monitor를 직접적으로 타입을 받아서 사용하면 나중에 입출력 부품이 바뀌거나 하는 경우에 유지보수에 어려움이 생김

따라서 마찬가지로 입출력 인터페이스 클래스를 따로 생성하고, 상위 모듈인 Computer에서는 이 인터페이스의 "포인터"를 받아서 결합력(의존성)을 낮춘다.
