Youtube登録者5000人突破!!

【C++】ポインタ 【はじめてのC++プログラミング入門講座 #10】

はじめてのC++プログラミング入門講座では、プログラミングをやったことのない人でも分かるように、C++について解説します。まずは、C++を知るところからはじめて、初心者がつまづきやすい「ポインタ」までをゴールとして進めていきます。全て無料で学べる内容となってますので、ぜひ最後までお付き合いください。

丁寧に学ぶをモットーに、ひとつずつじっくりと理解しけるように解説します。 はじめは少し退屈かもしれませんが、基礎を身に着けておくと、後が楽になります。また、プログラミング学習でよくあるのが、飛ばしすぎて途中で挫折してしまうことです。続けさえすれば力になります。じっくりゆっくり学んでいきましょう。

本講座はC++の開発環境が必要です。環境構築には第1回のVisual Studioの環境構築を参考にしてください。下記からどうぞ。

前回の第9回では、オーバーロードとテンプレートについて解説しました。詳細は下記からどうぞ。

オーバーロードとテンプレートは関数とセットで覚えたいので入門講座で取り上げましたが、実際のところ オーバーロードもテンプレートもワンランク上の知識です。うまく使いこなしたプログラムを書くのはまだ難しいと思うので、頭の片隅に置いておいてください。他人のプログラムを見たときに理解できるだけでも十分価値があります。

重要なポイントは下記の通りです。 オーバーロードとテンプレートは、どちらも柔軟な関数を作るために非常に役に立ちます。

  • オーバーロードは異なる型、引数の数に対応できる関数を作成できる
  • 同じ関数名を使えば、オーバーロードできる
  • 異なる型に対応できる関数を作りたいならテンプレートを使う
  • テンプレートの書き方は難しそうに見えるが、基本的にコピペでOK
  • オーバーロードとテンプレートを加えても関数呼び出しのやり方は変わらない

ポインタ

今回は、C言語で多くの初心者がつまづきがちな「ポインタ」を解説します。このように言われるので、難しく感じがちですが、図でイメージを持っておくと実は意外と簡単です。

全ての変数は、メモリに記憶されるときに「アドレス」を持ちます。このアドレスを指すのが、「参照」と呼ばれる方法です。そして、そのアドレスが持っている変数を指し示すのが、ポインタです。

例えば、変数だけで使用していると、関数を呼び出すときは引数としてコピーが作成されます。しかし、参照渡しをすると、元の変数を変更できるので、コピーを作る必要がなくなります。

大した差ではないように思うかもしれませんが、これが結構重要です。大きなデータだとこれだけでメモリオーバーになりエラーとなったりします。このように、メモリを最小限に抑える等のメリットがあると理解しておくと、理解が進むでしょう。

難しければ、ポインタは飛ばして、参照渡しだけでも理解しておくと今後の負担が小さくなります。

#include <iostream>
using namespace std;

void doubled(int* x, int* y);

int main()
{
	int x = 10;

	cout << &x << endl;

	int* xPtr;
	xPtr = &x;

	cout << *xPtr << endl;
	cout << xPtr << endl;

	int y = 10;
	int* yPtr = &y;

	cout << *yPtr << endl;

	doubled(xPtr, yPtr);

	cout << *yPtr << endl;
}

void doubled(int* x, int* y) {
	*y = *x * *x;
}

出力結果は下記のようになります。

0137FA9C
10
0137FA9C
10
100

ライブラリ

#include <iostream>
using namespace std;

・・・

int main()
{
・・・
}

今回もインクルードするライブラリは入出力ライブラリのiostreamだけです。残りのコードは全てメイン関数の中に書いていきます。

using namespace std; によって、標準ライブラリを使う際の名前空間であるstd::を省略表記できるようにしています。

メイン関数

今回は先にメイン関数から解説していきましょう。


	int x = 10;

	cout << &x << endl;

	int* xPtr;
	xPtr = &x;

	cout << *xPtr << endl;
	cout << xPtr << endl;

	int y = 10;
	int* yPtr = &y;

	cout << *yPtr << endl;

	doubled(xPtr, yPtr);

	cout << *yPtr << endl;

アドレス

まずはアドレスについて理解しましょう。

全ての変数はPCのメモリに保管されます。整数型の変数も浮動小数点型の変数も、配列も全てメモリに保管されます。

メモリに保管されるということは、変数を置いている場所が存在するということになります。この変数の置かれた場所というのをアドレスといいます。つまり、全ての変数はアドレスを持つということになります。ちなみにアドレスは日本語訳すると住所です。

変数のアドレスを取得することを参照といいます。プログラムでは、&xを使用します。

	int x = 10;

	cout << &x << endl;

上記プログラムでは、まず変数xに10を代入して、&xを画面出力することでxのアドレスを確認しています。それによって、xのアドレスとして下記の出力結果が得られます。

0137FA9C

上記が変数xのデータが入っているメモリのアドレスです。16進数表記なので0~Fまでの数字とアルファベットの組み合わせで表記されます。

下記のように変数とアドレスはセットであることを覚えておいてください。

ポインタ

次はポインタについて説明します。

ポインタは和名だと間接参照演算子と呼ばれます。難しく聞こえますが、実はこっちの呼び方のほうが機能がわかりやすいです。

ポインタは、他の変数の値をアドレスからたどる機能です。厳密に言うと、アドレスを使用して間接的に他の変数の値を参照する演算子です。

ポインタはわかりにくいので、先に図例を見ておきましょう。アドレスの値は今回のプログラムの結果とは違いますが、気にしないでください。ピンクがポインタ、緑が普通の変数を表しています。

ポインタは、本来は変数のあるべきところにアドレスが入ります。そして、そのアドレスを使用して他の変数の値を調べに行きます。間接参照演算子の意味が何となくわかると思います。

では実際のプログラムに移りましょう。

	int* xPtr;
	xPtr = &x;

	cout << *xPtr << endl;
	cout << xPtr << endl;

まずポインタを宣言します。ポインタの宣言は、int* xPtr; のように変数の前にアスタリスク(*)をつけます。この状態ではまだ値は入っていません。

ポインタに値を代入します。xPtr = &x; を行うことで、変数xのアドレスをポインタに渡すことができます。ポインタは、アスタリスクを外した状態ではアドレスを表します。つまり、この作業では変数xのアドレスをxPtrに渡しています。

*xPtr と xPtr を画面に出力した結果を見てみましょう。

10
0137FA9C

*xPtr は間接的に変数xの値を参照しています。そのため、変数xのアドレスから変数xの値をたどるような形で値を取得しています。

また現在 xPtr には変数xのアドレスが入っているので、下の出力は変数xのアドレスとなります。

動作が理解できない場合は、下記イメージを思い出してください。

変数yについても同様の作業を行います。


	int y = 10;
	int* yPtr = &y;

	cout << *yPtr << endl;

	doubled(xPtr, yPtr);

	cout << *yPtr << endl;

上3行のプログラムで実行された結果が画面出力されます。*yPtrには10が入っています。

10

doubled(xPtr, yPtr); の部分で、関数doubledにポインタを渡します。

このように関数に渡すのはポインタでも大丈夫です。もしポインタを渡した場合は、アドレスが関数に渡されることになります。

ただし、ポインタを関数に渡すときは注意してください。ポインタで関数を呼び出すと、関数内で変数を変更した場合に変数の中身が変わります

この特性により関数の使い方は大きく変わります。

普通に変数を渡した場合と比べてみましょう。普通に変数を渡した場合では返り値として値を返します。そのため、値を2倍するdoubled関数の引数はx1つで返り値がy1つでした。

一方で、ポインタの場合はアドレスが渡されるので、入力値のxPtrと出力先のyPtrを渡しています。yPtrは関数内で変更されます。

その結果、*yPtr として画面出力されるのは*xPtr の2倍された結果である100となります。

100

ポインタを関数に渡すという部分がまだ完全に理解できていないと思うので、関数の中身も見ていきましょう。

関数(ポインタ渡し)

メイン関数でdoubled関数という第一引数(ポインタ)を2倍した結果を第二引数(ポインタ)に入れて返す関数を使いましたが、ここからはその中身を見ていきましょう。

プロトタイプ宣言を使用することで宣言と定義を分けて書いています。宣言の説明は不要だと思うので、定義部分だけ説明していきます。

・・・

void doubled(int* x, int* y);

int main(){ ・・・ }

void doubled(int* x, int* y) {
	*y = *x * *x;
}

まず、引数がポインタで指定されていることがポイントになります。関数にポインタを渡したい場合は必ずこの書き方になります。

関数呼び出しでは、doubled(xPtr, yPtr) という形でアドレスを渡し、関数の引数ではdoubled(int* x, int* y) というポインタのアドレスを受け取ることを明示しています。

ちょっとわかりにくいかもしれませんが、ポインタを関数に渡したい場合にこれ以外の書き方はありません。覚えてしまっても大丈夫です。

doubled関数内のxやyは xPtr と yPtrのアドレスを示しています。そのため、xやyをポインタとして使用すると xPtr と yPtr が持っていた値を参照できます。

普通に関数に引数を渡した場合は値がコピーされるので、引数を変更してもメイン関数に何の影響もありませんでした。しかし、ポインタ渡しでは違います。アドレスを参照しているということは、引数を変更するとメイン関数内の値も変わります。簡単に言うと、ポインタ渡しでは同じアドレスを参照しているので、メイン関数と同じ変数を使用しているということになります

*y = *x * *x; 関数の中身はポインタの演算です。xやyはアドレスが渡されているため、ポインタの形で演算をします。*xには10が入っているので、ここでは10×10の結果を*yに渡しています。

これで第一引数として渡したxPtrの参照先(*xPtr)である10の二倍した結果を第二引数として渡したyPtrの参照先(*yPtr)に渡すことができました。

特に引数を渡す部分は直感的に違和感があると思います。何回も使用して慣れるようにしてください。

配列と参照

配列を関数に渡す場合もアドレスを渡すことになります。ポインタのイメージが掴めていれば配列の参照渡しも理解できるので、一緒に学んでおきましょう。

ここでは参照渡しについて学びます。

下記のプログラムを扱います。

#include <iostream>
using namespace std;

void func(int t[]);

int main() {
	int s[] = { 10,20,30,40,50 };

	func(s);

	for (int i = 0; i < 5; i++) {
		cout << s[i] << " ";
	}
}

void func(int t[]) {
	t[0] = 100;
}

出力結果は下記の通りです。

100 20 30 40 50

メイン関数

まずはメイン関数の中身を見ていきましょう。

	int s[] = { 10,20,30,40,50 };

	func(s);

	for (int i = 0; i < 5; i++) {
		cout << s[i] << " ";
	}

1行目で配列の宣言と代入を行っています。今回は整数型の5つの要素の配列を作成しています。

次に関数を実行しています。関数はfunc関数という引数の先頭要素を変更する自作の関数を使用します。返り値はなく、引数に手を加えます。

ここで重要なのが、配列の変数名だけを使用した場合(ここでは配列名sのみの書き方の場合)、配列の先頭アドレスを示します

配列は必ず連続したメモリに保存されるというルールがあります。そして、配列が終わった後のメモリ空間には「\0」という記号が入るルールになっています。

そのため、配列を渡す場合は、先頭アドレスだけを渡せば配列の全ての情報のアドレスがわかることになります。

これは非常に画期的で、関数に大量のデータを渡したい場合にコピーが不要となり、高速化できます。

プログラム上では、関数に配列の先頭アドレスを渡しているということを理解しておいてください。

最後のfor文は、配列内要素の画面表示です。func関数によって配列に手を加えた結果は下記となります。

100 20 30 40 50

関数(参照渡し)

次は関数を見ていきましょう。プロトタイプ宣言を使用しているので、宣言と定義がわかれています。

メイン関数では、func関数に対して配列の先頭アドレスを渡しました。

・・・

void func(int t[]);

int main() { ・・・ }

void func(int t[]) {
	t[0] = 100;
}

関数の引数を見てください。(int t[]) という配列のような書き方となっています。

メイン関数を見直すとわかりますが、これは配列の宣言と同じ形です。ここで配列を宣言して、配列への要素の代入は引数から先頭アドレスを渡すことで行われます。配列の参照渡しでは毎回同じ形になります。

処理の中身は簡単ですね。配列の0番目の要素に100を代入しています。配列tはメイン関数の配列sと同じアドレスを参照しているので、ここでtを変更するとメイン関数の配列sにも反映されます。

配列を関数に渡す際は、同じメモリアドレスを参照することになります。予想外に上書きしてしまわないように気を付けてください。

おわりに

今回はポインタに関する説明と関数へのポインタ渡しと参照渡しについて解説しました。

ポインタ自体がそもそも難しい上に、関数にデータを渡そうとするとさらに難しくなります。もしつまづいた場合は要素ごとに分割してじっくり学びなおしてみてください。

大事なポイントは下記の通りです。

  • 全ての変数はアドレスを持つ
  • &をつけると変数のアドレスが得られる
  • ポインタは間接参照演算子とも呼ばれる
  • ポインタは値の代わりに他の変数のアドレスを入れることで、他の変数の値をたどる
  • ポインタはアスタリスク(*)をつけて使用する
  • ポインタとして宣言すると、何も付けない場合はアドレスを表す
  • 関数にポインタを渡すと、メイン関数で使用していた変数を参照できる
  • 参照渡しでも、メイン関数で使用していた変数を参照できる

アドレスの概念を理解することがまずは初めの一歩です。全ての変数はアドレスを持つということを意識してプログラムを見てみてください。

そして、特に難関と言われるポインタは、通常は値の入っていたところに他の変数のアドレスが入っています。そのため混乱しやすいですが、図で理解できればかなり簡単になります。ポインタで他の変数を指す流れをまずは理解しましょう。

関数へのポインタ渡しと参照渡しは少し理解に時間がかかると思います。関数の宣言時にポインタやアドレスを渡していることがはっきりと分かるような書き方としていることを意識して見てみてください。配列の場合はt[]という書き方でしたが、変数の場合は&を付けて渡します。

色々頑張ってまとめて覚えるよりは、まずは誰かの書いたプログラムを読めるようになることが重要です。頭の片隅に残せるように、重要ポイントだけでも理解できればOKです。

youtubeでも解説しています。図が多いのでこっちのほうがわかりやすいかもです。

はじめてのC++プログラミング入門講座はこれにて終了です。続きはステップアップとして初心者講座を作成します。初心者講座ではオブジェクト指向を解説していきますので、そちらもご覧頂けると幸いです。