C言語のポインタとは?記述法と配列、構造体、関数への渡し方
C言語を扱う上で、ポインタは避けて通れない部分です。上手に使えるようになればプログラミングの幅が広がり、記述行数も少なくすることも可能です。
この記事では、そんなポインタの基礎から実際の使用方法を紹介したいと思います。
1.ポインタとは
1-1.ポインタとは
ポインタを一言で表すと、変数のアドレスを格納している変数になります。アドレスが何かというと、変数のメモリ上の位置(番地)のことを意味しています。
変数を宣言すると、使用するコンピュータのメモリが変数のサイズ分割り当てられます。このメモリ上の位置を表す言葉がアドレスになります。
以下にイメージ図を記します。以下の図における変数”a”のポインタは、10024になります。
※上記の例ではアドレスを10進数として表記していますが、16進数で表示するのが一般的です。
1-2.ポインタの記述方法
ポインタ変数の宣言方法は、変数名の先頭に「*」(アスタリスク)を付けることで、宣言した変数がポインタであることを意味します。
また、通常の変数のアドレスを取得する場合は、変数名の先頭に「&」(アンパサンド)を付けます。
簡単な例を以下に記します。
int number; // 通常の変数の宣言 int *p_number; // ポインタ変数の宣言 p_number = &number; // 変数numberのアドレスをp_numberに代入
上記の例では、ポインタ変数p_numberに、変数numberのアドレスを代入する処理となります。また、変数の型としてint型を使用していますが、型は全ての型で使用する事が可能です。
例えば、構造体をtypedefで型宣言した構造体型にも適用可能です。構造体のポインタについては2-2章で後述しています。
2.ポインタの使い方
2-1.ポインタと配列
配列として宣言した変数もポインタとして扱うことができます。
宣言方法は他のポインタと同様に、「*」を先頭に付加するだけで、使用する際も先頭に&を付加するだけです。
char c_str1[5]; // 通常の変数の宣言 char *p_str1; // ポインタ変数の宣言 p_str1 = &c_str1[0]; // 変数c_str1のアドレスをp_str1に代入
ここまでは配列でないポインタと変わりませんが、配列の場合、配列の要素数を表す大カッコ([n])を省略するとアドレス(つまり配列変数のポインタ)を指すことになります。
ですので、次のように記述しても同じになります。
char c_str1[5]; // 通常の変数の宣言 char *p_str1; // ポインタ変数の宣言 p_str1 = c_str1; // 変数c_str1のアドレスをp_str1に代入
ここでの注意点は、[n]を省略した場合は必ず先頭の要素であることです。つまり、配列のn番目を指定したい場合は省略することはできません。
また、strcpy、strcmp等、文字列を操作する標準関数を目にすることがあるかと思います。これらの関数で引数に使用している型はchar型のポインタになります。
char buf1[4], buf2[4]; int result; strcpy(buf1, “AAA”); // char型の配列変数に、「AAA」という文字列を代入 strcpy(buf2, “AAB”); // char型の配列変数に、「AAB」という文字列を代入 out = strcmp(buf1, buf2); // buf1とbuf2を比較し、比較結果をoutに代入
2-2.ポインタと構造体
1-2章で少し触れましたが、構造体もポインタとして使用する事ができます。構造体ポインタと通常の構造体と異なる点は、メンバ変数へのアクセスするための演算子が異なる点です。
通常のポインタでメンバ変数にアクセスする場合は、「.」(ドット演算子)を使用しますが、ポインタで宣言された構造体のメンバ変数にアクセスするには「->」(アロー演算子)を使用します。
以下に例を記します。
// 構造体定義 typedef struct { int m_num1; int m_num2; } struct_sample; void func1(struct_sample *inout) { struct_sample * p_struct; // 構造体型のポインタの宣言 if(inout == NULL) { return; } // ポインタを代入 p_struct = (struct_sample*)inout; if(p_struct->m_num1%10 < 5) // メンバ変数m_num1と10の剰余が5未満の場合 { // ポインタ構造体のメンバ変数へのアクセスはアロー演算子を使用 // m_num1を3倍した数をm_num2に代入 p_struct->m_num2 = p_struct->m_num1 * 3; } } int main() { struct_sample strct_sample; // 構造体の宣言 // strct_sample構造体を0で初期化(各メンバ変数に0を代入する処理と同等) memset(strct_sample, 0x00, sizeof(strct_sample)); strct_sample.m_num1 = 102; // 構造体のメンバ変数m_num1に102を代入 // メンバ変数のアクセス方法はドット演算子を使用 func1(&strct_sample); // 構造体をポインタで関数に渡す printf(“m_num1の値は%dです\n”, strct_saple.m_num1); printf(“m_num2の値は%dです\n”, strct_saple.m_num2);
【出力結果】
m_num1の値は102です m_num2の値は306です
上記の構造体を使用した例は、mainで宣言した構造体をポインタで関数func1に渡し、関数内でさらに加工するというものになります。
ポインタは構造体のある場所を指しているので、双方向で変数を編集することが可能です。
2-3.関数ポインタ
関数ポインタとは、関数が格納されたアドレスです。
関数も変数と同様にポインタとして使用する事が可能です。これの意味するところは、関数ポインタを変更することで、同じ関数ポインタで別の関数を呼び出すことができるということです。ただし、関数のプロトタイプを合わせることが必要です。
それでは、使い方から見ていきましょう。
typedef double (*func)(double, double); // 関数ポインタの型宣言 // 関数ポインタに登録する関数1 int rectangle(double num1, double num2) { // 引数1と引数2を辺とした長方形の面積を返す return(num1 * num2); } // 関数ポインタに登録する関数2 double circle(double num1, double num2) { // 引数1を半径とした円の面積を返す num1 * num1 * 3.14; } int main() { double num1, num2, result; func *p_func; num1 = 10; num2 = 7; p_func = &rectangle; // 関数ポインタにrectangleのポインタを代入 result = p_func(num1, num2); printf(“関数ポインタp_func()の戻り値は%.2fです\n”, result); p_func = &circle; // 関数ポインタにcircleのポインタを代入 result = p_func(num1, num2); printf(“関数ポインタp_func()の戻り値は%.2fです\n”, result);
【出力結果】
関数ポインタp_func()の戻り値は70.00です 関数ポインタp_func()の戻り値は314.00です
上記の例において、2番目の円の面積を求める関数では引数2を使用していませんが、問題はありません。
関数ポインタを使用するためには、型を合わせる必要があるため、使用しない引数があっても問題はないのです。つまり、複数の関数を関数ポインタで使用する場合は一番引数が多い関数に合わせることで、関数ポインタを使用する事ができます。
3.さいごに
以上、C言語におけるポインタの基本的な使い方をまとめました。C言語を扱う上でポインタは必須であり、ポインタを覚えればこれほど便利なものはありません。
C言語は最近の高級言語では見えない部分であるメモリとの関連性などが分かりやすい言語であり、ポインタを理解することがその一歩ではないかと思います。
尚、本記事内で例として使用したコードはあくまで使い方を示すためのものであるため、実務での使用方法に合わないものを含んでいます。
コメント