(전체가 아니라 C#과 차이가 있는 부분을 중심으로 요약 정리)

표준 라이브러리는 거의 모든 컨테이너에 적용할 수 있는 제네릭 알고리즘을 다양하게 제공한다. 이러한 알고리즘을 활용하면 컨테이너에 담긴 원소를 검색하고, 정렬하고, 가공하고, 다양한 연산을 수행할 수 있다.

표준 라이브러리 알고리즘의 가장 큰 장점은 각 원소의 타입이나 컨테이너의 타입과는 독립적이라는 점이다. 게다가 모든 작업을 반복자 인터페이스만으로 처리한다.

알고리즘 개요

표준 라이브러리 알고리즘은 모두 함수 템플릿으로 구현됐으며, 여기 나온 템플릿 타입 매개변수는 반복자 타입인 경우가 많다. 반복자 자체는 이 함수에 전달된 인수로 결정한다. 함수 템플릿은 대부분 함수에 전달된 인수를 보고 템플릿 타입을 추론하기 때문에 알고리즘을 템플릿이 아닌 일반 함수처럼 호출해서 쓸 수 있다.

반복자 인수는 주로 반복자 범위 (시작과 끝의 쌍)로 표현한다. 반복자 범위를 표현할 때는 첫 번째 원소는 포함하고 마지막 원소는 제외한 반개방(half-open) 범위로 받는 컨테이너가 많다. 끝 반복자(end iterator)는 실제로 ‘마지막 바로 다음’ 항목을 가리킨다.

알고리즘에 전달할 반복자는 일정한 요건을 갖춰야 한다. 예컨대 copy_backward()는 양방향 반복자를 지정해야 하며, stable_sort()는 랜덤 액세스 반복자를 지정해야 한다. 다시 말해 이런 알고리즘은 요건을 갖추지 못한 반복자를 가진 컨테이너에 대해서는 작동하지 않는다..

(이하 설명 생략)

find()와 find_if() 알고리즘

find()는 주어진 반복자 범위에서 특정한 원소를 검색한다. 이 알고리즘은 모든 종류의 컨테이너 원소를 찾을 때 활용할 수 있다. find()는 원소를 찾으면 그 원소를 참조하는 반복자를 리턴하고, 원소를 찾지 못하면 주어진 범위의 끝을 가리키는 반복자(끝 반복자)를 리턴한다. 참고로 find()를 호출할 때는 지정한 범위에 컨테이너의 모든 원소를 담지 않아도 된다. 다시 말해 부분 집합만 지정해도 된다.

Caution) find()에서 원소를 찾지 못하면 내부 컨테이너의 끝 반복자가 아니라 함수를 호출할 때 지정한 끝 반복자를 리턴한다.

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

int main()
{
  int num;
  vector<int> myVector;

  while(true)
  {
    cout << "Enter a number to add (0 to stop): ";
    cin >> num;

    if (num == 0)
    {
      break;
    }
    myVector.push_back(num);
  }

  while(true)
  {
    cout << "Enter a number to lookup (0 to stop): ";
    cin >> num;

    if (num == 0)
    {
      break;
    }

    auto endIt = cend(myVector);
    auto it = find(cbegin(myVector), endIt, num);

    if (it == endIt)
    {
      cout << "Could not find " << num << endl;
    }
    else
    {
      cout << "Found " << *it << endl;
    }
  }

  return 0;
}

find()를 호출할 떄 cbegin(myVector)와 endIt을 인수로 지정했다. 여기서는 vector에 있는 원소를 모두 검색하도록 endIt을 cend(myVector)로 정의한다. 일부분만 검색하려면 이 두 반복자를 적절히 변경한다.

C++ 17부터 추가된 if 문의 이니셜라이저를 적용하면 find()를 호출하고 결과를 검사하는 작업을 다음과 같이 한 문장으로 표현할 수 있다.

if (auto it = find(cbegin(myVector), endIt, num); it == endIt)
{
  cout << "Could not find " << num << endl;
}
else
{
  cout << "Find " << *it << endl;
}

참고로 map과 set처럼 find()를 자체적으로 정의해서 클래스 메서드로 제공하는 컨테이너도 있다.

Caution) 제네릭 알고리즘과 동일한 기능을 제공하는 메서드가 컨테이너에 있다면 컨테이너의 메서드를 사용하는 것이 좋다. 훨씬 빠르기 때문이다.

find_if()는 인수로 검색할 원소가 아닌 프레디케이트 함수 콜백(predicate function callback)을 받는다는 점을 제외하면 find()와 같다. 프레디케이트는 true나 false를 리턴한다. find_if() 알고리즘은 지정한 범위 안에 있는 원소에 대해 인수로 지정한 프레디케이트가 true를 리턴할 때가지 계속 호출한다. 프레디케이트가 true를 리턴하면 find_if()는 그 원소를 가리키는 반복자를 리턴한다.

bool perfectScore(int num)
{
  return num >= 100;
}

int main()
{
  int num;
  vector<int> myVector;

  while(true)
  {
    cout << "Enter a number to add (0 to stop): ";
    cin >> num;

    if (num == 0)
    {
      break;
    }
    myVector.push_back(num);
  }

  auto endIt = cend(myVector);
  auto it = find(cbegin(myVector), endIt, perfectScore);

  if (it == endIt)
  {
    cout << "Not perfect scores" << endl;
  }
  else
  {
    cout << "Found a \\"perfect\\" score of " << *it << endl;
  }

  return 0;
}

find_if()를 호출할 때 다음과 같이 람다 표현식을 지정해도 된다. 여기서 람다 표현식의 강력함을 엿볼 수 있다. 이렇게 하면 perfectScore()란 함수를 따로 정의할 필요가 없다.