C++の関数は、これまでにいくつかのコンセプトで使ったり書いたりしてきました。 ここで少し、技術的な話をしましょう。 すぐに参照できるように、よく使われる用語を次のコードスニペットにまとめました。 C++は空白を無視するので、各要素が1行に収まるように整形を変えています。
// Function declaration:
bool // Return type
admin_detected(string user, string password) // Type signature
; // Don't forget the ';' for the declaration
// Function definition:
bool // Return type
admin_detected // Function name
(string user, string password) // Parameter list
{ return user == "admin" && password == "1234"; } // Function body
宣言は、その名前・戻り値の型・仮引数リストを持つ関数があることをコンパイラーに伝えるメモのようなものです。 定義がないと、コードは動きません。 宣言は必須ではありませんが、定義より前にその関数を使う場合には必要になります。 宣言は、循環参照のような問題を解決でき、インターフェースと実装を分離するのにも使えます。
const修飾子値が初期化されたあとに変更されないようにしたいことがあります。
C++では、定数の修飾子としてconstキーワードを使います。
const int number_of_dragon_balls{7};
number_of_dragon_balls--; // compilation error
定数の名前は UPPER_SNAKE_CASE で書かれているのをよく目にします。 ほかに決まった書き方がないのであれば、この書き方はマクロ用に取っておくのがおすすめです。
一度設定した定数変数を変更しようとすると、コードはコンパイルできません。 これは意図しない変更を防ぐのに役立つだけでなく、コンパイラーによる最適化の可能性も広げます。 また、ある部分が影響を受けないと分かっていれば、人間にとってもコードを考えやすくなります。
constは、関数の仮引数の修飾子としても使えます。
string guess_number(const int& secret, const int& guess) {
if (secret < guess) return "lower.";
if (secret > guess) return "higher.";
return "exact!";
}
関数にconst参照を渡すと、その値が変更されないことが保証されます。
長めの文字列のように、コピーにコストがかかるオブジェクトでは、const参照をよく使います。
const修飾子の3つ目の用途は、クラスのインスタンスを変更しないメンバー関数です。
class Stubborn {
public:
Stubborn(string reply) {
response = reply;
}
string answer(const string& question) const {
if (question.length() == 0) { return ""; }
return response;
}
private:
string response{};
};
Stubbornのメンバー関数answerは、仮引数としてconst string&参照を使っています。
これにより、関数に渡された元のオブジェクトをコピーする処理を避けられます。
仮引数リストが違えば、複数の関数に同じ名前を付けられます。 これを関数のオーバーロードと呼び、よく似た処理を行う関数によく使われます。
戻り値の型を除いた関数のヘッダーが、その関数の__型シグネチャ__です。 型シグネチャが変われば、別の関数になります。
play_soundの例には、さまざまな場面に対応するために6つのオーバーロードがあります。
// different argument types:
void play_sound(char note); // C, D, E, ..., B
void play_sound(string solfege); // do, re, mi, ..., ti
void play_sound(int jianpu); // 1, 2, 3, ..., 7
// different number of arguments:
void play_sound(string solfege, double duration);
// different qualifiers:
void play_sound(vector<string>& solfege);
void play_sound(const vector<string>& solfege);
型シグネチャは、関数名・仮引数の数・それぞれの型・修飾子によって決まります(仮引数の名前は含まれません)。 戻り値の型は型シグネチャには含まれないので、戻り値の型だけが違う2つの関数があるとコンパイルエラーになります。 どちらを使うべきか分からないため、コンパイラーはエラーを出します。
関数によってはとても長くなり、その呼び出しの多くで、ほとんどの仮引数に同じ値を使うことがあります。 そうした呼び出しでの繰り返しは、デフォルト引数を使えば避けられます。
void record_new_horse_birth(string name, int weight, string color="brown-ish", string dam="Alruccaba", string sire="Poseidon");
record_new_horse_birth("Urban Sea", 130); // color will be brown, dam "Alruccabam", sire "Poseidon"
record_new_horse_birth("Highclere", 175, "off-white", "Fall Aspen"); // sire will be "Poseidon"
関数宣言は定義より先に読まれることが多いので、デフォルト引数は宣言で設定するのがよいでしょう。 ある仮引数にデフォルト値が宣言されている場合、その右側にある仮引数にもすべてデフォルト値の宣言が必要です。 複雑な関数のオーバーロードは、デフォルト引数を使ってより少ない関数に整理し、保守しやすくできることがあります。
この演習では、お気に入りの料理本に載っている絶品ラザニアの準備と調理に関するコードを、もう少し書いていきます。
タスクは5つあります。 最初のタスクは調理そのものに関するもので、残りの4つは完璧な準備に関するものです。
次に作るラザニアのために、調理を楽しめるよう、十分な時間を確保しておきたいところです。 ラザニアにどの層を重ねるかは、すでに決めてあります。 そこで、それをもとに準備にどれくらい時間がかかるかを見積もりたくなります。
preparationTimeという関数を実装してください。この関数は、層のベクターをstringとして、1層あたりの平均準備時間(分)をintとして受け取ります。
関数は、層の数をもとに、合計の準備時間の見積もりをintとして返すようにします。
平均準備時間が渡されなかった場合は、デフォルト値の2を使います。
std::vector<std::string> layers{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"};
preparationTime(layers, 3)
// => 18
preparationTime(layers)
// => 12
時間を確保するだけでなく、夢のラザニアを作るのに十分なソースと麺があることも確かめたいところです。 ラザニアの麺の層1つにつき、麺が50グラム必要です。 ソースの層1つにつき、ソースが0.2リットル必要です。
quantitiesという関数を定義してください。この関数は、層のベクターをstringのパラメーターとして受け取ります。
関数は、料理を作るのに必要な麺とソースの量を求めます。
結果は、あらかじめ定義されているamountという_struct_として返します。
amountには2つのメンバー変数があり、1つはnoodlesの重さをintで、もう1つはsauceの量をdoubleで表します。
struct amount { int noodles; double sauce; };
amount needed{quantities({"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})};
// needed.noodles => 100
// needed.sauce => 0.4
少し前に友人を訪ね、そこでラザニアを食べました。 とてもおいしく、何か特別なものがありました。 友人は材料のリストを送ってくれ、リストの最後の項目が、その料理をあれほど特別にした「秘密の材料」だと教えてくれました。 そこで、その秘密の材料を自分のレシピにも加えたくなります。
addSecretIngredientという関数を書いてください。この関数は2つのvector of stringsをパラメーターとして受け取ります。
最初のパラメーターは自分のレシピの材料への_reference_で、2番目の文字列の_vector_には友人が送ってくれた材料が入っています。
自分の材料リストの最後の要素は、常に"?"です。
関数はそれを、友人のリストの最後の項目で置き換えます。
注意: addSecretIngredientは何も返しません。自分の材料のリストを直接変更してください。
友人の材料のリストも(パフォーマンスを向上させるために)参照にできますが、変更してはいけません。
const参照を使って、そのリストが変更されないようにしてください。
std::vector<std::string> friendsList{"noodles", "sauce", "mozzarella", "kampot pepper"};
std::vector<std::string> myList{"noodles", "meat", "sauce", "mozzarella", "?"};
addSecretIngredient(myList, friendsList);
// myList => {"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}
料理本に載っている分量では、ラザニアは2人分しかできません。 次はもっと多くの人のために料理したいので、人数に応じた分量を計算したくなります。
scaleRecipeという関数を実装してください。この関数は2つのパラメーターを受け取ります。
doubleの分量のベクター関数は、作りたい人数に必要な分量を表す新しいベクターを返します。
ただし、元のレシピはそのままにしておきたいところです。
つまり、この関数ではquantities引数を変更してはいけません。
std::vector<double> quantities{1.2, 3.6, 10.5};
scaleRecipe(quantities, 4);
// => { 2.4, 7.2, 21 }
子供の頃の話を覚えています。家族の最も深い料理の秘密を守る、曽祖伯母の物語です。 伝説によれば、彼女は想像を超える風味を解き放つ鍵を握っているそうですが、その方法は型破りです。 彼女はリストや計量を扱いません。ささやきで語り、一度に1つの材料だけを明かすのです。
期待と不安が入り混じる中、曽祖伯母に連絡してみることにします。 予想どおり、彼女は謎めいた口調で応じ、神秘に包まれた1つの秘密の材料だけを教えてくれます。
曽祖伯母からの贈り物は複雑さを1層加えるもので、それをレシピに使いたくなります。
addSecretIngredient関数の別バージョンを実装してください。これは2つの引数、自分の材料リストを表すベクターへの参照と、曽祖伯母からの謎めいた贈り物を表す1つの文字列を受け取ります。
std::vector<std::string> myList{"noodles", "meat", "sauce", "mozzarella", "?"};
std::string secretIngredient{"spice melange"};
addSecretIngredient(myList, secretIngredient);
// myList => {"noodles", "meat", "sauce", "mozzarella", "spice melange"}
勇敢なシェフに幸運を祈ります!