C言語の配列とポインタの使い方~この2つの関係性について~
- 作成日: 2021-09-23
- 更新日: 2024-03-12
- カテゴリ: C言語
ポインタと配列の使い方
C言語にはポインタと配列があります。
これら2つはいっしょに使うことが出来ます。
具体的にどう使うのか、その関係性についてこの記事で解説します。
結論から言うとポインタに配列を代入して使うこと多いです。
ポインタへの配列の代入方法、使用方法についても解説していきます。
C言語や他の言語を扱うYoutubeも公開しています。
興味がある方は以下のリンクからご覧ください。
関連記事
C言語のポインタのポインタを解説
プログラミングのポインタをわかりやすく解説【C言語】
C言語のポインタのメリットとは?コピーしますかメモリを共有しますか
C言語の配列の使い方
- C言語の配列とポインタの使い方~この2つの関係性について~
ポインタと配列の関係性は?
C言語のポインタと配列の関係はどういったものでしょうか?
この2つは切っても切れない関係性を持っています。
というのも、ポインタと配列は一緒に使うことが多いのです。
ポインタは配列を便利に扱うために存在します。
具体的にはどういうことなのでしょうか?
配列はポインタ変数に代入できる
配列変数はポインタ変数に代入することができます。
つまり配列をポインタに保存できるわけです。
ということはポインタを通して配列を使うことが可能になるわけです。
int a[] = {1, 2};
int *b = a; // ok
これは実際に可能で、ポインタから代入された配列にアクセスすることができます。
関連記事
C言語で配列を使って代入する方法
配列変数にポインタは代入できない
逆に配列変数にはポインタは代入できません。
例えば↓のようなコードはだめです。
int a[] = {1, 2};
int *b;
a = b; // error: assignment to expression with array type
配列は配列型であってポインタ型ではありません。
そのためポインタや他の変数のアドレスを代入することはできません。
ポインタには配列のアドレスが保存される
ポインタには配列の何が保存されるのかと言うと、配列のアドレスが保存されます。
配列を参照することで配列のアドレスを取得することが可能です。
ポインタ変数には配列のアドレスを代入し、ポインタ変数はそのアドレスを参照して配列にアクセスします。
つまりポインタから配列の要素の参照や要素への代入などの処理は、配列のアドレスを参照して行っているということになります。
逆に言うと、この配列のアドレスが間違っているとポインタ変数は機能しません。
ちゃんと存在する配列のアドレス、それもメモリの範囲内のアドレスである必要があります。
配列のアドレスはどうやって確認するか?
ポインタ変数に代入する配列のアドレスはどうやって確認するのでしょうか?
その前に配列の仕組みですが、配列は連続したメモリ領域に確保されます。
つまり配列の先頭のアドレスがわかれば、配列の各要素にもアクセスできるということになります。
配列の先頭アドレスは、ただ単に配列変数を参照することで確認することができます。
配列の先頭アドレスを表示する
それでは配列の先頭アドレスを表示してみましょう。
↓のコードで確認できます。
#include <stdio.h>
int main(void) {
int a[] = {1, 2};
printf("先頭アドレス = %p\n", a);
// 先頭アドレス = 0x7fff934198d8
return 0;
}
↑のコードで出力される配列a
のアドレスは、環境によって変わります。
printf()
の出力指定子%p
を使うとアドレスを出力できます。
↑のように配列の先頭アドレスは、ただ単に配列変数を参照するだけで確認できます。
配列の要素のアドレスを表示する
では次に配列の要素のアドレスを出力してみましょう。
#include <stdio.h>
int main(void) {
int a[] = {1, 2};
printf("先頭アドレス = %p\n", a);
// 先頭アドレス = 0x7ffdffb0dfc8
printf("要素[0]のアドレス = %p\n", &a[0]);
// 要素[0]のアドレス = 0x7ffdffb0dfc8
printf("要素[1]のアドレス = %p\n", &a[1]);
// 要素[1]のアドレス = 0x7ffdffb0dfcc
printf("(a + 1)のアドレス = %p\n", (a + 1));
// (a + 1)のアドレス = 0x7ffdffb0dfcc
return 0;
}
↑のように&a[0]
とすると配列の先頭アドレス(0番目の要素)にアクセスできます。
そして&a[1]
とすると配列の2つ目の要素のアドレスにアクセスできます。
(a + 1)
とした場合は同様に2つ目の要素のアドレスにアクセス可能です。
つまり&a[1]
と(a + 1)
は同じ意味になります。
C言語ではこの2つの書き方をどちらも使うことが出来ます。
この方法で取得できるアドレスはポインタ変数に代入することが出来ます。
その場合は、たとえば配列の2番目の要素のアドレスをポインタに代入したとします。
そうするとそのポインタからは2番目の要素を基点に配列にアクセスできるようになります。
ポインタに配列を代入する
では実際にポインタ変数に配列を代入します。
↓のコードで確認できます。
int a[] = {1, 2};
int *b = a; // ok
配列a
のアドレスをポインタ変数b
に代入しています。
こうすることでポインタ変数b
から配列a
にアクセスすることができるようになります。
配列とポインタの型は同じにしておく必要があります。
↑の例で言うと、配列a
はint
型の配列です。
そのためポインタ変数b
もint
型のポインタにしています。
例外としてはvoid
型のポインタです。
void
型のポインタはあらゆる型を代入することが出来ます。
配列の要素のアドレスをポインタに代入する
次に配列の要素のアドレスをポインタ変数に代入してみたいと思います。
int a = {1, 2};
int *b = &a[1];
↑の場合、配列a
の添え字1
の要素(先頭から2番目)のアドレスをポインタ変数b
に代入しています。
こうするとポインタ変数b
には配列a
の2番目の要素のアドレスが保存されます。
そのため配列b
にアクセスしてb[0]
のように参照すると、それは配列a
の2番目の要素になります。
関連記事
C言語で配列を使って代入する方法
ポインタに代入した配列はどのように使うか?
配列を代入したポインタ変数はどのように使うのでしょうか?
なにかポインタ変数に代入することで使い方が変わるのでしょうか?
具体的に見ていきたいと思います。
関連記事
C言語で配列を使って代入する方法
基本的には配列と同じ使い方
配列をポインタ変数に代入した場合、そのポインタはほとんど配列と同じ振る舞いになります。
つまり添え字による要素の参照や要素への代入なども配列と同じように行えます。
注意点としてはsizeof
演算子の挙動が変わるということです。
sizeofの挙動に注意
配列を代入したポインタ変数は、厳密に言うと配列型ではなくてポインタ型です。
ポインタ型を通して配列にアクセスしてるわけです。
配列をsizeof
した場合、その結果は配列全体のバイト数になります。
しかしポインタ変数をsizeof
した場合はポインタ変数のバイト数になります。
つまり配列全体のバイト数を取得しようとしてポインタをsizeof
した場合、予想外の結果が返ってくることになります。
このように配列とポインタのsizeof
の挙動は違いますので注意が必要です。
要素の参照
配列を代入したポインタから、配列の要素を参照したい場合です。
これは配列の場合と同じく角カッコと添え字を使って参照します。
#include <stdio.h>
int main(void) {
int a[] = {1, 2};
int *b = a;
printf("%d\n", b[0]); // 1
printf("%d\n", b[1]); // 2
return 0;
}
↑の場合、配列a
のアドレスをポインタ変数b
に代入しています。
そしてポインタ変数b
に角カッコと添え字でアクセスしています。
その結果、配列a
の各要素にアクセスすることができて、要素の値を参照できています。
要素への代入
配列を代入したポインタから、配列の要素に代入する場合です。
これも配列と同じように書くことが出来ます。
#include <stdio.h>
int main(void) {
int a[] = {1, 2};
int *b = a;
b[0] = 100;
printf("b[0] = %d\n", b[0]);
// b[0] = 100
printf("a[0] = %d\n", a[0]);
// a[0] = 100
return 0;
}
↑の場合、ポインタ変数b
には配列a
のアドレスが代入されています。
b[0] = 100;
とやってポインタ変数を通じて配列の1番目の要素の値を上書きします。
その結果、配列a
の1番目の要素の値が書き変わります。
このようにポインタ変数を使うと配列の値を間接的に書き換えることができます。
関連記事
C言語で配列を使って代入する方法
sizeofによるサイズの確認方法
さきほども書きましたが配列とポインタのsizeof
の挙動は違います。
ここが配列とポインタを扱う上でC言語のややこしいところです。
逆に言うとここを理解すれば配列とポインタを扱う上でややこしいバグを生まなくて済みます。
配列は配列全体のサイズを確認できる
sizeof
で配列を参照するとその配列全体のサイズを得ることができます。
#include <stdio.h>
int main(void) {
int a[100];
printf("a[] size = %d bytes\n", sizeof(a));
// a[] size = 400 bytes
return 0;
}
↑の場合、配列a
は要素数100のint
型の配列です。
つまりsizeof(int) * 100
が配列全体のサイズです。
結果は400バイトになります。
ポインタは配列のサイズを確認できない
ではポインタはどうでしょうか?
配列を代入したポインタ変数のサイズを確認してみます。
#include <stdio.h>
int main(void) {
int a[100];
int *b = a;
printf("sizeof *b = %d bytes\n", sizeof(b));
// sizeof *b = 8 bytes
return 0;
}
筆者の環境では8バイトと出力されました。
これはポインタ変数のサイズが出力されています。
つまり、配列を代入したポインタをsizeof
した場合、配列全体のサイズではなく、ポインタ変数のサイズが取得できるわけです。
ここは間違えやすいので注意が必要です。
番兵を持った配列とポインタ
配列を代入したポインタはsizeof
で配列全体のサイズを得ることができません。
そのためポインタから配列の長さを得て、for
文で回すなどと言ったこともできません。
ポインタをfor
文で回したい場合、配列に番兵を持たせておくと可能になります。
番兵とは配列内に保存する終端要素のことです。
たとえば↓のように使います。
#include <stdio.h>
int main(void) {
int a[] = {1, 2, -1};
int *b = a;
for (int *p = b; *p != -1; p += 1) {
printf("%d\n", *p);
}
return 0;
}
↑の場合、配列a
に保存されている-1
という要素が番兵です。
for
文でポインタを回す時に、この-1
をチェックしてfor
文の終了条件にします。
こうするとポインタをインクリメントしていって配列の各要素にアクセスすることができるようになります。
番兵を使った場合、ポインタ変数単体でfor
ループを回すことができるようになります。
番兵を使わない場合は配列の長さを別で取得しておく必要があります。
関連記事
C言語の配列の要素数を得る方法
constを付けた配列とポインタ
配列とポインタにはconst
を付けることができます。
const
を付けた配列はconst
を付けたポインタに保存可能です。
const int a[] = {1, 2};
const int *b = a;
おわりに
今回はC言語の配列とポインタの使い方とその関係性について見てきました。
ポインタは配列にアクセスできますが、sizeof
の挙動が変わるなど一部注意が必要になります。
🦝 < 配列とポインタはマブダチ
🐭 < バグりそうなやつは大体友達