- ベストアンサー
配列表現とポインタ表現
配列とポインタの2通りの表現で表せる場面によく遭遇します。例えば、pというdouble型の配列に乱数を10個発生させて格納したい時など、 for(i=0;i<10;i++) *(p+i) = (double) rand(); for(i=0; i<10; i++) p[i] = (double) rand(); のように、配列とポインタの2通りの表現が考えられると思いますが、複雑な場合などは特に、見た感じは配列のほうが分かりやすいと思います。 まだ、C言語の初級から中級向けの本しか読んでいないのですが、標準関数の多くがポインタを引数や返り値としていることを知りました。わざわざポインタ表現にすることの意義は、実行速度が上がることと、標準関数の多くがポインタを引数や返り値としているからと理解して良いのでしょうか。
- みんなの回答 (7)
- 専門家の回答
質問者が選んだベストアンサー
その1 >*(p+i) = (double) rand(); >p[i] = (double) rand(); この2者は全く同じです。 同じと言うのは「結果的に同じ動作をする」と言う意味でなく、全く同じコードの2種類ある表記方法と言う意味です。 p[i]はコンパイラによって*(p+i)と解釈され実行コードに落とされます。 よって、非常に奇妙な話ですが、i[p]と書くことも許されます。*(p+i)も*(i+p)も同じですから。 もちろん、許されるだけで、敢えてそのような書き方をするべきでないことは当然ですが。 その2 そもそも配列を関数の引数・戻値にすることは出来ません。 だからこそ関数の引数・戻値はポインタになっているのです。当たり前の話です。 このため、ポインタを引数に取る関数に配列名を渡すと、配列名はその配列の先頭要素を指すポインタとして読み替えられるという規則があります。 厳密に言うと一部の例外を除き配列名は常にそのように解釈されます。「その1」で書いたこともこの規則に当てはまります。 これは初心者が引っかかりやすい点で、まともな入門書なら必ず説明してあるはずです。 詳しくお知りになりたければ下記のサイトの06あたりを読まれることをお薦めします。 なお、どうしても配列を引数・戻値にしたい場合(値渡しにする必要がある)は配列を構造体に入れるなり、C++であればクラスするなりの手を使います。 その3 C++になるとイテレータ(というものが存在する)との互換のためポインタ表記の方が便利ですね。
その他の回答 (6)
>わざわざポインタ表現にすることの意義は、実行速度が上がることと、標準関数の多くがポインタを引数や返り値としているからと理解して良いのでしょうか。 1 実行速度が上がるか? 上がりません。 p[i] は *(p+i)と解釈、つまり同じです。 但し、構造体などの場合は、関数で構造体をそのまま渡すのと、構造体のポインタを渡すのでは、ポインタの方が速いです。 2 標準関数の多くがポインタを引数や返り値としているから? ・ポインタでなければ実行できないから。 ・配列を渡すときに大きさが不明だから。 ・構造体などを渡すのでポインタの方が効率がよいから。 だと、思います。 ちなみに自分は、配列風にで宣言したら配列型、ポインタ風に宣言したらポインタ表記と決めています。 void set(double* p) { int i; for(i = 0; i < 10; i++) *(p+i) = (double) rand(); } void set(double p[]) { int i; for(i = 0; i < 10; i++) p[i] = (double) rand(); } No.6さんへ >はバグが含まれており、正確には以下のように書くべきでしょう。 >for(i=0;i<10;i++) >*(p+i*sizeof(double)) = (double) rand(); ちょっと、違うと思います。 pがchar*だった場合、左辺のアドレス計算は合いますが右辺の値がcharに変換されて代入されてしまいます。 pがdouble*だった場合、左辺のアドレス計算が期待通りになりません。
お礼
構造体などの場合はポインタのほうが早いのですね。確かに構造体は配列丸ごと渡せてしまいますから、値を全て渡すよりも先頭アドレスだけ渡すほうが早いのは、何となくイメージできます。ポインタでなくては関数を実行できないというのが最大の理由のようですね。ありがとうございました。
補足
今回頂きましたご回答の全てが良回答に値すると感じられるもので、大変参考になりました。ただ、ポイント発行は2つまでしかできないので、心苦しいのですが2つのご回答にポイントを発行させて頂きました。みなさまC言語に精通されているだけでなく、ご説明も非常にお上手なので初級の私でも結構理解できました。ありがとうございました。
- yasuch
- ベストアンサー率41% (27/65)
*(p+i) と p[i] は、pの定義次第で、結果は同じではありません。 つまり char *p; なのか double *p; で、結果は変わってきます。 for(i=0;i<10;i++) *(p+i) = (double) rand(); はバグが含まれており、正確には以下のように書くべきでしょう。 for(i=0;i<10;i++) *(p+i*sizeof(double)) = (double) rand(); こうなると、シンプルにpはdoubleのポインタで定義して for(i=0;i<10;i++) p[i] = (double) rand(); とした方が、可読性が高いと思いますよ。
お礼
あらら、バグでしたか。失礼致しました。 ご回答ありがとうございました。
- ency
- ベストアンサー率39% (93/238)
すでに何名かの方が回答されている通り、コンパイラによって配列は式の中では先頭要素へのポインタに読みかえられます。 ですので、下記の2つのソースコードからはからはまったく同じバイナリコードが生成されることなります。 すなわち、実行速度に差はありません。 > for(i=0;i<10;i++) > *(p+i) = (double) rand(); > > for(i=0; i<10; i++) > p[i] = (double) rand(); C は配列に対する処理が非常に貧弱な言語です。 関数に配列を渡すこともできなければ、関数から配列を返すこともできません。 できないので、代わりに先頭要素へのポインタを使うわけですね。 # ついでに言うなら、配列はコピーも一括ではできませんよね。 # いちいち for 文でグルグルまわさないといけないと…。 # 構造体みたいに代入文でコピーできてしまったらよいのですけどねぇ。 ちなみに、私の場合こんな場合でも配列表現を使ってしまいます。 -------------------------------------------------------- 【例1】 void func( double *ptr, int num ) { for ( i = 0; i < num; i++ ) ptr[i] = (double) rand(); } # 配列名を関数に渡せば、その関数には先頭要素へのポインタが # 渡ってくるだけですが、配列と同様の表記が可能です。 -------------------------------------------------------- 【例2】 double *ptr; ptr = malloc( 10 * sizeof(double) ); for ( i = 0; i < 10; i++ ) ptr[i] = (double) rand(); # ptr は配列ではなくポインタですが、やはり配列と同様の表記が # できます。 -------------------------------------------------------- こんな私なので、ポインタ演算を使うことがほとんどありません。 それでも、配列とポインタは別物です。 配列定義とポインタ定義が、まったく別物なのは言うまでもないでしょう。 同様にそれぞれの宣言も別物になります。 式の中で、配列が先頭要素のポインタに読み替えられるだけです。 # 一部の例外あり。。。 # たとえば、sizeof を取るときは、ポインタサイズではなく配列サイズ # を取得できますよね。 ちなみに、関数の引数に配列 (っぽい) ものを書くこともできますが、これだってコンパイラはポインタとしか認識しません。 void func( double ptr[], int num ) { …… } 関数の仮引数宣言の場合に限り、配列宣言はポインタ宣言に読みかえられます。 配列とポインタについては、関係が微妙です。 ですので、誤解や勘違いが非常に多いのも事実でしょう。 No4 KoHal さんの参考URL のページは、非常に勉強になると思います。 ご一読することをおすすめします。
お礼
私が配列とポインタを同じだと思っていたのは、たまたま関数に配列、ではなく先頭要素のアドレスを示すポインタを渡す表記が、何となく配列を渡しているように見えるためだったようです。ポインタでも配列表記が使えることを知ったのは勉強になりました。ありがとうございました。
- YKKIKS63
- ベストアンサー率44% (22/50)
実行速度うんぬんはよく知りませんが,C言語において関数間でやりとりできるのは,つきつめると数値のみで,関数への入力は複数OKでも,戻り値は一つに限られます。数学の関数を考えると良いでしょう。y = f(a, b, c, ~)とすれば,関数fの入力はいくつでもOKですが,関数fの戻り値はただ一つの"y"という値しかとれないのと同じです。しかし,処理によっては,複数の処理結果が必要となることは多々あります。このような時には,複数のデータのかたまり(例えば構造体,配列)の先頭のアドレスというひとつのデータを,"ポインタ"でやりとりすると,複数のデータを処理して,各データの処理結果を他の関数に返す...ということができるのです。なお,「標準関数の多くがポインタを使っているからポインタ表現する意義がある」のではなく,標準関数の多くは,その標準関数で何らかの処理をして,一連の複数のデータをその標準関数を呼び出している関数に返す必要があるからポインタを引数や返値として使っているのです。 余談になりますが,私の個人的な好みを言うと,普通に配列で記述できるのであればわざわざポインタ表現にする必然性はないと思います。(見た目はかっこいいと感じるかもしれませんが。)但し,前述のようにポインタ表現にしなければならない,あるいは,ポインタ表現を使った方が処理記述が楽,簡潔になる等の場合は別ですよ。 因みに,ある本で書かれているのですが,配列表現は,ポインタ表現の簡易的な書き方とのことです。("p[i]"は,"*(p + i)"と同じ)
お礼
本にポインタ表現のほうが実行速度が上がることもある、と書いてあったのですが、みなさまのご回答を拝見する限り、その点でのポインタを使うメリットはあまりなさそうです。仰るように、関数に引数を渡して複数の値を返す場合はポインタの参照渡しを使うしかない、ということも本に書いてありました。ポインタ表現はあとで見直しても一見には分かり辛いので、仰るように必要な部分だけ使うのが良いのかもしれませんね。ありがとうございました。
- tatsu99
- ベストアンサー率52% (391/751)
ポインタ表現にするか、配列にするかは、そのときの要件によるのではないでしょうか。 例えば、例のようにdouble型10個の配列のようにあらかじめ配列の数が決まっている場合は、配列として定義しp[i]のように記述したほうが、わかりやすい為、そうすべきです。ところが、配列の数が、事前に決められず、外部から与えられるようなケースの場合は、まず、配列の数を、取得後、その数分の領域を確保(malloc)することになります。その場合は、mallocから返されるのは、確保したメモリの先頭アドレス(=ポインター)となります。そのような場合は、*(p+i)とか、*pとかのようなポインターを使用した記述になります。
お礼
動的なメモリ割り当てというものですね。確かにmallocはポインタを返すので配列表現は使えませんね。mallocを始めとしてポインタを返す標準関数が多いから、わざわざポインタ表現を使うのかと解釈していました。ありがとうございました。
- sha-girl
- ベストアンサー率52% (430/816)
for(i=0;i<10;i++) *(p+i) = (double) rand(); としたのではp[i]と実行速度は変わらないでしょう。 for(i=0;i<10;i++) *(p++) = (double) rand(); とし、rand以外の処理があれば効率は上がるかもしれませんが その辺の最適化はコンパイラに依存するので、 なんともいえません。
お礼
ありがとうございました。 ポインタ式のほうが配列を使った式よりも高速な実行コードを作成できることがあると本に書いてあったので、とにかくポインタにすれば良いのだとそのまま鵜呑みにしていたのですが、書き方によるのですね。
お礼
ありがとうございました。 ご回答ならびに参考URLのおかげで配列とポインタが全く異なるものだということがよく理解できました。