ここでは変数が格納されているコンピュータ内の "住所" である"アドレス"とそれを指し示す変数型である"ポインタ"について学ぶ.
アドレス・ポインタの概念は若干分かりにくいところがあるが,うまく活用すれば関数・配列の操作において便利である.
我々が変数を宣言する時,コンピュータ内のメモリ(記憶域)のいずれかがその変数の保存に充てられる.
この充てられたメモリのコンピュータ内の住所を "アドレス" とよぶ.ある変数が割り当てられたメモリのアドレスを示すには "&" 演算子を使用する.
例えば example10-1.c は変数aを宣言し,その中身の値と割り当てられているメモリのアドレスを表示するプログラムである.
#include <stdio.h> int main(){ int a = 1; printf("aの値は%dです.\n",a); //&aはaのアドレスを指す. printf("aのアドレスは%pです.\n",&a); return 0; } |
各自プログラムを実行して,変数の保存された "中身" と変数の "アドレス" は全く違うことを確認してみるよい.
printf("%p",&a)
はaのアドレスの表示を行う処理であり,&aとすることで16進数にて変数aのアドレスが表示される.
アドレス型の変数を表示するには%pを用いることに注意.
ポインタ(変数のアドレスを"指し示す"変数型)を用いれば変数のアドレスを指示し,アドレス操作や変数の中身を操作することが可能になる.
example10-2.c はint型のポインタ型変数ptrを定義してその動作を確認するプログラムである.
#include <stdio.h> int main(){ int a = 1; //int型ポインタの宣言 int *ptr; ptr = &a; printf("ptrはaのアドレスを指し,%pとなります\n", ptr); printf("*ptrはaの値を指し, %dとなります\n", *ptr); //ポインタを通して間接的にaの値を操作できる. *ptr = 128; printf("*ptrの値は%d aの値は%d,同じです\n", a,*ptr); return 0; } |
ここで
int *ptr;
がint型のポインタの宣言である.他の変数型のポインタを定義する場合 int を double や Char に変えればよい.
ptr は変数のアドレスを指示しており,*ptrは変数の中身を指示している.
すなわち
ptr=&a;
はポインタ ptr は a のアドレスを指示することを意味する.このように定義すると *ptr を用いて a の中身を表示したり,間接的にaの値を操作できる.
例えば
*ptr=128;
とすることは a=128; としたことと同じである.
int aといった変数は我々が処理の便宜上定義した名前であり,その変数は同じ関数(ブロック)内でしか認識されない.
例えばmain関数内で定義した int a と別の関数で定義した int a は名前は同じであるが全く別のアドレスに割り当てられた別物である.
しかしもし,使用している変数のメモリのアドレスを関数ごとに共有しておけば,関数を跨いで共通の変数を扱うことができる.
ポインタを使用することによりこれを実現できる.
example10-3.c は関数により a, b 2つの変数の値を入れ替えるプログラムである.
#include <stdio.h> //xとyの値を交換する関数 void swap(int *x, int *y){ int temp=*x; *x=*y; *y=temp; } int main(){ int a = 1; int b = -1; printf("aの値は%d,bの値は%dです.\n",a,b); swap(&a,&b); printf("aとbの値を入れ替えました.\n"); printf("aの値は%d,bの値は%dです.\n",a,b); return 0; } |
void swap(int *x, int *y)
のように関数の引数をポインタとすると,
swap(&a,&b);
という風に変数のアドレスを関数に受け渡すことが可能になる.
受け渡されたアドレス x, y に対応する変数値 *x, *y を用いれば別の関数で宣言された変数値の操作が可能になる.
int temp=*x;
*x=*y;
*y=temp;
このように変数のアドレスがわかれば,戻り値を用いなくとも別の関数(swap)内でmain関数にて宣言された変数の値を加工することができる.
C言語にて配列を宣言した場合,その配列の各要素のアドレスはその順序に従って連番になっている.
例えば int a[10]を宣言した場合,a[0]のアドレスが01番とすると,a[1]は02,a[2]は03,...,といった具合である.
このことを利用すればアドレスを用いて配列の操作が可能になる.
example10-4.c は文字列 "ABCDEFGHIJ" において,キーボードから与えられた整数xについて,x文字以降の文字列のみを表示するプログラムである.
#include <stdio.h> //キーボードから整数xを読み込み,文字列s[]="ABCDEFGHIJ"のx文字以降のみ表示する. int main(){ char str[] = "ABCDEFGHIJ"; int x; printf("10以下の整数を入力してください.\n"); scanf("%d", &x); printf("%sの%d文字目以降は%s",str,x,str+x); return 0; } |
配列を宣言した後,その配列名の表示は配列の先頭のアドレスを意味する.
すなわち
printf("%s",str);
とすると str は配列 str の str[0] のアドレス (&str[0]) を意味しており,&str[0]のアドレスから始まる文字配列strに含まれる文字列を書き出す命令となる.
従って
printf("%s",str+x);
とすると str+x は配列 str の先頭のアドレスから x ずれたアドレスから始まる文字列を書き出せという命令になる.
配列の各要素のアドレスはその順序に従って連番になっているので str に保存された x 文字目以降のみ表示できる仕組みである.
int **ptr;
といったようにポインタを指すポインタ型を宣言することもできる.これを二重ポインタという.
例えば二次元配列の配列名は二重ポインタである. example10-5.c はこれを確認するプログラムである.
#include <stdio.h> int main() { int b[3][3]={{11,12,13},{21,22,23},{31,32,33}}; printf("%d\n", b[2][1]); printf("%d\n", *(*( b+2)+1)); return 0; } |
すなわちb は二重ポインタであり,*(*(b+2)+1))とb[2][1]は同じ中身を参照する.
二重ポインタは文字列の配列の操作において有用である.
例えば
char *sarr[] = {“yamada”, “yodobashi”, “kizu”}
と宣言すると sarr[0]="yamada", sarr[1]="yodobashi", sarr[2]="kizu" となり,文字列の配列を作成することができる.