• ベストアンサー

配列表現とポインタ表現

配列とポインタの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言語の初級から中級向けの本しか読んでいないのですが、標準関数の多くがポインタを引数や返り値としていることを知りました。わざわざポインタ表現にすることの意義は、実行速度が上がることと、標準関数の多くがポインタを引数や返り値としているからと理解して良いのでしょうか。

質問者が選んだベストアンサー

  • ベストアンサー
  • KoHal
  • ベストアンサー率60% (110/181)
回答No.4

その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++になるとイテレータ(というものが存在する)との互換のためポインタ表記の方が便利ですね。

参考URL:
http://www.kouno.jp/home/c_faq/
civic00
質問者

お礼

ありがとうございました。 ご回答ならびに参考URLのおかげで配列とポインタが全く異なるものだということがよく理解できました。

その他の回答 (6)

noname#15691
noname#15691
回答No.7

>わざわざポインタ表現にすることの意義は、実行速度が上がることと、標準関数の多くがポインタを引数や返り値としているからと理解して良いのでしょうか。 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*だった場合、左辺のアドレス計算が期待通りになりません。

civic00
質問者

お礼

構造体などの場合はポインタのほうが早いのですね。確かに構造体は配列丸ごと渡せてしまいますから、値を全て渡すよりも先頭アドレスだけ渡すほうが早いのは、何となくイメージできます。ポインタでなくては関数を実行できないというのが最大の理由のようですね。ありがとうございました。

civic00
質問者

補足

今回頂きましたご回答の全てが良回答に値すると感じられるもので、大変参考になりました。ただ、ポイント発行は2つまでしかできないので、心苦しいのですが2つのご回答にポイントを発行させて頂きました。みなさまC言語に精通されているだけでなく、ご説明も非常にお上手なので初級の私でも結構理解できました。ありがとうございました。

  • yasuch
  • ベストアンサー率41% (27/65)
回答No.6

*(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(); とした方が、可読性が高いと思いますよ。

civic00
質問者

お礼

あらら、バグでしたか。失礼致しました。 ご回答ありがとうございました。

  • ency
  • ベストアンサー率39% (93/238)
回答No.5

すでに何名かの方が回答されている通り、コンパイラによって配列は式の中では先頭要素へのポインタに読みかえられます。 ですので、下記の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 のページは、非常に勉強になると思います。 ご一読することをおすすめします。

civic00
質問者

お礼

私が配列とポインタを同じだと思っていたのは、たまたま関数に配列、ではなく先頭要素のアドレスを示すポインタを渡す表記が、何となく配列を渡しているように見えるためだったようです。ポインタでも配列表記が使えることを知ったのは勉強になりました。ありがとうございました。

  • YKKIKS63
  • ベストアンサー率44% (22/50)
回答No.3

実行速度うんぬんはよく知りませんが,C言語において関数間でやりとりできるのは,つきつめると数値のみで,関数への入力は複数OKでも,戻り値は一つに限られます。数学の関数を考えると良いでしょう。y = f(a, b, c, ~)とすれば,関数fの入力はいくつでもOKですが,関数fの戻り値はただ一つの"y"という値しかとれないのと同じです。しかし,処理によっては,複数の処理結果が必要となることは多々あります。このような時には,複数のデータのかたまり(例えば構造体,配列)の先頭のアドレスというひとつのデータを,"ポインタ"でやりとりすると,複数のデータを処理して,各データの処理結果を他の関数に返す...ということができるのです。なお,「標準関数の多くがポインタを使っているからポインタ表現する意義がある」のではなく,標準関数の多くは,その標準関数で何らかの処理をして,一連の複数のデータをその標準関数を呼び出している関数に返す必要があるからポインタを引数や返値として使っているのです。 余談になりますが,私の個人的な好みを言うと,普通に配列で記述できるのであればわざわざポインタ表現にする必然性はないと思います。(見た目はかっこいいと感じるかもしれませんが。)但し,前述のようにポインタ表現にしなければならない,あるいは,ポインタ表現を使った方が処理記述が楽,簡潔になる等の場合は別ですよ。 因みに,ある本で書かれているのですが,配列表現は,ポインタ表現の簡易的な書き方とのことです。("p[i]"は,"*(p + i)"と同じ)

civic00
質問者

お礼

本にポインタ表現のほうが実行速度が上がることもある、と書いてあったのですが、みなさまのご回答を拝見する限り、その点でのポインタを使うメリットはあまりなさそうです。仰るように、関数に引数を渡して複数の値を返す場合はポインタの参照渡しを使うしかない、ということも本に書いてありました。ポインタ表現はあとで見直しても一見には分かり辛いので、仰るように必要な部分だけ使うのが良いのかもしれませんね。ありがとうございました。

  • tatsu99
  • ベストアンサー率52% (391/751)
回答No.2

ポインタ表現にするか、配列にするかは、そのときの要件によるのではないでしょうか。 例えば、例のようにdouble型10個の配列のようにあらかじめ配列の数が決まっている場合は、配列として定義しp[i]のように記述したほうが、わかりやすい為、そうすべきです。ところが、配列の数が、事前に決められず、外部から与えられるようなケースの場合は、まず、配列の数を、取得後、その数分の領域を確保(malloc)することになります。その場合は、mallocから返されるのは、確保したメモリの先頭アドレス(=ポインター)となります。そのような場合は、*(p+i)とか、*pとかのようなポインターを使用した記述になります。

civic00
質問者

お礼

動的なメモリ割り当てというものですね。確かにmallocはポインタを返すので配列表現は使えませんね。mallocを始めとしてポインタを返す標準関数が多いから、わざわざポインタ表現を使うのかと解釈していました。ありがとうございました。

  • sha-girl
  • ベストアンサー率52% (430/816)
回答No.1

for(i=0;i<10;i++) *(p+i) = (double) rand(); としたのではp[i]と実行速度は変わらないでしょう。 for(i=0;i<10;i++) *(p++) = (double) rand(); とし、rand以外の処理があれば効率は上がるかもしれませんが その辺の最適化はコンパイラに依存するので、 なんともいえません。

civic00
質問者

お礼

ありがとうございました。 ポインタ式のほうが配列を使った式よりも高速な実行コードを作成できることがあると本に書いてあったので、とにかくポインタにすれば良いのだとそのまま鵜呑みにしていたのですが、書き方によるのですね。

関連するQ&A

  • 配列ポインタの関数中のメモリ領域

    C初心者です。 関数中で配列ポインタを宣言する場合についての質問です。 たとえばDouble型の2次元のローカルな配列ポインタを用いる場合、 その配列要素数が100である場合は void 関数名(引数1,引数2,...){ int i; double *a[2]; for(i=0;i<2;i++){ a[i] = (double*)malloc(100*sizeof(double)); } for(i=0;i<2;i++){ free(a[i]); } } またこの値を引数1とする場合、引数1をoutとすると void 関数名(double *out,....) とし、 for(i=0;i<2;i++){ out[i] = a[i]; } とすればよいのでしょうか? もしこれがあっているとすると、つぎのような現象で困っています。 配列要素数を50000個ぐらいとし、複数の関数で、同様に mallocを用いて、配列ポインタのローカルでメモリ領域を確保しようとした場合、コンパイルは成功するのですが、その後実行すると、エラーが発生したというメッセージとともにコマンドウィンドが強制終了します。 コンパイラはVisual C++ EXpress Edition 2008です。 データサイズを小さくすると、エラーは起きません。 malloc関数で確保するメモリサイズは、関数の入力引数で定義された変数を用いて計算しており、データサイズに応じて変更されます。 よろしくお願いいたします。

  • できているとは、思うのですが。ポインタの配列を

    コンパイラではちゃんと動いてます。 1 2 3 0 1 2 3 という具合です。 気になるのは、printarrayの部分が正しいのか、ちょっと悩んでいます 問題としては main関数では0の値を読み込むまで最大99(MAX-1)個の値を配列 xに読み込んでいる。引数のポインタからの値を、値が0になるまで すべて1行に1つづつ画面に出力する関数printarray()を作成し、 プログラムを完成せよ。 引数はアドレスとして受け取る事。(配列としてでなく) フォーマットは、 "%d¥n" とする。(余計な出力はしない事。) (0は出力しない。) main内部を変更してはならない。 以下がソースです。 ご指摘よろしくお願いします。 #include <stdio.h> #define MAX 5 void printarray(int *); int main() { int x[MAX], i; int *p; x[MAX-1] = 0; for (i = 0, p = x; i < MAX-1; ++i, ++p) { scanf("%d", p); if (*p == 0) { break; } } printarray(x); return 0; } void printarray(int *a) { int i,*p; for(i = 0, p = a; i < MAX-1; ++i,++p) { if(*p == 0){ // continue; break; }else{ printf("%d\n", *(a+i)); } } }

  • ポインタ配列

    "one","two","three","four","five","six","seven","eight","nine","ten" のポインタ配列の文字列を、ASCIIコード順に並べ変えようと思ったのですが、 もうどこが間違っているかさえわからないぐらいになってしまいました。 まだまだはじめたばかりなもので、わからないことだらけなんで、 できるだけわかりやすい説明おねがいします。 関数の引数に問題があるのじゃないかと思ったのですが、 何かいいアドバイスありましたら、お願いします。 #include <stdio.h> /* 関数のプロトタイプ宣言 */ int strmp(char *,char *); void cpy(char *,char *); int main (void) { /* ポインタ配列の定義 */ char *x[10]={"oneee","twooo","three","fourr","fivee","sixxx","seven","eight","ninee","tennn"}; /* ポインタのポインタの定義 */ char **pp=x; char k[100]; char *p=k; int i,t,a,b,c,d; a=0; /* ポインタ配列を自作関数を使って、ASCIIコードの大きいほうからに並び替える */ for(i=0;i<9;i++) { for(t=1;t<10;t++) { a=strmp(*(pp+i),*(pp+t)); if(a<0) { cpy(p,*(pp+i) ); cpy(*(pp+i),*(pp+t) ); cpy(*(pp+t),p); } } } for(i=0;i<10;i++) { printf("%s ,",x[i]); } printf("\n"); return 0; } /* 文字の比較をする関数 */ int strmp(char *x,char *y) { int i; for(i=0;*(x+i)==*(y+i);i++) { if( *(x+i)=='\0') { return 0; } } return *(x+i)-*(y+i); } /* 文字をコピーする関数 */ void cpy(char *a,char *b) { int i; for(i=0;*(b+i)!='\0';i++) { *(a+i)=*(b+i); } *(a+i)='\0'; }

  • 配列とポインタでの書き直しその2

    配列とポインタでの書き直し(c++)その1 のつづき cout << endl; cout << setw(5) << "Sum" << setw(12) << "Number" << setw(14) << "Probability" << setw(10) << "Error" << endl; cout << "-----------------------------------------" << endl << endl; cout << setiosflags(ios::fixed | ios::showpoint); for (int k = 0; k <= 10; k++) { probability = static_cast<double>(sum[k])/throws; cout << setw(4) << k+2 << setw(12) << sum[k] << setprecision(6) << setw(14) << probability << setprecision(2) << setw(11) << getError(probability, k, error) << endl; } return 0; } // Roll two dice. int rollDice() { int dice1, dice2; dice1 = rand()%6 +1; dice2 = rand()%6 +1; return (dice1 + dice2); } // Example: for error dice 12 // // prob = sum[10] / throws, // // // |prob - e[10]| // error = ----------------- x 100 // e[10] // double getError(double p, int i, double e[]) { return ((fabs(p - e[i])/e[i])*100); } 関連URL: http://www.okweb.ne.jp/kotaeru.php3?q=243537

  • 「配列名はポインタ」という表現は間違っているのでは?

    よくネット上で、「配列名はポインタ」というような表現が出てきますが、 「配列名はポインタになっているのである。 http://www.wakhok.ac.jp/~kanayama/C/99/node111.html」 このような表現は、間違っているのではないでしょうか? C言語を作った本人によるK&Rのp.121には、次のように書かれています。 配列名とポインタの間には、心に留めておかなければならない違いが一つある。 ポインタは変数であり、したがってpa=aやpa++は意味のある演算である。   ←(1) しかし配列名は変数ではない。したがって、a=paやa++のような構文は正しくない。 ←(2) (1)より、 ポインタは、変数である ので、この対偶をとると、 変数でないものは、ポインタではない  ←(3) となります。 (2)より、 配列名は、変数ではない  ←(4) となるので、 (3)と(4)から、 配列名は、ポインタではない ←(5) となります。 そうすると、「配列名はポインタ」というような表現は間違っていることになり、誤解の元になるので、改めるべきではないかと思うのですが。

  • 引数で指定された配列の要素数の取得

    どうもこんにちは。 C言語でプログラムを作成しています。 ある関数に配列を渡すことを考えていますが、渡した配列の要素数を取得する方法は何かありますか? 標準の関数を見ても、配列の先頭アドレスのポインタとともに、配列の要素数を渡しているものばかりで、配列のポインタを渡しているものは見かけません。 要素数があらかじめわかっていれば、それを引数の型に指定できますが、呼び出されるまで不明な場合はうまくいきません。 配列の要素数も引数として一緒に渡す必要がありますか? [作ってみたサンプル] #include <stdio.h> #include <stdlib.h> #include <string.h> // func1 と func2 をまとめられないだろうか。。。 void func1(int (*p)[10]) { ________int n = sizeof(*p) / sizeof((*p)[0]); ________int i; ________for (i = 0; i < n; i++) { ________________printf("%d\n", (*p)[i]); ________} ________printf("\n"); } void func2(int (*p)[5]) { ________int n = sizeof(*p) / sizeof((*p)[0]); ________int i; ________for (i = 0; i < n; i++) { ________________printf("%d\n", (*p)[i]); ________} ________printf("\n"); } int main(int argc, char *argv[]) { ________int ary1[10] = { 2, 4, 6, 8, 0, 1, 3, 5, 7, 9 }; ________int ary2[5] = { 3, 6, 9, 12, 15 }; ________func1(&ary1); ________func2(&ary2); ________return 0; }

  • 二次元配列とポインタについて

    関数内の変数宣言にて (1) int *( p[ 5 ] ); (2) int ( *p )[ 5 ]; の違いを教えて下さい。 (1)は *p[ 5 ] と同義のようで int実体のポインタとなるp[ 0 ]からp[ 4 ]の 配列が作られるようです。 つまり領域に作られるのは 5つの連続したint型へのシングルポインタであり その他のint実体やダブルポインタは 領域に作られないと認識しております。 (2)との違いが分かりません。 領域では実際に何が作られるのかという点と このように演算の優先順位がある場合に どのような順番で解釈すればよいのかという点について ご説明頂けると助かります。 ではよろしくお願いします。

  • ポインタによる関数への配列渡し

    林晴比古さんの「新C言語入門」でC言語を勉強している初心者です。 現在ポインタの勉強をしています。色々教科書の文例等をポインタで書くとどうなるか試しております。 上書P200練習問題2に「配列の最大値を返す(その際配列の長さを渡す)」プログラムがあり、それをポインタで渡すプログラムに直してみました。 仮引数に「maxdata」を設定し、そのアドレスを関数側に渡し、関数側ではポインタとして受け取る(そうすれば関数側からはreturnで値を返す必要がない)、と考え、下記のように書いてみました。 #include <stdio.h> void max_of_array(int n[], int len, int *ans); int main(void) { int dt[6] = {50,20,80,30,10,40}; int maxdata; max_of_array(dt,6,&maxdata); printf("最大値=%d\n", maxdata); return 0; } void max_of_array(int n[], int len, int *ans) { int i; ans = &n[0]; for (i=1; i<len; i++){ if (*ans < n[i]) *ans = n[i]; } } しかしコンパイルすると、何故か「最大値=1」となってしまいます。(正しくは80です) 他にも色々試してみましたがうまくいかず、かなり考えてみたのですがどうしても分かりません。お分かりの方、どうすれば正しくなるのが教えてください、よろしくお願いします。

  • 多次元配列のポインタ渡し

    C++を使用しています。 多次元配列を関数の引数として渡したいとき、関数側では void A::Func(int a[10][20][30])~ 呼びだし側では Finc(a); とやればいいのはわかります。 お聞きしたいのは、仮引数として呼び出された配列(上でいうa)をクラスのメンバ変数として保持したい場合の方法です。 aは先頭アドレスなのでそこを差すポインタを受ければいい、っていうことはわかりますが、 この方法ですと、受けたメンバ変数が配列みたいに[]を使ってアクセスできません。 (メンバ変数のポインタは配列じゃないから当然ですよね) これを通常の配列みたいに扱えるようにするにはどうしたらいいでしょうか。

  • 配列が使えない

    double Rand[] = new double[5]; Rand[0] = 6.28; Rand[1] = 7.28; Rand[2] = 8.28; Rand[3] = 9.28; Rand[4] = 10.28; 以上のような配列の記述を含んだソースファイルをコンパイルすると ']'がありません といったメッセージが出てきてコンパイルエラーが出てきます。 ただし double Rand[] = {6.28,7.28,8.28,9.28,10.28}; といった宣言の仕方をするとコンパイルエラーが出てきません。 for文などを使いたいので最初のほうの方法で配列を使いたいのですがどうしたらよいのでしょうか。 Java初心者の私にぜひ教えてください。お願いします。

専門家に質問してみよう