🗃️ 配列
🎯 配列を関数へ渡す書き方を身につけ、値渡しとの違いに気づける
配列も関数に渡せます。書き方はこうです。
#include <stdio.h>
int sum_of(int a[], int n) // 配列と、その要素数を受け取る
{
int sum = 0;
for (int i = 0; i < n; i++) {
sum += a[i];
}
return sum;
}
int main(void)
{
int score[5] = {60, 85, 40, 90, 75};
printf("%d\n", sum_of(score, 5)); // 350
return 0;
}渡すときは score と角カッコなしの名前だけを書きます。受け取る側は int a[] と書き、個数は空のままにします。
なぜ要素数も一緒に渡すのか
ここが最大のポイントです。関数の中では、受け取った配列が何個の要素を持つのか分かりません。sizeof(a) / sizeof(a[0]) を関数の中で書いても、正しい要素数にはなりません。だから個数を第2引数で一緒に渡すのが、Cの標準的な作法です。
「配列を渡すときは、個数もセットで渡す」。これは決まり文句として覚えてください。
なお int a[] の代わりに int a[5] と書くこともできますが、コンパイラはこの 5 を確認しません。書いてあっても守られない数字は、かえって誤解のもとです。素直に int a[] と書き、個数は引数で渡しましょう。
書き換えると、呼び出し元も変わる
t07-07で「引数はコピーが渡る」と学びました。ところが配列は様子が違います。
#include <stdio.h>
void double_all(int a[], int n)
{
for (int i = 0; i < n; i++) {
a[i] *= 2; // 関数の中で書き換える
}
}
int main(void)
{
int a[3] = {1, 2, 3};
double_all(a, 3);
printf("%d %d %d\n", a[0], a[1], a[2]); // 2 4 6 ← 変わっている!
return 0;
}main の配列がしっかり書き換わりました。値渡しの話と矛盾しているように見えますが、そうではありません。配列そのものはコピーされておらず、渡っているのは「配列が置いてある場所」だからです。関数は、その場所へ出向いて中身を書き換えています。
100万要素の配列を毎回コピーしていたら遅くて仕方がない、という現実的な理由もあります。値渡しの原則は生きたまま、渡っている中身が違う——正体は第2部で明かします。
読むだけなら const を付ける
書き換えない配列には const を付けておくと、うっかり書き換えたときにコンパイラが教えてくれます。
int sum_of(const int a[], int n)戻り値では配列を返せない
関数から配列そのものを返すことはできません。関数の中で作った配列は、return した瞬間にフレームごと片づけられてしまうからです(t07-06の話です)。そのためCでは、「呼び出し元が用意した配列を渡し、関数はそこへ書き込む」という形をよく使います。上の double_all がまさにその形で、標準ライブラリの関数にも同じ設計が並んでいます。
罠・注意
#define で一元管理しましょう。sizeof(a) を書いても配列全体の大きさにはなりません。要素数の計算は、配列を宣言した場所でだけ有効です。int a[] と int a[5] はどちらも書けますが、意味は同じです(個数はチェックされません)。書いてある数字を信用しないでください。まとめ: 配列は「名前だけ」を渡し、要素数を必ず添える。関数の中での書き換えは呼び出し元に届きます。
一連の処理に名前を付けてひとまとめにしたもののこと。同じ処理を何度も書かずに、名前を呼ぶだけで再利用できる部品にあたる。printfやscanfも、標準ライブラリがあらかじめ用意してくれている関数の一種。
配列の要素数を超えた添字にアクセスしてしまう重大な事故のこと。コンパイルは通ってしまうため、実行時に予期せぬ動作を引き起こす。他の変数の領域を書き換えてしまい、原因の分かりにくいバグにつながりやすい。
もっと先へ:ポインタ・メモリ・ファイル入出力・セキュアコーディングを含む全26トラックと、段位検定・模試のフルセットは完全版に収録しています。