第10章: 標準テンプレートライブラリ (STL) ②:アルゴリズムとラムダ式

ようこそ、C++チュートリアルの第10章へ!前の章ではSTLのコンテナについて学び、様々なデータを効率的に格納する方法を見てきました。しかし、データを格納するだけではプログラムは完成しません。そのデータを並べ替えたり、検索したり、特定の処理を施したりといった「操作」が必要です。

この章では、STLのもう一つの強力な柱であるアルゴリズムライブラリを学びます。これらのアルゴリズムは、コンテナ内のデータを操作するための汎用的な関数群です。そして、アルゴリズムをさらに柔軟かつ強力にするための現代的なC++の機能、ラムダ式についても解説します。これらをマスターすれば、驚くほど少ないコードで複雑なデータ操作が実現できるようになります。

イテレータ:コンテナとアルゴリズムを繋ぐ架け橋

アルゴリズムは、特定のコンテナ(std::vectorstd::list など)に直接依存しないように設計されています。では、どうやってコンテナ内の要素にアクセスするのでしょうか?そこで登場するのがイテレータ (Iterator) です。

イテレータは、コンテナ内の要素を指し示す「ポインタのような」オブジェクトです。ポインタのように * で要素の値を参照したり、++ で次の要素に進んだりできます。

ほとんどのコンテナは、以下の2つの重要なイテレータを取得するメンバ関数を持っています。

  • begin(): コンテナの先頭要素を指すイテレータを返す。
  • end(): コンテナの最後の要素の次を指すイテレータを返す。これは有効な要素を指していない「番兵」のような役割を果たします。

アルゴリズムは、この begin()end() から得られるイテレータのペアを使い、操作対象の「範囲」を指定します。範囲は半開区間 [begin, end) で表され、begin が指す要素は範囲に含まれ、end が指す要素は含まれません。

簡単な例を見てみましょう。イテレータを使って vector の全要素を表示するコードです。

iterator_example.cpp

このように、イテレータはコンテナの種類を問わず、統一的な方法で要素にアクセスする仕組みを提供します。これが、アルゴリズムが様々なコンテナに対して汎用的に機能する理由です。

便利なアルゴリズム

C++の標準ライブラリには、<algorithm> ヘッダと <numeric> ヘッダに数多くの便利なアルゴリズムが用意されています。ここでは、特によく使われるものをいくつか紹介します。

`std::sort`: 要素を並べ替える

名前の通り、指定された範囲の要素をソートします。デフォルトでは昇順に並べ替えます。

sort_example.cpp

`std::find`: 要素を検索する

指定された範囲から特定の値を持つ要素を検索します。

  • 見つかった場合: その要素を指すイテレータを返します。
  • 見つからなかった場合: 範囲の終端を示すイテレータ (end()) を返します。

この性質を利用して、要素が存在するかどうかをチェックできます。

find_example.cpp

`std::for_each`: 各要素に処理を適用する

指定された範囲の全ての要素に対して、特定の関数(処理)を適用します。ループを書くよりも意図が明確になる場合があります。

// 3番目の引数に関数を渡す std::for_each(numbers.begin(), numbers.end(), print_function);

ここで「特定の処理」をその場で手軽に記述する方法がラムダ式です。

ラムダ式:その場で書ける無名関数

ラムダ式(Lambda Expression)は、C++11から導入された非常に強力な機能です。一言で言えば、「その場で定義して使える名前のない小さな関数」です。これにより、アルゴリズムに渡すためだけの短い関数をわざわざ定義する必要がなくなり、コードが非常に簡潔になります。

ラムダ式の基本的な構文は以下の通りです。

[キャプチャ](引数リスト) -> 戻り値の型 { 処理本体 }
  • [] キャプチャ句: ラムダ式の外にある変数を取り込んで、式の中で使えるようにします。
    • []: 何もキャプチャしない。
    • [=]: 外の変数を全て値渡し(コピー)でキャプチャする。
    • [&]: 外の変数を全て参照渡しでキャプチャする。
    • [x, &y]: 変数 x は値渡し、変数 y は参照渡しでキャプチャする。
  • () 引数リスト:通常の関数と同じ引数を取ることができます。
  • -> 戻り値の型: 戻り値の型を指定します。多くの場合、コンパイラが推論できるため省略可能です。
  • {} 処理本体: 関数の処理内容を記述します。

std::for_each とラムダ式を組み合わせた例を見てみましょう。

for_each_lambda_example.cpp

このコードは、for_each の3番目の引数に直接処理を書き込んでいます。非常に直感的で読みやすいと思いませんか?

ラムダ式は、特に std::sort のソート順をカスタマイズする際に真価を発揮します。例えば、数値を降順にソートしたい場合、比較ルールをラムダ式で与えることができます。

lambda_sort_example.cpp

この章のまとめ

この章では、STLのアルゴリズムとラムダ式について学びました。

  • イテレータは、コンテナの要素を指し示すオブジェクトであり、アルゴリズムとコンテナの間のインターフェースとして機能します。
  • <algorithm> ヘッダには、std::sort (ソート)、std::find (検索)、std::for_each (繰り返し処理) といった、汎用的で強力なアルゴリズムが多数用意されています。
  • ラムダ式は、その場で定義できる無名関数であり、アルゴリズムに渡す処理を簡潔かつ直感的に記述することができます。
  • キャプチャ機能を使うことで、ラムダ式の外にある変数を取り込んで処理に利用できます。

コンテナ、イテレータ、アルゴリズム、そしてラムダ式。これらを組み合わせることで、C++におけるデータ処理は、他の多くの言語に引けを取らない、あるいはそれ以上に表現力豊かで効率的なものになります。

練習問題1: 文字列の長さでソート

std::vector<std::string> を用意し、格納されている文字列を、文字数が短い順にソートして、結果を出力するプログラムを作成してください。std::sort とラムダ式を使用してください。

ヒント: ラムダ式は2つの文字列を引数に取り、1つ目の文字列の長さが2つ目の文字列の長さより短い場合に true を返すように実装します。

practice10_1.cpp

練習問題2: 条件に合う要素のカウント

std::vector<int> に整数をいくつか格納します。その後、ラムダ式と std::for_each(または他のアルゴリズム)を使って、以下の2つの条件を満たす要素がそれぞれいくつあるかを数えて出力してください。

  1. 正の偶数である要素の数
  2. 負の奇数である要素の数

ヒント: カウント用の変数を2つ用意し、ラムダ式のキャプチャ句で参照キャプチャ ([&]) して、式の中でインクリメントします。

practice10_2.cpp