size - みる会図書館


検索対象: Accelerated C++ : 効率的なプログラミングのための新しい定跡
104件見つかりました。

1. Accelerated C++ : 効率的なプログラミングのための新しい定跡

3.2 平均ではなくメジアンを使う を分けた方がわかりやすいでしよう。 43 メジアンを見つけるため、 vector である homework のサイズを 2 回知る必要となります。 1 度は、サイズが 0 でないことを確かめるとき、もう 1 度は真ん中を見つけるときです。サイズを 2 度求めることを避けるため に、これはローカルな変数に保持することにします。 typedef vector く double> : : size-type vec-sz; vec_sz SIZe = homework. size() ; vector 型には vector く double>: :size_type という型と size という関数が付随しています。これらのメン バは stri Ⅱ g のメンバと同様に使うことができます。 size ー type は vect 。 r のサイズを格納できることが保証 された unsigned 型です。また、 size() は size-type 型の値である vector のサイズを戻す関数です。 vector のサイズが 2 度必要になるので、それをローカルな変数に格納します。 vector のサイズは環境に よって異なるかもしれないので、その違いを避けるために別の型を使わず、ライプラリがコンテナのサイズ用に 定義している size-type を使うのがよいのです。この理由で、上では size ー type 型を使っています。 しかし、この名前は長くて読み書きが面倒です。それでプログラムを短くするために、 t edef という機能 を使います。 typedef は変数の定義に使うのではなく、型名の別名を定義するために使います。つまり、上の コードでは、 vector く double>: :size_type という型の別名として vec_sz を定義しています。この typedef を使って定義した名前のスコープは他の名前と同じです。そのため、現在のスコープが終わるまで vec_sz を size-type の別名として使ってよいのです。 さて、 homework. size() の戻り値を格納する変数の型を決めたので、これをローカル変数に格納するのは簡 単です。その変数名を size とします。 size というのは vect 。 r のメンバ関数と同じ名前ですが、混乱するこ とはありません。なぜなら vector オプジェクトのサイズを求めるために、そのメンバ関数を使うときは、その オプジェクトの右側にドット「 . 」を置いてその右に size と書くからです。言葉を変えると、 vector のメンバ 関数の size と今定義した s ize では、スコープが違うのです。これらの名前はスコープが違うので、コンパイ ラは ( そしてプログラマも ) どちらの size だろうかと混乱することはないのです。 次に、データがない場合にメジアンを見つけることは意味が無いので、データがあるかどうかを調べます。 if (size cout くく endl くく " あなたの宿題の点数が必要です。 " やりなおしてください。 " くく endl; return 1 ; データがないかどうかは size が 0 かどうかを調べればわかります。もしデータがない場合、とても微妙です が、警告を表示しプログラムを止めることにします。 こで失敗を示すために 1 を戻すことにしました。第 0 章 で説明しましたが、システムは main が 0 を戻したときに、プログラムが成功して終了したと判断します。 0 以 外の戻り値がどう判断されるかは環境によって違いますが、大抵の場合、 0 以外は失敗と判断するのです。 データがある場合は、メジアンの計算に進みます。まず、最初に行うことはデータのソートです。これはライ プラリの関数を呼び出すことでできます。 sort(homework. begin() , homework. end() ) ;

2. Accelerated C++ : 効率的なプログラミングのための新しい定跡

Vec クラスの実装 11.2 193 size—type size() const { return limit ー data; } T& operator ロ (size-type i) { return dataCi] ; } const T& operator ロ (size-type i) const { return data [i] private : iterator data; iterator 1imit ; size 関数は要素を保持する配列の最後を指すポインタから最初のポインタを引くことで、配列のサイズを計算 する関数です。川 .1.4 で見たように、 2 つのポインタを引き算すると、その答えは ptrdiff_t 型で、 2 つの ポインタ間にある要素数になるのです。 s ize を使っても vec は変えないので、これは const メンバ関数にしま す。こうすることで、 const vec オプジェクトに対しても size を使えるわけです。 インデックス演算子は内部の配列の対応する要素を見つけ、その参照を戻すわけです。参照を戻しているの で、ユーザはインデックス演算子を使って vec の要素を変更できます。要素を書き換えられるようにするため に、実際には、 const の vec オプジェクトと const でない vec オプジェクトの 2 つの場合について考える必要 が出てきます。 const の場合は、 const の参照を戻していることに注意してください。 こうすることで、ユーザ はインデックスを、書き込みなしの読み取り専用に使えるようになったのです。 こで、その場合でも、値では なく参照を戻していることに注意してください。これにより標準の vector と同じになっているのです。このよ うにした理由は、コンテナ内のオプジェクトが大きな場合、コピーするより参照を渡すほうが効率がよいから 2 つの演算子は、同じ名前で同じ型 size ー type の値を 1 つだけ引数に取るので、驚くかもしれません。しか し、演算子も含めてすべてのメンバ関数は、非明示的にオプジェクトそのものを引数に取るのです。そのため、 操作の対象になるオプジェクトが const かどうかで区別され、そのようなオーバーロードが許されるのです。 11.2.5 反復子を戻す関数 次に反復子を戻す関数を考える必要があります。先頭の位置を示す反復子を戻す begi Ⅱと最後の 1 つ後を指 す反復子を戻す end の実装です。これらは、次のようにできます。 template く class T> class Vec { public : typedef T* iterator; typedef const T* const—iterator ; typedef size—t size—type ; typedef T value—type ; Vec() { create(); } explicit Vec(size—type Ⅱ , const T& val = T()) { create(), va1) T& operator ロ (size-type i) { return dataCi] ; } const T& operator ロ (size_type i) const { return data[i] ; } size—type size() const { return limit ー data; } / / 反復子を戻す新しい関数 iterator begin() { return data; } const—iterator begin() const { return data ・ iterator end() { return limit ; } const—iterator end() const { return limit ・ private : / / 追加しカ / / 追加しカ / / 追加しカ / / 追加しカ

3. Accelerated C++ : 効率的なプログラミングのための新しい定跡

44 第 3 章たくさんのデータを扱う この sort 関数はく alg 。 rit 血 > ヘッダに定義されているもので、コンテナ内のデータを非降順に並べ替えるので す。「非降順」はほとんど「昇順」と同じですが、同じデータもあり得るためそういう言葉を使います。 sort の引数はソートする範囲を指定しています。このために、 vector には 2 つのメンバ関数 begin と end があるのです。これらについては 5.2.2 でより詳しく説明しますが、 こでは homework. begin() が homework という vector オプジェクトの先頭を意味し、 homework. end() が homework の末尾 ( 最後の 1 つ 後 ) を意味するということだけ述べておきます。 この sort 関数は、新しくソートされた vector オプジェクトを生成するのではなく、指定された vector オ プジェクトのデータを並べ替えるのです。 。 mew 。 rk をソートした後は、その真ん中の要素を見つけます。 vec_SZ mid = size/2 ; double median; median = size % 2 0 ? (homework[mid] + homework[mid-l] ) / 2 homework [mid] ; 上の最初の行では、 vector の真ん中を見つけるため、 size を 2 で割っています。もし size が偶数なら割り算 の結果は正確なものです。もし奇数なら、割り算の結果の小数点以下は捨てられます。 メジアンの計算の仕方は、 size が偶数か奇数かで違います。もし size が偶数なら、メジアンは真ん中の 2 つ の要素の平均になります。 size が奇数のときは、真ん中の要素は 1 つきりなので、それがメジアンです。 メジアンを求めるェクスプレッションでは、 2 つの新しい演算子を使いました。剰余 (remainder) 演算子 % と 条件 (conditional) 演算子で、後者はしばしば ? : 演算子とよばれるものです。 剰余演算子の % は左のオペランドを右のオペランドで割った余りを戻す演算子です。 size を 2 で割った余り が 0 なら、プログラムが読み込んだデータ数は偶数ということになります。 条件演算子は if-then-else の短縮形です。上のコードでは、ま $size%2==0 を評価します。 ? の前の工クスプレ ッションは b 。。 1 値を戻す条件にするのです。これが true なら ? と : の間の値が戻され、 false なら : の後の値が 戻されるのです。つまり、読み込んだデータの数が偶数なら、 median には homework[mid] と homework[mid-l] の値の平均が入るのです。もし、読み込んだデータの数が奇数なら median には homework[mid] が入ります。 & & やⅡと同様に ? : 演算子も一番左のオペランドを最初に評価します。そして、その結果によって、残りのオペ ランドの 1 つだけが評価されます。 homeworkCmid] や homeworkCmid-1] は、 vector オプジェクトの要素のアクセス方法の 1 つを示していま す。 vector オプジェクトのすべての要素はインデックス (index) とよばれる整数値を付随して持っています。 つまり、 homework cmid] はインデックスが mid の要素ということになるのです。 2.6 から類推できるかもし れませんが、 homework の最初の要素が h 。 me ork [ 0 ] で、最後の要素が homeworkCsize ー 1 ] です。 コンテナ内の要素はみな指定された型の ( 名前のない ) オプジェクトです。したがって、 homework[mid] な どは double のオプジェクトです。そのため、 double 型のものにできることは、なんでも homework [mid] に こではこれらを足して 2 で割って平均を出しています。 できるわけです。 h 。 mew 。 rk の要素のアクセス方法がわかったので、メジアンの計算方法をもう 1 度説明します。まず、 size が偶数だとしましよう。そのとき mid は size/2 になります。そうすると、 homework の真ん中には要素がな く、真ん中の両側に mid 個の要素があることになります。

4. Accelerated C++ : 効率的なプログラミングのための新しい定跡

14.4 コピーがコントロール可能なハンドルの改良 if (cp) cp->regrade (final, thesis) ; else throw run—time—error("regrade Of unknown ; 265 14.4 コピーがコントロール可能なハンドルの改良 コピーをコントロールできる ptr ハンドルは便利なものですが、すべての望ましい機能があるわけではあり ません。たとえば、第 12 章で考えた str クラスを ptr を使って定義し直してみましよう。 12.3.4 では、 2 つ の str オプジェクトをつなげるときには、たくさんの文字のコピーを非明示的にしていることを見ました。参 typedef Vec く char> : : size—type size—type; / / インタフェ - スは以前と同し return *this ; std: :back-inserter(*data) ) ; std: :copy(s . data->begin() , s . data¯>end() , data. make-unique ( ) ; Str& operator + =(const Str& s) { public : friend std: : istream& operator>> (std: : istream&, class Str { / / これはうまくいくでしようか ? 照カウンタ付きの str クラスを使うことで、いくつかのコピーは避けられると考えるかもしれません。 Str&) ; size—type size() const { return data¯>size ( ) ; } const char& operator ロ (size—type i) const { return (*data) return (*data) [i] ; data. make—unique ( ) ; char& operator ロ (size-type i) { / / 必要なときには make-unique を使う std: :copy(), j, std: :back-inserter(*data)) ; template く class 工Ⅱ > str()n i, ln j) : data(new Vec く char>) { Str(size—type Ⅱ , char c) : data(new Vec く char>(ll' c)) { } std: :back-inserter(*data) ) ; std: :copy(cp, cp + std: :strlen(cp) , Str(const char* (p) : data(new Vec く char>) { str() : data(new Vec く char>) { } / / コンストラクタの定義は変えて pt てを生成するようにした private : / / vector を指す ptr オプジェクト Ptr く Vec く char> > data; / / 12.3.2 、 12.3 . 3 にあるように定義 std: :ostream& operator くく (std: :ostream&, Str operator 十 (const str&, const Str&) ; const Str&) ;

5. Accelerated C++ : 効率的なプログラミングのための新しい定跡

218 第 12 章値のように振る舞うクラスオブジェクト 別な理由は見当たりません。さらに、いくつかの文字列を続けてつなげていけるようにしたいのです。これは sl 、 s2 、 s3 を str オプジェクトとすると、次のような工クスプレッションを可能にしたいということです。 sl + s2 + s3 このように使うためには、この演算子の戻すものは str であるとわかります。 以上の考察から、この演算子は次のようなメンバでない関数として実装することになります。 Str operator + (const Str&, const Str&) ; この定義の前に少し考えてみると、。 pert 。 r + を定義するなら。 perat 。 r + = も定義したほうがよいと思うかもし れません。たとえば、 str クラスのユーザが、 s と s 1 をつないだものをあらためて s にしたいと考えるかもし れません。これは次のような形にしたいものです。 S 十 S 1 ; S S 十 = S 1 ; 実は、後で、。 perat 。 r + の定義を書くには、。 perat 。 r + = を先に書いたほうが簡単だとわかります。単純な + 演算 子と違って、複合型演算子である + = は左辺のオペランドを変えてしまいます。そのため、これは str クラスのメ ンバ関数にしましよう。文字列をつなぐ新しい関数の定義を付け加えて、 str の最終版は次のようになります。 class Str { / / 12 . 3 . 2 で定義した人力演算子 friend std: : istream& operator>>(std: : istream&, str&) ; public : Str& operator + =(const Str& s) { std::copy(s . data. begin(), s . data. end(), std: :back-inserter(data) ) ; return *this ; / / 前と同し typedef Vec く char> : : size_type size_type; Str(size-type Ⅱ , char c) : data(), c) { } Str(const char* (p) { std: :copy(cp, cp + std: :strlen(cp) , std: :back-inserter(data)) ; size—type size() const { return data. size() ; } const char& operator [ ] (size—type i) const { return data[i] char& operator ロ (size-type i) { return data[i] ; } std: :copy(), j, std: :back-inserter(data)) ; template く class ln> Str()n i, ln j) { private : Vec く char> data; / / 12 . 3 . 2 で定義したもの std: : ostream& operator くく (std: : ostream&, Str operator 十 (const Str&, const Str&) ; const Str&) ;

6. Accelerated C++ : 効率的なプログラミングのための新しい定跡

7.4 文を作る 135 範囲を [O, RAND-MAX] から、 CO, Ⅱ ) にすることです。最初の範囲は 0 と RAND-MAX を含み、求めている範囲 では 0 を含み n は含まないことに注意してください。また、Ⅱく = RAND-MAX とします。 ここで rand ( ) % で十分だと思うかもしれません。これは rand ( ) をⅡで割った余りという意味です。実 用に当たっては、この方法は 2 つの問題を持っています。 もっとも大きな問題は、実際的なものです。 rand ( ) は本当にランダムな数ではなく、ランダムに近い数 ( 擬 似乱数、 pseudo-random-number) を戻すだけだということです。多くの C ト + コンパイラでは、割る数を小さく すると、余りはランダムでなくなってきます。たとえば、 rand ( ) が偶数と奇数を交互に戻すようになることも あります。そのような場合、 n を 2 にすると、 rand ( ) % n は 0 と 1 を交互に戻すことになります。 さらに、 rand ( ) % Ⅱにはもう 1 つ微妙な問題があります。もし、Ⅱが大きいと RAND-MAX は n でうまく割 れないのです。たとえば、 RAND-MAX が 32767 ( 規格を満たすコンパイラの RAND ー MAX に許された最小の数 ) と し、Ⅱが 20000 とします。この場合、 rand ( ) % n を 10000 にする可能性が 2 つあります (rand ( ) が 10000 か 30000 を戻す場合 ) 。しかし、 rand() % Ⅱが 15000 になる可能性は 1 つしかありません ( rand ( ) が 15000 の場合 ) 。そのため、 rand() % Ⅱが 10000 を戻す確率が 15000 を戻す確率の倍になってしまうのです。 こでは別の方法を採用します。それは、範囲を割って、同じ大きさのバケッ このような問題を避けるため、 の集まりにしてしまう方法です。それからランダムな整数を求め、バケツに対応する整数を戻すのです。バケッ の大きさは同じで、どのバケツにも入らないランダムな整数もあります。その場合は、バケツに入る数が出るま でランダムな数を試し続けるのです。 この関数は説明をするより書いてしまった方が簡単です。 / / [ 0 , Ⅱ ) の範囲のランダムな整数を戻す int nrand (int Ⅱ ) if ( Ⅱく = 0 ⅡⅡ > RAND-MAX) throw domain—error ( "Argument tO nrand is out Of range " ) ; const int bucket—size = RAND-MAX / Ⅱ ; int r ; rand() / bucket-size ; do r while (r > = Ⅱ ) ; r ; 整数の割り算は余り部分を切り捨てるので、 bucket-size の定義はその影響を受けます。これは RAND-MAX/n が割り算の正確な答えに等しいかそれより小さいということです。結果として、 bucket-size は n * bucket-size く = RAND-MAX の範囲の最大の整数ということになります。 次は do vhile ステートメントです。これは while ステートメントに似ていますが、常にその中身の処理を最 低 1 回は実行し、それから最後に条件をテストすることが違います。この条件が true ならループを繰り返し、 条件が f alse になるまで処理を実行します。今の場合、ループの中身は r をバケツの番号にセットするだけで す。バケッ 0 は rand() の戻すランダムな数が [O, bucket-size) の範囲を意味し、バケッ 1 はランダムな数 が [bucket-size, bucket-size * 2 ) の範囲を意味し ... 以下同様です。もし、 rand() の戻す値が大きすぎ て r > = n になった場合は、バケツのどれかに入るまでループを繰り返します。そして、適当な値が定まれば、 この関数はその r を戻して終了します。 たとえば、 RAND-MAX を 32767 とし、 n を 20000 とします。そうすると、 buket-size は 1 になり、 nrand()

7. Accelerated C++ : 効率的なプログラミングのための新しい定跡

88 typedef string: : size—type string_size; string—size = 0 ; / / 不変な表明 : [ もとの i , 現在の i ) までを処理 while ( i ! = s . s ize ( ) ) { / / 始めにある空白を無視する / / 不変な表明 : [ もとの i , 現在の i ) はすべて空白 while (i ! = s . size() & & isspace(s[i])) 十十 ; / / 次のデータ ( 単語 ) の終わりを見つける string—size 」 第 5 章 ン一ヶンシャルコンテナと string の解析 / / 不変な表明 : [ もとの j , 現在の j ) はすべて空白ではない ! = s. size() & & !isspace(s[j])) while (j / / もし空白でない文字を見つけたら if (i ! = / / i から j ー i までのコピーを格納 ret. push—back(). substr(i , j 1 return ret ; 右両側のオペランドが共に true かどうかをテストし、それらの少なくとも一方が false なら false を戻す演 ま $isspace は char を引数に取り、それが空白かどうかを判定し、その結果を戻す関数です。 & & 演算子は、左 while (i ! = s . size() & & isspace(s[i])) たくさんあります。 i を空白でない文字が現われるまで進めるのです。これは次のステートメントで行われますが、説明することが け、そのインデックスを i にします。入力される引数の文字列には空白文字が複数並んでいることもあるので、 この while の中では、 2 つのインデックスの位置を決めます。ます、 s の中で空白でない最初の文字を見つ 一番外側の while のチェックで、最後の単語を処理した後にはループが終了することが保証されます。 たのです。これから i は単語ごとに進ませ、単語の始めを指すようになります。 2 回以上使うので、短縮名を定義しました。これは続く宣言を簡単にするためで、 3.2.2 で同じようなことをし のものです。次の 2 つのステートメントは、インデックスの i を定義し初期化するものです。また、この型は ます、 ret の定義から始めています。これは引数の string オプジェクトから切り出した単語を格納するため そのオプジェクトの不要なコピーを避けることができるのです。 そして 4.1.2 で見たように、 const の参照を使うことで、オプジェクトが変更されないことを保証しながら、 にしました。単に、 s から単語をコピーするだけなので、 split 関数は stri Ⅱ g オプジェクトを変更しません。 さて、 split 関数にはパラメータが 1 つしかありません。これは co Ⅱ st string への参照で、 s という名前 ているのです。 呼ばれていたヘッダが C ト + に引き継がれたものです。最初の c が、これはもとは C のヘッダだったことを示し す。一般的にいうと、このヘッダには個々の文字を処理する有用な関数が定義されています。 C では ctype と これまでに必要だった標準ヘッダに加えて、このコードは isspace を定義しているく cctype > ヘッダが必要で

8. Accelerated C++ : 効率的なプログラミングのための新しい定跡

46 int main( ) / / 学生の名前を人力させる cout くく " 姓を入力してください : string name ; Cin > > name ; " くく name くく " さるノ ! " くく endl; cout くく " こんにちは、 / / 中間試験と期末試験の点数を人力させる cout くく " 中間試験と期末試験の点数を人力してください : double midterm , final ; cin > > midterm > > final; / / 宿題の点数を人力させる cout くく " 宿題の点数を全部に人力してください " " ( ただし、最後は end-of-file) vector く double> homework ; double x; くく endl ; " やりなおしてください。 cout くく endl くく " あなたの宿題の点数が必要です。 if (size vec_sz Size = homework. Size() ; typedef vector く double> : : size—type vec—sz ; / / 宿題の点数が人力されたかをチェック homework. push-back (x) ; while (cin > > x) / / 不変な表明 . こまで読み込んだ点数はすべて h 。 mew 。て k に記録されている 第 3 章 たくさんのデータを扱う return 1 ; / / 宿題の点数をソート sort (homework. begin() , / / 宿題のメジアンを計 vec_sz mid = size/2; double median ; homework. end() ) ; 0 ? (homework [mid] + homework [midl] ) / 2 size % 2 median homework [mid] ; / / 最終成績を計算して出力 cout . precision() ; streamsize prec cout くく " あなたの最終成績 . ・ " くく setprecision(3) くく 0 . 2 * midterm + 0 . 4 * f inal + 0 . 4 * median くく setprecision(prec) くく endl; return 0 ; 3.2.3 追加コメント かった場合、メジアンの計算のコードは問題を起こします。なぜでしようか。 プログラムの実行を続行した場合、どうなるかを知っておく価値があります。入力が無くそれをチェックしな つまり、どうすればよいかわからないのです。ですから、プログラムの終了は正しいと思います。しかし、もし を終了すべきなのかをもう少し検討しましよう。論理的には、データがない場合のメジアンは定義できません。 前節のプログラムにはいくつか重要なポイントがあります。まず、 homework が空の場合に、なぜプログラム

9. Accelerated C++ : 効率的なプログラミングのための新しい定跡

c 。 mpare 関数を定義したので、ライプラリの s 。 rt 関数の 3 番目の引数に ソートができます。 sort(students. begin() , students . end() , compare) ; 64 第 4 章プログラムとデータの構成 compare を渡すことで vector の こうすると、 sort が要素を比較する場合、 compare 関数が呼ばれて比較が行われるのです。 4.2.3 レポートの作成 学生の成績処理に必要な関数をすべて書いたので、 int main ( ) string: : size—type maxlen = 0 ; Student—info record; vector く Student_info> students ; レポートを作成することができます。 / / すべてのデータを読み込み、最長の名前の長さを求める while (read(cin, record)) { maxlen = max(maxlen, record. name . size() ) ; students. push—back (record) ; / / アルファベット順に並べる sort (students . begin() , students . end() , compare) ; for (vector く Student—info> : : size—type i i ! = students . size() ; 十十 i ) { / / 名前を出力し、全体が maxlen + 1 文字になるように名前の右側に空白を人れる return 0 ; cout くく end1 ; cout くく e . what() ; } catch (domain-error e) { くく setprecision(prec) ; cout くく setprecision(3) くく final—grade streamslze prec cout . precision() ; double final-grade = grade (students [i] ) ; try { / / 最終成績を計篁し出力する くく string(maxlen 十 1 students [i] . name . size() , cout くく students [i] . name 見して、 max の振る舞いは明らかでしよう。しかし、 1 つだけ明らかでないことがあります。それは 8.1.3 で まず、最初にライプラリの関数でく alg 。 rit 血 > ヘッダに定義されている max という関数を使っています。 このコードの大部分はすでに見ていますが、いくつか新しいこともあります。 説明する複雑な理由によって、引数は正確に同じ型のものでないといけないということです。この事情により、 maxlen という変数の型を単純に int にすることはできす、 string::size-type としたのです。 students [i] . name string (maxlen + 1 . s ize ( ) ,

10. Accelerated C++ : 効率的なプログラミングのための新しい定跡

78 第 5 章 インデックスが i の要素 ン一ヶンシャルコンテナと string の解析 students . siz() = れ すでに調べた要素 students . siz() = れ一 1 すでに調べた要素 不合格まだ処理していない要素 ( これらはコピーされる ) まだ処理していない要素 erase は ve ct or の要素数を 1 つ減らすだけでなく、削除した要素の次の要素をインデックス i にし、それ以 後の要素を前にずらします。そのため、 i を 1 つ増やさなくても、 eras e の後はこれが削除した要素の次の要素 を指すようになっています。そのため、次のループのステップのために、 i を増やしてはいけないのです。 もし、調べている学生のデータが不合格でなければ、これはそのまま student s に残したいわけです。その場 合は、 while で次のステップにいくために、 i を 1 っ増やさなければなりません。 students の全データを調べたかどうかは、 i と studnts . size() を比べて判定しています。 vector の要素 を削除すると、その全要素数が 1 つ減ります。そのため、毎回の判定に studnts . size() を使うことは重要で す。たとえば、次のように size という変数を作り、そこにサイズを記録したとします。 / / 誤解に基づく「改良」のため、以下のコードは間違いです。 students . size() ; vector く Student—info> : : size—type size while (i ! = size) { if (fgrade(students [i] ) ) { fail. push-back(students [i] ) ; students. erase (students . begin() 十 i) ; } else 十十・ これは間違いです。なぜなら、 erase を呼び出すたびに stude Ⅱ ts の全データ数が変わってしまうからです。 のように s ize に最初のデータ数を記録し、 1 つでもデータを削除すると、 student s に対して多すぎる処理を してしまうのです。その結果、 student s Ci] は存在しないデータを意味することになります。幸い、 s ize ( ) 関 数の呼び出しには普通あまり時間がかかりませんので、この呼び出しのオーバーヘッドは無視できるものです。 5.1.2 シーケンシャルアクセス対ランダムアクセス こまでに作った 2 つの extract ー fails 関数は、コンテナを使う多くのプログラムに共通する性質を持って います。それはコードを見るだけではあまり明らかではありませんが、これらの関数はコンテナの要素にシーケ ンシャルに ( 順番に ) アクセスしているだけだということです。つまり、どちらの関数も、学生のデータを 1 つ ひとつ最初から順番に調べていき、何をするか判断しているということです。 これがあまり明らかでないのは、 students の要素にアクセスするのに、インデックス i を使っているからで す。整数 i の値はいろいろ変えられるのですが、上のコードでは、コンテナのデータにシーケンシャルにアクセ スするように、注意して i の値を変えています。しかし、 students Ci] というアクセス方法は、暗に、シーケン シャルとは限らずに、いろいろな順番でデータにアクセスする可能性があることを示唆しています。