C言語関係掲示板

過去ログ

No666 ポインタがよく分かりません。

[戻る] [ホームページ]
No.7402

ポインタについてです
投稿者---中学生(2003/06/14 23:01:44)


僕はポインタがよく分かりません。
本を何冊も何回も読んでもわかりませんでした。
今は、*や&を見るだけで頭が痛くなります。
でも、Cを使うには必要なことなのでこの際しっかり身につけようと
思います。で、やっぱり&や*の意味が分かりません。
こんな簡単な質問をここでどうかするのもなんですが、
どうか答えてください。

No.7404

Re:ポインタについてです
投稿者---aki(2003/06/15 00:16:35)


下のプログラムは理解できますか?
メモリを表す図を書いて説明してみてください。

メモリはこんな感じで書いて下さい。

address  +------------+
         |            |
         +            +
         |            |
         +            + 
         |            | 
         +            + 
         |            | 
         +------------+ 

#include <stdio.h>

int main(void)
{
    int x;
    int *p;

    x = 123;
    p = &x;

    printf("x = %d\n", x);
    printf("*p = %d\n", *p);

    return 0;
}

私もこのプログラムの説明を書きます。あなたの説明と
比べてみましょう。


No.7412

Re:ポインタについてです
投稿者---中学生(2003/06/15 18:19:41)


>>メモリを表す図を書いて説明してみてください。

#include <stdio.h>
整数型の変数xとポインタ変数pを宣言し、xに123を代入。
ポインタ変数pにはxのアドレスを代入。よって、
最初のprintfで「x=123」を表示し、次のprintfでポインタ変数の値である123を「*p=123」として表示。説明ってこんな感じですか?
ご迷惑をおかけしてすみません

No.7430

Re:ポインタについてです
投稿者---aki(2003/06/15 23:43:31)


int x;
int *p;
>整数型の変数xとポインタ変数pを宣言し、

O.K

x = 123;
>xに123を代入。

O.K

p = &x;
>ポインタ変数pにはxのアドレスを代入。

O.K

printf("x = %d\n", x);
>よって、最初のprintfで「x=123」を表示し、

O.K

printf("*p = %d\n", *p);
>次のprintfでポインタ変数の値である123を「*p=123」として表示。

ここはおかしいです。書き直してみてください。


No.7405

Re:ポインタについてです
投稿者---PSB(2003/06/15 00:20:51)


せっかく
http://www9.plala.or.jp/sgwr-t/c/sec10.html
というページがあるのだから、この章を読んで分からない事を質問してみては?


No.7406

Re:ポインタについてです
投稿者---ともじ(2003/06/15 00:22:06)


こんばんは。

>僕はポインタがよく分かりません。
>本を何冊も何回も読んでもわかりませんでした。

どんな本を読みましたか。よかったら教えてください。

>今は、*や&を見るだけで頭が痛くなります。
>でも、Cを使うには必要なことなのでこの際しっかり身につけようと
>思います。で、やっぱり&や*の意味が分かりません。

* や & をわかりやすく説明するには、順序だてた長い説明が必要に
なります。掲示板の書き込みではなかなか大変です。あなたが、どこまで
わかっているのかを示してもらえると、その続きから説明ができるので、
どこまでは理解しているかを教えてくれますか。

それから、このホームページにもポインタの説明は載っています。
図も書いてありますから、掲示板での説明よりは親切なつもりです。
わからないところを質問してくれるとありがたいです。

No.7413

Re:ポインタについてです
投稿者---中学生(2003/06/15 18:23:03)


>どんな本を読みましたか。よかったら教えてください。
独習Cです。
>わからないところを質問してくれるとありがたいです。
NULLなどの使い方や、ポインタが一体何の役に立つのか、
どう考えても理解できません。わかるのはakiさんにレスした
問題くらいです。

No.7422

Re:ポインタについてです
投稿者---ともじ(2003/06/15 22:07:33)


>>どんな本を読みましたか。よかったら教えてください。
>独習Cです。

独習書の定番ですね。最初の1冊目としては難しいと言う人もいるようです。
ポインタに絞った本も何冊も出ています。私が読んだことがあるのは、
1.「C言語ポインタ完全制覇」前橋和弥著 技術評論社
2.「秘伝C言語問答 ポインタ編」柴田望洋著 ソフトバンク
3.「C言語ポインタが理解できない理由」朝井淳著 技術評論社
です。
いずれも、ポインタの知識を深めるのに役立ちました。

違いをあげると、次のような点でしょうか。
1.はユーモアも含みストレートな作者の意見も述べるなど、他書と
一風違った雰囲気で飽きさせません。
2.は学校の先生と学生の掛け合いで説明を進めるなどわかりやすく
ポインタについて説明しています。標準関数の説明も豊富です。
3.は CPU やメモリ、OSなどのコンピュータの基礎知識の解説から入り、
ポインタへの説明につなげています。

こういった書で、ポインタへの知識を深めるのもよいのでは、という
ことで簡単な紹介をしました。

>NULLなどの使い方や、ポインタが一体何の役に立つのか、
>どう考えても理解できません。わかるのはakiさんにレスした
>問題くらいです。

akiさんへの回答をみると、本当に基本的なことはわかっているようです。

NULL というのは、malloc関数などの動的メモリ確保の関数で、メモリ
確保に失敗した場合などに返却されます。他にも、リスト構造の
最終ポインタを示したりと、ポインタが実際のアドレスを指せないとき
に使います。

ポインタが役に立つのは、一番は関数です。ポインタを使うと関数側から
間接的に呼び出し元の関数の値を参照することができます。
この辺のところは、こちらを参照してください。
http://www9.plala.or.jp/sgwr-t/c/sec11-2.html
http://www9.plala.or.jp/sgwr-t/c/sec11-3.html

単に同一関数内では、ポインタではなく配列を使えば十分でしょう。
昔はポインタの方が実行速度が速いと言われましたが、最近のコンパイラ
はそういうこともないようです。


No.7407

Re:ポインタについてです
投稿者---YuO(2003/06/15 01:54:55)


長文になってしまいました。
でもって,行頭に#があるに行は,私の雑感だの予防線だのが書いてあるので,読まない方がよいかもしれません。


>僕はポインタがよく分かりません。
>本を何冊も何回も読んでもわかりませんでした。
>今は、*や&を見るだけで頭が痛くなります。
>でも、Cを使うには必要なことなのでこの際しっかり身につけようと
>思います。で、やっぱり&や*の意味が分かりません。

ポインタには,
・別名を作り出す
という機能と,
・配列にアクセスする(=配列反復子)
という機能の,全く異なる二つの機能があります。

ポインタがわからなくなる原因はこの二つを混ぜてしまうことで,
ポインタで失敗する原因もこの二つを混ぜてしまうことだと思っています。
#アドレスがどうこう,ってのも原因かも……<アセンブラやったことのある人はわかりやすいでしょうけど。

コンパイラから見るとこれらの機能が分けられているわけではないです。
ただ,プログラムする側としては,これら二つの機能をきっちりわけて考えたほうがよいです。
#わけないと,mallocで得たポインタを演算して値を変えてしまい,freeで落ちたりする。


まず,別名を作り出すという機能。
これは,「このオブジェクトはここにあるよ」という情報を格納することで,
本来の名前以外の名前でもそのオブジェクトにアクセスすることができるようにする機能です。
#mallocで得たポインタも,mallocで作り出したオブジェクトへの別名として働いています。

典型的な物は,
int n;
int * p;

p = &n;  /* pはnを指している */
*p = 10; /* pが指している先(つまりはn)に10を代入する */
printf("%i\n", n);
とやると,10と出力される,というものです。

単項&演算子は,オブジェクトへのポインタを生成する演算子ですが,
結局は別名を作り出す演算子です。

そして,別名を使ってオブジェクトにアクセスするには,単項*演算子を使います。
#構造体には,->演算子もありますが。←単項*演算子と.演算子の省略形。

単項*演算子を利用せずにポインタ型の変数にアクセスすると,
この変数はどのオブジェクトの別名を保持しているか,ということを処理することになります。
代入演算子は,保持しているオブジェクトの別名を代入しますし,
等価演算子(==, !=)は同じ別名を保持しているかを調べます。


次に,配列反復子の機能。

反復子というのは,あるコンテナ(=配列とかリストとか)の内部を公開せずに,
コンテナの要素をアクセスするための手段として提供されている物です。

ポインタに対して次の演算子を適用できるのは,この機能で利用するための物です。
++ -- +(二項) -(二項) += -= [] < > <= >=


で,「反復子に1を足す」ということは,
「現在の反復子が指している要素の次の要素を指す反復子を取得する」ということです。

で,困ったことに,配列反復子の取得や反復子が指している要素へのアクセスは,
別名と同じ方法でアクセスすることになります。

典型例としては,
int main (int argc, char *argv[]) {
    char ** p;
    for (p = &argv[0];         /* pに配列argvの最初の要素を指すポインタを代入 */
         p != &argv[0] + argc; /* pが配列末尾を指すまでループ */
         ++p) {                /* argvの次の要素を指すようにする */
        puts(*p);
    }
    return 0;
}
のような使い方が配列反復子としての使い方になります。
#関数宣言子中の配列型はポインタ型になる,という規則はここでは意図的に無視しています。

あと,配列名を式中で使った場合,配列名が
・単項&演算子のオペランドではない
・sizeof演算子のオペランドではない
時,配列の先頭要素へのポインタになります。つまり,上のプログラムは,
int main (int argc, char *argv[]) {
    char ** p;
    for (p = argv; p != argv + argc; ++p) {
        puts(*p);
    }
    return 0;
}
と同じです。
#配列int a[10]に対してa[3]というアクセスは,*(&a[0] + 3)と処理されていたりする……。
#↑&a[0]は「aの最初の要素の反復子」の意味です。これを書かないと,定義が循環するので。


あ,書き忘れていた。
ポインタの宣言子(宣言中での*と思ってください)は,配列や関数の宣言子よりも結合が弱いです。
つまり,上記char *argv[]は,「argvは要素数不定のchar *型の配列」と読みます。

はっきりいって,配列反復子は使わなくてもプログラムは書けます。
わかりにくいと感じるなら,ポインタを配列反復子として使わないのがよいでしょう。
配列反復子の演算は,すべて配列のインデックスの演算をするだけで処理することができます。


で,先ほど予防線として書いた,関数宣言子中での規則というのは,
関数の宣言や定義における仮引数内に何らかの型Tのn個の要素からなる配列型の変数fooを宣言(or定義)すると,
T foo[n]
は,
T *foo
と読み替えられる,というものです。
あくまで関数の仮引数のみであることに注意してください。

これも,同じであることは気にせずに,
・配列がほしいならT foo[n]と書く
・ポインタがほしいならT * fooと書く
とするとよいでしょう。


No.7408

Re:ポインタについてです
投稿者---A(2003/06/15 10:10:05)


始めは「変数を裏ルートで操作する」くらいに覚えておく程度でいいと思います。
*p = 20とすると他の変数の値が20になるとか、
関数の引数にポインタを使えば元の内容を変えられるとか。
例えば変数(int)(のポインタ)を二つ受けとって内容を入れ替えるには
void swap(int *a, int *b)
{
    int temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

として、
swap(&x, &y)などどして呼べばいいのです。
そして*がついた引数には&を付けて変数を渡す、と覚えていれば。
いきなり飲み込もうとせずに、使ってみて「便利かも」くらいでよいと思います。
はじめは誰もが補助輪つけてたじゃないですか。
(最近は乗れない人いるらしいですが・・・)

No.7409

Re:ポインタについてです
投稿者---名無し(2003/06/15 13:56:04)


ポインタを理解するには、まずメモリを理解する必要がある。

http://www.pro.or.jp/~fuji/mybooks/cpro/index.html
http://www.pro.or.jp/~fuji/mybooks/cdiag/

まあ、この辺りでも嫁。