この春になると新人研修が始まります。私の会社でもC言語研修が行われていて、数年日一度、2日間、召喚されて、講師になるのですが、新人さんから「ポインタが難しい。」という話をよく伺います。
20年やっててもポインタを含むコードの理解は難しい
ポインタと言ってもいろいろな利用方法があり、ポインタを複雑な使い方をされると今でもさっぱりわからないことはよくあります。でも、それは書いた人が悪い!と心のなかで叫び割り切っています。
関数ポインタで関数のリストを作ってシーケンス処理を書いたり、リスト処理もポインタの集まりですのでどう使っているとか調べるのには今でも数時間とかかったりします。
大体の人は、ダブルポインタは普段は使わないですし。関数ポインタはコールバックの設定やスレッドを起こすような処理でも無ければ使わないです。
私は凡庸なプログラマーですので、今でも複雑なポインタを含む処理は読むのも時間がかかってしまいます。
初心者が「複雑なポインタを含む処理が理解できない!」といわれても、エースでも無い限りは、いつまでも理解できないものです。
ですので、ポインタは難しいけど、難しい使い方は後回しでもいいです!
自分がやりたい処理が書ける程度のポインタは次のものが一番多いと思います。
最初に絶対に使いこなすべき配列のポインタ処理
とはいえ、ポインタを全く使わないでプログラムを書くことはなかなかないと思います。
一番良く使うポインタは、関数の引数のポインタ渡しです。
下のような関数にポインタを渡して関数内で上書きしてもらう使い方。
#include <stdio.h>
void test(<span class="bold-red">int *out</span>){
*out = 1;
*(out+1) = 1;
return;
}
int main()
{
int a[2] = {0};
printf("before %d %d\n",a[0], a[1]);
test( a ) ;
printf("after %d %d\n",a[0],a[1]);
return 0;
}
これが理解できていれば、とりあえずは大丈夫です。
構造体のポインタもよく使う。実はこっちのほうが多いかも
構造体をポインタで関数に渡すときは、こんな感じ。下の例では関数で構造体を初期化しています。
#include <stdio.h>
typedef struct {
char name[50];
int year;
int month;
int day;
} birthday_t;
void birthday_init(birthday_t *input){
birthday_t z={{0}};
*input = z;
}
void main(void)
{
birthday_t fujita = {
{"fujita"},
2021,5,5
};
printf("before[%s] ",fujita.name);
printf("%d/%d/%d\n",fujita.year,fujita.month,fujita.day);
birthday_init(&fujita);
printf("after[%s] ",fujita.name);
printf("%d/%d/%d\n",fujita.year,fujita.month,fujita.day);
return;
}
これが理解できれば十分!あとは実際に書きながら身につければ良いと思います。
関数ポインタは極力使わない。
関数ポインタは
- コールバック関数の設定
- スレッド起動
- シーケンス処理の手段
初心者のうちは、関数ポインタは1と2のとき以外、極力使わないほうが無難です。
例えば、3のシーケンス処理を組むときに関数ポインタを使うことがあるのですが、、、、、私は嫌いです。最後まで面倒見るのだったら使っていいけど他人に助けてもらうつもりなら、難読なため使わないほうが良いです。
仕事の場合は関数ポインタを使っていいかどうか不安になると思います。事前に設計や実現方法について先輩や上司に詳しく相談してください。
まとめ
- 複雑なポインタ処理はベテランでも理解しにくいことがある。初心者が理解できないのは当たり前
- 関数の引数のポインタは覚えましょう
- ダブルポインタは使う機会はほぼないため使うときに勉強すれば十分間に合う
- 関数ポインタは読みにくくなってしまうので極力使わないほうが無難
ポインタが難しいのはベテランでも同じです。ポインタを実際に使うときに勉強し直せばなんとかなります。ポインタで挫折してやめてしまうのはとてももったいないですから、ポインタは苦手のままでもいいので次に行きましょう。
コメント