0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

はじめに

この記事について

「C言語の基礎を学ぼう」をテーマに、自身の知識 + α をアドベントカレンダーにまとめます。
25日間でC言語をマスターしよう - Qiita Advent Calendar 2025 - Qiita

こんな方を対象としています

  • コンピュータがプログラムをどのように動かしているか知りたい/知らない方

  • プログラミングをしてみたい方

  • C言語初心者の方

キーワード

  • 配列

  • ポインタ

説明

配列とポインタ

配列のアドレスを見てみましょう。

#include <stdio.h>
int main(void) {
    short a[5];
    int i;
    for (i = 0; i < 5; i++) {
        printf("&a[%d] : %p\n", i, &a[i]);
    }
    return 0;
}
&a[0] : 00000078B0FFFAF2
&a[1] : 00000078B0FFFAF4
&a[2] : 00000078B0FFFAF6
&a[3] : 00000078B0FFFAF8
&a[4] : 00000078B0FFFAFA

shortは2バイトですので、配列は 連続したアドレスにメモリが確保されている ことがわかります。

次に、配列の変数名 a をそのままアドレスとして表示してみます。

#include <stdio.h>
int main(void) {
    short a[5];
    int i;
    for (i = 0; i < 5; i++) {
        printf("&a[%d] : %p\n", i, &a[i]);
    }
    printf("a     : %p\n", a);
    return 0;
}
&a[0] : 000000A19FDFF712
&a[1] : 000000A19FDFF714
&a[2] : 000000A19FDFF716
&a[3] : 000000A19FDFF718
&a[4] : 000000A19FDFF71A
a     : 000000A19FDFF712

&a[0]a が等しいことがわかります。

以上より、配列は下記のような特徴があります。

  • aa[0] のポインタ

  • &a[n]a + n と等しい(アドレス)

  • a[n]*(a + n) と等しい(値)

表にまとめると下記のとおりです。

配列のアドレス 書き方2
&a[0] a
&a[1] a + 1
&a[2] a + 2
&a[3] a + 3
&a[4] a + 4
配列の値 書き方2
a[0] *a
a[1] *(a + 1)
a[2] *(a + 2)
a[3] *(a + 3)
a[4] *(a + 4)

プログラムで確かめます。

#include <stdio.h>
int main(void) {
    int a[5] = {11, 12, 13, 14, 15};
    int i;
    for (i = 0; i < 5; i++) {
        printf("&a[%d] : %p   ", i, &a[i]);
        printf("a + %d : %p\n", i, a + i);
    }
    printf("\n");
    for (i = 0; i < 5; i++) {
        printf("a[%d] : %d   ", i, a[i]);
        printf("*(a + %d) : %d\n", i, *(a + i));
    }
    return 0;
}
&a[0] : 000000FBFEBFFB30   a + 0 : 000000FBFEBFFB30
&a[1] : 000000FBFEBFFB34   a + 1 : 000000FBFEBFFB34
&a[2] : 000000FBFEBFFB38   a + 2 : 000000FBFEBFFB38
&a[3] : 000000FBFEBFFB3C   a + 3 : 000000FBFEBFFB3C
&a[4] : 000000FBFEBFFB40   a + 4 : 000000FBFEBFFB40

a[0] : 11   *(a + 0) : 11
a[1] : 12   *(a + 1) : 12
a[2] : 13   *(a + 2) : 13
a[3] : 14   *(a + 3) : 14
a[4] : 15   *(a + 4) : 15

2次元配列とポインタ

2次元配列は少々特殊ですが、似たような書き方をすることができます。

まずはアドレスを確認してみましょう。

#include <stdio.h>
int main(void) {
    int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
    int i, j;
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 2; j++) {
            printf("&a[%d][%d] : %p\n", i, j, &a[i][j]);
        }
    }
    return 0;
}
&a[0][0] : 00000024EEDFF820
&a[0][1] : 00000024EEDFF824
&a[1][0] : 00000024EEDFF828
&a[1][1] : 00000024EEDFF82C
&a[2][0] : 00000024EEDFF830
&a[2][1] : 00000024EEDFF834

連続したアドレスになっています。

次に a[0] a[1] a[2] の値を確認します。

#include <stdio.h>
int main(void) {
    int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
    int i, j;
    for (i = 0; i < 3; i++) {
        for (j = 0; j < 2; j++) {
            printf("&a[%d][%d] : %p\n", i, j, &a[i][j]);
        }
    }
    for (i = 0; i < 3; i++) {
        printf("a[%d] : %p\n", i, a[i]);
    }
    return 0;
}
&a[0][0] : 00000038BBDFFDE0
&a[0][1] : 00000038BBDFFDE4
&a[1][0] : 00000038BBDFFDE8
&a[1][1] : 00000038BBDFFDEC
&a[2][0] : 00000038BBDFFDF0
&a[2][1] : 00000038BBDFFDF4
a[0] : 00000038BBDFFDE0
a[1] : 00000038BBDFFDE8
a[2] : 00000038BBDFFDF0

&a[0][0] = a[0] &a[1][0] = a[1] &a[2][0] = a[2] となっています。

次に a[0] a[1] a[2] a の値を確認します。

#include <stdio.h>
int main(void) {
    int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
    int i;
    for (i = 0; i < 3; i++) {
        printf("&a[%d] : %p\n", i, &a[i]);
    }
    printf("a     : %p\n", a);
    return 0;
}
&a[0] : 000000B29CFFF7E0
&a[1] : 000000B29CFFF7E8
&a[2] : 000000B29CFFF7F0
a     : 000000B29CFFF7E0

&a[0]a が等しいことがわかります。

まとめると下記のようになります。

配列のアドレス 書き方2 書き方3
&a[0][0] a[0] *a
&a[0][1] a[0] + 1 *a + 1
&a[1][0] a[1] *(a + 1)
&a[1][1] a[1] + 1 *(a + 1) + 1
&a[2][0] a[2] *(a + 2)
&a[2][1] a[2] + 1 *(a + 2) + 1
配列の値 書き方2 書き方3
a[0][0] *a[0] **a
a[0][1] *(a[0] + 1) *(*a + 1)
a[1][0] *a[1] **(a + 1)
a[1][1] *(a[1] + 1) *(*(a + 1) + 1)
a[2][0] *a[2] **(a + 2)
a[2][1] *(a[2] + 1) *(*(a + 2) + 1)

最後にプログラムでも確認してみましょう。
わかりやすくするためにfor文を使わずに書いています。

#include <stdio.h>
int main(void) {
    int a[3][2] = {{1, 2}, {3, 4}, {5, 6}};
    // アドレス
    printf("%p %p %p\n", &a[0][0], a[0],     *a          );
    printf("%p %p %p\n", &a[0][1], a[0] + 1, *a + 1      );
    printf("%p %p %p\n", &a[1][0], a[1],     *(a + 1)    );
    printf("%p %p %p\n", &a[1][1], a[1] + 1, *(a + 1) + 1);
    printf("%p %p %p\n", &a[2][0], a[2],     *(a + 2)    );
    printf("%p %p %p\n", &a[2][1], a[2] + 1, *(a + 2) + 1);
    // 値
    printf("%d %d %d\n", a[0][0], *a[0],       **a            );
    printf("%d %d %d\n", a[0][1], *(a[0] + 1), *(*a + 1)      );
    printf("%d %d %d\n", a[1][0], *a[1],       **(a + 1)      );
    printf("%d %d %d\n", a[1][1], *(a[1] + 1), *(*(a + 1) + 1));
    printf("%d %d %d\n", a[2][0], *a[2],       **(a + 2)      );
    printf("%d %d %d\n", a[2][1], *(a[2] + 1), *(*(a + 2) + 1));
    return 0;
}
0000000BC5BFFE00 0000000BC5BFFE00 0000000BC5BFFE00
0000000BC5BFFE04 0000000BC5BFFE04 0000000BC5BFFE04
0000000BC5BFFE08 0000000BC5BFFE08 0000000BC5BFFE08
0000000BC5BFFE0C 0000000BC5BFFE0C 0000000BC5BFFE0C
0000000BC5BFFE10 0000000BC5BFFE10 0000000BC5BFFE10
0000000BC5BFFE14 0000000BC5BFFE14 0000000BC5BFFE14
1 1 1
2 2 2
3 3 3
4 4 4
5 5 5
6 6 6

練習

2次元配列の練習です。

1. リバーシ(オセロ)の盤面を表示しよう

ランダムなリバーシ(オセロ)の盤面を表示してみよう。

  • $8 \times 8$ の盤面
  • 各マスごとに黒、白、空白をランダムに設定する
○   ●   ● ○ ● ○ 
○ ● ●   ○   ● ●
○ ○ ○   ●   ○
  ●   ○ ● ○ ○ ○
○ ○ ● ● ○ ●
  ○     ○     ●
○ ● ○ ● ○ ● ○ ●
● ○ ●       ● ○

ポイント

2次元配列をランダムに0、1、2で初期化しましょう。
その後、0を空白、1を黒、2を白として表示すればよさそうです。

疑似乱数は下記のように使用します。

#include <stdio.h>
#include <stdlib.h> // srand()、rand()に必要
#include <time.h> // time()に必要
int main(void) {
    int r;

    // 疑似乱数生成準備としてシード値を設定する。
    // シード値にはプログラム実行時刻を用いることが多い。
    // (実行ごとにランダムにするため)
    srand((unsigned int)time(NULL));

    // rand()で乱数を生成する。
    // 生成した値を加工して欲しい乱数にする。
    // 今回は3で割ることで0,1,2の乱数を生成する。
    r = rand() % 3;

    printf("%d", r);
    return 0;
}
1

解答例

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(void) {
    int reversi[8][8], i, j;
    srand((unsigned int)time(NULL));
    // ランダムに初期化
    for (i = 0; i < 8; i++) {
        for (j = 0; j < 8; j++) {
            reversi[i][j] = rand() % 3;
        }
    }
    // 表示
    for (i = 0; i < 8; i++) {
        for (j = 0; j < 8; j++) {
            switch (reversi[i][j]) {
                case 0: printf("● "); break;
                case 1: printf("○ "); break;
                case 2: printf("  "); break;
            }
        }
        printf("\n");
    }
    return 0;
}
○   ●   ● ○ ● ○ 
○ ● ●   ○   ● ●
○ ○ ○   ●   ○
  ●   ○ ● ○ ○ ○
○ ○ ● ● ○ ●
  ○     ○     ●
○ ● ○ ● ○ ● ○ ●
● ○ ●       ● ○

内部データは数値にしておき、表示のときに変換するといった手法はよく使われます。

おわりに

通常の配列がポインタのように操作できるのは理解しやすいと思いますが、2次元配列とポインタの関係は複雑ですね。まだ完全には理解しきれていない気がします。むずかC。

参考文献

↓↓↓ はじめてプログラミングを学んだときに読んだ本です ↓↓↓
詳細(プログラミング入門 C言語)|プログラミング|情報|実教出版

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?