「6さいからのプログラミング」第8話では、メインメモリのアドレスを直接やりとりしたり走査できる「ポインタ」について解説します。1変数のアドレス第7話では、同じ処理を何度も行うときに、選択処理や反復処理を用いることでシンプルに書けることを示しました。今回は、たくさんの変数をシンプルに扱う方法について解説します。C言語で30個の変数を扱うとき、例えば図1-1のように書くことができます。variables.cこの場合、各変数に5を代入したいときには「a=5;b=5;c=5;...」と何度も書く必要があります。仮にこれらの変数がメインメモリ上に連続して存在していたなら、図1-2のように、メインメモリのアドレスを変数のサイズ分だけ進めながら書き換えることで、単純に代入できそうです。変数のアドレス例えばこの例では、変数aが存在しているアドレスがA0番地なので、A0番地から順に、int型のサイズである4バイトずつ移動しながら値を書き換えると、すべての変数が書き換えられることになります。これを実現するものとして、C言語には「ポインタ」という機能が用意されています。2ポインタ「ポインタ」とは、「変数のアドレスを入れる変数」です。何の型の変数のアドレスを入れるかによってポインタには種類があり、その変数の型のうしろに「*」を付けて表現します。int型の変数のアドレスを入れるポインタの型は「int*」型です。ポインタは変数の一種に過ぎないので、プログラム上でも変数と同じように扱います。ptr;」と書きます(図2-1)。pointer.c9行目には変数aの前に「&」が付いていますが、これはその変数のアドレスを返すという演算子です。「ptr=&a;」で、ポインタptrには変数aのアドレスが入ります。ここでは仮に、そのアドレスをA0番地としておきましょう。ptrにはA0が入ります。ptr;」の「*」とは別物で、ポインタに入っているアドレスの場所に存在している変数を返す演算子です。ptrには現在A0が入っていますので、「*ptr」とはA0番地に存在している変数、すなわち変数aとなります。「*ptr=3;」とは「a=3;」のことで、変数aの値が3に書き換わります(図2-2)。ptr」に入っています。ややこしいですが、つまりポインタの前に付ける「*」演算子は、ポインタに入っているアドレスの先を変数に見立てて、値を読み書きできるというものです。メインメモリの03番地の値を書き換えたい場合は、「ptr=3;」として、「*ptr=1234;」とすれば書き換わります。この場合、メインメモリの03番地が読み書きできる領域とは限らないため、おそらくエラーが発生します。読み書きできるメインメモリの領域を操作しなければなりません。3ポインタのポインタさて、ポインタとは変数のアドレスを入れる変数でした。ここで、ポインタ自体も変数の一種であることを踏まえると、「ポインタのアドレスを入れるポインタ」というものを考えることができます(図3-1)。pointer2.cこのプログラムでは「ptrptr」が「ポインタのポインタ」になっています。「int*」型のポインタのアドレスを入れるポインタを作りたい場合には、int*に「*」を付けた「int**」が型となります。この「ptrptr」に対し「*ptrptr」と書くと、ptrptrに入っているアドレス先の変数を指しますので、「ptr」のことを意味します。これに*演算子を付けて「**ptrptr」とすると、「*ptr」のこと、つまりptrに入っているアドレス先の変数を指しますので、「a」のことを意味します。結果的に「**ptrptr=3;」とは「a=3;」と同等で、aに3が入ります(図3-2)。ポインタのポインタポインタを使うと30個もの変数をシンプルに扱えたのと同様に、ポインタのポインタを使うと30個ものポインタを扱うときにシンプルに書けます。4ポインタ演算ポインタに対する演算として、「+」と「-」の演算子があります。これは「ポインタ+整数」もしくは「整数+ポインタ」のように書き、ポインタに入っているアドレスから指定した整数個分の変数のサイズだけ進めたり戻したりすることができます。int*型のポインタpにアドレスA0が入っているとき、「p+1」はA0からint型1つ分だけ進めたアドレスになります。int型が4バイトの環境では「p+1」はA4です(図4-1)。ポインタの加減算このように「*p」「*(p+1)」「*(p+2)」のように1ずつ増やしていくと、pに入っているアドレスA0から出発して、メインメモリ上の連続したint型の変数を順に読み書きすることができます。5void*ポインタの一種として「void*」型があります。これは、void型の変数のアドレスを入れるポインタ、という意味ではなく、「単にアドレスを入れるだけのポインタ」だと捉えてください。void*は単にアドレスを入れるだけなので、例えばvoid*型のポインタpがあったとき、「p+1」「p-1」といったアドレスの演算はできません。「*p」としてアドレス先にアクセスすることもできません。void*型は主に、どんな型の変数のアドレスが入るか判らないときに使われます。6NULLアドレスの0番地は特別で、通常どの変数のアドレスでもないことを意味します。stdio.hなどの中ではこの0番地を「NULLヌル」と名付けており、ポインタがどの変数のアドレスも入れていないことを示したい場合にNULLを入れます(図6-1)。変数名[要素数];」のように書きます。a[30];」と書くと、メインメモリ上にint型変数30個分の連続した領域が確保されます。このとき「a」の名前は、ポインタのように扱えます。「*a」「*(a+1)」「*(a+2)」...「*(a+29)」と書け、30個分の変数の値を読み書きできます。ポインタとは異なり、「a=0;」のような、aが保持するアドレスそのものを書き換える操作はできません。このように、連続した領域を確保しその先頭アドレスを保持するaのことを、「配列はいれつ」といいます。a[30];」として作成した配列aに対し「*a」「*(a+2)」のようにアクセスできますが、C言語にはさらにこれを見やすくする仕組みがあり、「*a」のことは「a[0]」、「*(a+2)」のことは「a[2]」と書けます(図7-1)。a[30];」と作成すると、あたかもa[0]~a[29]の30個のint型変数が作られたかのように見せるもので、人間にとって直観的な記法となっています。この記法はポインタにも同様に使え、ポインタpに対し「p[5]」などと書けます。この「a[b]」の記法はコンパイル時に「*(a+b)」の形に自動的に置き換えられます。それでは、30個のint型変数を作成してそれぞれに5を代入するプログラムを書いてみましょう(図7-2)。array.cfor文と組み合わせることで、とてもシンプルに書けました。8動的な配列C/C++以外の言語の配列は、C/C++とは仕組みが異なることがあります。JavaやC#の配列は、配列の作成時に要素数が固定されません。実体はヒープ領域に作られ、配列は要素数と実体への参照を持っています(図8-1)。...」という値が配列に入っていますが、JavaやC#ではこれらはヒープ領域に置かれています。ヒープ領域はプログラムの実行中に自由に確保や解放ができるので、要素数を柔軟に変えることができるという仕組みです。先ほどと同様、30個の変数を作成してそれぞれに5を代入するプログラムをC#で書くと、図8-2のようになります。int[30]」でヒープ領域に連続した領域が確保され、aにそこへの参照を代入しています。9連想配列PHPなどのスクリプト言語の配列は、これまでとは異なる「連想配列れんそうはいれつ」と呼ばれるものです。連想配列は、メインメモリ上に連続した領域があるのではなく、要素番号と値との対応が保持される仕組みです。配列aに対し、a[2]に5を入れたり、a[100]に8を入れた段階で、メインメモリ上にこれらの対応関係が作られます(図9-1)。連想配列このため、a[100]を使ってもメインメモリ上にたくさんの領域が確保される心配がない上に、「a["apple"]」のように要素番号に整数以外の値を用いることもできます。この図のように、a[2]に5、a[100]に8、a["apple"]に4を代入するプログラムをPHPで書くと、図9-2のようになります。array.phpPHPでは変数名の前に「$」を付けます。2行目で空の連想配列を作ってaに代入し、3~5行目で値を代入しています。最後に、文字列について解説します。文字列とは、基本的には文字型の配列です。JavaやC#の配列は先ほど紹介したように柔軟なため、JavaやC#における文字列も自由に長さを変えられる領域を持った扱いやすいものになっています。C言語の文字列は通常、領域の大きさが固定されています(図10-1)。文字列C言語の配列は要素数を保持しないので、メインメモリ上のどこまでが文字列なのかが判りません。そこで文字列の最後に'\0'という特殊な文字を付けることで文字列の終端を表すことにしています。'\0'とは、文字コードが0の文字です。C言語での文字列の扱いを具体的なプログラムで示すと、図10-2のようになります。str.cプログラム中に「"Hello"」のように文字列リテラルを書くと、静的領域にあらかじめ埋め込まれた文字列の先頭アドレスになります(図10-3)。文字列リテラル図の例では、静的領域の30番地から「Hello」の値が格納されているため、ソースコード中に"Hello"と書くと30番地が返ります。7行目の「strcpy関数」は、第2引数の文字列を第1引数にコピーする関数で、「string.h」の中に存在します。よって「strcpy(s,"Hello");」を実行すると、s[0]~s[5]にはそれぞれ「'H'」「'e'」「'l'」「'l'」「'o'」「'\0'」の6文字が入ります。終端文字'\0'があるので、配列sの領域は1文字分多めに確保しておく必要があります。printf関数の「%s」は、渡されたアドレスから終端文字までの文字列を画面に表示する機能です。よって8行目の「printf("%s\n",s);」で画面には「Hello」が表示されます。9行目の「s[1]='i';」により、「Hello\0」は「Hillo\0」に書き換わります。同様に、10行目の「s[2]='\0';」で「Hi\0llo\0」になります。2回目のprintfでは、sの中身である「Hi\0llo\0」を表示しようとしますが、この文字列の途中には終端文字があるため、画面には「Hi」とだけ表示され、残りの「llo」は無視されます。今回は、メインメモリを自在に操作するための「ポインタ」や、たくさんの変数をシンプルに扱える「配列」、そして「文字列」の仕組みについて説明しました。次回は、さらにプログラムをシンプルに書くために、型そのものを自分で作成する方法について解説します。前へ目次ホームへ次へ©Kuina-chan1579922130jaf。