C言語 配列を関数に渡す

以前勉強したときに、関数との値のやりとりについては、次の2パターンがあると理解した。

  • 値渡し:引数に変数が渡される。変数自体は変化しない。
  • ポインタ渡し:関数に変数の値を変えて欲しいときに使用。引数が配列のときはポインタ渡しを使う必要がある

今回は、配列を関数に渡す状況について確認してみたい。

#include <stdio.h>

main()
{
        char color[3][20] = {   //3行×20文字の文字配列を定義
                "Konnichiwa",
                "Hello",
                "GutenTag"
        };
        chang(color);
}

void chang(char arg{}[20]) {  //ポイント1 ※文字化けで一つ目の括弧を{}としています
        puts(arg[0]);          //ポイント2
}

これだと動いた。ポイントは2つ。

  1. 関数の引数は、最初の要素数のみ省略する(arg[ ][20]の部分)
  2. 関数内の表記は、arg[行番号]とすれば行毎取り出せる。

ちなみに、コンパイル時に警告が表示される。

list2-29.c:13:6: 警告: ‘chang’ と型が競合しています [デフォルトで有効]
 void chang(char arg{}[20]) {
^ list2-29.c:10:2: 備考: 前の ‘chang’ の暗黙的な宣言はここです chang(color); ^

「競合」とあるが、何と競合しているのか明記されていない。「暗黙的な宣言」と競合しているということなのか?とりあえず、保留!

【追記】

上記は、関数changを定義する前に、main()内でchangが使われていることが理由。コンパイラはchangの引数が正しいかどうか判断が出来ない事により警告が表示されていた。したがって、#include <stdio.h>の次の行に、void chang(char arg[ ][20]);と書き加えれば上記警告は解消。

 

【疑問点1】

関数内で配列の置き換え等が出来るか、試してみる。

#include <stdio.h>
void chang(char arg[ ][20])
main() { char color[3][20] = { "Konnichiwa", "Hello", "GutenTag" }; chang(color); } void chang(char arg{}[20]) { //※文字化けで一つ目の括弧を{}としています
arg[0] = "Japanese"; //【追記】参照 }

chang(color)により、colorの配列の1番目(color[0])が"Japanese"に変わることを期待したが、、、コンパイルの結果は以下の通り。

test.c: 関数 ‘chang’ 内:
test.c:14:9: エラー: 型 ‘char[20]’ への型 ‘char *’ からの代入時に互換性のない型です
  arg[0] = "Japanese";
         ^

arg[0]は引き数の配列の1行目を指す筈であるが、、、代入という行為は「互換性が無い」という理由で拒否されている。

【追記】

以前学習したように、文字列の代入はstrcpyを使えば良い。次のように書きかえればエラーは発生しない。

#include <stdio.h>
#include <string.h>             //関数strcpyを使用するため
void chang(char arg{}[40]);     //※文字化けで一つ目の括弧を{}としています
void main() { char color[3][40] = { "Konnichiwa", "Hello", "GutenTag" }; chang(color); } 

void chang(char arg{}[40]) { //※文字化けで一つ目の括弧を{}としています strcpy(arg[0],"Japanese"); }

【疑問点2】

配列の定義時のように書き変えて見る。

#include <stdio.h>
void chang(char arg[ ][20])
main() { char color[3][20] = { "Konnichiwa", "Hello", "GutenTag" }; chang(color); } void chang(char arg{}[20]) { //※文字化けで一つ目の括弧を{}としています
arg[0][20] = {"Japanese"}; }

コンパイルの結果は、、、

test.c: 関数 ‘chang’ 内:
test.c:14:13: 警告: 代入でポインタからキャスト無しに整数を作成しています [デフォルトで有効]
  arg[0][20] = "Japanese";
             ^

またも失敗。arg[20]はポインタと認識されているのか?しかし、配列を引数にとる関数で、ポインタを受け取ることの出来る場合は1次元配列の時のみのはずである(本によると)。「ポインタからのキャスト」という点はまだ理解に至ってない。

配列の加工については課題を残したが、とりあえず先に進む。

 

【追記】

1次元配列の場合は、置き換えることは簡単に出来る。

#include <stdio.h>
void chang(int arg);  //関数のプロトタイプ宣言(忘れると警告が表示される)

main() {
        int seq[6] = {
        1, 2, 3, 4, 5, 6
        };

        int count;

        for (count = 0; count < 6; count++) {
                printf("変更前の文字列の%d番目は%d\n",count,seq[count]);
        }
        printf("\n");

        chang(seq);

        for (count = 0; count <6; count++) {
                printf("変更後の文字列の%d番目は%d\n",count,seq[count]);
        }
}

void chang(int arg[]) {
        arg[0] = 6;
        arg[1] = 5;
        arg[2] = 4;
        arg[3] = 3;
        arg[4] = 2;
        arg[5] = 1;
        }

1次元なら意図したとおりになる。

変更前の文字列の0番目は1
変更前の文字列の1番目は2
変更前の文字列の2番目は3
変更前の文字列の3番目は4
変更前の文字列の4番目は5
変更前の文字列の5番目は6

変更後の文字列の0番目は6
変更後の文字列の1番目は5
変更後の文字列の2番目は4
変更後の文字列の3番目は3
変更後の文字列の4番目は2
変更後の文字列の5番目は1

また、関数部分は次の様にポインタの引数にしても同じ結果になる。

void chang(int *arg) {
        *arg = 6;
        *(arg + 1) = 5;
        *(arg + 2) = 4;
        *(arg + 3) = 3;
        *(arg + 4) = 2;
        *(arg + 5) = 1;
        }

1次元配列を引数にする場合は、ポインタの形式で表記する方が一般的なのだそう。理由は「ポインタ変数に格納されているアドレスを受け取ることもある」だかららしい。「配列を渡すときは、コピーではなくてアドレスが渡る」という原理である。…だから、関数に対して配列そのものを渡す(上記ならchang(seq))ようにすることが出来る。ちなみに、構造体のコピーは関数に渡せるが、配列全体を関数に渡すことは出来ない。