[c++] Modern C++ 정리

Madeline👩🏻‍💻·2024년 5월 9일
0

개발 지식

목록 보기
11/12
post-thumbnail

참고 자료: 인프런 <객체지향 개념에 바탕을 둔 Modern C++프로그래밍>

Modern C++

STL

  1. 컨테이너: std::pair, vector, deque, list, map, stack, queue
  2. 반복자 iterator
  3. 알고리즘 - std::sort, find, transform, for each, generate, binary search

std::vector

동적 배열을 캡슐화한 순차 컨테이너

  • 메모리에서 요소들은 연속해서 저장
  • 저장공간이 자동으로 확장
  • 추가적인 요소들을 저장하기 위해 크기보다 더 큰 공간 차지
#include <iostream>
#include <vector>
#include <algorithm>
#include <random>

int main() {
	vector<int> vec {0,1,2,3};
    vec.push_back(4); // 끝에 요소 추가
    vec.pop_back(); // 끝 요소 삭제
}

vec.size() <- 원소들 개수
vec.capacity() <- 추가 공간까지 해서 공간 크기

  • 배열을 이용한 초기화
int intA[3] = {10, 20, 30};
vector<int> vint2(intA, intA + 3);

for(unsigned int i = 0; i < vint2.size(); i++) {
    cout << vint2[i] << " "; // 10 20 30
    cout << vint2.at(i) << " "; // 위랑 똑같
}
  • vector<'T'> 크기 조정
vint2.resize(5); // size: 5, capacity: 6
vint2.shrink_to_fit(); // size: 5, capacity: 6

std::iterator

  • 포인터를 일반화한 것(걍 포인터임)
  • 컨테이너 안의 한 위치를 나타냄
  • 컨테이너 안의 요소들을 순회 혹은 반복하는데 사용
  • 컨테이너와 알고리즘을 연결하는 중요한 역할
  • 반복자 범주: category

-> begin(), end() 이런것도 iterator여서 값 아니고 포인터임 주소!!!참조!!!!!

vector<int> vInt(5);
    for( vector<int>::iterator it = vInt.begin(); it != vInt.end(); ++it) {
        *it = 20;// 원소의 값을 지정함
        cout << *it << " "; // 20
    }
    cout << endl;
    // C++11 ~
    for(int &v: vInt) {
        v = 20;
        cout << v << " ";
    }
    //const_iterator도 있음 그냥 상수

알고리즘

  • 검색, 정렬, 세기, 변환, …
vector<int> vInt3{1,2,3,4,5}; // list 초기화
    random_device rd;
    shuffle(vInt3.begin(), vInt3.end(), default_random_engine(rd()));
    
    for(const int& v: vInt3) {
        cout << v << " ";
    }
    cout << endl;
    
    sort(vInt3.begin(), vInt3.end());
    for(const int& v: vInt3) {
        cout << v << " ";
    }

inline 함수 vs macro 함수

inline 함수

  • (기존처럼) 함수를 주소로 호출하는 게 아니라, 코드가 들어감
  • multiple definitions are permitted
  • 컴파일러 최적화 but 코드가 길어지고 컴파일 시간이 길어짐
inline int max(const int i, const int j) {
    return (i > j) ? i : j;
}
cout << max(10, 20) << endl; // 20

max 함수 주소 호출이 아니라, 코드가 저 자리에 들어가서 실행되는 구조~

#define unsafeABS(i) ( (i) >= 0 ? (i) : (-i) ) // 매크로
inline int safeABS(int i) { return i >= 0 ? i : -i; } // 인라인

매크로랑 인라인의 동작 구조를 비교해보자

// inline vs 매크로:
// 1. inline
int x = -10;
cout << safeABS(x++) << endl; // 10
// 2. macro
int y = -10;
cout << unsafeABS(y++) <<  endl; // 9

똑같은 코드의 절댓값 구하는 매크로, 인라인 함수의 반환값이 각각 10, 9로 다르다.
왜냐면 매크로는 컴파일 전에 전처리기에서 처리된다. y++ 값이 들어가서, 10이 아니라 9가 반환된다.

static

static 변수 활용 예시 (1)

  • static: 값이 함수 호출 간에 유지됨!!!! 프로그램 끝날때까지 반복문 돌때마다 카운트된다.
int incrementCount() {
    **static** int counter = 0;
    counter++;
    return counter;
}
int sumUpTo(const int upTo) {
    int sum = 0;
    for(int i=1; i<= upTo; i++) {
        sum += i;
    }
    return sum;
}

위 함수에서 counter는 static, sum은 그냥 내부 지역변수로 선언해줬따.

while(true) {
    int number;
    cin >> number;
    if(number <= 0) {
        break;
    }
    const int counter = incrementCount();
    cout << "counter: " << counter << endl;
    const int sum = sumUpTo(number);
    cout << "sum of 1 to " << number << ": " << sum << endl;
}

돌려보면, 저 지역변수는 계속 초기화되지만, static으로 선언한 카운트는 값이 유지된다.

static 변수 활용 예시 (2)

enum Grade {
    FRESH = 1,
    SOPHOMORE,
    JUNIOR,
    SENIOR
};
string getGradeLabel_1(const Grade grade) { // 방법1. 낮은 성능 유발
    const string gradeLabels[] = { "Fresh", "Sophomore", "Junior", "Senior" };
    return gradeLabels[grade-1];
} // string을 반환할 때 복사한 값을 반환하는거다
// 참조를 반환
string& getGradeLabel_2(const Grade grade) { // 방법2. 안전하지 않음
    string gradeLabels[] = { "Fresh", "Sophomore", "Junior", "Senior" }; // 지역변수
    return gradeLabels[grade-1];
}// -> 지역변수의 참조를 반환하는건 안전하지 않아. 없어진 메모리를 참조할 수 있어서.
string& getGradeLabel_3(const Grade grade) { // 방법3. 가장 바람직
    static string gradeLabels[] = { "Fresh", "Sophomore", "Junior", "Senior" };
    return gradeLabels[grade-1];
} // static으로 선언한 지역변수는 메모리에서 안없어지니까 참조를 반환할 수 있어
cout << getGradeLabel_1(FRESH) << endl;
cout << getGradeLabel_2(FRESH) << endl; // 얜 출력안되던데
cout << getGradeLabel_3(FRESH) << endl;

재귀 함수

  • 재귀적 함수 -> stack overflow(스택 공간의 부족)을 유발
int binarySearch(const vector<int>& numbers, int min, int max, int target) {
    int mid = (min + max) / 2;
    if(min >= max) {
        return -1; // 없다
    }
    if(numbers[mid] == target) {
        return mid;
    } else if(target < numbers[mid]) {
        return binarySearch(numbers, min, mid-1, target);
    } else {
        return binarySearch(numbers, mid+1, max, target);
    }
}

당연히 종료조건 잘정하는게 중요하지

int data[] = {1,3,5,8,20,30};
    vector<int> numbers(data, data+sizeof(data)/sizeof(int));
    cout << binarySearch(numbers, 0, numbers.size() - 1, 50) << endl; // 이분탐색 -> 정렬해야 가능

assert: 방어 코드

long factorial(const int number) {
    assert(number >= 0); // 재귀함수의 무한반복을 막아
    if(number == 1) {
        return 1;
    }
    return number * factorial(number - 1);
}
int number1 = -2;
const long result = factorial(number1);
cout << "Factorial of " << number1 << " : " << result << endl;

assert로 미리 막는거지 재귀 못하게

cf. binary search -> 반복문 사용

int binarySearch(const vector<int>& numbers, int min, int max, const int target) {
    while(max>min) {
        const int mid = (min + max) / 2;
        if(numbers[mid] == target) {
            return mid;
        }
        if (min >= max) {
            return - 1;
        }
        if(target < numbers[mid]) {
            max = mid - 1;
        } else {
            min = mid + 1;
        }
    }
    return -1;
}

이분탐색 -> 정렬해야 가능

함수 포인터

  • 함수를 파라미터로서 사용함

int (fp)(int, int)
반환타입 (
변수이름)(매개변수)

int (*functionPointer)() = A; // A함수의 포인터
functionPointer = B; // 그 포인터가 B를 가리키게
  • 함수 포인터 예시(1)
int functionPointer(int n) {
    cout << n << endl;
    return n*n;
}
int (*pFunc)(int) = functionPointer;
// std::function<int(int)> pFunc = f;
int v = pFunc(7);
cout << "v = " << v << endl;
  • 함수 포인터 예시(2)
void Sort(int* arr, int size, bool(*compare)(int, int)) {
    for(int i=0; i<size-1; i++) {
        for(int j=i+1; j<size; j++) {
            if(compare(arr[i], arr[j])) {
                swap(arr[i], arr[j]);
            }
        }
    }
}
bool asc(int a, int b) { return a > b; }
bool desc(int a, int b) { return a < b; }
int arr[3] { 2, 1, 3};
Sort(arr, 3, asc);
    
Sort(arr, 3, desc);

functor

  • 함수 호출 연산자, 함수 객체, 함수자
// 1) 함수 객체
Multiply multiply;
cout << "함수 객체 " << multiply(10, 2) << endl;
    
// 2) 람다
auto lambdaMultiply = [](int x, int y) {
    return x * y;
};
cout << "람다식 " << lambdaMultiply(10, 2) << endl;

람다 함수

람다 함수, 익명 함수

형식

[] (매개변수) {
  함수 동작
}(호출 할 때 인자)
  • '[ ]': 캡처 부분 -> 외부 변수 활용
  • 변경 X, 사용 -> '[변수]' 값 복사
  • 변경, 사용 -> '[&변수]' 참조
[](int a, int b) {
    cout << "lambda: " << a+b << endl;
}(30, 40);
int result1 = 1, result2 = 2, result3 = 3, result4 = 4;
  
[result1, result2](int a, int b) {
    cout << result1 + a + b << endl;
}(10, 20); // <- a = 10, b = 20
  
[&result3, &result4](int a, int b) {
    result3 = 22;
    result4 = a + b;
    cout << result3 + result4 << endl;
}(10, 20);  
  
  • '[=]': 외부 변수 전부 복사해서 람다 함수 내에서도 사용
  • '[&]': 외부 변수 전부 참조해서 람다 함수 내에서도 사용, 값 변경

auto를 이용해서 람다함수를 -> 변수로
1.

auto func1 = [](int a, int b) {
    return a * b;
};
cout << "func1 " << func1(2, 10) << endl;
int num = 20;
auto func2 = [&num](int a) {
    num += a;
};
func2(100);
cout << "num: " << num << endl;

람다 함수 예외처리

cout << "람다 함수 예외 처리" << endl;
auto divide = [](double a, double b) {
	if (b==0.0) {
		throw invalid_argument("Divisor cannot be zero");
	}
	return a / b;
};
    
try {
	double result = divide(5.0, 0.0);
} catch (const invalid_argument& e) {
	cerr << e.what() << endl;
}

std::function

  • std::function -> 호출할 수 있는 객체의 형태(함수 포인터, 람다 함수, functor같은 함수 객체 담을 수 있음) == Callable
  • 일반 함수 포인터와 다르게, 람다 함수나 bind 또한 멤버 함수에 대한 포인터도 사용 가능
  • 함수 포인터를 체계화시키는 기능
  • 함수 포인터를 변수처럼 주고 받고 할 수 있음
function<void(int, int)> func = Test;
    
Apple a;
function<void(Apple&, int, int)> func1 = &Apple::Shout; // -> Shout을 함수 포인터로 생성
    
func1(a,1,2);
a.Shout(1, 2);

=> Shout 함수 포인터 -> func1 함수 포인터에 대입해서
=> 이제 func1의 이름으로도 Apple::Shout 쓸 수 잇음

std::bind

  • bind: 함수 포인터에서 매개변수를 고정

  • 반환값은 function!!!!!!!!!!!!!!

  • 특정 인수에 대해서만 함수를 실행시키고 싶을 때, 특정 인수와 특정 함수를 묶어줌
    => 파라미터의 자료형으로부터 자유로워진다!

function<int(int)> func2 = bind(Test, placeholders::_1, 2);
func2(3); // 5
  • placeholder: 인자 인덱스(29까지 가능) 첫번째 인자가 2
profile
🍎 Apple Developer Academy@POSTECH 2기, 🍀 SeSAC iOS 4기

0개의 댓글