8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

配列のアドレスとポインタの関係性

Last updated at Posted at 2020-10-08

##前書き
前回ポインタの理解のためのアドレスの考え方という憎たらしい記事を書いてしまったので、その続きとして読んでいただければと思う。
前回に引き続き初学者から、中級程度の理解の方への記事である。(ちょっとテンポが上がるので初学者向けでもなくなるかも…)

 配列ー?完璧だから問題なーい

とか。

お前の記事う〇こやな。

とかいう人はUターンしてほしい。
それ以外は次へ進んでほしくない。

追記(2020/10/12)
とてもたくさんの意見をいただいたので注記を追加させていただきます。
記事の中で明らかに記述が誤解を招くもの、あるいは初心者向けに不適切な内容については今後もご意見を頂戴したく存じます。(誤字のご指摘や、記述のない内容などご意見ありがとうございます!)
コメントの多さと、著者の力のなさから編集作業にお時間いただいております。また、コメントの中でも意見の分かれる内容については編集の手をいったん止めさせていただくことをご了承ください。

##配列とアドレスの話
「アドレスの話した後に配列の話ってなんだよ」と思うだろうが、今後の理解のためには(特に多次元配列とポインタのポインタの理解とかのためには)ぜひ読んでいただきたい内容である。
さて本題に入ろう。

以下の__配列の先頭の要素のアドレス__を表すならどうしたらいいだろう?

/*定義*/
//char型の要素数6のAという配列
char A[6];

要素が6つある__配列の先頭アドレス__を表したい。
以下のように書く人もいるだろう。

//配列Aの先頭のアドレス
&A[0]

もちろん、前回の記事を見て書いてくれたのならすごくうれしいし花丸を差し上げたい。
が、これ以外にも実は表し方がある。それが以下のような表し方である。

//配列Aの先頭アドレス
A

は?と思う人もいれば、当たり前だと思う人もいると思う。
配列のアドレスは意外と議論に上がりにくく見落とされがちなポイントだと個人的に思う。

__配列の先頭アドレス__は__配列名だけ__で示すことができる。

つまり__A&A[0]__は指すものが同じということである。

追記(2020/10/12)
@mitsuruk9999 様からご指摘をいただきました。
あくまでこの一文で示したかったことは「A&A[0]は__指すもの__が実質同じになる」ということです。もちろん、A&A[0]の性質は根本的に異なります。
&A[0]は配列の0番目の要素のアドレスを、Aは配列全体における先頭要素のアドレスとして配列の先頭のアドレスを指します。
これらのことから、二つの指すものが実質同じであると記述しました。

##ポインタと配列の関係

さて、配列における先頭アドレスはわかった。__アドレスの指す内容__を扱いたいならどうすればいいだろう?
前回のようにポインタ変数に入れて扱おうか?
試しにポインタ変数を宣言して、その内容として配列の先頭アドレスを格納させてみる。

/*宣言文*/
//char型の要素数6のAという配列
//char型のポインタ変数
char A[6] = "ABCDE";
char *A_p;

/*処理*/
//ポインタ変数に配列の先頭アドレスを格納
A_p = A;
//格納されたアドレスの中身を書き換える
*(A_p) = 'N';
//配列Aに格納された要素をすべて表示する。
printf("%s",A);

まずA[6]の配列の要素をABCDEとして初期化した。その後実際にA_pというchar型のポインタ変数を宣言し、その内容に配列の先頭アドレスを格納させた。
そのあとのprintfの%sは、Aを文字型配列として表示させるために用いる書式指定子である。

はて、この出力は以下の通りになる。

NBCDE

先頭アドレスをポインタ変数に格納して、そのアドレスが指す先、つまり先頭アドレスの要素を書き換えたのだから当たり前だ。

でも、こんな風に__わざわざ__ポインタ変数を用意する必要はあるだろうか…?先頭アドレスがわかっているのだから、ほかに短く書けないだろうか?
否、以下のように書ける。

/*宣言文*/
//char型の要素数6のAという配列
char A[6] = "ABCDE";

*A = 'N';
printf("%s",A);

このようにしても、先ほどのポインタ変数を用意した場合と同じ出力となる。
つまり__配列として宣言しても、ポインタのように扱って中身をいじくることができる__のだ。

##先頭要素以外の要素に対するアプローチ
__配列の先頭アドレスをそのままポインタ変数のように扱うことができる__のがわかった。
これもまた飛ばされがちな内容の一つである。(もちろん学校ではやってた、私が寝てただけ)

つまり、__*(配列名)__で、__先頭アドレスの内容__を表すことができる。
なら、その次の要素を表したり書き換えたりすることはできるだろうか?

やってみよう。
以下のように書けば、配列の先頭から何番目の要素と指定することができる。

*(A + "何番目の要素か")

0番目なら、__*(A + 0)とすれば__先頭アドレスの要素先頭から2番目の要素__なら*(A + 2)__でよいのだ。
試しに少し書き方が楽になる例をここに示す。

/*宣言文*/
//char型の要素数6のAという配列
//int型の初期値0のiという変数
char A[6] = "ABCDE";
int i = 0;

/*処理*/
//*(A+i)
while (*(A + i) != '\0')
{
    *(A + i) = 'F' + i ;
    i++;
}
printf("%s",A);

出力結果は以下の通り。

FGHIJ

c言語において任意に定義されたchar型の配列の最後にはnull文字('\0')が付加されている。
(そのために要素数6として6文字目がnull文字になるようにしているが、それはここでは扱わない)
つまり、このソースリストにおいては配列の終端文字('\0')になるまでの間一文字ずつ値を代入していっている。

つまり、配列であっても添え字を必ずしも用いなくてもポインタで代用できる。
また、配列に対してはわざわざポインタ変数を別に作る必要がないと分かったと思う。

##配列をポインタで
先にここまでの流れをまとめる。

  1. 配列のアドレスの表し方
  2. 配列の先頭アドレスを示すポインタ表現
  3. 配列の要素をポインタ表現で表す

と進んできた。
__配列の先頭アドレス__は__配列名__で表せたのだ。
今度は__ポインタ変数__を定義して__配列として扱う__ことについて考えたい。

例えば以下の変数について考えてみよう

/*char型のポインタ変数a*/
char *a;

この要素に無理やり値を代入してみる。

/*char型のポインタ変数a*/
char *a = "ABCDE";

printf("%s",a);

出力結果は以下の通り。

ABCDE

今まで、ポインタ変数の意味はアドレスを格納するだけで済んでいたのに、今度は実際の値を代入することができるようになった。
あたかも実際の値を代入できているかのように表示できるようになった。
実際には値を格納しているメモリのアドレスを格納させている。

追記(2020/10/8)
ご指摘をいただけたので、上記の説明について追記いたします。
実際にはポインタ変数aに値を代入しているわけではなく、"ABCDE"という文字列が格納されたメモリのアドレスを代入しています。
実際の値をいじるのであれば、for文でaから(a + 4)までAからEを入れていくほうが適切です。

追記(2020/10/12)
@sigma_signature 様からご指摘をいただきました。
初期化に関しての記述がありませんでした。申し訳ありません。
ポインタを定義しただけでは値が定まっていません。
初期化に関する記述は他の記事を参照していただけますと幸いです。

例えばこの状態で以下のように表示させてみる。

/*宣言文*/
/*char型のポインタ変数a*/
char *a;
//while文を回すためのint型のiという変数を定義
int i = 0;

/*処理*/
//初期化
a="ABCDE";
while(*(a + i) != '\0')
{
    printf("%c", *(a + i));
    i++;
}

この出力結果は以下のようになる。

ABCDE

一文字ずつ表示させているだけなので先ほどとやっていることは変わらない。
だが、先ほど配列に用いたアプローチと同じことがポインタで宣言した場合にも行うことができた。

これができるなら、添え字を使った表現もできるだろうか?
もちろんできる。
以下にそのコードを示す。やることは同じ。ポインタで定義された配列の要素を1文字ずつ表示させる。

/*宣言文*/
/*char型のポインタ変数a*/
char *a;
//while文を回すためのint型のiという変数を定義
int i = 0;

/*処理*/
//初期化
a="ABCDE";
while(a[i] != '\0')
{
    printf("%c", a[i]);
    i++;
}

出力は先ほどと同じなので割愛するが、ポインタで宣言しても、配列として宣言しても、処理を行う際は同じようにかけることが分かった。

ここまでのポインタ変数の役割と、その使い方、配列の性質をまとめておこう。

  1. 絶対忘れないでほしい。本来のポインタはアドレスを格納するもの。

  2. __*(ポインタ変数の名前)とすれば(ポインタ変数)__が格納するアドレスの中身を扱うこともできる。

  3. __配列の先頭アドレス__は__配列名__で表される。

  4. 配列はポインタを使ってアクセスすることができる。 配列を定義したら、ポインタ変数を宣言しなくても、__((配列名(つまり先頭アドレス)) + "何番目の要素か")__とすれば__その要素のアドレス__を示すことができる。

  5. 要素の内容__を扱いたいときは*をアドレスの前につければよい__。

6.ポインタ変数__として定義されたものは配列になることができる__配列と同様の書き方でアクセスすることができる。またその際には配列演算子'[]'を用いる。

追記(2020/10/12)
こちらの__6番目__の記述に関して @hidezzz 様 @mitsuruk9999 様からご指摘をいただきました。
正しくは上記に書き換えさせていただいた通りでございます。
表現方法等に関してご意見ある方はコメントいただけますと幸いです。

##まとめ

偉ぶった文章書いて申し訳ない。読んでくれてありがとう。

ここまでが初心者が始めてポインタを扱って一番詰まる内容だと思われる。
このすべてを一度に覚えるのは厳しいので、ぜひ何度か読んで実際に書いて試してもらいたい。
また、今後多次元配列とポインタについての学習の際にもこの内容は非常に大切になる。
どんなに自信があっても、あいまいな理解のままでは次が理解できないので中上級者の皆さんも一度目を通していただきたい。

意見や質問、批判は随時受付中~~(できれば批判はお手柔らかに頼みたい)~~

次は中上級者向けの多次元配列とポインタの関係について書く~~(予定。こうやってまた自分の首を絞める)~~。

追記(2020/10/12)
へこたれずに書いてみる予定(いつ書き終わるのかという質問はご法度)

8
4
25

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
8
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?