STLコンポーネントアルゴリズム

STLコンポーネントアルゴリズム

STL は、OOP と従来のプログラミングの両方で使用できる多数のテンプレート クラスと関数を提供します。 STL の約 50 個のアルゴリズムはすべて完全に汎用的であり、特定のデータ型に依存しません。次のサブセクションでは、3 つの基本的な STL コンポーネントについて説明します。

1) イテレータはコンテナ内のオブジェクトにアクセスする方法を提供します。たとえば、反復子のペアを使用して、リストまたはベクトル内のオブジェクトの範囲を指定できます。イテレータはポインタのようなものです。実際、C++ ポインタもイテレータの一種です。ただし、イテレータは、operator*() やその他のポインタのような演算子のメソッドを定義するクラスのオブジェクトになることもできます。

2) コンテナは、テンプレート クラスのメソッドとして提供されるリスト、ベクター、デキューなどのデータ構造です。コンテナ内のデータにアクセスするには、コンテナ クラスによってエクスポートされた反復子を使用します。

3) アルゴリズムは、コンテナ内のデータを操作するために使用されるテンプレート関数です。たとえば、STL はベクトル内のデータをソートするために sort() を使用し、リスト内のオブジェクトを検索するために find() を使用します。関数自体は、操作するデータの構造や型に依存しないため、単純な配列から非常に複雑なコンテナーまで、あらゆるデータ構造で使用できます。

関数と関数オブジェクト

STL では、関数はアルゴリズムと呼ばれ、標準の C ライブラリ関数よりも汎用的です。 STL アルゴリズムは、operator() 関数をオーバーロードすることによってテンプレート クラスまたはテンプレート関数として実装されます。これらのクラスは、コンテナー内のデータに対してさまざまな操作を実行する関数オブジェクトを作成するために使用されます。次のセクションでは、関数と関数オブジェクトの使用方法について説明します。

関数とアサーション

多くの場合、コンテナ内のデータに対してユーザー定義の操作を実行する必要があります。たとえば、コンテナー内のすべてのオブジェクトを反復処理して、独自の関数にコールバックできるようにする STL アルゴリズムが必要な場合があります。例えば

  1. #include <iostream.h>  
  2. #include <stdlib.h> // random()、srandom() が必要 
  3. #include <time.h> // time() が必要 
  4. #include <vector> // ベクトルが必要 
  5. #include <algorithm> // for_each() が必要 
  6.  
  7. #define VSIZE 24 // ベクトルのサイズ 
  8. vector< long > v(VSIZE); // ベクトルオブジェクト 
  9.  
  10. // 関数のプロトタイプ 
  11. voidを初期化します( long &ri);
  12. void表示( const  長い&ri);
  13. bool isMinus(定数  long &ri); // 述語関数 
  14.  
  15. intメイン()
  16. {
  17. srandom( time(NULL) ); // シード乱数ジェネレータ 
  18.  
  19. for_each(v.begin(), v.end(), initialize); //通常の関数を呼び出す 
  20. cout << "符号付き長整数のベクトル" << endl;
  21. for_each(v.begin(), v.end(), show);
  22. cout << 終了;
  23.  
  24. // 述語関数を使用して負の値をカウントする 
  25. //  
  26. 整数カウント = 0;
  27. ベクトル< long >::イテレータ p;
  28. p = find_if(v.begin(), v.end(), isMinus); //アサーション関数を呼び出す 
  29. while (p != v.end()) {
  30. カウント++;
  31. p = find_if(p + 1, v.end(), isMinus);
  32. }
  33. cout << "値の数: " << VSIZE << endl;
  34. cout << "負の値: " << count << endl;
  35.  
  36. 0を返します
  37. }
  38.  
  39. // ri を符号付き整数値に設定する 
  40. void初期化( long &ri)
  41. {
  42. ri = ( ランダム() - (RAND_MAX / 2) );
  43. // ri = ランダム();  
  44. }
  45.  
  46. // riの値を表示 
  47. void表示( const  長い&ri)
  48. {
  49. cout << ri << " " ;
  50. }
  51.  
  52. // ri が 0 より小さい場合は true を返します 
  53. bool isMinus(定数 長い&ri)
  54. {
  55. 戻り値(ri < 0);
  56. }

いわゆるアサーション関数は、bool 値を返す関数です。

関数オブジェクト

STL アルゴリズムにコールバック関数を渡すだけでなく、より複雑な操作を実行するためにクラス オブジェクトを渡す必要がある場合もあります。このようなオブジェクトは関数オブジェクトと呼ばれます。実際、関数オブジェクトはクラスですが、コールバック関数と同じようにコールバックできます。たとえば、関数オブジェクトは、for_each() または find_if() 関数によって呼び出されるたびに統計を保持できます。関数オブジェクトは、operator()() をオーバーロードすることによって実装されます。 TanyClass が operator()() を定義している場合は、次のように使用できます。

  1. TAnyClass オブジェクト; // オブジェクトの構築 
  2. object(); // TAnyClass::operator()() 関数を呼び出す 
  3. for_each(v.begin(), v.end(), オブジェクト);

STL はいくつかの関数オブジェクトを定義します。これらはテンプレートなので、long などの C/C++ ネイティブ データ型を含むあらゆる型に使用できます。一部の関数オブジェクトには、plus() や multiplies() など、その目的を明確に示す名前が付けられています。同様のメソッド greater() と less-equal() は、2 つの値を比較するために使用されます。

知らせ

ANSI C++ の一部のバージョンでは times() 関数オブジェクトが定義されていますが、GNU C++ では multiplies() という名前が付けられています。使用時にはヘッダー ファイル < functional> を含める必要があります。

関数オブジェクトの便利な応用例は、accumulate() アルゴリズムです。この関数はコンテナ内のすべての値の合計を計算します。このような値は単純な型である必要はなく、operator+() をオーバーロードすることでクラス オブジェクトにすることもできることに注意してください。

リスト8. accum.cpp

  1. #include <iostream.h>  
  2. #include <numeric> // 累積() が必要 
  3. #include <vector> // ベクトルが必要 
  4. #include < functional> // multiplies() (または times()) が必要 
  5. #定義 MAX 10  
  6. vector< long > v(MAX); // ベクトルオブジェクト 
  7. intメイン()
  8. {
  9. // 従来のループを使用してベクトルを埋める 
  10. //  
  11. ( int i = 0; i < MAX; i++)の場合
  12. v[i] = i + 1;
  13. // 含まれる値の合計を累積する 
  14. //  
  15. 長い合計 =
  16. 累積(v.begin(), v.end(), 0);
  17. cout << "値の合計 == " << sum << endl;
  18. // 含まれる値の積を累算する 
  19. //  
  20. ロング製品 =
  21. assemble(v.begin(), v.end(), 1, multiplies< long >()); // この行に注意してください 
  22. cout << "値の積 == " << product << endl;
  23. 0を返します
  24. }

コンパイル出力は次のようになります。

  1. $ g++ accum.cpp
  2. $ ./a.out
  3. の合計== 55
  4. の積== 3628800

『関数オブジェクトを使用するaccumulate()の使用法に注意してください。 accelerate() は内部的にコンテナ内の各オブジェクトと 3 番目の引数を multiplies 関数オブジェクトへの引数として渡し、multiplies(1,v) は積を計算します。 VC でのこれらのテンプレートのソース コードは次のとおりです。

  1. // テンプレート関数 蓄積 
  2. テンプレート<クラス_II、クラス_Ty>インライン 
  3. _Ty を累積します (_II _F、_II _L、_Ty _V)
  4. { (; _F != _L; +++F)の場合
  5. _V = _V + *_F;
  6. 戻り値(_V); }
  7. // テンプレート関数 BINOP で蓄積する 
  8. テンプレート<クラス_II、クラス_Ty、クラス_Bop>インライン 
  9. _Ty 累積 (_II _F、_II _L、_Ty _V、_Bop _B)
  10. { (; _F != _L; +++F)の場合
  11. _V = _B(_V、*_F);
  12. 戻り値(_V); }
  13. // テンプレート構造体 binary_function  
  14. テンプレート<クラス_A1、クラス_A2、クラス_R>
  15. 構造体バイナリ関数{
  16. typedef _A1 最初の引数の型;
  17. typedef _A2 2番目の引数型;
  18. typedef _R 結果型;
  19. };
  20. // テンプレート構造体は乗算します 
  21. テンプレート<クラス_Ty>
  22. 構造体乗算: binary_function<_Ty, _Ty, _Ty> {
  23. _Ty演算子()( const _Ty& _X, const _Ty& _Y) const  
  24. {戻り値(_X * _Y); }
  25. };

はじめに: STL がどのように実装されているかを深く理解したい場合、最も良い方法は、簡単なプログラムを書いて、プログラムに含まれるテンプレート ソース コードをコピーし、少し整理してみることです。そうすれば、理解できるようになります。したがって、「STL ソースコード分析」のような本を購入する必要はありません。これらの本は時間の無駄になる可能性があります。 』

(1)ジェネレータ関数オブジェクト

関数オブジェクトの便利なクラスの 1 つは「ジェネレーター」です。このような関数には独自のメモリがあり、以前の呼び出しの値を記憶することができます。たとえば、乱数生成関数。

通常の C プログラマーは、静的変数またはグローバル変数を使用して、前回の呼び出しの結果を「記憶」します。しかし、これを行うことの欠点は、関数をそのデータから分離できないことです。もう 1 つの欠点は、スレッドの安全性を確保するために TLS が必要になることです。明らかに、クラスを使用して「メモリ」の一部をカプセル化する方が安全で信頼性が高くなります。例を見てみましょう:

リスト9. randfunc.cpp

  1. #include <iostream.h>  
  2. #include <stdlib.h> // random()、srandom() が必要 
  3. #include <time.h> // time() が必要 
  4. #include <algorithm> // random_shuffle() が必要 
  5. #include <vector> // ベクトルが必要 
  6. #include < functional> // ptr_fun() が必要 
  7. 使用して 名前空間std;
  8. // ランダム化するデータ 
  9. int iarray[10] = {1、2、3、4、5、6、7、8、9、10};
  10. ベクトル< int > v(iarray, iarray + 10);
  11. // 関数のプロトタイプ 
  12. voidディスプレイ(ベクトル< int >& vr, const   char *s);
  13. 符号なし整数RandInt( const符号なし整数n);
  14. intメイン()
  15. {
  16. srandom( time(NULL) ); // シード乱数ジェネレータ 
  17. Display(v, "シャッフル前:" );
  18. pinter_to_unary_function<unsigned int , unsigned int >
  19. ptr_RandInt = ptr_fun(RandInt); // RandInt() へのポインタ // この行に注意してください 
  20. ランダムシャッフル(v.begin(), v.end(), ptr_RandInt);
  21. Display(v, "シャッフル後:" );
  22. 0を返します
  23. }
  24. // ベクトル vr の内容を表示 
  25. voidディスプレイ(ベクトル< int >& vr, const  文字*s)
  26. {
  27. cout << endl << s << endl;
  28. コピー(vr.begin(), vr.end(), ostream_iterator< int >(cout, " " ));
  29. cout << 終了;
  30. }
  31. // シーケンス内の次のランダムな値を n を法として返します 
  32. 符号なし整数RandInt(符号なし整数n定数)
  33. {
  34. ランダム()%nを返します
  35. }

コンパイルと実行結果は次のとおりです。

  1. $ g++ randfunc.cpp
  2. $ ./a.out
  3. シャッフル前:
  4. 1 2 3 4 5 6 7 8 9 10
  5. シャッフル後:
  6. 6 7 2 8 3 5 10 1 9 4

まず、次のステートメントでオブジェクトを宣言します。

  1. 単項関数へのポインター<unsigned int 、unsigned int >
  2. ptr_RandInt は ptr_fun(RandInt);

ここでは、STL 単項関数テンプレートを使用して変数 ptr_RandInt を定義し、関数 RandInt() へのアドレスを初期化します。単眼関数は 1 つの引数を受け取り、値を返します。これで、random_shuffle() は次のように呼び出すことができます。

  1. ランダムシャッフル(v.begin(), v.end(), ptr_RandInt);

この例では、ジェネレーターは単に rand() 関数を呼び出します。

定数参照に関するちょっとした問題 (翻訳なし、VC の例の const を削除)

#p#

(2)ジェネレータ関数クラスオブジェクト

次の例は、ジェネレータ関数クラス オブジェクトの使用を示しています。

リスト 10. fiborand.cpp

  1. #include <iostream.h>  
  2. #include <algorithm> // random_shuffle() が必要 
  3. #include <vector> // ベクトルが必要 
  4. #include < functional> // unary_function が必要 
  5. 使用して 名前空間std;
  6. // ランダム化するデータ 
  7. int iarray[10] = {1、2、3、4、5、6、7、8、9、10};
  8. ベクトル< int > v(iarray, iarray + 10);
  9. // 関数プロトタイプ 
  10. voidディスプレイ(ベクトル< int >& vr, const   char *s);
  11. // FiboRand テンプレート関数オブジェクトクラス 
  12. テンプレート<クラスArg>
  13. クラスFiboRand :パブリックunary_function<Arg, Arg> {
  14. 整数i, j;
  15. Arg配列[18]
  16. 公共
  17. フィボランド();
  18. 引数演算子()( const Arg& arg);
  19. };
  20. void main()
  21. {
  22. FiboRand< int > fibogen; // ジェネレータオブジェクトを構築 
  23. cout << "フィボナッチ乱数ジェネレータ" << endl;
  24. cout << "random_shuffle と関数オブジェクトを使用する" << endl;
  25. Display(v, "シャッフル前:" );
  26. ランダムシャッフル(v.begin(), v.end(), fibogen);
  27. Display(v, "シャッフル後:" );
  28. }
  29. // ベクトル vr の内容を表示 
  30. voidディスプレイ(ベクトル< int >& vr, const  文字*s)
  31. {
  32. cout << endl << s << endl;
  33. コピー(vr.begin(), vr.end(),
  34. ostream_iterator< int >(cout, " " ));
  35. cout << 終了;
  36. }
  37. // FiboRand クラスのコンストラクタ 
  38. テンプレート<クラスArg>
  39. FiboRand<Arg>::FiboRand()
  40. {
  41. シーケンス[17] = 1;
  42. シーケンス[16] = 2;
  43. ( int n = 15; n > 0; n—)の場合
  44. シーケンス[n] = シーケンス[n + 1] + シーケンス[n + 2];
  45. 私 = 17;
  46. 5;
  47. }
  48. // FiboRand クラス関数演算子 
  49. テンプレート<クラスArg>
  50. 引数 FiboRand<Arg>::operator()( const Arg& arg)
  51. {
  52. 引数k = シーケンス[i] + シーケンス[j];
  53. シーケンス[i] = k;
  54. 私 - ;
  55. j--;
  56. i == 0 の場合i = 17;
  57. j == 0 の場合、j = 17;
  58. k% 引数を返します
  59. }

コンパイルして実行した結果の出力は次のようになります。

  1. $ g++ フィボランド.cpp
  2. $ ./a.out
  3. フィボナッチ乱数ジェネレータ
  4. random_shuffleと関数オブジェクトを使用する
  5. シャッフル前:
  6. 1 2 3 4 5 6 7 8 9 10
  7. シャッフル後:
  8. 6 8 5 4 3 7 10 1 9

このプログラムは rand_shuffle をまったく異なる方法で使用します。フィボナッチ ジェネレーターは、以前の「使用」の結果を記憶するクラスにカプセル化されます。この例では、クラス FiboRand は配列と 2 つのインデックス変数 I と j を管理します。

FiboRand クラスは unary_function() テンプレートから継承します。

  1. テンプレート<クラスArg>
  2. クラスFiboRand: public unary_function<Arg, Arg> {...

Arg はユーザー定義のデータ型です。このクラスは、コンストラクターと operator() 関数の 2 つのメンバー関数も定義します。これにより、random_shuffle() アルゴリズムは FiboRand オブジェクトを関数のように「呼び出す」ことができます。

#p#

(3)バインダー関数オブジェクト

バインダーは、別の関数オブジェクト f() とパラメーター値 V を使用して関数オブジェクトを作成します。バインドされた関数オブジェクトはバイナリ関数である必要があります。つまり、2 つのパラメーター A と B を持ちます。 STL のバインダーは次のとおりです。

  • bind1st() は、値 V を最初の引数 A として受け取る関数オブジェクトを作成します。
  • bind2nd() は、値 V を 2 番目の引数 B として受け取る関数オブジェクトを作成します。

以下にいくつか例を挙げます。

リスト 11. バインダー.cpp

  1. #include <iostream.h>  
  2. #include <アルゴリズム>  
  3. #include <機能>  
  4. #include <リスト>  
  5. 使用して 名前空間std;
  6. //データ 
  7. int iarray[10] = {1、2、3、4、5、6、7、8、9、10};
  8. リスト< int > aList(iarray, iarray + 10);
  9. intメイン()
  10. {
  11. 整数k = 0;
  12. count_if(aList.begin(), aList.end(),
  13. bind1st(より大きい< int >(), 8), k);
  14. cout << "要素数 < 8 == " << k << endl;
  15. 0を返します
  16. }

アルゴリズム count_if() は、特定の条件を満たす要素の数をカウントします。 これは、関数オブジェクトとパラメータをオブジェクトにバンドルし、そのオブジェクトをアルゴリズムの 3 番目の引数として渡すことによって行われます。 この表現に注目してください:

  1. bind1st(より大きい< int >(), 8)

この式は、greater<int>() とパラメータ値 8 を関数オブジェクトにバンドルします。 bind1st() が使用されているため、この関数は次の式を計算することと同等になります。

8 > ク

式内の q はコンテナ内のオブジェクトです。したがって、完全な表現は

  1. count_if(aList.begin(), aList.end(),
  2. bind1st(より大きい< int >(), 8), k);

サイズが 8 以下のすべてのオブジェクトの数を数えます。

(4)関数オブジェクトの否定

否定関数オブジェクトは、別の関数オブジェクトから作成されるオブジェクトです。元の関数が true を返す場合、否定関数オブジェクトは false を返します。否定関数オブジェクトには、not1() と not2() の 2 つがあります。 not1() は単眼関数オブジェクトを受け入れ、not2() は両眼関数オブジェクトを受け入れます。負の関数オブジェクトはバインダーでよく使用されます。たとえば、前のセクションでは、bind1nd を使用して q <= 8 の値を検索しました。

  1. count_if(aList.begin(), aList.end(),
  2. bind1st(より大きい< int >(), 8), k);

q>8 のオブジェクトを検索する場合は、bind2st を使用します。次のように書くことができます:

  1. 開始 = find_if(aList.begin(), aList.end(),
  2. not1(bind1nd(より大きい< int >(), 6)));

bind1nd は単項関数を返すため、not1 を使用する必要があります。

概要: 標準テンプレートライブラリ (STL) の使用

多くのプログラマーは依然として標準の C 関数を使用していますが、それはメルセデスを探すのにロバに乗るようなものです。最終的にはそこにたどり着くでしょうが、多くの時間を無駄にしてしまうことになります。

ただし、標準の C 関数 (フォーマットされた出力に sprintf() を使用するなど) を使用すると便利な場合もあります。ただし、C 関数はエラーを報告するために例外メカニズムを使用しないため、新しいデータ型の処理には適していません。さらに、標準の C 関数ではメモリ割り当て技術が使用されることが多く、経験の浅いプログラマーは簡単にバグを記述する可能性があります。 。

C++ 標準ライブラリは、データ セットを処理するためのより安全で柔軟な方法を提供します。 STL はもともと、HP Labs の Alexander Stepanov と Meng Lee によって開発されました。最近、C++ 標準委員会は STL を採用しましたが、実装ごとに詳細はまだ異なります。

STL の 2 つの主な特徴は、データ構造とアルゴリズムの分離と非オブジェクト指向性です。オブジェクトへのアクセスは、ポインタのような反復子を通じて実現されます。コンテナーは、リンク リストやベクターのようなデータ構造であり、テンプレート形式で提供されます。アルゴリズムは、コンテナー内のデータを操作するために使用される関数テンプレートです。 STL はテンプレートに基づいているため、あらゆるデータ型と構造に使用できます。

【編集者のおすすめ】

  1. C++ でのメモリリークの検出
  2. C/C++ の基本的な前処理命令の概要
  3. C++ のクラス オブジェクト メモリ モデルの詳細な紹介
  4. C++ 基礎 const の使い方の詳細な紹介
  5. C++初心者のためのconstの使い方の詳細な説明

<<:  C++開発におけるデータ構造とアルゴリズムの分離についての簡単な説明

>>:  MySQLインデックスの背後にあるデータ構造とアルゴリズムの原理

ブログ    
ブログ    
ブログ    

推薦する

テスラですら理解できない、車両と道路の連携が自動運転の究極のソリューションなのか?

[[434381]]最初は1兆円、次に1.2兆円と、テスラの時価総額は新たな高値を更新し続けました...

デジタル変革の本質、道筋、段階、課題を1つの記事で解説

01エンタープライズデジタルトランスフォーメーションの本質デジタル化により、人間が暮らす現実世界と仮...

AI の成功のための 10 の重要な役割

あらゆる業界でますます多くの企業がビジネス プロセスを変革するために人工知能 (AI) を導入してい...

AI が加速的な進化を促進 Qualcomm AI & IoT 開発技術オープンデーが間もなく開催

携帯電話からウェアラブルデバイス、翻訳製品まで、人工知能は人々の日常生活に広く浸透しています。 5G...

試験形式がAIベースになったとき、「AI+教育」の関係をどうバランスさせるのか?

[[237498]]画像出典: Visual China私のクラスメイトの劉一木は留学の準備をして...

ディープラーニングを用いた医療画像解析: ファイル形式

[[198733]]今年 3 月に開催された NVIDIA の GTC 2017 カンファレンスでは...

AmapとDAMO Academyが共同で車載ARナビゲーションを導入し、従来の運転体験を覆す

Amapは本日、車載ARナビゲーションを共同で立ち上げるためにDAMOアカデミーと協力関係を結んだと...

IBMの新しいデータ分析アルゴリズムは、20分で9TBのデータを分析できる

IBMは最近、スイスのチューリッヒ研究所がデータ分析アルゴリズムにおいて画期的な進歩を遂げ、膨大なデ...

ビジネスアナリストにとってAIが意味するもの

[[275322]]今日では、人工知能はもはや流行語ではなく、多くの環境ビジネスアナリストやその他の...

...

ディープラーニングの本質を探りますか?

[[184749]] 1. 人工知能の波が再び高まっている画期的な出来事:AlphaGoがイ・セド...

規制がなければ、AIは金融危機を引き起こす可能性がある

人工知能の影響はビジネス界のほぼすべての側面に広がっており、金融業界も例外ではありません。金融業界の...

...