関数とポインタと参照

説明に出てくるプログラムは必ず実行してみてください。

数字が
40 10 3 43 50 11
と並んでいるとします。例えば、これが人のあるグループの年齢のデータだとして、このグループに60歳以上の人がいるかどうか調べたいとします。

60歳以上の人がいるかどうか調べるための一つの方法はデータを一つずつ見て、60歳以上か調べることです。この方法だとデータの個数が6個ですから、6回調べる必要があります。

年齢のデータを小さい順(昇順)に並べると、
3 11 10 40 43 50
となりますが、この場合は一番右の数=50を見るだけでこのグループに60歳以上の人はいないことが分かります。

このようにデータをきれいに並べると色々良いことがあります。では、でたらめに並んでいる数をきれいに並べ替えるにはどうしたらいいでしょうか。

 

2つの変数の内容を交換する。

並べ替えの最も基本的な操作は、2つの変数の内容を交換することです。

このプログラムはa[0]をa[1]の内容を交換することを意図して書かれていますがうまく行きません。どうなるか実行して確かめてください。

//プロジェクト名: function1
int main(int argc, char* argv[])
{
       
int a[2];
        a[0] = 1;
        a[1] = 2;
       
        printf("交換前 a[0]=%d, a[1]=%d\n", a[0], a[1]);

        //交換する
        a[0] = a[1]; //a[1]の内容をa[0]に代入
        a[1] = a[0]; //a[0]の内容をa[1]に代入
        printf("交換後 a[0]=%d, a[1]=%d\n", a[0], a[1]);

    getc(stdin);//Enterキーが押されるまで待つ

    return 0;//処理の終わり
}

何が悪いか分かりますか?

a[0] = a[1];

としたときにa[0]には2が代入されますが、代入前にa[0]に入っていた1が消えてしまいます。そのため次に

a[1] = a[0];とするとこのときにはa[0]には2が入っていますからa[1]には1ではなく2が入ります。

これは、初心者はよくするミスです。正しいプログラムは以下の通りです。

//function1を修正すること
int
main(int argc, char* argv[])
{
       
int a[2];
        int tmp;
        a[0] = 1;
        a[1] = 2;
       
        printf("交換前 a[0]=%d, a[1]=%d\n", a[0], a[1]);

        //交換する
       
tmp = a[0]; //a[0]の内容を保存する
        a[0] = a[1]; //a[1]の内容をa[0]に代入
        a[1] = tmp; //a[0]の内容をa[1]に代入
        printf("交換後 a[0]=%d, a[1]=%d\n", a[0], a[1]);

    getc(stdin);//Enterキーが押されるまで待つ

    return 0;//処理の終わり
}

今度は、うまく行きます。どうしてうまく行くのかプログラムをよく見て理解してください。

 

ここから本題に入ります。”交換する”ことは良く行いますので関数にしておきましょう。

//function1を修正する

void swap(int a, int b)
{
          int tmp = a;
          a = b;
          b = tmp;
}

int main(int argc, char* argv[])
{
       
int a[2];
        a[0] = 1;
        a[1] = 2;
       
        printf("交換前 a[0]=%d, a[1]=%d\n", a[0], a[1]);

        swap(a[0], a[1]);//交換する

        printf("交換後 a[0]=%d, a[1]=%d\n", a[0], a[1]);

    getc(stdin);//Enterキーが押されるまで待つ

    return 0;//処理の終わり
}

実行してみてください。実はこれもうまく行きません。

 

値渡し (call-by-value)

上の例が、うまく行かないのは、swap関数が値渡しで定義されているためです。

void swap(int a, int b) //←この書き方が値渡し
{
          int tmp = a;
          a = b;
          b = tmp;
}

値渡しの場合、文字通り、変数の値が関数に渡されます。そして、渡された値が関数の中で変化しても関数の外側へは伝わりません。つまり、main関数内でswap(a[0], a[1]);としても、a[0]とa[1]は変化しないということです。ためしに以下のプログラムを実行してみてください。

//function1を修正する。
void
swap(int a, int b)
{
          int tmp = a;
          a = b;
          b = tmp;
          printf("swap内[a=%d,b=%d]\n", a, b);
}

int main(int argc, char* argv[])
{
       
int a[2];
        a[0] = 1;
        a[1] = 2;
       
        printf("交換前 a[0]=%d, a[1]=%d\n", a[0], a[1]);

        swap(a[0], a[1]);//交換する

        printf("交換後 a[0]=%d, a[1]=%d\n", a[0], a[1]);

    getc(stdin);//Enterキーが押されるまで待つ

    return 0;//処理の終わり
}

swap関数の中では、変数aとbの入れ替えが正しく行われています。

この例では、値渡しは悪いような誤解を与えるかもしれませんが、値渡しがとても役に立つときもあります。そのことは別の機会に説明します。

 

参照渡し(call-by-reference)

引数として渡した変数の変更が関数の外側にも反映するようにするには以下のプログラムのように&をつけて参照渡しにします。

//fucntion1を修正する
void
swap(int& a, int& b)//←参照渡しの書き方
{
          int tmp = a;
          a = b;
          b = tmp;
}

int main(int argc, char* argv[])
{
       
int a[2];
        a[0] = 1;
        a[1] = 2;
       
        printf("交換前 a[0]=%d, a[1]=%d\n", a[0], a[1]);

        swap(a[0], a[1]);//交換する

        printf("交換後 a[0]=%d, a[1]=%d\n", a[0], a[1]);

    getc(stdin);//Enterキーが押されるまで待つ

    return 0;//処理の終わり
}

この場合、main関数で、swap(a[0], a[1]);とするとswapの定義のほうの変数aがa[0]と変数bがa[1]とそれぞれ同一のものとして扱われます。よって今度は、swap関数内の変数の変更が関数の外へも反映されます。

 

ポインタ渡し(call-by-pointer)

参照渡しと同じことをポインタ渡しでも行うことが出来ます。

//プロジェクト名: function2
void
swap(int* a, int* b)//←ポインタ渡しの書き方
{
          int tmp = *a;
          *a = *b;
          *b = tmp;
}

int main(int argc, char* argv[])
{
       
int a[2];
        a[0] = 1;
        a[1] = 2;
       
        printf("交換前 a[0]=%d, a[1]=%d\n", a[0], a[1]);

        swap(&a[0], &a[1]);//交換する

        printf("交換後 a[0]=%d, a[1]=%d\n", a[0], a[1]);

    getc(stdin);//Enterキーが押されるまで待つ

    return 0;//処理の終わり
}

ちょっと*や&が沢山でてきて少し見づらくなりますが、同じ結果になります。main関数の中のswap(&a[0], &a[1]);に注意してください。ポインタ渡しの場合渡すのはアドレス(場所)です。変数のアドレスは変数の名前の前に&を付けることは前に説明しました。また、

int& a = b;と

int*b = &a;の

意味の違いを混同しないでください。前者は参照で後者はアドレスです。

 

ただ、このままだとポインタ渡しはただ見づらい書き方という誤解を招きそうなので別の例を用意しました。

//プロジェクト名: function3
void
swap(int* a)//←ポインタ渡しの書き方
{
          int tmp = a[0];
          a[0] = a[1];
          a[1] = tmp;
}

int main(int argc, char* argv[])
{
       
int a[2];
        a[0] = 1;
        a[1] = 2;
       
        printf("交換前 a[0]=%d, a[1]=%d\n", a[0], a[1]);

        swap(a);//交換する

        printf("交換後 a[0]=%d, a[1]=%d\n", a[0], a[1]);

    getc(stdin);//Enterキーが押されるまで待つ

    return 0;//処理の終わり
}

ポインタを使うと配列を一度に関数に渡すことが出来ます。swap関数の引数が1つになったことに注意してください。

int a[2];としたときaは配列の先頭のアドレスになります。

混乱した人は前の説明とあわせて何回か読み返してください。

分かった人は、次に進みましょう。