配列とポインタ: 密接な関係
配列名の実体はポインタである。C言語の衝撃的な事実。
配列の崩壊 (Decay)
配列名が、式の中で「先頭要素へのポインタ」に変換される現象。
糖衣構文
読み書きしやすくするための記法。nums[i] は *(nums+i) のこと。
配列名 = アドレス
C言語における配列名(例: arr)は、ほとんどの場面で
「配列の先頭要素のアドレス」として振る舞います。つまり arr
と &arr[0] は同じです。
このため、配列を関数に渡すと、実際には配列全体がコピーされるのではなく、 「先頭の住所(代表者)」だけが渡されます。これを「配列の崩壊 (Decay)」と呼びます。巨大な配列でも一瞬で関数に渡せるのはこのためです。
配列の崩壊 (Array Decay)
関数内では配列のサイズ情報が失われます。ポインタとして扱われるからです。
void func(int *arr) { // int arr[] と書いても同じ // ここでは arr は単なるポインタ // 配列のサイズはわからない! (sizeof(arr) は 8 になる)}
int main(void) { int nums[5] = {1, 2, 3, 4, 5}; // 配列名 nums は &nums[0] と等価 func(nums);
// 証明 if (nums == &nums[0]) { printf("Same address!\n"); } return 0;}[] の正体
普段使っている <code>[]</code> は、実はポインタ演算のショートカット(糖衣構文)に過ぎません。
int nums[5] = {10, 20, 30, 40, 50};
// 実は同じ意味printf("%d\n", nums[2]); // 30printf("%d\n", *(nums + 2)); // 30
// なので、こんな気持ち悪いことも書ける(書くな)printf("%d\n", 2[nums]); // 30// 理由: *(2 + nums) と展開されるから実践テクニック
セットで渡す
配列を関数に渡すとサイズがわからなくなるため、C言語では一般的に「ポインタ」と「サイズ(要素数)」をセットで渡します。
void process(int *arr, int size) { for(int i=0; i<size; i++) { ... }}演習課題
課題1: 最大値検索
整数配列とそのサイズを受け取り、最大値を返す関数 int max(int *arr, int size) を実装してください。
合格ライン
配列名がポインタになることを説明できる
arr[i] と *(arr+i) が同じだと知っている
配列を関数に渡すときはサイズも必要だと理解している