• 締切済み

ポインタのポインタの必要性

書物によるとポインタのポインタの使用例として「ポインタの配列」はポインタを使ってアクセスすることができます。」[*]とありますが、どうしてポインタのポインタが必要なのかがいまいちピンと来ません。 どういう場合なのかを知りたく思っています。 [*]サンプルスクリプト ===================================================== char *mnthp[3] = {/* ポインタの配列の宣言 */ "January", "February", "March" }; char **p1;/* 「ポインタのポインタ」の宣言 */ int i, j; p1=mnthp;/* 「ポインタのポインタ」にポインタの配列 */ /* の先頭番地を設定 */ /***** 例1 *****/ for (i = 0; i < 3; i++) {/* 「ポインタのポインタ」の値を変えずに */ printf("%s\n", *(p1 + i));/* 相対的に文字列を出力 */ } ==> このようなことをしなくとも printf("%s", mnthp[i]); で値を参照出来ると思われる。 ===================================================== [*] http://www9.plala.or.jp/sgwr-t/c/sec10-4.html 宜しくお願い致します。

みんなの回答

回答No.10

リンクリストはどうでしょうか。 たとえばリストからある要素を探して削除するなんて関数を書こうとするとポインタのポインタがあると便利です。

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

ご質問の例の中にある *( p1 + i ) ですが、p1[i] とすることだってできるわけです。 実は両者は C のコンパイラにとっては、まったく同じ意味なんです。 これは C のコンパイラが配列をポインタに読み替えてしまうからなんですね。 *(p1 + i) 等ポインタ演算と呼ばれる表記方法は、配列のインデックス形式で置き換えることができる場合がほとんどだと思います。 # 配列形式のほうが見た目も見やすいですし、私は好きなんですよね。 # 普通にポインタに対して p1[i] というようにインデックス表記もします。 そういう意味では、式の中での演算子としての [ ] は配列とは無関係なもので、ポインタ演算のもう一つの表記方法と言えるかもしれません。 # 配列がポインタに成り下がってしまう以上、そういう意味しかないのかも # しれませんけどね。 あ…だからといって「配列=ポインタ」などという、非常にありがちな間違いをしないようにしてくださいね。 配列とポインタは宣言や定義については意味合いがまったく異なります。 char hoge[10]; char *hoge; これらがあきらかに別物なのは、説明するまでもないと思います。 ただしこれらが一致する瞬間があるんです。 それが関数の仮引数宣言のときなんです。 この場合に限り、まったく同じ意味になります。 void func( char hoge[10] ); void func( char hoge[] ); void func( char *hoge ); 上記3つに違いはありません。 しかも、配列はコンパイラによってポインタに成り下がっているわけですから、引数に渡されるものも実はポインタでしかなくて、すでに配列ではありません。 その意味では 3つ目の宣言が一番実体をよく表していることになります。 # string系の関数のプロトタイプ宣言はポインタ表記になっていますね。 最初の 2つに char*型の変数を渡しても何も怒られることはありません。 これら3つはまったく同じなんですから。。。 さて、話をご質問の件に戻しましょうか。 で、この法則はポインタを指すポインタにも適用されます。 つまり、次の2つの main 関数の宣言に、違いはまったくありません。 int main( int argc, char *argv[] ); int main( int argc, char **argv ); char*型の配列と char*型を指すポインタに見えますが、前者だって実は char*型を指すポインタが渡されるんです。 ここまで来ると、どっちを使うかは完全に好みの問題で、「どちらがより良い」などという議論そのものが意味のあることだとは思えません。 # というよりも、この場合関数の仮引数宣言の「char *argv[]」という表記そのものが # char*型を指すポインタ、つまりポインタを指すポインタの宣言なわけですから。。。 ご質問の例に戻ると、その例は「説明のための例」でしかないと思います。 書籍レベルの小さなプログラムでは、説明のための例にしかならなくても仕方ないのかもしれませんね。 実際に使用する場面としては、一人で作れる程度のプログラムではなかなかめぐり合うことはないかもしれません。 複数人である程度の規模の大きさのプログラムを共同で作成するときなんかには、当たり前のように登場してきます。 っていうか、main() 関数ですでにお目見えしているわけで。。。 このように関数の引数で見かけることが多いと思います。 う~ん、なんだかあまりまとまっていないような気が。。。 とりあえず、こんな感じでいかがでしょうか。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.8

行列を扱う汎用的な関数を書こうとすると「ポインタのポインタ」は必須.

  • don_go
  • ベストアンサー率31% (336/1059)
回答No.7

/* QSORT.C: コマンドライン引数を読み出し、qsort 関数を * 使ってソートします。ソート後の引数を表示します。 */ #include <stdlib.h> #include <string.h> #include <stdio.h> int compare( const void *arg1, const void *arg2 ); void main( int argc, char **argv ) { int i; /* argv[0] はソート対象外です。 */ argv++; argc--; /* クイック ソートのアルゴリズムを使って、残りの引数をソートします。 */ qsort( (void *)argv, (size_t)argc, sizeof( char * ), compare ); /* ソート後のリストを表示します。 */ for( i = 0; i < argc; ++i ) printf( "%s ", argv[i] ); printf( "\n" ); } int compare( const void *arg1, const void *arg2 ) { /* 2つの文字列を最後まで比較します。 */ return _stricmp( * ( char** ) arg1, * ( char** ) arg2 ); } MSDNライブラリ qsort 参考

  • rickky
  • ベストアンサー率66% (2/3)
回答No.6

>難しい例題を持ち出さなくても > >int main(int argc, char **argv) > >で使われていますけど.... argvの仮引数宣言は、 int main(int argc, char *argv[] ) で、宣言してもよい。 むしろ、ANSI-C規格でmain関数の仮引数宣言では、 char **argv; より、 char *argv[]; で解説するのが普通になっている。 だから、2重ポインタの例題を、argv引数を取り上げて説明を受ける機会が無い初心者が多くなった。 いずれにせよ、2重ポインタの『必要性』の質問に対して、argv引数を持ち出すのは混乱の原因になる。 C言語の仮引数宣言は、意味があっていればどのように宣言しても間違いではない。**argv を *argv[] としてもよい。しかし初心者がこれを理解するまでには時間と労力が掛かる(そもそも実引数と仮引数の言葉の意味を知らない人が勉強するから)。 これを省くために、2重ポインタでargvを宣言するより、ポインタ配列で宣言した方が、直感的に理解がしやすい。 そのため2重ポインタがどうしても必要となる、その存在意義を理解する場面を説明することによって、2重ポインタを学習するのがよい。 ただ初心者向け例題が、思い浮かばなかった。 int a; int *p; int **pp; p=&a; pp=&p; **pp = 10; printf("%d\n",a); 普通の教科書に出てくるような、これに似たようなプログラムを理解しても、2重ポインタの必要性なんて、全然わからないのは、当然だと思う。

  • yokomaya
  • ベストアンサー率40% (147/366)
回答No.5

少しアレンジしたのをごらん戴けますでしょうか? char *mnthp[] = {/* ポインタの配列の宣言 */ "January", "February", "March" ,NULL }; char **p1;/* 「ポインタのポインタ」の宣言 */ int i, j; p1=mnthp;/* 「ポインタのポインタ」にポインタの配列 */ /* の先頭番地を設定 */ /***** 例改 *****/ while (*p1) {/* 「ポインタのNULLチェック */ printf("%s\n", *p1++);/* 文字列を出力後インクリメント */ } 実用ということですので、僕が思うには全配列に処理するとき配列が何個あるかをまったく意識せずに最後のNULLを目印にできるこれが一番感動的でしたけどね。**argvもそうなってると思います。 *p1++が如何にもCらしいし。

  • don_go
  • ベストアンサー率31% (336/1059)
回答No.4

難しい例題を持ち出さなくても int main(int argc, char **argv) で使われていますけど....

  • rickky
  • ベストアンサー率66% (2/3)
回答No.3

初心者向け例題は、あまり意味がありません。 そのようなプログラムを眺めても、2重ポインタの必要性など、わからないでしょう。 例えば次のような例題をプログラミングで考えてみて下さい。 【例題】:  テキストファイルのすべての行の内容を、メモリ上に読み込むプログラムを作成する。  条件として、テキストファイルの1行の最大文字数は136文字以内とする(1行の最大文字数に制限を付けないとすごく難しくなるので制限を入れます)。  また、コンピュータのメモリ上に読み込んだテキストファイルの各行は、無駄の無いような状態とする。例えば2次元配列で buf[行数][138](ヌル文字を含めた137で宣言してもよいが、奇数より偶数がよい) のような宣言をしてはいけない。これだと136文字未満だと、空き領域が無駄になる。  なお、テキストファイルは2度読みする。1回目の読み込みで、テキストファイルの全体行数を求める。2回目の読み込みで、各行の内容をメモリに格納する。 このようなテキストファイルの読み込み処理部分をC言語で考えてください。 プログラムでは、2重ポインタ(ポインタのポインタ)変数の宣言が必要になります。 初心者向け解説では、2重ポインタが必要となる例題を真正面から取り上げることができないので、どうしても現実的でない話をすることになります。 malloc関数を勉強すれば、2重ポインタの例題はすぐに出てきます。 ポインタ配列領域を動的にメモリに取ろうとすれば、2重ポインタ変数宣言が必要になるためです。 char **bufpp; bufpp = (char **)malloc( 100行*sizeof(char *)); そして bufpp[10行目] = (char *)malloc(テキストファイル10行目の文字数+1); 注:+1は、ヌル文字を入れるため。 初心者向けでは、別の回答にもあるように、ポインタ配列を関数に受け渡す場面で、2重ポインタで受けます。 でも2重ポインタがイヤだったら、見かけ上、ポインタ配列宣言で受け取ってもよい。     void put_data(char **p1) この宣言がイヤなときは、     void put_data(char *p1[3] ) としてもよい。     void put_data(char *p1[] ) としてもよい。 数字を省略するのが、C言語の仮引数的には美しいかな?。 だから2重ポインタのその表現上を避ける方法は考えられる。 でも、malloc関数を使い出すと、どうしても2重ポインタ変数宣言が必要となります。 だから初心者には、難しいと思います。

tk_1980024
質問者

お礼

rickkyさんどうも有難うございます(しばらく別の仕事をアサインされ放置プレーとなってしまいましてすみませんでした)。 動的にメモリに取ろうとする時に必要、ということでピンと来ました。 助かりました、有難うございます。

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

>printf("%s", mnthp[i]); で値を参照出来ると思われる。 それはそうです。 必要がないならそのように使う必要はないです。 ポインタ自体をそのアドレスを使ってポインタでアクセスしたい場合に使います。

  • galluda
  • ベストアンサー率35% (440/1242)
回答No.1

がると申します。 まぁあまり使わないのですが、時々必要な場面があるので。「使い方を覚えておく」程度でよろしいのではないでしょうか? 今回のサンプルからもうちょっとだけ「実務に近い」ソースに落とすと、例えばこんな感じになります。 #include <stdio.h> void put_data(char **p1) { int i; for (i = 0; i < 3; i++) {/* 「ポインタのポインタ」の値を変えずに */ printf("%s\n", *(p1 + i));/* 相対的に文字列を出力 */ } } int main() { char *mnthp[3] = {/* ポインタの配列の宣言 */ "January", "February", "March" }; /* 関数をcall put_data(mnthp); }

tk_1980024
質問者

お礼

galludaさんありがとうございます。ちょっとの間放置でしてすみませんでした。

関連するQ&A

  • ポインタについて

    C言語では char *p; と宣言しておいてそのポインタに p = "ABC"; というように指定できると思います。 参考URL http://www9.plala.or.jp/sgwr-t/c/sec10-3.html このようにしてポインタを使用した場合、freeで開放する必要はあるのでしょうか。 配列で文字列を指定した場合 char str[] = "ABC"; スタック領域に確保されるので、宣言したメソッドが終わった場合、自動的に開放されると思いますが、malloc等でヒープ領域にメモリを確保した場合、freeで開放されるまではメモリ上に残っていると思います。 動きを見てみると、 char *p; p = "ABC"; もメソッドが終わってもメモリ上に残っているように思えるのですが、 開放する必要があるのでしょうか。 上記のような指定の仕方は使わなければいいだけなのですが、 気になってしまいました。 変な質問ですいませんが、宜しくお願いします。

  • ポインタ配列のプログラムについて

    ポインタ変数の配列のプログラム #include<stdio.h> void main() { char *p[] = {"JANUARY","FEBRUARY","MARCH","APRIL","MAY","JUNE", "JULY","AUGUST","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER"}; int i,j; *p[0]=*p[2]; *p[0]=*p[2]; for(i = 0;i < 12; ++i){ printf("%2d 月:%s\n",i+1,p[i]); } for(i = 0;i < 12; ++i){ j = -1; do{ ++j; printf("%4x ",p[i]+j); } while (*(p[i]+j) != '\0'); printf("\n"); j = -1; do{ ++j; printf(" %c ",*(p[i]+j)); }while (*(p[i]+j) != '\0'); printf("\n"); } } について、このプログラムを最も大きい値(ASCIIの文字コードが最も大きい値)を出力するプログラムに変更したいのですが、ASCIIの文字コードが何なのかよく分かりません。分かる方いましたら教えてください。よろしくお願いします。

  • ポインタ配列について

    下のプログラムは、ポインタ変数の配列のプログラムです。 このプログラムを、月の順番を入れ替えて出力するプログラムに変更するにはどのようにすれば良いか分かる方いましたら、是非教えてください。例えば、JANUARYとMARCHを入れ替えて出力するプログラムなどです。 #include<stdio.h> void main() { char *p[] = {"JANUARY","FEBRUARY","MARCH","APRIL","MAY","JUNE", "JULY","AUGUST","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER"}; int i,j; *p[0]=*p[2]; *p[0]=*p[2]; for(i = 0;i < 12; ++i){ printf("%2d 月:%s\n",i+1,p[i]); } for(i = 0;i < 12; ++i){ j = -1; do{ ++j; printf("%4x ",p[i]+j); } while (*(p[i]+j) != '\0'); printf("\n"); j = -1; do{ ++j; printf(" %c ",*(p[i]+j)); }while (*(p[i]+j) != '\0'); printf("\n"); } }

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

    ポインタ変数の宣言 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という名のポインタのポインタで受け取るというのも、よくわからなくなっています。 おそらくポインタ配列に対する理解がどこかでずれているようですが、自分でどこがわからないのかわからなくなっています。 どうかご教授ください。

  • c言語のポインタ配列

    下記のようなポインタ配列が合った場合、 char *mnthp[4] = { "January", "February", "March", "April" }; Februaryに別の文字列を代入したいのですが、どうすればよいのでしょうか?

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

    ポインタの配列の動的確保について教えてください。 入力した数値をポインタ配列に入れるプログラムです。 下記のように書いてみました。(見づらくてごめんなさい) #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));   ・・・ ポインタの配列を宣言して、配列の各要素に動的確保するのと ポインタのポインタを宣言し、ポインタ配列を動的確保して、再度配列の要素に動的確保するのとでは、何か違いがあるのでしょうか? ポインタのポインタを宣言し、ポインタ配列を確保する必要性が良く分かっていないのです。 ネット等で調べて見たのですが、理解力がないのかよく分かりませんでした。 どうか教えてください。

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

    #include <stdio.h> int main() { char x[3]; char *y; x[0]='a'; x[1]='b'; x[2]='\0'; y="abc"; printf("xの値は%s\n",x); printf("yの値は%s\n",y); } 通常の配列宣言では、このままだと文字列をまとめて 代入できないのに対して、ポインタ変数ならまとめて代入することができるのは何故ですか?そういう仕組みだと言われてしまえば、それまでなんですが・・・

  • ポインタ?

    #include <stdio.h> main() { char s[] = "I love cat and dog."; char c = 'a'; char *p = s; int n = 0; printf("\"%s\"の中から\'%c\'をさがします。\n", s, c); while(*p != '\0') { if(*p == c) { printf("%d番目で発見しました。\n", p - s + 1); ++n; } ++p; } if(n == 0) { printf("1つも見つかりませんでした。\n"); } else printf("全部で%d個見つかりました。\n", n); return 0; } わからないので質問したいのですが、これは「Cの絵本」という、 本に出ているサンプルプログラムです。 わからないところは、 12行目の、printf("%d番目で発見しました。\n", p - s + 1); ところです。その、「p - s」のところが特にわかりません。 ポインタって言うのは、アドレスを格納する変数ですよね? その、pからsを引いても0になるんじゃないかと思って、理解が できません。どうして、このp-sで、cの位置が発見できるのかが 理解できません。最後の+1は配列が0から始まるんで+1にすれば いいのはわかるんですが、p-sでどんなことが起きているかが 理解できなくて。ポインタをちゃんと理解できていないから、 こういった疑問が出てくるんですかね? ほかの参考書も本屋さんに行って見てみようと思っているんですが、 どなたか教えていただけませんか? よろしくお願いします。

  • ポインタ配列のプログラムについて

    ポインタ変数の配列のプログラム #include<stdio.h> void main() { char *p[] = {"JANUARY","FEBRUARY","MARCH","APRIL","MAY","JUNE", "JULY","AUGUST","SEPTEMBER","OCTOBER","NOVEMBER","DECEMBER"}; int i,j; *p[0]=*p[2]; *p[0]=*p[2]; for(i = 0;i < 12; ++i){ printf("%2d 月:%s\n",i+1,p[i]); } for(i = 0;i < 12; ++i){ j = -1; do{ ++j; printf("%4x ",p[i]+j); } while (*(p[i]+j) != '\0'); printf("\n"); j = -1; do{ ++j; printf(" %c ",*(p[i]+j)); }while (*(p[i]+j) != '\0'); printf("\n"); } } について、このプログラムを、先頭の文字(1月ならJ、2月ならFなど)のアドレスを変化させて、アルファベット順の次の文字に変更するプログラム(1月ならJ→K、2月ならF→G)に書き換えたプログラムを作成せよ。 この問題が分かる方いましたら、解き方だけでもいいので、是非教えてください。よろしくお願いします。

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

    C言語を独学で始めたばかりの者です。 ポインタのところまでいきましたが、以下のプログラムを配列で書き替えようと思ったのですが、test4ができません。test1から3 までは配列を使って書けました。 #include <stdio.h> main() { char s[10]; char *p; strcpy(s, "ABCDE"); p=s; printf("------test1\n"); printf("s=%s p=%s\n", s, p); printf("------test2\n"); putchar(*p); putchar(*(p+1)); putchar(*(p+2)); putchar('\n'); printf("------test3\n"); *p='m'; *(p+1)='n'; printf("s=%s\n", s); printf("------test4\n"); while(*p) { *p=*p+1; ++p; } printf("s=%s\n", s); } この最後のtest4を配列で書くとどうなるのでしょうか。質問文が長くて申し訳ないのですが、お願いいたします。

専門家に質問してみよう