C言語入門::ポインタ
ポインタです.なんか,難しいというイメージがあるみたいですが,順を追って理解すればそれ自体は難しくありません.
図がないと分かり難い….そのうち書く予定.
アドレス
ポインタを理解する上で必用になるのがコンピュータのメモリの仕組みです.プログラム実行時,プログラム本体や変数はメモリ上に存在しています.
メモリには,二進数の「0」と「1」が記憶できるようになっていますが,多くのコンピュータでは,2進数を8桁(8ビット)まとめて「バイト」という単位で管理しています.メモリには,1バイトごとにアドレス(=番地)という番号が付いていて,読み書きするときは,そのアドレスを指定して操作することになります.
変数はメモリ上のどこかに存在しているので,アドレスを調べることができます.変数名の前に「&」をつけると,その変数のアドレスを意味します.
#include <stdio.h>
int main()
{
int x;
printf("変数xのアドレス:%x\n",&a);
return 0;
}
とすると,変数のアドレスを見ることが出来ます(%xは16進数表示です).どんなアドレスになるかは実行環境によって違います.
ポインタ
ポインタとはアドレスを入れておく変数のことです.変数宣言時に,変数の前に「*」を付けると,ポインタになります.どのようなデータのアドレスを格納するかで型が決まります.
以下は,int型のデータへのポインタです.
int *p;
特に型が決まっていないポインタは,「void*」を使います.(「char*」で代用することもありますが…)
試しに
int x=123; int *y: y=&x; *y=456;
この処理の後,xの値はどうなっているでしょうか?
ポインタへの加算
ポインタはアドレスが入っているので,普通の整数と同じように演算が出来ます.アドレスを乗算することはないので,殆どの場合,加減算だけでしょう.
ポインタに整数を加算すると,そのポインタが指すデータ型の大きさを1単位としてアドレスが移動します.これは最初わかりにくいと思うので気をつけてください.
int x=123; int *y=&x: y++;
とすると,intが32ビット(4バイト)の環境では,yは4増加します.もちろんchar型ならば,1です.
配列とポインタ
int a[10];
の,「a」は配列の先頭のアドレスを表します.なので,ポインタに代入できます.また, *(a+x) は a[x] と同じです.つまり,配列とポインタは同じようにアクセスすることが出来ます.
int a[5]; int *b; b=a; *b=100; *(a+1) = 200; b[2]=300; (b+2)[1]=400;
a[0]~a[3]に何が入っているでしょうか?
構造体とポインタ
構造体を関数の引数として渡した場合,関数のに渡されるのは構造体のコピーになります.つまり,関数呼び出し時に知らないうちにコピーを作っているということです.数バイトのデータなら問題にならないかもしれませんが,数KBの構造体だったら処理が遅くなる原因になります.メモリも無駄に消費します.
そこで,普通はポインタを渡します.(*a).b は a->b と書けます.そのほうが楽ですね.
#include <stdio.h>
#include <string.h>
struct PERSON{
char name[16];
char tel[16];
int birth_year;
int birth_month;
int birth_day;
};
void print_profile(struct PERSON *p)
{
printf("Name: %s\n",p->name);
printf("TEL: %s\n",p->tel);
printf("Birthday: %04d-%02d-%02d\n",p->birth_year,p->birth_month,p->birth_day);
}
int main()
{
struct PERSON haruhi;
strcpy(haruhi.name,"Suzumiya Haruhi");
strcpy(haruhi.tel,"090-12349876");
haruhi.birth_year=1987;
haruhi.birth_month=8;
haruhi.birth_day=4;
print_profile(&haruhi);
return 0;
}
ポインタを使ってデータを渡した場合,関数内でデータを変更すると,関数の外にも影響が残るので注意してください(それが便利でもあるのですが).
関数とポインタ
関数もコンピュータのメモリ上に存在している以上,アドレスが存在します.C言語では関数へのポインタを使うことができます.
関数の宣言で,関数名の前にポインタの「*」を付けて括弧でくくります.もし括弧が無いと「ポインタを返す関数」になってしまいます.
関数のアドレスは関数の名前で参照できます.なんか変な感じもしますが….
#include <stdio.h>
int sum(int a,int b)
{
return a+b;
}
int mul(int a,int b)
{
return a*b;
}
int main()
{
int (*pfunc)(int a,int b);
int x,y;
pfunc = sum;
x=pfunc(3,4);
pfunc = mul;
y=pfunc(3,4);
printf("x,y = %d,%d\n",x,y);
return 0;
}
場合によって呼び出す関数が変わって,しかも関数がたくさんあるときに便利です.
あと,関数に関数ポインタを渡して,関数内から使うという場合があります.これを「コールバック」といいます.
#include <stdio.h>
#include <string.h>
void test( void(*pfunc)(char *) )
{
pfunc("Haruhi");
pfunc("Kyon");
}
void haruhi(char *name)
{
if (strcmp(name,"Haruhi")==0) {
printf("そう,私はHaruhiです\n");
} else {
printf("%sって何?\n",name);
}
}
void kyon(char *name)
{
if (strcmp(name,"Kyon")==0) {
printf("そう,私はKyon\n");
} else {
printf("私は%sじゃなくてKyonです\n",name);
}
}
int main()
{
test(haruhi);
test(kyon);
return 0;
}
細かいことは気にしないでください.
関数ポインタの宣言はややこしくて,たくさん書いてると憂鬱な気分になってくるので,typedefした方がいいかもしれません.上の例にあわせるなら,
typedef void (*LPFUNC)(char *); LPFUNC pfunc = kyon;
ですね.
文字列とポインタ
文字列が「文字の配列の先頭アドレス」であるという話はしました(たぶん).
ということはですよ.
char c;
char *p="abcdefg1234";
c=p[3];
printf("%d\n",c);
ということはもちろんできます.
さらに,
#include <stdio.h>
int main ()
{
int i;
for (i=0;i<10;i++)
printf("%d: %c\n",i,"ABCdefg1234"[i]);
return 0;
}
なんてこともできますね(最後の4が無駄ですが気にしない).
ちょっとした小話
ポインタを使うと,変数のデータにアクセスする方法が増えます.そうすると,どこで変数の内容が変化しているのかわからなくなるかもしれません.
そうならないために,出来る限り見通しの良いプログラムを書くように心がけましょう.
- まとまった処理は関数に分ける (100行を超える関数が出てきたら少し考えましょう)
- グローバル変数は極力使わない (一度に覚えられる数…7個くらいまでなら許せる?)
- 分かりやすいコメントを書く (理解不能なコメントは逆効果です)
関数内で値を書き換えないことを保障する「const」というキーワードがあるのでそれも調べてみてください.
この文書の履歴
- 2006-05-13 少し書く