【C言語】ポインターを超わかりやすく解説!(入門編)

はじめに

どうも! 高杉 皆為(@MinatameT)です。

C言語の学習で特につまずきやすいとされているのが「ポインター」の部分です。多くの方と同じく、私もポインターに対して苦手意識がありますし、学生の頃は全然わかりませんでした。

  • ポインターとは何を表しているのか?
  • 通常の変数とポインター変数の文法の違いは?
  • scanf関数などで変数の前につける「&」の意味は?
  • ポインター変数の前につける「*」の意味は?

ポインターの初学者の方には、こうした疑問があると思います。

そこで、この記事では、C言語のポインターについて、私なりに超わかりやすく解説しています。もし全然理解できなければ、お問合せフォームから苦情を送信してもらっても構いません。

※数理情報系の用語は長音が省略されることが多いです。ポインターもポインタも同じです。ここでは、「ポインター」と表記しています。

それでは解説していきます。ちょっと長いですが、頑張りましょう。

よろしくお願いします!

変数はメインメモリーの中に置かれる

C言語でプログラミングするとき、「変数」を利用します。

例えば、int aなら「整数型の変数a」という意味です。double xなら「小数点型の変数x」という意味です。

これらの変数はどこに存在するかと言うと、パソコンの「メインメモリー」という部品の中です。

メインメモリーには、たくさんのデータが部屋ごとに置かれています。イメージとしては、人がたくさん住んでいるマンションのようなものです。

ここで、整数型の変数aとb、小数点型の変数xを宣言したとします。次のようなコードですね。

int a;
int b;
double x;

このとき、メインメモリーの中身の一部は、次の図のようになっています。

部屋番号と変数の対応

※わかりやすいように「~号室」と表記していますが、実際は「番地」と呼ばれます。

上の図では、aは101に、bは102に、xは103に存在していることがわかります。別の言い方をすると、101という場所にはa、102という場所にはb、103という場所にはxがある……ということです。

この変数の「場所」って、どうやったらわかるんですか?

ナイスな質問です。これから説明していきます。

変数の場所を確認する方法

まずは、次のコードを見てください。

#include<stdio.h>

int main(void)
{
  int a; //通常の変数

  a = 50; //aに「50」を代入する。

  printf("aの値は%dです。\n",a); //aの値(整数型なので%d。)

  return(0);
}

これは、aに50を代入し、aの値を表示するプログラムです。超簡単ですね。

では、aの場所をどのように表すのか……ということですよね。

それは、変数名の前に「&」をつけることで、その変数の場所を表します

例えば、&aなら変数aの場所、&bなら変数bの場所を示します。

先程のコードを修正した、次のコードを見てください。

#include<stdio.h>

int main(void)
{
  int a; //通常の変数

  a = 50; //aに「50」を代入する。

  printf("aの場所は%pです。\n",&a); //aの場所(場所なので%p。)

  return(0);
}

このように「&」をつけることで、変数の場所を確認することができます。

注意点は、printf関数で変数の場所を表示するとき、「%p」と記述することです。%dは整数値、%fは小数値を表しますが、%pはメインメモリー上の場所を示します。

じゃあ、scanf関数で&をつけていたのは、もしかして……。

「入力値をこの場所に入れてください」という意味ですね。例えば、&nならnの場所を表しています。

これらのことを踏まえて、先程の図(メインメモリー内部のイメージ図)を再掲します。

部屋番号と変数の対応

101にはaが、102にはbが、103にはxが置かれてあります。これらの場所をC言語で表すためには、「変数名の前に&をつければ良い」ということがわかりました。

ここで、a = 50、b = 70、x = 2.4だとします。

aの場所を表す!

aは101にあるので、&aは101です。

bの場所を表す!

bは102にあるので、&bは102です。

xの場所を表す!

xは103にあるので、&xは103です。

なるほどー。「変数名の前に&」で、その変数のある場所を指すんですね。

その通りです。それでは、次の見出しから「ポインター」の登場です!

ポインターは変数の場所を表す

ポインターを使う場面とは?

変数の場所を指す方法として、「変数名の前に&をつける」ということを何度も述べました。

しかし、実は、これだけでは不十分な場合があります。

えっ? でも、しっかりと場所は確認できますよね?

場所を確認をするだけなら、これで十分なのですが……。

自作関数を作ったときに、ある関数から別の関数に「変数の値をコピーする」ということをよくやります。例えば、A関数とB関数があるとして、A関数とB関数はお互いの変数の情報を知らないのです。関数同士で情報を共有していないからです。

※「関数って?」となった方は、今は気にしないでください。後で勉強すればOKです。

このとき、関数同士の情報共有のために、ポインター変数を使って変数の値をコピーする必要があるのです。例えば、自作関数からmain関数に変数の値をコピーしてあげるときには、ポインター変数を使います。

……とは説明したものの、ここで複数の関数を使った例を挙げてしまうと、多くの読者さんを混乱させてしまいかねません。そこで、main関数だけでポインターを利用する、最も簡単な例を紹介します。

ポインターの文法

まず、ポインター変数を宣言するときには、ポインター変数名の前にアスタリスク(*)をつけます

int a; //通常の変数
int *ap; //ポインター変数

このように、aの場所情報を持つ予定のポインター変数*apを宣言しました。ちなみに、変数名の由来ですが、変数aのポインター(pointer)を略してapです。

このポインター変数*apに、aの場所情報を代入して(教えて)あげます。

その方法ですが、「ap = &a;」とすれば、apの値はaの場所になります。なぜなら、&aはaの場所だからです。そのままですね(笑)。

なんで「*ap」じゃなくて「ap」なんですか? ポインター変数にはアスタリスク(*)がいるのでは……?

これは最高の質問ですね。今から説明します。

実は、ポインター変数を宣言するときにアスタリスク(*)をつけたのは、この変数が「ポインター機能を持つ」ということをパソコンに教えてあげるためです。アスタリスク(*)をつけなければ、ポインター変数として機能しません。

これはポインター変数の宣言のための「特別なルール」だと思ってください。

宣言時と宣言の後から(通常時)では、ポインター変数の表し方に違いがあります。宣言時の文法は、前述のとおり「ポインター変数名の前にアスタリスク(*)をつけるだけ」です。

宣言の後からは、次のようになります。

  • ポインター変数名だけ:変数の場所(部屋番号)
  • ポインター変数名の前にアスタリスク(*):その場所にある変数の値

今回の例だと、apならaの部屋番号で、*apならaの値です。したがって、aの部屋番号を確認したりするときはap、aの値を確認したりするときは*apと表します。

これらを踏まえて、具体的な例を見てみましょう。次のコードを見てください。

#include<stdio.h>

int main(void)
{
  int a; //通常の変数
  int *ap; //ポインター変数

  a = 50; //aに「50」を代入する。
  ap = &a; //apに「aの場所」を代入する。この行は超重要!

  printf("aの場所は%pです。\n",ap); //apの値(場所なので%p。)
  printf("aの値は%dです。\n",*ap); //apの中身(整数型なので%d。)

  return(0);
}

まず、apにaの場所(部屋番号)を教えています。

printf関数では、apと*apの表示結果の違いを確認しています。思い出してほしいのですが、apはaの部屋番号で、*apはaの値でしたね。

ここで、apは場所なので「%p」、*apは整数値なので「%d」を使うことに注意してください。

これらのことを踏まえて、先程の図(メインメモリー内部のイメージ図)を再掲します。

*apとは?

ap(101号室)に*ap(50)が入っている……ということです。

*bpとは?

bp(102号室)に*bp(70)が入っている……ということです。

*xpとは?

xp(103号室)に*xp(2.4)が入っている……ということです。

さて、ここまでがポインターの基礎知識となります。

ちょっと難しいですね。ポインターのことを完璧に理解している自信がありません。

ポインターは、実際に使ってみないとわかりにくいですね。……ということで、確認問題を用意しています。

確認問題

問題

まず、int型の変数a、bと、double型の変数xを宣言してください。aの値は「50」、bの値は「70」、xの値は「2.4」とします。

次に、各変数に対応させるポインター変数も宣言してください。そして、それらのポインター変数に対応させる変数の「場所」をそれぞれ代入してください。

最後に、printf関数を使って、aの場所、aの値、bの場所、bの値、xの場所、xの値を出力させましょう。

ただし、printf関数では、各変数の場所と値に「ポインター変数」を使うものとします。つまり、a、b、xで値を、&a、&b、&xで場所を表さないでください。

解答例

#include<stdio.h>

int main(void)
{
  int a; //通常の変数
  int b; //通常の変数
  double x; //通常の変数
  int *ap; //ポインター変数
  int *bp; //ポインター変数
  double *xp; //ポインター変数

  a = 50; //aに「50」を代入する。
  b = 70; //bに「70」を代入する。
  x = 2.4; //xに「2.4」を代入する。
  ap = &a; //apに「aの場所」を代入する。この行は超重要!
  bp = &b; //bpに「bの場所」を代入する。この行は超重要!
  xp = &x; //xpに「xの場所」を代入する。この行は超重要!

  printf("aの場所は%pです。\n",ap); //apの値(場所なので%p。)
  printf("aの値は%dです。\n",*ap); //apの中身(整数型なので%d。)
  printf("bの場所は%pです。\n",bp); //bpの値(場所なので%p。)
  printf("bの値は%dです。\n",*bp); //bpの中身(整数型なので%d。)
  printf("xの場所は%pです。\n",xp); //xpの値(場所なので%p。)
  printf("xの値は%fです。\n",*xp); //xpの中身(小数点型なので%f。)

  return(0);
}

これは解答例ですので、少々異なっていても構いません。

変数の場所は、使っているコンピューターによって異なります。私の場合は、次のような実行結果になりました。

aの場所は0018FF50です。
aの値は50です。
bの場所は0018FF4Cです。
bの値は70です。
xの場所は0018FF44です。
xの値は2.400000です。

メインメモリーの0018FF50号室の中に50が、0018FF4C号室の中に70が、0018FF44号室の中に2.400000が入っていることがわかりました。

ポインターの解説はここまでです。基本的なことですが、慣れないと難しいですね。皆さん、お疲れ様でした。


 

みなためじゃんけん

 

このコーナーは、私と擬似的にじゃんけんできるコーナーです。

 

みなためじゃんけん、じゃんけんぽん!

 

私が出したのは……





 

パーでした! チョキの勝利です!



この記事をSNSでシェアする

プログラミングカテゴリーの最新記事(5件)

最新記事(10件)

管理人のTwitter

内部リンク集