5 분 소요

1. Algorithm

1.1. 멤버 함수가 아닌 일반 함수로 되어 있음

  • STL 알고리즘은 특정한 컨테이너가 아닌 다양한 컨테이너에 대해서 사용할 수 있음
#include <algorithm>
// 특정한 함수는 다른 헤더에 들어있음
#include <numeric>
#include <memory>

1.2. 알고리즘은 컨테이너를 알지 못함

  • remove 알고리즘은 컨테이너의 크기를 줄이지 않음
  • 인자로 전달된 반복자가 어떤 컨테이너인지 알수 없음
  • 컨테이너의 크기를 줄이려면 컨테이너의 erase 멤버함수를 사용해야 됨

image

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

int main()
{
    vector<int> v = {1,2,3,1,2,3,1,2,3,1};
    auto p = remove(begin(v), end(v), 3);
    for (auto& i : v)
        cout << i << " "; // 1,2,1,2,1,2,1 2,3,1
    cout << endl;

    v.erase(p, end(v));
    
    for (auto& i : v)
        cout << i << " "; // 1,2,1,2,1,2,1    
}

1.3. 알고리즘보다 멤버 함수가 좋은 경우가 있음

  • list에서 remove를 수행할 때는 요소를 당기는 것보다는 요소 자체를 제거하는 것이 효율적
  • list에서는 remove 알고리즘보다는 remove 멤버 함수를 사용하는 것이 좋음
#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;

int main()
{
    // vector<int> v = {1,2,3,1,2,3,1,2,3,1};
    list<int> v = {1,2,3,1,2,3,1,2,3,1};
    // 리스트의 경우 알고리즘 remove를 사용해서 앞으로 당기는 것보다 자체를 제거하는 것이 좋음
    v.remove(3);
    for (auto& i : v)
        cout << i << " ";
}

2. 함수 객체

  • 최초에는 () 연산자를 재 정의해서 함수처럼 사용 가능한 객체
  • 요즘에는 () 연산자를 사용해서 함수처럼 호출 가능한 모든 객체를 뜻함
#include <iostream>
using namespace std;

// () 를 사용해서 호출하는 것들
// 1. 함수
// 2. 함수 포인터
// 3. ()를 재정의한 클래스
// 4. 람다표현식

struct Plus
{
    int operator()(int a, int b) const
    {
        return a+b;
    }
};

int main()
{
    Plus p;
    int n = p(1,2); // p.operator()(1,2)
    cout << n << endl;

    // a + b  -> a.operator+(b)
    // a - b  -> a.operator-(b)
    // a()    -> a.operator()()
    // a(1,2) -> a.operator()(1,2)
}
  • 장점
    • 알고리즘에 전달 시 일반 함수는 인라인 치환이 안되지만 함수 객체는 인라인 치환 가능
    • 상태를 가질 수 있음
  • STL이 제공하는 함수 객체
#include <functional>
// 산술연산
plus<>
minus<>
multiplies<>
divides<>
modulus<>
negate<>
// 비교연산
equal_to<>
not_equal_to<>
greater<>
less<>
greater_equal<>
less_equal<>
// 논리연산
logical_and<>
logical_or<>
logical_not<>
#include <iostream>
#include <functional>
using namespace std;

int main()
{
    plus<int> p;
    cout << p(1,2) << endl;

    multiplies<int> m;
    cout << m(3,4) << endl;
}

3. 알고리즘과 함수

  • STL 알고리즘은 함수를 인자로 가지는 경우가 많음
    • 알고리즘의 활용도를 높여줌
    • for_each, transform
    • 단항 함수 : 인자가 1개인 함수
    • 이항 함수 : 인자가 2개인 함수
  • 일반 함수 뿐 아니라, 함수 객체, 람다 표현식을 사용할 수 있음
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
#include <fstream>
using namespace std;

// for_each는 인자를 1개밖에 못받기 때문에 ostream 등을 추가할 수 없음
void foo(int& a){
    cout << a << " ";
}

// 함수객체를 활용
struct Show
{
    string sep;
    ostream& os;
    Show(ostream& o = cout, string s = "\n") : os(o), sep(s) {}
    void operator()(int a) const {
        os << a << sep;
    }
};

int main()
{
    vector<int> v = {1,2,3,4,5,6,7,8,9,10};

    // 1. 일반 함수 전달
    for_each(begin(v), end(v), foo);
    cout << endl;
    // 2. 함수 객체 전달
    Show s(cout, "\t");
    for_each(begin(v), end(v), s);
    // 3. 람다 표현식
    for_each(begin(v), end(v), [](int a) {cout << a << endl;} );
}

4. std::transform

  • 인자로 전달된 컨테이너의 모든 요소에 연산을 적용 후 다른 컨테이너에 복사하는 알고리즘
  • 단항 함수인 경우
#include <iostream>
#include <list>
#include <algorithm>
using namespace std;

// 단항 함수인 경우
int foo(int a) { return a + 10; }

int main()
{
    list<int> s1 = {1,2,3,4,5};
    list<int> s2 = {0,0,0,0,0};

    // transform( begin(s1), end(s1), begin(s2), foo);
    transform( begin(s1), end(s1), back_inserter(s2), foo);

    for(auto& i : s2)
        cout << i << " "; // 11 12 13 14 15
                          // back_inserter 0 0 0 0 0 11 12 13 14 15
}
  • 이항 함수인 경우
#include <iostream>
#include <list>
#include <algorithm>
#include <functional>
using namespace std;

// 이항 함수인 경우
int foo(int a, int b) { return a + b; }

int main()
{
    list<int> s1 = {1,2,3,4,5};
    list<int> s2 = {1,2,3,4,5};
    list<int> s3 = {0,0,0,0,0};

    // 일반 함수
    // transform( begin(s1), end(s1), begin(s2), begin(s3), foo);
    // 함수 객체
    // transform( begin(s1), end(s1), begin(s2), begin(s3), multiplies<int>());
    // 람다 표현식
    transform( begin(s1), end(s1), begin(s2), begin(s3),
                            [](int a, int b){ return a + b;} );

    for(auto& i : s3)
        cout << i << " "; // 2 4 6 8 10
                          // multiplies 1 4 9 16 25
}

5. 여러가지 알고리즘

  • 조건자 (Predicator)
    • bool 타입을 리턴하는 함수 객체
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

bool foo( int a) { return a % 3 == 0; }

int main()
{
    vector<int> v = {6,9,3,1,2};

    auto p = find( begin(v), end(v), 3);
    // 주어간 구간에서 첫번째 나오는 3의 배수를 찾고 싶을 때
    auto p1 = find_if( begin(v), end(v), foo);
    auto p2 = find_if( begin(v), end(v), 
                            [](int a) { return a % 3 == 0;});
    cout << *p << endl;     // 3
    cout << *p1 << endl;    // 6
    cout << *p2 << endl;    // 6
}
  • 알고리즘의 4가지 형태
    • 기본 버전
    • 조건자 버전
      • _if
    • 복사 버전
      • _copy
    • 조건자 복사 버전
      • _copy_if
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

template<typename T>
void show(T& v) {
    for( auto& i : v)
        cout << i << " ";
    cout << endl;
}

bool foo( int a) { return a % 3 == 0; }

int main()
{
    vector<int> v1 = {1,2,3,4,5,6,7,8,9,10};
    vector<int> v2 = {0,0,0,0,0,0,0,0,0,0};

    // 기존 버전
    // remove( begin(v1), end(v1), 3);

    // 조건자 버전
    // remove_if( begin(v1), end(v1), [](int a){ return a % 3 == 0;});

    // 복사 버전, 리턴 값은 v2의 반복자
    // auto p = remove_copy( begin(v1), end(v1), begin(v2), 3);
    // v1 = 1 2 3 4 5 6 7 8 9 10
    // v2 = 1 2 4 5 6 7 8 9 10 0

    auto p = remove_copy_if( begin(v1), end(v1), begin(v2), 
                                      [](int a){ return a % 3 == 0;});
    // v1 = 1 2 3 4 5 6 7 8 9 10
    // v2 = 1 2 4 5 7 8 10 0 0 0
    show(v1); 
    show(v2); 
}
  • remove_copy
    • copy 후에 remove 하는 것 보다 동시에 하는 것이 빠름
  • sort_copy (x)
    • copy 후에 sort해야되기 때문에 성능 향상이 없어서 제공하지 않음

6. std::bind

  • M항 함수의 인자를 고정한 새로운 함수를 생성
  • C++11 부터 지원
#include <functional>
  • placeholder(_1, _2, … ) 는 std::placeholders namespace에 있음
  • 일반 함수 뿐 아니라 함수 객체, 멤버 함수, 람다 표현식 등에도 사용 가능
#include <iostream>
#include <functional>
#include <algorithm>
using namespace std::placeholders;
using namespace std;

void foo( int a, int b, int c, int d)
{
    printf("%d, %d, %d, %d\n", a,b,c,d);
}

int main()
{
    // 4항 함수
    foo(1,2,3,4);
    std::bind(&foo, 1,2,3,4)();

    // 4항 함수를 2항으로
    std::bind(&foo, 10, _2, _1, 20)(1,2); // 10, 2, 1, 20

    // 4항 함수를 3항으로
    std::bind(&foo, 10, _2, _1, _3)(30,1,2); // 10, 1, 30, 2

    modulus<int> m;
    cout << m(10, 3) << endl;
    bind(m, 10, 3)();   // 10 % 3
    bind(m, _1, 3)(7); //  7 % 3

    // 3의 배수가 아닌 것 찾기
    int x[3] = {2,6,9};
    auto p = find_if(x, x+3, bind(m, _1, 3));
    cout << *p << endl; // 1
}

참고

codenuri 강석민 강사 강의 내용기반으로 정리한 내용입니다.
코드누리


This is personal diary for study documents.
Please comment if I'm wrong or missing something else 😄. 

Top

댓글남기기