• 締切済み

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

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

みんなの回答

noname#30727
noname#30727
回答No.3

(1) int *( p[ 5 ] ); pは配列→pはint*を5つ格納する配列 (2) int ( *p )[ 5 ]; pはポインタ→pはint[5]へのポインタ <例> int array[3][5]; sub(array); ~~~~~~~ void sub(int (*p)[5]) { p[2][2] = 3; } 1次元配列では特に意識しなかったものが、2次元以上の配列では考える必要があることがわかると思います。

  • leaz024
  • ベストアンサー率75% (398/526)
回答No.2

宣言される変数がどのような性質を持つのか判読するには、まず宣言される変数に着目し、優先順位や結合規則に従って演算子を見ていきます。 (1) の p は [5] と結合しているので、p は「5つの要素を持つ配列」だと分かります。 それが * と結合しているので、「配列の要素はポインタ」だと分かります。 最後に int を見て、「ポインタが指すデータはint型」だと分かります。 int *p[5] の場合、p に * と [5] が両方くっついていますが、間接参照演算子より配列演算子の方が優先順位が上のため、(1) と同じ意味になります。 (2) の場合、p は ( ) によって * と結合しているので、p は「ポインタ変数」だと分かります。(用意されるのはポインタ1つ分のメモリだけです。) それが [5] と結合しているので、「ポインタが指すデータは、要素5つの配列」だと分かります。 最後に int を見て、「配列の要素はint型」だと分かります。 これだけ読むと何のための変数か分かりませんが、実はこれは「int型ニ次元配列へのポインタ変数」なのです。例えば int a[3][5] という二次元配列があった時、この変数 a へのポインタ p の宣言が int (*p)[5] となります。 配列のメモリ確保の仕組み上、何次元配列でもポインタは *p でいいだろうと思われがちなのですが、このポインタは二次元以上の配列の代わりにはならないのです。これは次の「配列とポインタの関係」から容易に理解できることと思います。 一次元配列の場合、   int a[10];   int *p;   p = a; のようにポインタ p を用意すると、p は a の代わりに使えるようになります。例えば   p[3] = 100; とすると、これは   a[3] = 100; を行ったのと同じことになります。 この配列とポインタの関係は、二次元以上の配列でも成り立つべきです。つまり   int a[3][5];   ?    ・・・a へのポインタ p の宣言   p = a; とした時、   a[1][0] = 100; の代わりに   p[1][0] = 100; とできるべきです。 ところが p の宣言を int *p とした場合、この代替関係は成り立ちません。 アドレス的には p[5] とすればよいだけなのですが、この方法には何のメリットもなく、やはり p[1][0] とできて初めて「これぞ二次元配列へのポインタ」と言えるわけです。 しかし、この p には一次元目の要素数情報がないため、p[1][0] が p から何番目のintデータになるのか計算できないので、このようにアクセスすることができないのです。 そこで二次元配列へのポインタ用に、一次元目の要素数情報を与えた宣言が int (*p)[5] です。 この p は「5つの要素を持つint型配列」単位で操作を行うポインタなので、*p のサイズ(p++ で増加する値)は sizeof(int)*5 バイトであり、p[0]、p[1]、p[2] はそれぞれ a[0]、a[1]、a[2] と等価となります。 また、p[0] や p[1] が指している先は配列なので、さらに配列参照することで各int型データにアクセスすることができます。つまり、p[1][0] で a[1][0] にアクセスできるのです。

  • BLUEPIXY
  • ベストアンサー率50% (3003/5914)
回答No.1

(1)をp1 (2)をp2として説明します。 私の処理系では sizeof(p1)は20バイトになって これは、4バイトサイズのポインタが5つ確保されたということです。 質問文に書かれている通りです。 sizeof(p2)は4バイトとなって、 たんなる1つのポインタです。 これは、どういう型へのポインタかというと int data[5]; のようなint型の5のサイズをもつ配列へのポインタです。 int (*p2)[5]; int i; static int data[3][5]={ { 1,2,3,4,5},{6,7,8,9,10},{11,12,13,14,15}}; p2=&data[0]; p2++; for(i=0;i<5;i++) printf("%d,",(*p2)[i]); のようなプログラムでは、 6,7,8,9,10, が表示されます。 宣言における解釈の優先は、カッコによって変えられます。 どうしてそうなるかというと、 それが、有意義だから(そういうのが必要だから)で 例えば int *p[ 5 ]; が int *( p[ 5 ] ); に解釈されて int ( *p )[ 5 ]; に解釈されないのは、妥当なことだと思います。 (普通上に解釈すると思う。そして、処理系もそうなっている) そして、必要な時には、宣言上の解釈の順位を変えることができるのも妥当だと思います。 解釈の簡単な仕方は、*で何が復元されるのかということを考えることだと思います。 (1)の例で言えば *(p[])はintになる (2)の例で言えば (*p)で配列の名前部分になる と考えれば少し考えやすいのではないかなと思います。

関連するQ&A

  • ポインタ

    ポインタの勉強をはじめたばかりの者なのですが、たとえば int *p などでポインタを宣言する場合には、メモリ上で変数pにはアドレスを格納するだけの領域が与えられます、このとき何バイトの領域が与えられるのでしょうか?

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

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

  • 二次元配列のポインタについて教えて下さい

    今日はCの配列のポインタについて質問いたします、宜しくお願いします。 1次元の配列からは、 =================================== int *p , a={1,2,3}; p = &a; printf("%d\n" , p[1] ) ; printf("%d\n" , *p[1] ) ; ===================================== でaの値がとれますが、 二次元の配列では下のような書き方ではエラーになります。 何故でしょうか、どう書いてやればいいのでしょう。 ===============================================  printf("%d\n" , pbb[1][1] ) ;  printf("%d\n" , *pbb[1][1] ) ; =============================================== 宜しくお願いします。

  • ポインタの宣言

    ポインタを宣言するとメモリ上に、ポインタ変数を格納するための領域が確保されます。ポインタ=アドレスというのは大丈夫なのですが、 int *b のようにどうして、ポインタに型があるのでしょうか?単に変数のアドレスを表示するだけならば型はいらないと思うのですが。 またこのとき宣言された変数は *b ではなくて b であってますよね?

  • ポインタ変数とポインタのポインタ

    ポインタ変数の宣言 char *a[]; をしたとき僕の中では a[0],a[1]...という、ある文字列A,B,C...の最初のアドレスを指すポインタが、配列になっているものを宣言していると理解していました。 しかしこの次に、ポインタのポインタが出てきました。僕はこれを、 ある変数を指し示すアドレスのアドレスである、と理解しました。 この2つは1つめはいくつかのアドレスを指し示すもの、2つ目は1つのアドレスを指し示すものであるとして、僕の中で異なったものであると理解していましたが、参考書「C標準コースウェア」によると プログラムにおいて、関数でポインタ配列を受け取るときchar *p[]はchar **pとしてもよい と書かれており、またその実例として、 (9-5) #include <stdio.h> void disp (char *p[],int n){ int i; for (i= 1;i<n;i++){ printf("%s\n",p[i]); } } int main(void){ char *girl[] = {"Arica","Candy","Lisa"}; disp (girl,sizeof(girl)/sizeof(girl[0])); return 0; } というプログラムが書かれていました。 ここで一気に訳が分からなくなりました。 char *girl[] = {"Arica","Candy","Lisa"}; と宣言されているため、 girl[0]はAricaという文字列の最初のアドレスを指すポインタ、 *girl[0]はAricaという文字列を直接指し示していると解釈しています。 girlは{"Arica","Candy","Lisa"}という文字列の配列の最初のアドレスを指し示していると考えました。 sizeof(girl)を使った時に不思議なのですが、 girlはどのように配列の終わりを理解しているのでしょうか? (配列の要素数を渡していない点が不思議です。) また、 disp側が受け取ったのは*girl[]であり、いくつかのポインタの配列ですが、渡したものはgirlという要素数がないポインタ1つだけです。 そして最初の疑問が出てくるわけですが、*p[]を**pと書きかえてみると、 文字列のアドレスを示すgirlという名の1つのポインタを渡すと、pという名のポインタのポインタで受け取るというのも、よくわからなくなっています。 おそらくポインタ配列に対する理解がどこかでずれているようですが、自分でどこがわからないのかわからなくなっています。 どうかご教授ください。

  • ポインタと多次元配列についての質問です

    私の使っている本に、 『ポインタを使って多次元配列にアクセスするには、コンパイラが自動で行っていることを手作業で 行わなければなりません。たとえば、次の配列には各列に5つの要素があります。 float balance[10][5]; したがって、ポインタを使ってbalance[3][1]にアクセスするには、次のようなコードを使用 しなければなりません。 float *p; p = (float *) balance; *(p + (3*5) +1) 目的の要素に到達するには、行番号に行の要素数を掛けてから、その要素の行内での番号を 加えなければなりません。上記の例では、balanceをfloat* にキャストする必要がありました。 配列要素を手作業で指定する都合上、ポインタ演算をfloatポインタに基づいて行わなければなりません。 しかし、balanceによって生成されるポインタの型はfloatの2次元配列です。そこでキャストが 必要になるわけです。』 とあります。 【質問1】なぜ、p = (float *) balance; なのか。p = (float) *balance; ではないのか? 【質問2】本文「上記の例では………必要になるわけです。」の意味がわかりません。 未熟者の私ですがどうか教えてください。

  • ポインタ配列の動的確保

    ポインタの配列の動的確保について教えてください。 入力した数値をポインタ配列に入れるプログラムです。 下記のように書いてみました。(見づらくてごめんなさい) #include<stdio.h> #include<stdlib.h> #define kensu 3 main() { char abc[kensu+1]={'A','B','C','\0'}; char *ptr[kensu]; int i; printf("3つの整数を入力して下さい。\n"); for(i=0;i<kensu;i++){ ptr[i]=(char*)malloc(sizeof(char)*10); if(ptr[i]==NULL){ printf("メモリの取得に失敗しました"); exit(1); } printf("整数%c:",abc[i]); fgets(ptr[i],10,stdin); if(ptr[i][strlen(ptr[i])-1]=='\n') ptr[i][strlen(ptr[i])-1]='\0'; } for(i=0;i<kensu;i++) free(ptr[i]); } ちゃんと動いているようです。 しかし、ポインタ配列の動的確保をネットで調べてみると、ポインタのポインタ(?)を使って、下記のように2度mallocしています。 #include <stdio.h> #include <stdlib.h> #define N 3 int main(void) { char** arr; int i,j; arr = (char**)malloc(N * sizeof(char*)); /* ポインタ配列を確保 */ /* 配列の要素それぞれにつき、メモリ領域を確保 */ for(i=0;i<N;i++) arr[i] = (char*)malloc(N * sizeof(char));   ・・・ ポインタの配列を宣言して、配列の各要素に動的確保するのと ポインタのポインタを宣言し、ポインタ配列を動的確保して、再度配列の要素に動的確保するのとでは、何か違いがあるのでしょうか? ポインタのポインタを宣言し、ポインタ配列を確保する必要性が良く分かっていないのです。 ネット等で調べて見たのですが、理解力がないのかよく分かりませんでした。 どうか教えてください。

  • C言語、配列とポインタとアスタリスクの関係

    ちょっと行き詰まっています。 苦しんで覚えるCで勉強しているのですが、まさに苦しんでいます。 http://9cguide.appspot.com/19-01.html #include <stdio.h> #include <stdlib.h> int main() { int i; int *heap; heap = (int *)malloc(sizeof(int) * 10); if (heap == NULL) exit(0); for (i = 0;i < 10;i++) { heap[i] = i; } printf("%d\n",heap[5]); free(heap); return 0; } int *heap; ここで int ポインタを宣言しています。 heap = (int *)malloc(sizeof(int) * 10); ここでヒープを確保しています。(int *) のキャストも sizeof(int) も理解できました。 for (i = 0;i < 10;i++) { heap[i] = i; } まず1点目の疑問はここです。 変数 heap は「ポインタ変数」です。それでいて配列です。 ポインタ変数は、プログラムの文中で通常の変数として使うときには「*heap」のように先頭にアスタリスクを付けなければならかなったと記憶しています。 アスタリスクなしの「heap」はアドレス格納用の変数ではないでしょうか。 printf("%d\n",heap[5]); そして、その疑問をよそに、この命令が成り立っているようです。 画面上に出される結果は「5」であり、変数「heap」がただの配列として機能しているように見えます。 この printf 次のように書き換えると、エラーが出てコンパイルできませんでした。 書き換え実験1 printf("%p\n",*heap[5]); アスタリスクを付けて、通常の変数として扱い、受ける方も「%d」から「%p」に書き換えてアドレスを表示してみようと思ったのですが、 「「pointer」を付け忘れています。」というエラーが表示されました。 書き換え実験2 printf("%p\n",heap[5]); 受ける方を「%d」からポインタを受ける「%p」にしましたが、変数の方はアスタリスクなしです。 すると、結果はアドレス「00000005」が返ってきました。 (変数にアスタがないのになぜ?) 書き換え実験3 printf("%d\n",*heap[5]); これはもうめちゃくちゃですが、一応やってみました。コンパイルエラーで、 「「pointer」を付け忘れています。」というエラーが表示されました。 つまり、こういうことです。 0:printf("%d\n",heap[5]); //5 1:printf("%p\n",*heap[5]); //エラー 2:printf("%p\n",heap[5]); //00000005 3:printf("%d\n",*heap[5]);//エラー この結果から推測するに、アスタリスクはそもそも付けるとエラーになり、アドレスを表すか、そのアドレスに格納された値を表すかを切り替えるには、単にその変数を受ける「%d」や「%p」を変えるだけ、ということになるのだと思います。 mallocで返ってくるのは、ポインタ変数(の配列)だと思うので、変数のモードを切り替えるためにアスタリスクが必要なのだと思っていましたが、どこかで重大な勘違いをしているようです。 この件について、どなたか教えていただけないでしょうか。

  • C言語初心者です。ポインタについて教えて下さい。

    ポインタの宣言で次のように宣言します。 int *p1,p2; p1はポインタですが、p2はポインタになるのでしょうか。 それとも普通のp2という変数になるのでしょうか。 友人との間で意見が分かれています。

専門家に質問してみよう