初投稿です。
「学んだことはブログなどに書き込んでアウトプットすると良い」
らしいので試してみます。
今回は、C言語のポインタについて学んだことをメモ兼アウトプットします。
学ぶのに使った本は「C言語ポインタ完全制覇(ISBN978-4-7741-9381-6)」
という本です。
今回は第一章の内容をメモしていきます。
ポインタ型とは
例えば「int型」や「double型」が存在するが、それらから派生して作り出される型。
ポインタ型にさせるためには、型に「*」を付け加える。
そうすることで「intへのポインタ型」「doubleへのポインタ型」へと派生される。
ちなみに*は「間接演算子」と呼ぶ。
int iHoge; //int型
int* pHoge; //intへのポインタ型
で、このポインタ型には値として、メモリのアドレスを入れることができる。
例えば、iHogeのアドレスをpHogeに入れたいときには、変数に「&」を付け加える。
&は「アドレス演算子」と呼び、変数のアドレスを表示させるためのもの。
iHoge = 10;
pHoge = &iHoge; //iHogeのアドレスを、pHogeに代入
↓ このようにしたときのイメージ
このときの状況は、「pHogeは、iHogeを指している」と言うらしい。
次のように、ポインタ型に対して*をつけることで
指した先の値を表示することができる。
printf("%p",(void*)pHoge); //「0x01234」が表示される
printf("%d",*pHoge); //「10」が表示される
添字演算子
配列の要素を参照する [ ] を「添字演算子」と呼ぶ。
ちなみに、宣言のときの * や [ ] と、式の中に現れる * や [ ] は全く別もの。
と、本の筆者は述べてました。
この添字演算子も、ポインタと似たような性質を持っていますが
詳細は後で書きます。
ポインタ演算
C言語には、ポインタ演算という独特な機能がある。
次のようにしたとき、例えば「0x01234」が出力されるとする
int iHoge = 10;
int* pHoge = &iHoge; //iHogeのアドレスを、pHogeに代入
printf("%p\n",(void*)pHoge);
次に、pHogeに対して1加算すると、型のサイズ分だけアドレスが増加する。
「0x01234」の4バイト先なので、出力は「0x01238」になる。
※今回の例ではint型は4バイトとする
iHoge += 1;
printf("%p\n",(void*)pHoge);
ヌルポインタ
「ヌルポインタ」とは何も指していないことが保証されるポインタのことを言う。
ちなみに一昔前のネットミーム「ぬるぽ」もここから来てるんだとか。
・・・話を元に戻して
ヌルポインタを表す定数値として、通常はマクロNULLが使用されるとのこと。
自分の環境のNULLは次のようになってました。
この0という数字は、「ゼロ番地」的な意味合いになるみたいです。
C言語では、0という定数は、ポインタとして使う場合ではヌルポインタとして扱われる。
とのことです。
なので次のように、ポインタ型に対して0を代入することができます。
ただし0以外の値はただのint型になるので、環境によっては警告が出ます。
*hoge = 0; // コンパイラがNULLポインタとして読み替える。
*hoge = 3; // 環境によっては警告が出る
ただ「単に定数0を渡しているプログラムは移植性が低い」と筆者は述べてました。
ヌルポインタを表現したいときは0ではなくNULLを代入すべき、と解釈しました。
ヌル(NULL)とナル(\0)の違い
・ヌルポインタ
ポインタ変数が、有効なメモリ領域を指し示していない場合に使用される。
・ナル
文字列の終端を示す時に使用される。
よくある間違いとして、文字列の終端させるためにNULLを使うのは間違い。
hoge[len] = NULL; //間違い
配列
次の「例1」と「例2」では、同じことをしている。
int iHoge[5] = { 0 , 1 , 2 , 3 , 4}
int* pointer;
int i;
// 例1
for(i = 0; i < 5; i++) {
printf("%d\n", *( p + i ));
}
// 例2
for(i = 0; i < 5; i++) {
printf("%d\n", p[i] );
}
どちらも出力は以下のようになる。
0
1
2
3
4
まず、添字演算子 [ ] は、宣言と式では全く異なるもの
という前提があるらしい。
- 宣言で使う添字演算子 [ ] は、これが「配列である」ということを表す。
- 式の中で使う添字演算子 [ ] は、例1にある *( p + i ) の簡略記法。
という具合。
なので、ポインタ型変数 pointerに配列の先頭アドレスを渡すとき
p = &array[0]
と
p = array
は、どちらもやっていることは変わらない。
つまり、式の中での
p[ i ]
これは
*( p + i )
を略したもの、ということになるらしい。
シンタックスシュガー。
またC言語においては、関数の引数として配列を渡すことができない。
一見配列を渡しているように見えるプログラムでも
実際は、配列の先頭アドレスを渡しているだけになる。
(ということは、引数には配列の要素数も必ず渡さなければ危険ということか・・・)
つまりは、次のような関数を作ったとしても
int func1( int a[] )
int func2( int a[10] )
結局値として渡されるのは配列の先頭アドレスなので
int func3( int* a )
このような書き方をしているのと全く一緒。コンパイルによって書き換えられるらしい。
個人的に思ったのは、func1、func2の書き方は、引数として配列を渡してほしい時とか
特定の要素数だけの配列を渡してほしいときに、あえて書く時ぐらいかな?
その②に続きます。