すらいむLv1024@wiki

ポインタ

最終更新:

slimelv1024

- view
メンバー限定 登録/ログイン

ポインタとは

アドレスを記憶する変数です

ポインタを話す上でこれは欠かせない要素なのでよく覚えていてください
ポインタはアドレスを記憶し間接的にメモリを参照することが出来ます
メモリ上のアドレスを指定してあげることで間接的に変数や配列の操作を行うことが出来ます


アドレス

アドレスとはメモリ上の番地のことです
プログラムもデータもこのメモリ上のアドレスに記憶されています
今まで使ってきた変数や配列はメモリ上に割り振られ、それぞれのアドレス持っています
それぞれの領域は型のデータサイズに依存し、char型なら1バイトの領域を占め、int型の要素数3の配列なら12バイトの領域を占める


変数や配列のアドレス

 int a=123;
 char str1[]="ABC";
 char str2[][7]={"TANAKA",
         "SATOU",
         "SUZUKI"}
 
 printf("a=%d\n",a);
 printf("&a=%p\n",&a);
 printf("\n");
 
 printf("str1=%p\n",str1);
 printf("str1[0]=%c\n",str1[0]);
 printf("str1[1]=%c\n",str1[1]);
 printf("str1[2]=%c\n",str1[2]);
 printf("&str1[0]=%p\n",&str1[0]);
 printf("&str1[1]=%p\n",&str1[1]);
 printf("&str1[2]=%p\n",&str1[2]);
 printf("\n");
 
 printf("str2=%p\n",str2);
 printf("str2[0]=%p\n",str2[0]);
 printf("str2[1]=%p\n",str2[1]);
 printf("str2[2]=%p\n",str2[2]);
 printf("str2[0][0]=%c\n",str2[0][0]);
 printf("str2[1][0]=%c\n",str2[1][0]);
 printf("str2[2][0]=%c\n",str2[2][0]);
 printf("&str2[0][0]=%p\n",&str2[0][0]);
 printf("&str2[1][0]=%p\n",&str2[1][0]);
 printf("&str2[2][0]=%p\n",&str2[2][0]);

指定している変数は同じなのに値が違って出てきます
これは&でメモリ上のアドレスを指定しているからです
&(アンパサンド)はアドレス演算子と呼ばれ、変数のアドレスを取り出します
プログラマにはどこのアドレスに変数が割り当てられたのか分からないので、アドレスを求めるためにアドレス演算子を使います
scanfで使う&も同じでアドレスを渡していたのです

変数名 変数の値
&変数 変数のアドレス
   
配列名 配列の先頭アドレス
配列名[添字] 配列要素の値
&配列名[添字] 配列要素のアドレス
   
配列名 配列先頭アドレス
配列[行] 行の先頭要素のアドレス
配列[行][列] 配列要素の値
&配列名[行][列] 配列要素のアドレス

一次元配列のアドレスを見てもらえると分かりやすいと思いますが、配列のアドレスは並んでいます
配列は必ず並びでアドレス領域が確保されます
これは二次元配列でも同じです


puts関数とgets関数

入出力の項で少しだけ触れたこの関数ですが、二つとも引数でアドレスを受け取れるように出来ています
 char str[256];
 puts("文字列を入力してください")
 gets(str);
 puts(str);


ポインタ

一番最初に書きましたがポインタとはアドレスを記憶する変数です
今まで数字や文字を記憶するのに変数を使って来ました
それと同じように変数のアドレスを記憶するのがポインタ変数なのです
しかし、ポインタの意義はこの記憶されているアドレスを使って間接的にメモリを参照することです
ちょっとややこしいですが、一つ一つ確認していきましょう


ポインタの宣言

ポインタも変数なので宣言する必要があります
 データ型* ポインタ名;
 データ型 *ポインタ名;

どちらも同じポインタ変数の宣言です
人によって宣言の仕方は違うので両方覚えておきましょう
ここで使われてる*はポインタ宣言子と呼ばれます


アドレスの代入

当然ポインタも変数なので宣言しただけでは中に不定値が入っています
 ポインタの変数名=&代入する変数名

先ほどの宣言とは違い、扱いは変数なのでここでは*は尽きません
しかし、ポインタが記憶できるのはアドレスなので、代入する変数には&を忘れないでください
変数とは少し扱いが違いますが、ここまでがポインタ変数の下準備です


メモリの参照

 *ポインタ名

ここで使われている*は間接参照演算子と呼ばれおり、ポインタの指すアドレスの中身を表します
ポインタの宣言のときにも使いましたが、あの*とはまったくの別物です
また、計算式のとき乗算として扱われる*とも別物です
乗算演算子とポインタ宣言子、間接参照子の三つの意味合いが*にあるのを覚えておきましょう


ポインタの簡単な使用例

言葉だけでは分かりにくいと思うので実際にプログラムとして動かしてみましょう
 int num=100;
 int *p1,*p2;   //ポインタの宣言
 
 p1=#     //p1にnumのアドレスを代入
 p2=p1;      //p2にp1のアドレスを代入
 *p2=*p1+1;     //p1の指すアドレスの中身参照し、参照した値+1をp2の指すアドレスの中に代入
 
 printf("numの値は%d numのアドレスは%pです\n",num,&num);
 printf("*p1の値は%d *p1のアドレスは%pです\n",*p1,p1);
 printf("*p2の値は%d *p2のアドレスは%pです\n",*p2,p2);

実行結果
 numの値は101 numのアドレスは01EFD84です
 *p1の値は101 *p1のアドレスは01EFD84です
 *p2の値は101 *p2のアドレスは01EFD84です

すべて同じ値とアドレスが出てきたと思います(アドレスの出す値は環境によって変わります
p2はp1のアドレスを記憶し、p1はnumのアドレスを記憶しています
p2のアドレスの指す中身はp1であり、p1のアドレスの指す中身はnumなのです
つまりp2は間接的にnumの100を取得し、間接的にnumの値を変えることが出来たのです


ポインタと配列

配列の場合もポインタの宣言、アドレスの代入、アドレスの参照を必ず行います
ただし、配列の場合参照する方法が二種類あります
 char name[7]="TANAKA";
 char *p1,*p2;
 int i;
 
 p1=name;
 p2=name;
 
 for(i=0;*(p1+i)!=\0;i++){//方法1
  printf("%c",*(p1+i));
 }
 
 while(*p2!=\0){//方法2
  printf("%c",*p2);
  p2++;
 }

方法1はポインタの値を変えずに参照しています
この書き方で注意しなければならないのは()です
()を忘れると*p1にiを代入すると言う式になってしまいます

方法2はポインタの値を変えて参照しています
こちらの方法は値を更新してしまっているため、もう一度使うときに更新したポインタを元に戻さないと、別のエリアを参照してしまいます
参照だけならいいですが、書き込みだった場合別のエリアのデータを破壊してしまいますので、注意が必要です


ポインタと文字列リテラル

配列の項で文字列は書き換えることが出来ないと説明しましたが、ポインタを使うことで文字列を書き換えることが可能です
 char *p;
 p="ABC"

このABCというのはメモリ上のどこかに用意された文字列です
文字列の先頭アドレスを取得することで、文字列の情報を入手することが出来ます
つまりポインタが記憶する文字列の先頭アドレスを変えれば、文字列を書き換えることが出来るのです


ポインタの配列

ポインタでも配列を扱うことが出来ます
 データ型 *配列名[要素数]

複数の文字列は二次元配列で扱っていましたが、ポインタの配列を使うと一次元配列で扱うことが出来ます
 char *p[3]={"abc","defghi","jk"};
 puts(str[0]);
 puts(str[1]);
 puts(str[2]);

二次元配列と違い確保した空いてるスペースにNULL文字を入れる必要がない代わりに、文字列が並びで確保されず、文字列の先頭アドレスを記憶する領域が必要になるため、どちらを使うかは二つの特色を考えて選択しましょう
目安箱バナー