STL은 다양한 컨테이너와 독립적으로 동작하는 범용 알고리즘을 제공
특정 원소 값을 찾거나, 정렬을 하는 등의 기능을 표준 라이브러리에서 바로 사용 가능
특정 컨테이너의 내부 구현을 몰라도 동일한 방식으로 알고리즘을 적용 가능
컨테이너 내부의 데이터를 정렬하는 함수
기본 타입(int,double 등)의 경우 사용자 정렬 함수가 없으면 오름차순으로 정렬, 또는 사용자 정렬 함수를 정의 가능
사용자 정렬 함수 comp(a,b) 구현 시 규칙
find는 컨테이너 내부에서 특정 원소를 찾아 해당 원소의 반복자를 반환하는 함수
find(first,last,찾을 값)과 같이 사용 시 규칙
알고리즘 사용 예시 코드
#include <iostream>
#include <algorithm> //sort, find 포함
#include <vector>
using namespace std;
class Person {
private:
string name;
int age;
public:
Person(string name, int age) : name(name), age(age){}
string getName()const {
return name;
}
int getAge() const{
return age;
}
};
bool compareByAgeAndName(const Person& a, const Person& b){ //다중 기준 정렬함수(나이 오름차순 -> 이름 오름차순)
if(a.getAge() == b.getAge()){
return a.getName() < b.getName(); //이름 오름차순
}
return a.getAge() < b.getAge();
}
bool compare(int a, int b){
return a>b; //내림차순 비교 함수
}
int main(){
int arr[] = {5,2,9,1,5,6};
int size = sizeof(arr) / sizeof(arr[0]);
//sort를 활용해서 기본타입 배열을 정렬하는 예시(정렬 기준 없음)
sort(arr, arr+ size); //오름차순 정렬
sort(begin(arr),end(arr)); //배열의 시작 포인터 부터 비열의 끝 포인터까지 정렬
//sort를 활용해서 기본타입 배열을 정렬하는 예시(정렬 기준 있음)
sort(arr,arr+size, compare);
//sort를 활용해서 기본타입 벡터를 정렬하는 예시(정렬 기준 없음)
vector<int> vec = {5,2,9,1,5,6};
sort(vec.begin(),vec.end());
//sort를 활용해서 기본타입 벡터를 정렬하는 예시(정렬 기준 있음)
sort(vec.begin(),vec.end(),compare);
//sort를 활용해서 class 타입 벡터를 정렬하는 예시
vector<Person> people = {
Person("Alice",30),
Person("Bob",25),
Person("Charlie",35),
Person("Alice",25)
};
sort(people.begin(),people.end(),compareByAgeAndName);
//벡터에서 특정 값 찾기
auto it = find(vec.begin(),vec.end(),9); //특정 값 9를 찾음
if(it!=vec.end()){
cout<<"값 9가 벡터에서 발견됨, 위치 : "<<(it-vec.begin())<<endl;
}else{
cout<<"값 9가 벡터에 없음"<<endl;
};
//배열에서 특정 값 찾기
auto it_arr = find(arr,arr+size,5);
if(it_arr!=arr+size){
cout<<"값 5가 배열에서 발견됨, 위치 : "<<(it_arr-arr)<<endl;
}else{
cout<<"값 5가 배열에 없음"<<endl;
};
//문자열에서 특정 문자 찾기
string str = "hello world!";
auto it_str = find(str.begin(),str.end(),'o');
while(it_str != str.end()){
cout<<"문자 'o'가 문자열에서 발견됨, 위치 : "<<(it_str-str.begin())<<endl;
it_str = find(next(it_str), str.end(),'o');
}
for(int i=0; i<size; i++){
cout<<arr[i]<< " ";
}
cout<<endl;
for(int num : vec){
cout << num << " ";
}
for(auto& person : people){
cout<<person.getName()<<" " << person.getAge()<<endl;
}
return 0;
}
컨테이너 구현 방식에 의존하지 않고 알고리즘을 활용하는데 문제 없음 (반복자를 기반으로 알고리즘이 동작)
반복자는 컨테이너의 요소에 대한 일관된 접근 방법을 제공, 알고리즘이 특정 컨테이너의 내부 구현과 무관하게 동작
순방향 반복자
순방향 반복자는 앞에서 뒤로 순차적으로 순회하는 반복자
컨테이너 처음부터 표기하면
begin() -> end()
역방향 반복자
역방향 반복자는 컨테이너의 마지막 원소부터 첫 번째 원소까지 역순으로 순회할 수 있도록 해주는 반복자
컨테이너 처음부터 표시하면
rend() -> rbegin()
반복자 사용 예시 코드
#include <vector>
#include <map>
#include <string>
#include <algorithm>
#include <iostream>
using namespace std;
int main() {
// 🔹 벡터에서 순방향 반복자를 사용하는 예시
vector<int> numbers = {1,2,3,4,5,6,7,8,9,10};
for(auto it = numbers.begin(); it != numbers.end(); ++it) {
// ⚡ 순방향 반복자를 사용해 짝수만 출력
// ⚡ iterator는 ++it 사용 권장 (it++는 임시 객체 생성)
if(*it % 2 == 0) {
cout << *it << " ";
}
}
// 🔹 맵에서 순방향 반복자를 사용하는 예시
map<string, int> scores = {{"Alice",90}, {"Bob",85}, {"Charlie",88}};
for(auto it = scores.begin(); it != scores.end(); ++it) {
// ⚡ 맵의 키-값 쌍 출력
cout << it->first << ": " << it->second << endl;
}
// 🔹 문자열에서 순방향 반복자를 사용하는 예시
vector<string> words = {"apple", "banana", "cherry", "date"};
string target = "cherry";
auto it = find(words.begin(), words.end(), target);
if(it != words.end()) {
cout << target << " 해당 문자 벡터 내에 존재" << endl;
} else {
cout << "문자 벡터 내에 존재하지 않음" << endl;
}
// 🔹 벡터에서 역방향 반복자를 사용한 예시
vector<int> numbers_r = {10,15,20,25,30};
for(auto it = numbers_r.rbegin(); it != numbers_r.rend(); ++it) {
// ⚡ 반복자는 순방향, 역방향 모두 동일한 형태
// ⚡ 역방향이면 내부적으로 뒤집힌 형태로 적용
if(*it % 2 == 0) {
cout << *it << " ";
}
}
// 🔹 맵에서 역방향 반복자를 사용한 예시
map<string,int> scores_r = {{"Alice",90}, {"Bob",85}, {"Charlie",88}};
for(auto it = scores_r.rbegin(); it != scores_r.rend(); ++it) {
cout << it->first << ": " << it->second << endl;
}
// 🔹 문자열에서 역방향 반복자를 사용한 예시
vector<string> words_r = {"apple","banana","cherry","date"};
target = "banana";
auto it_r = find(words_r.rbegin(), words_r.rend(), target);
if(it_r != words_r.rend()) {
cout << target << " 문자 벡터 내에서 발견" << endl;
// 🔹 distance(a,b)는 a에서 b로 이동해야 하는 거리
cout << distance(words_r.rbegin(), it_r) << endl; // ⚡ 뒤에서부터 찾은 iterator 위치
cout << distance(words_r.begin(), it_r.base()) - 1 << endl; // ⚡ 앞에서부터 찾은 iterator 위치
// 🔹 it_r은 역방향 반복자
// 🔹 base() 사용 시 순방향 반복자 위치 반환
// 🔹 base()가 가리키는 원소는 riterator가 가리키는 원소 바로 다음
} else {
cout << "문자 벡터 내에서 발견되지 않음" << endl;
}
return 0;
}
STL 컨테이너는 반복자를 통해 거의 동일한 코드로 내부 구현에 의존하지 않고 순회할 수 있다는 것을 배웠습니다.
아래 코드스니펫에 작성된 초기화된 벡터와 맵을 순방향 반복자와 역방향 반복자를 활용해서
출력하는 코드를 작성하세요.
#include <iostream>
#include <vector>
#include <map>
using namespace std;
int main() {
// 벡터와 맵 데이터 정의
vector<int> vec = { 10, 20, 30, 40, 50 };
map<string, int> mp = {
{"Alice", 90},
{"Bob", 85},
{"Charlie", 95}
};
// 문제: 아래 부분을 완성하세요
return 0;
}
실습 코드 #include <iostream>
#include <vector>
#include <map>
using namespace std;
int main() {
// 벡터와 맵 데이터 정의
vector<int> vec = { 10, 20, 30, 40, 50 };
map<string, int> mp = {
{"Alice", 90},
{"Bob", 85},
{"Charlie", 95}
};
// 문제: 아래 부분을 완성하세요
for(auto it= vec.begin(); it!=vec.end(); ++it){ //auto 대신 vector<int>:: 로도 사용 가능
cout<<*it<<" ";
}
for(auto it=vec.rbegin(); it!=vec.rend(); ++it){ //auto 대신 vector<int>:: 로도 사용 가능
cout<<*it<<" ";
}
for(auto it=mp.begin(); it!=mp.end(); ++it){ //auto 대신 map<string,int>:: 로도 사용 가능
cout<<it->first<<" : "<<it->second<<endl;
}
for(auto it=mp.rbegin(); it!=mp.rend(); ++it){ //auto 대신 map<string,int>:: 로도 사용 가능
cout<<it->first<<" : "<<it->second<<endl;
}
return 0;
}
응집도는 클래스 또는 모듈 내부의 구성 요소들이 얼마나 밀접하게 관련되어 있는지를 나타냄
응집도가 낮은 경우
응집도가 낮은 경우란 서로 관련없는 기능들이 하나의 클래스에 포함된 경우
예를 들어,목표가 피자 배달이라고 가정 시
아래의 기능들이 한 클래스에 포함되어 있다면 응집도가 낮다고 할 수 있음
응집도가 낮은 경우(예시)
아래 기능을 제공하는 코드를 구현
1. 특정 문자열을 받고 메세지를 출력
2. 두수의 합 반환
3. 특정 문자열을 받고 역으로 출력
위의 3가지 기능 구현된 상태에서 아래 기능들 추가되는 상황
4. 두 수의 곱을 반환하는 기능을 추가
5. 특정 문자열을 받고 메시지를 출력하기 전 대문자로 변환
응집도가 낮은 코드
Utility 클래스 하나에 모든 걸 구현
응집도 낮은 경우 예시 코드
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
char if_upper(char c){
if(c=='o' || c=='e'){
return static_cast<char>(::toupper(c));
}
else{
return c;
}
}
//한 클래스 내에 모든 기능 구현(응집도가 낮은 경우)
class Utility {
public:
//기능을 추가 전 특정 문자열 받고 메세지를 출력, 두 수의 합, 특정 문자열 역으로 출력 기능 구현된 상태
//두 수의 곱을 반환하는 기능 추가
//특정 문자열을 받고 메세지를 출력하기 전 대문자로 변환 기능 추가
void printMessage(const string& message){
// 메세지 출력 전 대문자 변환기능 추가
string upperMessage = message;
transform(upperMessage.begin(),upperMessage.end(),upperMessage.begin(), ::toupper()); //문자열 관련 변환 적용하는 알고리즘 STL
//transform(변환 입력 시작 반복자, 변환 입력 끝 반복자, 변환 적용 시작 반복자, 변환 적용할 단항함수(대문자화, 소문자화, 람다 구성 조건부 변환 가능))
// transform(upperMessage.begin(),upperMessage.end(),upperMessage.begin(),[](char c){
// if(c == 'O' || c== 'E'){
// return static_cast<char>(::tolower(c));
// static_cast는 컴파일 타임에 타입 변환을 명확하게 지시
// }else{
// return c;
// }
// });
//람다 구성 대신 정의된 함수 조건부 변환도 가능
// transform(upperMessage.begin(),upperMessage.end(),upperMessage.begin(), if_upper);
cout<<"Message: " << upperMessage << endl;
}
void calculateSum(int a, int b){
cout<<"Sum: "<<(a+b)<<endl;
}
void calculateProduct(int a, int b){ //두수의 곱을 반환하는 기능
cout<<"Product: "<<(a*b)<<endl;
}
void reverseString(const string& str){
string reversed = string(str.rbegin(),str.rend());
cout<<"Reversed: "<<reversed << endl;
}
};
int main(){
Utility util;
util.printMessage("Hello");
util.calculateSum(5,10);
util.calculateProduct(5,10);
util.reverseString("World");
return 0;
}
응집도가 높은 경우
응집도가 높은 경우는 서로 관련 있는 모듈들만 하나의 class에 있는 경우
아래의 기능들이 한 클래스에 포함되어 있다면 응집도가 높다고 할 수 있음
응집도가 높은 경우(예시)
아래 기능을 제공하는 코드를 구현
1. 특정 문자열을 받고 메세지를 출력
2. 두수의 합 반환
3. 특정 문자열을 받고 역으로 출력
위의 3가지 기능 구현된 상태에서 아래 기능들 추가되는 상황
4. 두 수의 곱을 반환하는 기능을 추가
5. 특정 문자열을 받고 메시지를 출력하기 전 대문자로 변환
응집도가높은 경우 클래스를 목적에 따라 나누어서 구현
응집도 높은 경우 예시 코드
#include <iostream>
#include <string>
#include <algorithm> //for transform
using namespace std;
//각각의 클래스로 구현된 상태(응집도가 높은 경우)
//기능을 추가 전 특정 문자열 받고 메세지를 출력, 두 수의 합, 특정 문자열 역으로 출력 기능 각각의 클래스로 구현된 상태
class MessageHandler {
public:
void printMessage(const string& message){ //문자열 출력 전 대문자 변환 기능 추가
string upperMessage = message;
transform(upperMessage.begin(),upperMessage.end(),upperMessage.begin(), ::toupper);
cout<<"Messag: " << upperMessage <<endl;
}
};
class Calculator{
public:
void calculateSum(int a, int b){
cout<<"Sum: "<<(a+b)<<endl;
}
void calculateProduct(int a, int b){ //두 수의 곱 반환 기능 추가
cout<<"Prodcut: "<<(a*b)<<endl;
}
};
class StringManipulator{
public:
void reverseString(const string& str){
string reversed = string(str.rbegin(),str.rend());
cout<< "Reversed: " << reversed <<endl;
}
};
int main(){
MessageHandler messageHandler;
messageHandler.printMessage("Hello");
Calculator calculator;
calculator.calculateSum(5,10);
calculator.calculateProduct(5,10);
StringManipulator stringManipulator;
stringManipulator.reverseString("world");
return 0;
}
결합도는 모듈 또는 클래스 간의 의존성을 나타냄
결합도가 낮을수록 좋은 코드
결합도가 높으면 모듈 간 의존성 강해져, 하나의 모듈 변경 시 다른 모듈도 영향 받음
결합도가 높은 경우
예를 들어 자동차와 엔진의 관계 구현한다는 가정 시,
자동자 클래스가 디젤 엔진 클래스를 직접 포함하는 경우
결합도가 높은 경우 예시 코드
#include <iostream>
#include <string>
using namespace std;
//결합도가 높은 경우(신규 기능 추가 시 신규 클래스 개설 필요)
//기존 Engine 클래스
class Engine {
public:
string state;
Engine() : state("off"){} //Engine 생성자
void start(){
state = "on";
cout<<"Engine started"<<endl;
}
};
//새로운 ElectricEngine 클래스(기존 Engine과는 별도)
class ElectricEngine{
public:
string state;
ElectricEngine(): state("off"){};
void start(){
state = "on";
cout<<"Electirc Engine running silently"<<endl;
}
};
class Car{
public:
Engine engine; //Car 클래스는 여전히 Engine 클래스에 맞게 강하게 의존
//새로운 엔진 클래스 추가
ElectricEngine electricengine;
bool useElectric =false; //엔진 종류 늘어남에 따른 판단 연산 추가
void startCar(){
if(useElectic){
if(electricengine.state =="off"){
electricengine.start();
cout<<"Car Started (Electric)" << endl;
}
}
else{
if(engine.state=="off"){
engine.start();
cout<<"Cart started"<<endl;
}
}
}
};
결합도가 낮은 경우
반대로 자동차 클래스가 특정 엔진을 직접 포함하지 않고 인터페이스를 활용하는 방법
결합도가 낮은 경우 예시 코드
#include <iostream>
#include <memory>
#include <string>
using namespace std;
//공통 인터페이스 정의
class Engine{
public:
virtual void start() = 0; //가상 함수 정의,파생 클래스에서 반드시 재정의
virtual ~Engine() = default; //default는 기본 생성자 그대로 사용 의미
};
//DieselEngine 구현
class DieselEngine : public Engine {
public:
void start(){
cout<<"Diesel Engine started" << endl;
}
};
//새로운 ElectricEngine 구현
class ElectricEngine : public Engine {
public:
void start(){
cout<< "Electric Engine started silently" <<endl;
}
};
class Car{
private:
unique_ptr<Engine> engine; // 인터페이스에 의존하여 결합도 감소, 다른 엔진 모두 같은 방식으로 주입 가능
public:
//unique_ptr은 복사 불가로 move로 소유권 이전 필요
Car(unique_ptr<Engine> eng) : engine(move(eng)) {}
void startCar(){
engine->start();
cout<<"Car Started" << endl;
}
};
int main() {
auto engine_diesel = make_unique<DieselEngine>(); //unique_ptr를 만들면서 객체를 힙에 생성
Car myCar(move(engine_diesel));
myCar.startCar();
auto engine_electric = make_unique<ElectricEngine>(); //unique_ptr를 만들면서 객체를 힙에 생성
Car myCar2(move(engine_electric));
myCar2.startCar();
return 0;
}