こんにちは、ナナです。
初心者の方は、いざポインタを使ったプログラムを書こうとしたときに「何からすればいいんだっけ?」と、手が止まってしまうことがよくあります。
そんな方は「ポインタ」が使われる代表的なシーンを、まずは叩き込みましょう。
代表的なシーンを知れば、「このシーンではポインタを使えばいいよね。ポインタの使い方はこうだよね」と、パターン化することができるようになります。
このパターンを4つの手順で攻略します。
本記事では次の疑問点を解消する内容となっています。
では、「ポインタ」の基本的な使い方を学んでいきましょう。
ポインタの全貌を学びたい方は『C言語 ポインタを使いこなせ【身に付けるための9の極意】』の記事から順に読むことをお勧めします。
ポインタが必要となる代表的なシーンを知ろう!
師匠!術を出すときってタイミングが大事なんですよ。どんなときにどんな術を出すかで戦況が大きく変化するんです!
で、「ポインタの術」って結局どんなときに使うんですか?一番効果的なタイミングはいつなんですか?!それを伝授してください‼
そうだね。ポインタをどんな時に使うのか、これはすごく大事なことだね。
「ポインタ」を使う代表的なシーンがあるんだよ。このシーンで「ポインタ」の基本的な使い方を身に付けることをお勧めするよ。
ポインタが必要となるシーンとは、どのような時なのかを示しましょう。
「ポインタ」が登場するシーンと言えば!
ポインタが使われる代表的なシーンと言えば、
「関数から複数の結果を呼び出し元に出力したい!」
C言語でポインタが必要となる超・超・超・代表的なシーンがこれです!
これこそがポインタの基礎を理解するために必要なシーンなのです。
関数とは「サービス」ですよね。サービスから複数の情報を呼び出し元に提供したい!
そんな時に「ポインタ」は利用されるんです。
改めてサービスって何なんだろう?
複数の結果を出力するサービスなんて、全然思い浮かばない…。そもそもサービスなんて私は普段は全然利用してないし、そんなものある?
いやいや、皆さんの周りにサービスってたくさんあるんですよ。思い浮かばないのは、サービスとして捉えていないだけなんです。
皆さんの生活に直結したサービスを提供する人・モノ
コンビニの店員、ウェイター、ホテルマン、電子レンジ、自動券売機・・・ありとあらゆるものがサービスなんです。
皆さんが人やモノに対して「何かをしてもらう」といったシーンは全てサービスです。誰もが普段からものすごく利用してますよね!
ATMで表現する複数の結果を出力するサービス
それでは、皆さんにとって身近な銀行のATMサービスを、簡易的にプログラムで表現してみましょう。このサービスからポインタの使い方を学びますよ。
ATMでお金をおろすために必要な入力・出力情報を、次のものとして定義してみました。
このようにサービスとは、入力情報も出力情報も複数にわたることは普通に存在します。
入力情報は引数で渡せばよいので簡単です。問題は出力情報です。
関数からの出力の基本は「戻り値」を使うことですが、1つしか出力できません。そのため、出力情報3つのうち、残り2つは「戻り値」で出力することができません。
ここで登場するのが、関数の引数としてのポインタです。
今回は「お金」と「預金残高」の情報を、関数の引数のポインタを使って出力しましょう。
それでは、このATM関数をプログラム化していきましょう。
ATMという皆さんにとって身近なサービスを、「関数」という形でプログラムの姿に具現化していきますよ。
この中で「ポインタ」がどのように活躍するのかを学んでいきましょう!
弓矢でイメージ!ポインタを使う4つのStepとは
師匠!忍術は印を結ぶことで術を発動できるんです。臨・兵・闘・者・皆・陣・烈・在・前、忍法「火炎名刺手裏剣」!シュピッ!
ほらっ、早くポインタの術を使う印の順番を教えてくださいよっ。
コラッ、燃える名刺を投げるんじゃないっ!じゃあ、ポインタを使うための4つStepを解説するよ。
次のプログラムはATMを疑似的に関数化したものです。ATMを利用するのがmain関数です。
Step①~④とし、関数を呼ぶ側が①②、呼ばれる側が③④のステップでプログラム化します。
関数を呼ぶ側がすべきStepと、呼ばれる側がすべきStepを分離する意識で見てください。それぞれの役割が大事なんです。
Step① 的の作成:出力先情報を記憶する変数定義
弓矢を使うときは射る先の「的」が必要となりますね。「的」という目標物があって初めて弓矢を使う意味があります。
ATMサービスを利用したい人は3つの出力情報を欲しているため、その結果を記憶するための変数をそれぞれ定義します。これらの変数が弓矢に対する「的」を準備することになります。
ポインタを使うことばかりを意識すると、的となる変数定義を忘れてしまう方がいます。
ポインタは「とあるメモリを遠距離アクセスするための道具」なのですから、参照する先の変数を定義するのを忘れないようにしましょう。これがStep①です。
atmFunc関数を呼び出した結果、これらの変数に目的の情報が格納されることがゴールです。
Step② 照準の設定:変数の番地の取り出しと受け渡し
もし、皆さんが「的」を前にし「弓矢」を手にしたら、的に対して照準を合わせますね。ポインタも同じように参照したいメモリに対して照準を合わせる必要があります。
ポインタで照準を合わせるとは、参照したいメモリ番地をポインタ変数に格納することです。
ポインタで照準を合わせるためには、money変数から「100番地」という場所を得る必要があります。
C言語では、変数ラベルの付いたメモリ番地を取り出すための演算子があります。それが、「アドレス演算子」です。
ポインタを使うときによく出てきますので覚えておきましょう。
演算子と書き方
&変数ラベル
使用例
&money
説明
変数ラベルが貼られているメモリ番地を取得する。
実際のプログラム例では、「atmFunc関数」へ次のように引数を指定しています。
atmFunc(0x1234, 0x9999, 10000, &money, &balance);
これはatmFunc関数に対して「money変数とbalance変数のメモリ番地を渡すので、遠距離アクセスして値を設定してください」との意味となります。
そのためアドレス演算子で、変数の番地を受け渡しているのです。これがStep②です。
Step③ 弓矢の作成:ポインタの変数定義
呼び出し側の準備①②が整ったところで、次は呼び出される関数側の出番です。
ここでようやく「弓矢」となる「ポインタ」の登場です。
Step②のatmFunc関数の呼び出し側では、第4・5引数に変数の番地が渡されています。
番地を記憶するのはポインタ変数ですから、番地を受け取るatmFunc関数の引数はポインタ変数として定義する必要があります。
int atmFunc(short card, short pin, long reqMoney, long * pMoney, long * pBalance)
このように第4・5引数は「*」がついたポインタ変数を定義します。これがStep③です。
&moneyで取り出した番地はpMoney変数へ、&balanceで取り出した番地はpBalance変数へ格納されることで、照準が設定された「弓矢」ができあがります。
Step④ 矢を射る:ポインタ変数からの遠距離アクセス
最後のStepは照準を合わせた弓矢で目標物を射ることですね。
ポインタ変数が参照しているメモリへの遠距離アクセスは、次のように行います。
*pMoney = 10000;
*pBalance = 40000;
注目するのは左辺のポインタ変数です。
ポインタ変数に「*」を付けるとポインタの参照先を示すことになり、この代入はポインタが参照しているメモリへの代入と同じ意味を持ちます。これがStep④です。
演算子と書き方
*ポインタ変数ラベル
使用例
*pMoney
説明
ポインタ変数が参照しているメモリへアクセスする。ポインタ変数にしか付けられない。
弓矢となるポインタを使って間接的にメモリへアクセスするためこの名が付きます。間接参照している様子を図示すると、次のようにアクセスしていることになります。
プログラムレベルで表すと次のように呼び出し側の変数に遠距離アクセスしているのです。
Step①~④の結果:サービスの出力情報の確認
改めてmain関数の処理を見ても、money変数とbalance変数への代入処理がありません。
main関数だけを見ると不思議に感じますが、atmFunc関数によりポインタを経由してmoneyとbalance変数にはすでに値が設定されています。
最終的にprintf関数で、引き出し結果・お金・預金残高の中身は次のように出力されます。
0, 10000, 40000
この結果はatmFunc関数を呼び出すことで、3つの変数全てに代入を行ったのと同じ効果があるということを示します。
あたかも戻り値が3つに増えたような出力結果が得られました。これがポインタの使い方です。
このパターンこそがポインタを使う代表的なシーンなんです。まずは、このパターンを叩き込むことです。何回も読んで叩き込んでください。
Q&A:ポインタの使い方に関するよくある質問
ポインタの質問いいよー。
Q:イラストで「変数」と「ポインタ変数」の変数ラベルの色が違うけどなんで?
師匠!巻物に書いてあるイラストでラベルの色が2種類あります。色の付け方間違ってますよっ!
間違ってないよ。それは意識的に変えてあるんだよ。
本サイトでは、次のように変数とポインタ変数で、ラベルの色を意識的に変えてあります。
これは皆さんから見て「変数」なのか、「ポインタ変数」なのかを区別ができるように、色分けしてあります。
C言語自身も明確に変数とポインタ変数の扱いを、別のものとして区別しています。その意味合いを強めるために別の色にしてあるのです。
課題:ポインタの使い方が学べたかを確認しよう
もしも、プログラムが上手く動かなくて困ったときは、答えを見るのではなく「デバッガ」の使い方を学びましょう。
この記事を見ると問題の解決技術が身に付きます。困ったときのオススメ記事です!
課題1
課題内容
次の関数を作成せよ。
次のプログラムに上記関数を追加し、関数を呼び出せ。出力期待結果が表示されることを確認せよ。
main.c
#include <stdio.h>
int main(void)
{
// 底辺
unsigned short bottom = 1200;
// 高さ
unsigned short height = 380;
// 面積の格納先
unsigned long area;
// getAreaTriangle関数の呼び出し
printf("底辺:%d 高さ:%d 面積:%d", bottom, height, area);
return 0;
}
出力期待結果
底辺:1200 高さ:380 面積:228000
main.c
#include <stdio.h>
void getAreaTriangle(unsigned short bottom, unsigned short height, unsigned long * pArea)
{
*pArea = bottom * height / 2;
return;
}
int main(void)
{
// 底辺
unsigned short bottom = 1200;
// 高さ
unsigned short height = 380;
// 面積の格納先
unsigned long area;
// getAreaTriangle関数の呼び出し
getAreaTriangle(bottom, height, &area);
printf("底辺:%d 高さ:%d 面積:%d", bottom, height, area);
return 0;
}
ポインタの基本的な使い方を学ぶこと。
面積のareaの番地を渡すためには「&」をつける必要がありますね。getAreaTriangle関数では引数をポインタで定義し、算出した結果を「*」を付けて書き込みます。
ポインタを使う手順②~④を使ってますね。
課題2
課題内容
次の関数を定義せよ。
次のプログラムに上記関数を追加し、関数を呼び出せ。出力期待結果が表示されることを確認せよ。
main.c
#include <stdio.h>
int main(void)
{
long price = 10000;
// convConsumptionTax関数を呼び
// 消費税後の価格に変換
printf("税込価格:%d", price);
return 0;
}
出力期待結果
税込価格:10800
main.c
#include <stdio.h>
void convConsumptionTax(long * pPrice)
{
*pPrice *= 1.08;
return;
}
int main(void)
{
long price = 10000;
// convConsumptionTax関数を呼び
// 消費税後の価格に変換
convConsumptionTax(&price);
printf("税込価格:%d", price);
return 0;
}
convConsumptionTax関数が呼び出された段階で、すでにポインタの参照先に正当な価格情報が入っています。
自己代入形式で読みだしてから、税率を掛けて書き戻していますね。このように書くだけでなく読むことももちろんできるのです。