こんにちは、もがちゃんです。
C言語を学んでいて、多くの人がハマるのがポインタです。
ポインタだけならまだしも、ポインタのポインタとかも出てきますし
理解してしまえば、どうってことないんですが理解するまでに色々と苦労したりしますよね?
今回は、ポインタについて説明してみたいと思います。
ポインタが理解できるように簡単なサンプルソースも載せました。
この記事を読めば、きっとポインタが好きになるかもしれません。
目次
ポインタとは
ポインタとは、ある物がある場所を参照することです。
ある物とは何かというと、char型の値、int型の値、関数などです。
具体的に、C言語の記述で見てみましょう。
例えば、char型の値の場所を参照するとは、以下のような事です。
// 変数valueには、char型の値('a')を入れます
char value = 'a';
// char型ポインタ変数を宣言しました(char型の値の場所を入れるための変数)
char *pValue;
// char型変数valueの場所(アドレス)を、char型のポインタ変数に入れます
pValue = &value;
// char型のポインタ変数を使用して
// char型変数valueの場所を参照した結果を、char型変数refValueに入れます。
char refValue = *pValue;
場所(アドレス)を取得する場合は、&(アンパサンド)を使用します。
また、ポインタ変数(場所を入れるための変数)を宣言する場合は、*(アスタリスク)を使用し、場所(アドレス)を参照する場合も*(アスタリスク)を使用します。
同様に、int型の値の場所を参照してみましょう。
// 変数valueには、int型の値(1024)を入れます
int value = 1024;
// int型ポインタ変数を宣言します
int *pValue;
// int型変数valueの場所(アドレス)を、int型のポインタ変数に入れます
pValue = &value;
// int型のポインタ変数を使用して
// int型変数valueの場所を参照した結果を、int型変数refValueに入れます。
int refValue = *pValue;
同様に、int型ポインタの値の場所を参照してみましょう。
// 変数valueには、int型の値(1024)を入れます
int value = 1024;
// int型ポインタ変数を宣言します
int *pValue;
// int型変数valueの場所(アドレス)を、int型のポインタ変数に入れます
pValue = &value;
// int型ポインタのポインタ変数を宣言します
int **ppValue;
// int型ポインタ変数pValueの場所(アドレス)をint型ポインタのポインタ変数に入れます
ppValue = &pValue;
// int型のポインタのポインタ変数ppValueを使用して
// int型変数valueの場所を参照した結果を
// int型変数refValueに入れます。
int refValue = **ppValue;
今回の例でも、int型ポインタの値の場所(アドレス)を取得するには、&(アンパサンド)を使用します。
また、ポインタ変数ppValueは、int型ポインタのポインタになるので、*(アスタリスク)が2つになります。
関数のポインタ
ここでは、関数のポインタについて説明します。
具体的に、C言語での記述がどうなるか見てみましょう。
// 返却値の型がint型で引数無しの関数testFunc()を定義します。
int testFunc() {
return 0;
}
// 返却値の型がint型で引数無しの関数型ポインタ変数を宣言します。
int (*pFunc)();
// 返却値の型がint型で引数無しの関数testFuncの場所(アドレス)を
// 返却値の型がint型で引数無しの関数型ポインタ変数pFuncに入れます。
pFunc = testFunc;
// 返却値の型がint型で引数無しの関数型ポインタ変数を使用して
// 関数testFunc()を参照(実行)します。
pFunc();
関数のポインタの場合、関数の場所(アドレス)を取得するのに &(アンパサンド)は使用しません。関数名だけで関数の場所(アドレス)が取得できます。
また、関数型ポインタの変数を宣言する場合は、*(アスタリスク)の他、返却値の型、引数の型も使用しますが、関数を参照(実行)する場合は、*(アスタリスク)を使用せず、()を使用します。
※引数がある関数の場合は、()内に引数も指定します
ポインタの使い方サンプル
ポインタの使い方の簡単なサンプルを紹介します。
関数で返却値以外にも結果を返したい場合、パラメータに使用する
#include <stdio.h>
/*
* ポインタの使用例
*/
int division(int p1, int p2, int *p3);
int main(int argc, char *argv[]) {
int shou = 0;
int amari = 0;
shou = division(10, 3, &amari);
fprintf(stdout, "10 / 3 = shou[%d], amari[%d]\n", shou, amari);
}
/*
* 割り算する( p1 / p2 )
* 商:返却値
* 余り:p3
* ※ 0除算は考慮してません ※
*/
int division(int p1, int p2, int *p3) {
int ret = p1 / p2;
*p3 = p1 % p2;
return ret;
}

関数のポインタの配列を使用して処理を振り分ける
#include <stdio.h>
/*
* ポインタの使用例
*/
int func1(int p);
int func2(int p);
int func3(int p);
int func4(int p);
int func5(int p);
int main(int argc, char *argv[]) {
int (*pFunc[5])() = {
func1,
func2,
func3,
func4,
func5
};
for (int i = 0; i < 5; i++) {
//if文で分岐を作らなくても処理を振り分けられる
fprintf(stdout, "[%d]:[%d]\n", i, pFunc[i](i));
}
return 0;
}
// 関数1:処理事態に意味はありません。
int func1(int p) {
return p * 1;
}
// 関数2:処理事態に意味はありません。
int func2(int p) {
return p * 2;
}
// 関数3:処理事態に意味はありません。
int func3(int p) {
return p * 3;
}
// 関数4:処理事態に意味はありません。
int func4(int p) {
return p * 4;
}
// 関数5:処理事態に意味はありません。
int func5(int p) {
return p * 5;
}

ポインタのインクリメント・デクリメントの確認
#include <stdio.h>
/*
* ポインタの使用例
*/
int main(int argc, char *argv[]) {
char c[8] = { '0', '1', '2', '3', '4', '5', '6', '7' };
char *pC = &c[0]; // = c でも同じ
int i[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
int *pI = i; // = &i[0] でも同じ
// ポインタ変数のサイズ確認
fprintf(stdout, "sizeof pC[%ld], pI[%ld]\n", sizeof(pC), sizeof(pI));
// 参照1
fprintf(stdout, "ref1 *pC[%c], *pI[%d]\n", *pC, *pI);
// ポインタのインクリメント
pC++;
pI++;
// 参照2
fprintf(stdout, "ref2 *pC[%c], *pI[%d]\n", *pC, *pI);
// ポインタの加算(+2
)
pC += 2;
pI += 2;
// 参照3
fprintf(stdout, "ref3 *pC[%c], *pI[%d]\n", *pC, *pI);
// ポインタのデクリメント
pC--;
pI--;
// 参照4
fprintf(stdout, "ref4 *pC[%c], *pI[%d]\n", *pC, *pI);
return 0;
}

ポインタのまとめ
ポインタとは、ある物がある場所(アドレス)を参照すること。
ポインタ変数のサイズは、64bitの場合は8バイト、32bitの場合は4バイト
ポインタを加算・減算するとポインタの型のサイズ分加算、減算される。
ポインタ変数を定義する際は、*(アスタリスク)を使用する。
関数以外のアドレスを取得する場合は、&(アンパサンド)を使用する。
関数以外のアドレスを参照する場合は、*(アスタリスク)を使用する。
関数のアドレスを取得する場合は、関数名を使用する。
関数のアドレスを参照(実行)する場合は、*(アスタリスク)を付けたりしないで、通常の関数呼び出しのようにする。
ポインタ変数を使用する場合、ポインタ変数の中身(アドレス)が正しくない場合は、思わぬ場所を参照することになるので、ポインタ変数にアドレスを入れたり、加算・減算する場合には注意が必要です。
どうでしたでしょうか?
この記事の内容が少しでもポインタの理解に役立てたら幸いです。