関数は、有限のデータ(引数, argument, parameter)を受け取って、なにか処理や加工を行なった結果のデータ(戻り値, 返り値)を返すものです。 数学的な関数は、同じ引数が与えられれば同じ結果を返しますが、プログラムの関数はそうではありません。
関数は、パッケージ化の最小単位とも言えます。 まとまった処理を、関数として書いておけば、後から再利用できます。
引数リストParameterList
を受け取り、ReturnType
を返す関数は以下のように書きます。
ReturnType functionName(ParameterList)
{
FunctionBody
}
たとえば、int
型を2つ受け取って、それらの和を返す関数は、次のように書きます。
// int型を2つ受け取って、その和を返す関数
int AddInt(int a, int b)
{
return a + b;
}
return
文は、return <expr>;
という形式をとり、<expr>
を返し、処理を呼び出し元に復帰する文です。
簡単にいえば、結果を返してから、その関数を即座に終了させる効果があります。
もし、何も返さない関数を書きたいのであれば、ReturnType
をvoid
とし、関数を途中で終わらせたい場合にはreturn;
と書きます。
また、void
を返り値型とする関数の最後の文の後にはreturn;
文が暗黙的にあります。。
void foo(int a, int b)
{
if(a > 0)
return; // a > 0 の場合には、関数は終わり、即座に処理が呼び出し元に戻る
else
writeln(b - a);
// a <= 0 の場合にはここまで来て、処理が呼び出し元に戻る
}
関数の呼び出しと、呼び出し元への復帰のイメージを表した図です。
関数は、引数を受け取りますが、関数宣言で書かれているint a
やint b
を仮引数(parameter)といいます。
逆に、AddInt(4, 5)
とした場合の4
や5
は実引数(argument)といわれます。
仮引数は、関数本体で使わない場合には、型だけに省略することができます。
// intを3つ受け取るが、最後の1つは使わないのであえて書かない
int add(int a, int b, int)
{
return a + b;
}
通常、実引数は仮引数にコピーされ、関数に渡されます。 つまり、値型であれば仮引数を変更しても実引数には影響しませんが、参照型であれば、その参照を通して影響を受ける可能性があります。
// aは値型
void addToValue(int a, int b)
{
a += b;
}
// aはポインタ(参照型)
void addToRef(int* a, int b)
{
*a += b; // ポインタの参照先のインクリメント
a = null; // ポインタの書き換え
}
void main()
{
int m = 2,
n = 13;
addToValue(m, n);
writegln("m: %s, n:%s", m, n); // 2, 13
addToRef(&m, n); // ポインタ(参照型)を渡す
writeln("m: %s, n:%s", m, n); // 15, 13
// m が書き換えられてる!
}
もちろん、宣言された仮引数の型のリストと、実引数の型のリストが一致しなければ、コンパイル時にエラーがでます。
// test00901.d
int add(int a, int b) { return a + b; }
void main()
{
int a = add(3, 5),
b = add(3), // Error: function test00901.add (int a, int b) is not callable using argument types (int)
c = add(3, 4, 5), // Error: function test00901.add (int a, int b) is not callable using argument types (int, int, int)
d = add(3.0, 4); // Error: function test00901.add (int a, int b) is not callable using argument types (double, int)
}
仮引数にデフォルト値を設定することができます。
int getValue(int* p, size_t idx = 0)
{
return p[idx];
}
void main()
{
int* p = (new int[10]).ptr;
foreach(i, ref e; p[0 .. 10])
e = i;
p[0 .. 10].reverse;
writeln(p[0 .. 10]); // [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
// idxを指定して呼び出し
writeln(getValue(p, 4)); // 5
// idxを指定せずに呼び出すと、idxは0であると解釈される
writeln(getValue(p)); // 9
}
関数の引数にも、普通の変数と同様に記憶域クラスを付けることができます。
constが付けられた引数はconst(Type)
となり、書き換え不可能になります。
int getValue(const int* p)
{
//*p += 3; // p はconst(int*)型、*p はconst(int)型なので書き換え不可
return *p;
}
immutableと付けられた
/ const, immutable, inout, in, out, ref, scope, lazy, shared
引数に取りたい実引数の数が、実行条件によって変わることがあります。
たとえば、writeln
やwritefln
などのwrite
系の関数は、引数をいくらでも取ることができます。
このような関数を作るのには、様々な方法があります。
たとえば、次のように文字列をいくつか受け取って、それらを連結した文字列を返す関数は、次のように書けます。
string chainString(string[] str...)
{
string chained;
foreach(e; str)
chained ~= e;
return chained;
}
void main()
{
writeln(chainString());
writeln(chainString("foo"));
writeln(chainString("foo", "bar"));
writeln(chainString("foo", "bar", "hoge"));
}
可変長パラメータであるstr
に、引数のリストが入ります。
str
を関数外に移動することは不正です
(つまり、scope
が暗黙的に付いていると考えれる?[要出典])。
実際には、静的配列にすることもできます。 たとえば、いくつかの数を受け取って、その中で最も大きな整数を返す関数は次のように書けます。
T max(T, size_t N)(T[N] nums...)
if(N > 0)
{
T v = nums[0];
foreach(e; nums[1 .. $])
v = v > e ? v : e;
return v;
}
void main()
{
writeln(max(0, 1, 2, 3)); // 3
}
この関数は、テンプレート関数(Template Function)といい、任意の型Tと0より大きい任意のNに対してマッチングするテンプレート関数です。
また、クラスのインスタンスを組み立てることも可能です。
class Foo{ this(int x, int y){} }
// Fooのコンストラクタが呼ばれる
void foo(Foo foo...) // fooには暗黙的にscopeが付いているようなもの
{
writeln(foo);
}
void main()
{
foo(1, 2); // Fooのコンストラクタは(int, int)
}
たとえば、いろんな型の
/auto, auto ref
問題 -> 解答
-
問1
-
問題募集中