C言語:配列

| トラックバック(0)
※C言語初心者の為、内容に不備がある可能性があります。

C言語で配列を定義する。

#include <stdio.h>

int main() {
        int a[4];

        a[0] = 1;
        a[1] = 2;
        a[2] = 100;
        a[3] = 50;

        printf( "a[0] = %d, a[1] = %d, a[2] = %d, a[3] = %d\n", 
a[0], a[1], a[2], a[3] ); return 0; }

実行結果
a[0] = 1, a[1] = 2, a[2] = 100, a[3] = 50

もちろん以下のようにも書ける。
int a[4] = { 1, 2, 100, 50 };

int a[] = { 1, 2, 100, 50 };

int a[4] = { 1, 2 };
↑こうやって書くとa[2], a[3]は「0」が勝手に代入される!

int b[2] = { 1, 2, 3 }; と書くと以下のように怒られる。
array01.c:13: 警告: 配列初期化子内の要素が多すぎます
array01.c:13: 警告: (near initialization for 'b')
array01.c:13: 警告: unused variable 'b'

int c[] = { 1, 2,  };
↑この書き方はなぜか怒られない。

配列の要素は原則として、順番にメモリ上に並んでいるはずなので実際に各
要素のアドレスを出してみる。


#include <stdio.h>

int main() {
        int a[4] = { 1,2,3,4 }, i;

        for( i = 0; i < 4; i++ ) {
                printf( "&a[%d] = %p\n", i, &a[i] );
        }
        printf( "\na = %p\n", a );
        return 0;
}


実行結果
&a[0] = 0xbfa83570
&a[1] = 0xbfa83574
&a[2] = 0xbfa83578
&a[3] = 0xbfa8357c

a = 0xbfa83570

確かに4バイトずつつながってる。
最後のaの出力結果からわかるとおり、配列の名前はその配列の先頭の要素の
アドレスをあらわしている!

ということは、int型の配列であればaで示されている先頭のアドレスより4バイト進めると
a[1]のアドレスになるということ?以下で試してみる。


#include <stdio.h>
int main() {
  int a[] = {10, 20, 30, 40}, i, *p;
  p = a; /* pにa[0]のアドレスを代入 */ 
  for ( i = 0; i < 4; i++ ) {
    printf( "&a[%d] = %p, a[%d] = %d, *(p + %d) = %d\n",
            i, &a[i], i, a[i], i, *(p+i) );
  }
  return 0;
}

結果
&a[0] = 0xbff2ade8, a[0] = 10, *(p + 0) = 10
&a[1] = 0xbff2adec, a[1] = 20, *(p + 1) = 20
&a[2] = 0xbff2adf0, a[2] = 30, *(p + 2) = 30
&a[3] = 0xbff2adf4, a[3] = 40, *(p + 3) = 40


結果から、「int型へのポインタに1を加える」ということは「int型の大きさだけアドレスを1つ進める」
ということがわかる。これはどうも他の型についても同様みたい。

配列の名前aはポインタのように(a + 1)するとaより4番地進んだアドレスをあらわすらしい。
#include <stdio.h>
int main() {
  int *p, i, a[] = {20, 40, 80, 100};
  p = a;
  for( i = 0; i < 4; i++ ) {
    printf( "&a[%d] = %p, (p + %d) = %p, (a + %d) = %p\n",
            i, &a[i], i, p + i, i, a + i);
  }
  return 0;
}

実行結果
&a[0] = 0xbff0a8e8, (p + 0) = 0xbff0a8e8, (a + 0) = 0xbff0a8e8
&a[1] = 0xbff0a8ec, (p + 1) = 0xbff0a8ec, (a + 1) = 0xbff0a8ec
&a[2] = 0xbff0a8f0, (p + 2) = 0xbff0a8f0, (a + 2) = 0xbff0a8f0
&a[3] = 0xbff0a8f4, (p + 3) = 0xbff0a8f4, (a + 3) = 0xbff0a8f4


a[n] は *(a+n) と同じ!
配列とポインタは似てる。でもポインタは変数だから書き換えができた。
でも配列名は先頭要素のアドレスを示すもので、変数ではない。だから書き換えは
できない。
ポインタpに対してはp++可能だけど配列名aに対してa++はできない。
さらに・・・。
a[n]は*(a + n)と同じで、
*(a + n) と*(n + a)は実は同じ。。
だから、、、
「a +n」と[n + a]も実は同じ。。
*(a + n)がa[n]と同じなので、*(n + a)はn[a]と同じ。
つまり、、
a[n]はn[a]と書いても同じ!!!
a[5]を5[a]と書いても同じ意味(気持ち悪い。)
配列とポインタはすっごい密接。

う?ん。なんとなくという感じ。次は多次元配列。

int a[5][2] の配列はメモリ上では以下のように並んでいる。

a[0][0]
a[0][1]
a[1][0]
a[1][1]
a[2][0]
a[2][1]
a[3][0]
a[3][1]
a[4][0]
a[4][1]

1次元の配列と同様に「a」はa[0][0]のアドレスを表している。

配列の初期化方法
int a[5][2] = {0,1,2,3,4,5,6,7,8,9};
もしくは
int a[][2] = {0,1,2,3,4,5,6,7,8,9};
最初の要素数のみ省略が可能。

メモリに並ぶ順番⇒「多次元配列は、一番外側(右)から回る

a[2][0]
は式の中に2つの[]が存在している。
演算子の優先順位は等しいので結合規則 通りに→で評価される。
そのため(a[2])[0]と書いても同じ。
a[2]を仮に「A」と置き換えた場合A[0]となり、一次元の配列のように見える。
するとAが配列名で、A[0]のアドレスを表していることになる。
またA[0]は*(A + 0)とも書けた。
これを元に戻すと、、
*(a[2] + 0)
a[2]自体は*(a + 2)とかけたのでa[2][0]は・・・
*( *(a + 2) + 0 )
と書ける。


以上のことを確かめるプログラムを書いてみる。
#include <stdio.h>

int main() {
  int a[3][2] = {10, 20, 30, 40, 50, 60};
  int i, j;

  /* 各要素の値を確かめる */
  for( i = 0; i < 3; i++ ) {
    for( j = 0; j < 2; j++ ) {
      printf( "a[%d][%d] = %d\n", i, j, a[i][j] );
    }
  }
  printf( "\n" );

  /* 各要素のアドレスを確かめる */
  for( i = 0; i < 3; i++ ) {
    for( j = 0; j < 2; j++ ) {
      printf( "&a[%d][%d] = %p   (a[%d] + %d) = %p\n",
              i, j, &a[i][j], i, j, ( a[i] + j ) );
    }
  }
  printf( "\n" );

  /* a[m][n]を*(*(a + m) + n)と書けるのか確かめる */
  for( i = 0; i < 3; i++ ) {
    for( j = 0; j < 2; j++ ) {
      printf( "*(*(a + %d) + %d) = %d\n",
              i, j, *(*(a + i) + j));
    }
  }

  return 0;
}


実行結果
a[0][0] = 10
a[0][1] = 20
a[1][0] = 30
a[1][1] = 40
a[2][0] = 50
a[2][1] = 60

&a[0][0] = 0xbf97b3e0   (a[0] + 0) = 0xbf97b3e0
&a[0][1] = 0xbf97b3e4   (a[0] + 1) = 0xbf97b3e4
&a[1][0] = 0xbf97b3e8   (a[1] + 0) = 0xbf97b3e8
&a[1][1] = 0xbf97b3ec   (a[1] + 1) = 0xbf97b3ec
&a[2][0] = 0xbf97b3f0   (a[2] + 0) = 0xbf97b3f0
&a[2][1] = 0xbf97b3f4   (a[2] + 1) = 0xbf97b3f4

*(*(a + 0) + 0) = 10
*(*(a + 0) + 1) = 20
*(*(a + 1) + 0) = 30
*(*(a + 1) + 1) = 40
*(*(a + 2) + 0) = 50
*(*(a + 2) + 1) = 60


おんなじだ?。

少し実用的に使ってみる。

#define NO 3 /* NOを3と定義 */

#include <stdio.h>

int main() {
  int point[][2] = {
    80, 80,          /* 一人目の点数 英語、数学 */
    100, 98,         /* 二人目の点数 英語、数学 */
    60, 80,          /* 三人目の点数 英語、数学 */
  };

  int i, j, sum = 0, p_sum[NO];
  double ave;

  /* 英語の平均点を求める */
  for( i = 0; i < NO; i++ ) {
    sum += point[i][0];
  }
  ave = (double)sum / NO;
  printf( "英語の平均点は%5.1f点です\n", ave );

  /* 数学の平均点を求める */
  for( i = 0; i < NO; i++ ) {
    sum += point[i][1];
  }
  ave = (double)sum / NO;
  printf( "数学の平均点は%5.1f点です\n", ave );

  /* 個人別の合計点数を求める */
  for( i = 0; i < NO; i++ ) {
    p_sum[i] = 0;
  }

  for( i = 0; i < NO; i++ ) {
    for( j = 0; j < 2; j++ ) {
      p_sum[i] += point[i][j];
    }
    printf( "出席番号%dの総得点 %d\n", i + 1, p_sum[i] );
  }
  return 0;
}

実行結果
英語の平均点は 80.0点です
数学の平均点は166.0点です
出席番号1の総得点 160
出席番号2の総得点 198
出席番号3の総得点 140

配列はこんなところで。
猫でもわかるC言語プログラミング (猫でもわかるプログラミングシリーズ)
粂井 康孝
ソフトバンククリエイティブ
売り上げランキング: 141668
おすすめ度の平均: 4.0
3 悪いわけではありませんが
5 猫の好きな私に向いた入門書
5 困ったもんだ
3 手前のレビュアー達の書く通り
4 猫でもわかるかな?

トラックバック(0)

トラックバックURL: http://www.mogumagu.com/mt/mt-tb.cgi/11

2012年1月

1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31        

ウェブページ

このブログ記事について

このページは、モグマグが2010年4月16日 23:59に書いたブログ記事です。

ひとつ前のブログ記事は「C言語:ポインタ突入」です。

次のブログ記事は「C言語:文字と文字列」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

Powered by Movable Type 5.01