Skip to content

Instantly share code, notes, and snippets.

@k3kaimu
Last active December 19, 2015 18:19
Show Gist options
  • Save k3kaimu/5997753 to your computer and use it in GitHub Desktop.
Save k3kaimu/5997753 to your computer and use it in GitHub Desktop.
011 関数

関数とデリゲート

関数とは?

関数は、有限のデータ(引数, 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>を返し、処理を呼び出し元に復帰する文です。 簡単にいえば、結果を返してから、その関数を即座に終了させる効果があります。

もし、何も返さない関数を書きたいのであれば、ReturnTypevoidとし、関数を途中で終わらせたい場合にはreturn;と書きます。 また、voidを返り値型とする関数の最後の文の後にはreturn;文が暗黙的にあります。。

void foo(int a, int b)
{
    if(a > 0)
        return; // a > 0 の場合には、関数は終わり、即座に処理が呼び出し元に戻る
    else
        writeln(b - a);

    // a <= 0 の場合にはここまで来て、処理が呼び出し元に戻る
}

関数の呼び出しと、呼び出し元への復帰のイメージを表した図です。

function_call_flow

関数の引数

関数は、引数を受け取りますが、関数宣言で書かれているint aint bを仮引数(parameter)といいます。 逆に、AddInt(4, 5)とした場合の45は実引数(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が付けられた引数はconst(Type)となり、書き換え不可能になります。

int getValue(const int* p)
{
    //*p += 3;                  // p はconst(int*)型、*p はconst(int)型なので書き換え不可

    return *p;
}

immutable

immutableと付けられた

inout

in

out

ref

scope

lazy

shared

/ const, immutable, inout, in, out, ref, scope, lazy, shared

可変個引数関数

引数に取りたい実引数の数が、実行条件によって変わることがあります。 たとえば、writelnwriteflnなどの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

関数の属性

セーフ関数

pure

nothrow

inout

関数オーバーロード

関数ポインタ

static変数

ネスト関数

デリゲート

リテラル

関数リテラル

デリゲートリテラル

ラムダ関数

UFCS(Uniform Function Call Syntax)

CTFE(Compile Time Function Execution)

リンケージ

問題 -> 解答

  • 問1

  • 問題募集中

終わりに

キーワード

仕様

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment