C++には、同じ型の要素を順序を保って格納するための、さまざまなコンテナーが用意されています。
サイズが固定されたコンテナーにはstd::arrayがあり、動的にサイズを変更できるstd::vectorもあります。
配列やベクターを宣言するときは、そのコンテナーが保持する要素の型を指定する必要があります。 配列の場合は、サイズも指定します。 次の例で、2つのコンテナーの初期化のしかたを見てみましょう。
#include <array>
#include <string>
// std::array<element_type, size> variable_name {list of elements}
std::array<std::string, 3> indie_rock {"yeah", "yeah", "yeah"};
// indie_rock contains the elements "yeah" three times
ベクターは、今後大きくなるためのメモリーを確保するため、通常はより多くの領域を必要とします。 サイズを指定する必要はありません。
#include <vector>
// std::vector<element_type> variable_name {list of elements}
std::vector<int> countdown {3, 2, 1};
// countdown contains the elements 3, 2 and 1
補足:テンプレートの仕組みそのものを今知っている必要はありません。シラバスのツリーのさらに下に、テンプレートについての専用の項目があります。
要素にアクセスする関数は、ベクターと配列で共通です。
メンバー関数frontとbackを使うと、コンテナーの最初と最後の要素を取得できます。
特定の要素にアクセスするには、atや[]演算子もあります。
countdown[0];
// => 3
countdown.at(2);
// => 1
countdown[1] = 4;
// countdown now contains 3, 4 and 1
indie_rock.back() = "yeahs";
// indie_rock is now "yeah", "yeah", "yeahs"
補足:コンテナーの範囲にない位置を指定すると、
[]は未定義動作につながる可能性があります。at関数なら例外が送出されるので、長い目で見ればそのほうが楽かもしれません。
vectorはサイズが固定されていないので、要素を追加したり削除したりできます。
そのためによく使われる関数がemplace_backとpop_backです。
std::vector<std::string> witches {"Holly", "Alyssa", "Shannen"};
witches.pop_back();
// Shannen is no longer with the witches
witches.emplace_back("Rose");
// Rose has joined the team
コンテナーが空かどうかは、メンバー関数emptyで確認できます。
要素の数を知りたいときは、sizeを使います。
学生の試験を採点するティーチングアシスタントをしています。 結果を手作業で管理するのは、面倒なうえに間違いも起こりがちです。 そこで、クラスの結果を数えたり計算したりする関数をいくつか作って、少しだけ楽しくしてみることにしました。
試験の問題では「部分点」を与えられますが、試験全体の点数はintでなければなりません。
ですから、クラスの点数を扱う前に、成績を見直して、doubleの点数をintに変換する必要があります。
student_scoresのvectorを仮引数として受け取る関数round_down_scores()を作成しましょう。
学校が求めている丸め方は、切り捨てです。
幸いなことに、C++ではstatic_castを使ってある型を別の型にキャストできます。
doubleからintへのstatic_cast変換では、小数点以下の桁が切り捨てられます。
この関数は、入力のvectorを受け取り、すべての点数をintに変換した新しいvectorをreturnするようにします。
std::vector<double> student_scores {90.33, 40.5, 55.44, 70.05, 30.55, 25.45, 80.45, 95.3, 38.7, 40.3};
round_down_scores(student_scores)
// => {90, 40, 55, 70, 30, 25, 80, 95, 38, 40}
同じ結果を得る方法はほかにもありますが、_キャスト_はよく見かけるものです。
int new_PI{static_cast<int>(3.14) + static_cast<int>(0.001592653589)};
// new_PI is 3.
試験を採点していると、思っていたほど成績が振るわない学生がいることに気づきました。 しかし、気を取られて、_何人_いたのかを正確に記録するのを忘れてしまいました。
student_scoresのvectorを仮引数として受け取る関数count_failed_students()を作成しましょう。
この関数は、合格点に届いていない学生の数を数え、その数を整数として返すようにします。
試験で合格するには、40より大きい点数が必要です。
count_failed_students({90,40,55,70,30,25,80,95,38,40});
// => 5
お手伝いしている先生は、数値の点数だけでなく評定も付けたがります。 学生が試験で100点を取ることはめったにないので、「評定」の下限のしきい値は、達成された最高点をもとに計算し、その最高点と不合格のしきい値である**<= 40**の間で均等に刻みます。
試験の「最高点」を仮引数として受け取り、アメリカ式の各評定区間に対応する下限のしきい値のarray(vectorではありません)を返す関数letter_grades()を作成しましょう:["D", "C", "B", "A"]。
/*Where the highest score is 100, and failing is <= 40.
"F" <= 40
41 <= "D" <= 55
56 <= "C" <= 70
71 <= "B" <= 85
86 <= "A" <= 100
*/
letter_grades(100);
// => {41, 56, 71, 86}
/*Where the highest score is 88, and failing is <= 40.
"F" <= 40
41 <= "D" <= 52
53 <= "C" <= 64
65 <= "B" <= 76
77 <= "A" <= 88
*/
letter_grades(88);
// => {41, 53, 65, 77}
試験の点数が高い順に並んでいて、それぞれに対応する学生の名前もあります(試験の点数の順に並んでいます)。 それぞれの学生の名前と試験の点数を対応させて、クラス全体の順位を出力したいと考えています。
仮引数student_scoresとstudent_namesを受け取る関数student_ranking()を作成しましょう。
student_namesのvectorにある各学生の名前を、student_scoresのvectorにある点数と対応させます。
それぞれの引数のvectorは、点数の高い順に並んでいるものとします。
この関数は、<rank>. <student name>: <student score>という形式の文字列のvectorを返すようにします。
std::vector<int> student_scores {100, 99, 90, 84, 66, 53, 47};
std::vector<std::string> student_names {"Joci", "Sara","Kora","Jan","Indra","Bern", "Fred"};
student_ranking(student_scores, student_names)
// =>
// {"1. Joci: 100", "2. Sara: 99", "3. Kora: 90", "4. Jan: 84", "5. Indra: 66", "6. Bern: 53", "7. Fred: 47"}
試験で100点という「完璧な」点数を取ることはめったにありませんが、少なくとも1人の学生が達成したかどうかは気になるところです。
仮引数student_scoresとstudent_namesを受け取る関数perfect_score()を作成しましょう。
リストはタスク4と同じです。
この関数は、試験で100点を取った学生の_最初の_<name>を(文字列として)returnするようにします。
100点の学生が見つからなければ、空文字列""を返します。
perfect_score({"Nur", "Tony", "Fatima"}, {90, 80, 100});
// => "Fatima"
perfect_score({"Nur", "Tony"}, {90, 80});
// => ""