環境
$ uname -a
Linux 5.0.0-37-generic #40~18.04.1-Ubuntu SMP Thu Nov 14 12:06:39 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
ポインタ型には、ポインタ型の変数やポインタ型の値が存在.
- ポインタ型の値: メモリアドレス
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main(void){
int hoge = 1;
int huga = 2;
int *hogep; //[intへのポインタ]型の変数hogep
printf("&hoge %p\n", (void *)&hoge);
printf("&huga %p\n", (void *)&huga);
printf("&hogep %p\n", (void *)&hogep);
// ポインタ変数hogepにhogeのアドレスを代入
hogep = &hoge;
printf("&hogep %p\n", (void *)hogep);
// hogepを経由してhogeの値を表示
printf("hogep %d\n", *hogep);
// hogepを経由してhogeの値を変更
*hogep = 10;
printf("hoge %d\n", hoge);
return 0;
}
&hoge 0x7ffcdd36e778
&huga 0x7ffcdd36e77c
&hogep 0x7ffcdd36e780
&hogep 0x7ffcdd36e778
hogep 1
hoge 10
hogep = &hoge;
にてポインタ変数hogep
にhoge
のアドレスを代入.
よって
アドレス | 変数 | 値 |
---|---|---|
0x7ffcdd36e780 | hogep | 0x7ffcdd36e778 |
となる.
ポインタ変数hogep
が別の変数hoge
のアドレスを保持している.
この状態をhogepはhogeを指しているという。
printf("hogep %d\n", *hogep);
関節演算子を用いて、hogepからポインタを一つ手繰り寄せ、その値を表示.
ポインタにをつけることで、その指している先のものを表す.
ここでは、hogep
はhogeを指しているため, *hogepはhoge
と同じものを表す.
よって, 5を表す.
また, hogepの指す先の値を書き換えることで、元々の代入元の値も書き換えることが可能
*hogep = 10;
とすることで、結果的にhoge
の値も変更される. 出力結果には10と書かれている.
ヌルポインタ:何も指していないことが保証されているポインタ.
通常はマクロNULL
を使用する.
ヌルポインタは, 有効などんなポインタと比較しても等しくならないことが保証されているため, ポインタを返す関数の異常時の戻り値として使える.
例: 連結リストの末尾データ
文字列の終端は ナル文字('\0') である. ヌルポインタ(NULL)で終端するわけではない.
ナル文字は, 全てのbitが0であるバイトであり, 即ち値がゼロのchar型である.
ヌルポインタは、有効などんなポインタと比較しても等しくならないことが保証されているため、ポインタを返す関数の異常時の戻り値として使用できる。
int *p;
int i;
//以下の2式同じ
*(p + i);
p[i];
以下は全て同じ。 要素数が入っていたとしても無視される。
int func(int *a);
int func(int a[]);
int func(int a[10);
int func(double d);
//関数funcへのポインタを格納するポインタ変数
int (*func_p)(double);
"" で囲まれた文字列を文字列リテラルと呼ぶ。 文字列リテラルの方は「charの配列」。 よって、式の中では「charへのポインタ」に読み替えられる。
char *str;
str = "abc;
charの配列を初期化する場合は例外
// OK
char str[] = "abc";
char str[] = {'a', 'b', 'c', '\0'};
// NG
char str[4];
str = "abc";
次の例は、charの配列ではなくポインタを初期化しているので、前述の例外には当てはまらない
char *str = "abc";
この場合、"abc"は通常通り「charの配列」であり、式の中なので「charへのポインタ」に読み替えられて、それがstrに代入される。
char *color_name[] = {
"red",
"green",
"blue",
};
この例だと、識別子color_nameの型は「charへのポインタの配列」であり、 型分類である「配列」が、初期化子の最外周の中括弧に対応。 よって、"red"や"blue"は「charへのポインタ」に対応。
char color_name[][6] = {
"red",
"green",
"blue",
}
この例では、 この例では、識別子color_nameは「charの配列(要素数6)の配列」となる。 "red"や"blue"は「charの配列(要素数6)」に対応。
- scanf()でintの値を入力してもらう場合には、変数名に&をつけて渡すが、何故文字列を入力して貰う場合、&をつけなくても良いのか
- scanf()で文字列を入力して貰う場合、charの配列を渡すが、配列は式の中では先頭要素へのポインタに読み替えられるという規則により、ポインタになっており、そのポインタが値渡しされているので、scanf()側で呼び出し元の配列に結果を詰めることができる
C言語ポインタ完全制覇