Sponsored Link
 
>>  Main > Programing > Primary > 0010

D言語入門 第10章 - ポインタ

#01 - ポインタとは

概要

ちょっと難しいので概念だけつかみたい人はちょっと後のほうを見るといいでしょう。

さて、ポインタというのは、pointer、つまり、指すもの、という意味です。
ポインタの使用はコードが複雑になってしまうことが多いため、あまり好ましくないといわれています。
ですが、正しく理解して用法と用量を守れば、非常に強力な武器になりえます。
また、C言語やWindowsAPIの関連で使わざるをえなくなる場合もあります。
というわけで、敢えて、ここで紹介します。


まず、ポインタを語るにはメモリを語らなければなりません。
メモリというのは、ハードディスクドライブのように情報を記録しておくためのものではなく、一時的に情報を記憶するためのものです。
ですから、パソコンの電源を切ると、メモリは消滅します。
メモリというのは場所のようなもので、データ置場のような役割を果たします。
データを処理するには、メモリに一度データを置いておき、そこからデータを取り出して処理を施し、またデータを置きなおす、ということが行われています。


前回まで使ってきた変数ですが、これは宣言されることによって必要なメモリを確保します。
メモリが足りません、とか、そういうような言葉はここからきています。 必要なメモリが足りなくて確保できなかった、というわけです。
メモリが足りないとハードディスクドライブ(HDD)などからメモリをがんばって持ってこようとします(仮想メモリと言います)。 しかし、普通のメモリとHDDの仮想メモリでは、どう考えても仮想メモリの速度がべらぼうに遅いのです。 だから、快適な動作をしようと思ったらメモリを増築するわけなのです。 …話がそれました。
前回までのプログラムでは変数を用いてきましたが、この変数にはすべて名前が付いていましたね。
int data; この場合ならば「data」が名前です。 変数を使うということは、このように変数を宣言することでメモリが割り当てられ(intなら4バイトのメモリが割り当てられます)、この変数名を使用することで、そのメモリ上のデータにアクセスすることができました。
ところが、今回やるポインタというものを使うと、変数名でなくてもメモリ上のデータにアクセスすることができるようになります。


ところで、メモリにはアドレスというものがあります。
メモリ上の領域のある場所を指し示す場合、このアドレスというものが使われます。
データは、メモリのアドレスと使用する大きさがわかれば使用することができます。
この、特にメモリのアドレス、というやつがポインタです。
変数名を使うと、このあたりのことを自動でやってくれるため、気にしなくてもよかったわけです。
というわけで、変数名がわからなくてもアドレスさえ分かれば、ポインタの示すデータの型からサイズが推測できるため、データにアクセスできるというわけです。



さて、云々と長ったらしく説明してきましたが、とどのつまり、ポインタというものは、「データを指し示すもの」です。
変数が箱で、箱の中身がデータだとしたら、ポインタというのは、箱のある場所のことです。

ちょっと図解してみましょう。
まず、広い空間があります。 メモリです。



次に、変数を宣言してみましょう。 この広い空間に、箱を置く、ということですね。 ここでは2バイトのデータを宣言したとしましょう。
コードで言うと short data; となります。 2バイトのデータ型はshortという型なので。

これを図にすると…



これで、dataという変数が使えるようになったわけです。 dataという変数名を使えば、アドレスやサイズを意識することなく中身のデータにアクセスできます。
さて、このデータの入っている変数という箱ですが、この箱のある場所がアドレスに対応します。 図で言うと24という数値がそれです。 そのアドレスを記録するものがポインタというわけです。

この置かれた箱を、ポインタで表すとこんな感じです。



アドレス24にサイズが2バイトの箱がありますよ、ってことですね。
んで、この、アドレス24という情報が、ポインタってことです。



難しいように感じるかと思いますが、普段私たちの生活の中でも似たようなことを自然に行っています。
たとえば、URLなんかがそうです。
誰か人に見せたいホームページがあったとします。
どうやって見せますか?
ここでは2つの方法を紹介します。
1つ目はエクスプローラで「ファイル>名前を付けて保存」によって保存されたデータをメッセンジャーなどのファイル送信を使って渡す方法。
2つ目はURLを教えること。
前者が今までの変数で、後者がポインタと同じような操作です。
そのままのデータをやり取りするか、データを指し示すものでやり取りするか、そういった違いが、変数とポインタなのです。

今回のミソ

  • 変数を定義するとメモリを使う
  • 変数はメモリの中にデータを入れる
  • 変数はメモリのアドレスとサイズを知っている
  • アドレスと型さえ分かれば変数名を知らなくてもアクセスできる
  • データことを、実体とか言ったりする
  • アドレスのことをポインタという
  • サイズは型から推測できる

#02 - ポインタの利点

今回は…

ポインタを使うと何が得かについてお話します。
ポインタを使うことで特に効率的になる場面としては、「大きなサイズの構造体を関数で渡す場合」が挙げられます。
こんな感じの大きなサイズの構造体を考えてください
struct Parson
{
    real tall;      // 身長
    real weight;    // 体重
    real chest;     // 胸回り
    real west;      // 胴回り
    real hip;       // 腰回り
    real heads;     // 頭身
    real eyesize;   // 目の大きさ
    :
    :
    :
    :
    :
    real footsize;  // 足の大きさ
    real leglength; // 脚の長さ
    :
    :
    :
    real leg_strength; // 下肢筋力
    real perioral_muscle_force; // 口腔周囲の筋力
    :
    :
    :
}
こんな感じに、非常にメンバの多い構造体はその使用するメモリも非常に大きくなります。
これを、関数に渡す場合を考えましょう。
構造体は関数の実引数に指定されるとその時点でコピーを生成します。
つまり、非常に多くのメモリを使う構造体のコピーを関数に渡すごとに行うのです。

仮にこの構造体の使用するメモリが400バイトだったとしましょう。 intの4バイトデータを関数に渡す際にコピーが作られるのと違い、400バイトのコピーをするため、単純計算で実に100倍の時間がかかります。(実際にはそうとは限りませんが。)
こんなことをいちいちやっていたのでは非常に効率が悪いことが理解できると思います。

ではどうするのか。
ここでポインタを使うのです。

ポインタはデータのアドレスを渡すだけなので、コピーにかかる時間はほとんどありません。
ポインタの情報は32bitのPCでは4バイトです。 intと同じですね。 (64bitのPCでは8バイトです)
ポインタで関数に渡す場合は4バイトのコピーだけで済みます。
つまり、関数に渡すまでの時間を1/100に抑えることができるようになったわけです。


ほかにポインタの利点として、普通に関数に渡す場合と違い、構造体の内容を変更することもできるという利点があります。
普通に関数に渡す場合は、その時点で変数の内容をコピーし、コピーされたデータを関数の引数として使用します。
コピーされたデータにいくら変更を加えたところで、もともとのデータには影響がありません。
しかし、ポインタであれば内容をコピーすることなく、アドレスのみを渡すため、元の変数の内容を変更することができるのです。

関数を超えてアクセスし、変更できるという利点は、関数からのフィードバックに、戻り値1つだけではなく、複数のフィードバックを得ることができるということにもなります。
複数の値をフィードバックする場合、ポインタが役に立つ場合があります。

他にもポインタを使う利点はたくさんありますが、特に重要なのは上記2つです。

今回のミソ

  • 関数にポインタを渡すとどんなに巨大な構造体でも小さなサイズで渡すことができる。
  • 関数にポインタを渡すと元の変数の内容を変更できる。

#03 - ポインタの使い方

今回は…

そんなポインタの使い方を説明します。
ポインタを使うには、「ポインタ型の変数」を用意します。
ポインタは、アドレスをデータとして格納したものですから、やはり変数が必要なのです。

というわけで、
「((int型のデータ)を指し示すポインタ)型の変数」は、次のようにします。
int* ptr;
さて、このポインタですが、指したい情報がないと話になりませんね。
変数からアドレスを取り出してポインタとして扱う方法があります。
それが、 "&" です。

「(int型のデータ)を指し示すポインタ」は次のようにします。 // int型の変数;
int data;

// int型のデータを指し示すポインタを;
// int型のデータを指し示すポインタ型の変数に代入;
int* ptr = &data;

逆に、このポインタの指し示すアドレスから、情報を取り出すときにはどうしたらいいのでしょうか?
ポインタからデータを参照する方法があります。
それが、 "*" です。

「((int型のデータ)を指し示すポインタ型の変数)の指し示すint型のデータ」を参照するには次のようにします。
// int型のデータ;
int data;

// int型のデータを指し示すポインタを;
// int型のデータを指し示すポインタ型の変数に代入;
int* ptr = &data;
// int型のデータを指し示すポインタ型の変数の指し示すint型のデータを
// int型の変数に代入
int data2 = *ptr;
…さぁ、だんだん訳分からなくなってきたぞぅ?www

とりあえずサンプルコードに、ポインタの特徴と、構造体の変数のポインタを関数に渡す方法について書きますので参考にしてください。

今回のミソ

  • ポインタ型の変数を定義するには int* ptr;
  • データをポインタにするには &data
  • ポインタからデータを参照するには *ptr

サンプルコード

// writeflnを使うため
import std.stdio;
// sqrtを使うため
import std.math;


// 構造体でPOINT型を定義。
struct POINT{
    real x;
    real y;
    real z;
}

// 2つのPOINT構造体の変数へのポインタを取って、
// 2点間の距離を求める
real norm(POINT* a, POINT* b){
    // 構造体の場合は b がポインタだったとしても b.x などとしてもアクセスすることができる
    // 意味的には (*b).x と同じ意味。
    return sqrt((b.x-a.x)*((*b).x-(*a).x) + (b.y-a.y)*(b.y-a.y) + (b.z-a.z)*(b.z-a.z));
}


// main関数
int main(char[][] args)
{
    // int型の変数を宣言
    int data = 5;
    
    // dataの中身を出力
    writefln("data : %d", data);
    
    // int型を指し示すポインタ型の変数を宣言
    int* ptr = &data;
    
    // dataの中身を出力(変更なし)
    writefln("data : %d", data);
    // ptrの指し示すデータを出力(dataの中身が表示される)
    writefln("*ptr : %d", *ptr);
    
    // dataの中身を変更。
    data = 3;
    
    // dataの中身を出力(変更あり)
    writefln("data : %d", data);
    // ptrの指し示すデータを出力
    // ptrは一切変更していないのに出力されるデータが変わっているのに注目
    writefln("*ptr : %d", *ptr);
    
    // ptrの指し示すデータ(dataの中身)を変更
    *ptr = 100;
    
    // dataの中身を出力(変更あり)
    // dataは一切変更していないのに出力されるデータが変わっているのに注目
    writefln("data : %d", data);
    // ptrの指し示すデータを出力(dataの中身が表示される)
    writefln("*ptr : %d", *ptr);
    
    // POINT構造体の変数の宣言
    // まとめて宣言したい場合はこのようにする。
    POINT p1, p2;
    // 中身変更
    p1.x = 100;
    p1.y = 100;
    p1.z = 0;
    p2.x = 0;
    p2.y = 100;
    p2.z = 100;
    // 距離をとる
    // &p1や&p2としてポインタを渡している。
    writefln( "norm : %f", norm(&p1, &p2) );
    
    // 正常終了
    return 0;
}

実行結果

data : 5
data : 5
*ptr : 5
data : 3
*ptr : 3
data : 100
*ptr : 100
norm : 141.421356

まとめ

このサンプルコードで特に注目するべきところは、ポインタが指示しているデータは、ポインタ自体をいじらなくとも、変更されることがあるというあたりですね。
逆にこれを使うと、norm関数でやっているようにポインタを渡した状態で、ポインタの指し示すデータを書き換えることができるということもできるわけです。
ただ、これに関してはほかの方法が用意されているので、そちらを使ったほうがわかりやすくていいかもしれません。
「参照」と呼ばれるものです。
ポインタと似たようなものなのですが、若干異なります。
ポインタは場所、参照は言わば変数の別名のようなものです
これについてはまた今度解説します。

#summary - まとめ

第10章のミソ

  • 変数を定義するとメモリを使う
  • 変数はメモリの中にデータを入れる
  • 変数はメモリのアドレスとサイズを知っている
  • アドレスと型さえ分かれば変数名を知らなくてもアクセスできる
  • データことを、実体とか言ったりする
  • アドレスのことをポインタという
  • サイズは型から推測できる
  • 関数にポインタを渡すとどんなに巨大な構造体でも小さなサイズで渡すことができる。
  • 関数にポインタを渡すと元の変数の内容を変更できる。
  • ポインタ型の変数を定義するには int* ptr;
  • データをポインタにするには &data
  • ポインタからデータを参照するには *ptr

宿題

ポインタを使うと、関数の戻り値ではできない2つ以上の値のフィードバックが可能になります。
ためしに、
Wikipediaの、極座標系 (r, θ) から直交座標系 (x, y) への座標変換を与えるっていうあたりを見て、
極座標系のデータを入力し、直交座標系に変換してみましょう。
以下のmain関数は変えずに、 Polar2Rect 関数を考えてみましょう。
int main(char[][] args)
{
    real inAngle = 0.45;
    real inRadius = 5;
    real outX;
    real outY;
    Polar2Rect(inAngle, inRadius, &outX, &outY);
    writefln(outX);
    writefln(outY);
    return 0;
}
戻り値はないので void で return 文は必要ありませんね。
こんな感じでしょうか? void Polar2Rect(real angle, real radius, real* x, real* y) 出力はこのようになるはずです。
4.50224
2.17483

余裕があればこのようなものも試してみましょう。
構造体を使った例ですね。
int main(char[][] args)
{
    Polar2RectData data;
    data.angle = 0.45;
    data.radius = 5;
    Polar2Rect(&data);
    writefln(data.x);
    writefln(data.y);
    return 0;
}
出力は同じになるはずです。

コメント

さてさて、今回はポインタを説明しました。
これでだいたいC言語でできることはほとんどできるようになった感じです。
もちろんまだまだD言語の奥は深いのです。
次回は今までの内容の補強といったようなことをやっていきます。
「変数の寿命とスコープ」とか、「構造体のメンバまとめて定義(with文)」とか、「分岐には実はもうひとつ構文があった!(switch文)」とか、「繰り返しエクストラステージ~まだ俺のターンは終わってないぜ!~」とか。

そんなわけで、宿題できたら、あるいは予想ついたら次にいきましょう~

ページトップ / 目次 / 次へ
Copyright©SHOO All rights reserved. / LastModified : 2007/12/12(水)[05:52:18]