もくじ
はじめに
どうも! みなため(@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は101にあるので、&aは101です。
bは102にあるので、&bは102です。
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(101号室)に*ap(50)が入っている……ということです。
bp(102号室)に*bp(70)が入っている……ということです。
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が入っていることがわかりました。
ポインターの解説はここまでです。基本的なことですが、慣れないと難しいですね。皆さん、お疲れ様でした。