Comments
Description
Transcript
配列とポインタ
配列とポインタ、参考書 B.W.カーニハン / D.M.リッチー著 石田晴久訳 「プログラミング言語C 第2版」 共立出版(1989) 配列とポインタ(C言語) 第1章 やさしい入門 1.7 配列 第5章 ポインタと配列 2005-05 ∼ 2013-05 AKIYAMA M. 1 2 配列 多次元配列 配列 a 宣言 型名 配列名[要素数]; int b[4][3]; 連続した12( = 4x3)個 の記憶領域 a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] 例: int a[10]; 添字:先頭の要素の添字は 0 最後の要素の添字は 要素数-1 b b[0][0] b[0][1] b[0][2] b[1][0] b[1][1] b[1][2] b[2][0] b[2][1] b[2][2] b[3][0] b[3][1] b[3][2] 要素は右図のように並ぶ ( b[0][2]の次がb[1][0] ) a[0], a[1], a[2], … , a[9] の10個の 要素 3 4 配列要素に初期値を与える 配列要素の初期値設定 int a[10] = { 1,1,2,3,5,8,13,21,34,55 }; int b[4][3]={ {0,5,4}, {2,3,8}, {6,1,9}, {7,0,1} }; char c[]="Advanced C"; この場合、配列cの各要素に英字が1文字ず つ入る。cの最後の要素には値 0 が入る。 要素数は、右辺文字列の文字数+1 になる。 a[0] a[1] a[2] a[3] a[4] a[5] a[6] a[7] a[8] a[9] 1 1 2 3 5 8 13 21 34 55 b[0][0] b[0][1] b[0][2] b[1][0] b[1][1] b[1][2] b[2][0] b[2][1] b[2][2] b[3][0] b[3][1] b[3][2] 0 5 4 2 3 8 6 1 9 7 0 1 c[0] A d c[10] v a n c e d C 0 char c[80]="Advanced C"; では、配列のc[11]以降の要素は値0で初期化される。 5 6 1 (注意) 代入と初期化の違い (注意) 代入と初期化の違い • 代入:宣言済みの変数に値を保存する。 int x; x = 10; • 誤り int a[10]; char c[21]; a[10] = { 1,1,2,3,5,8,13,21,34,55 }; c = "Advanced Programming"; • 初期化:変数の宣言に続けて初期値を与える。 int y = 10; const double pi = 3.141592653589793238; /* 値を変更できない変数に初期値を与える。*/ const int n; n = 34; 7 ポインタ 8 アドレス演算子と間接演算子 • アドレス演算子 & &変数名 &配列要素 変数や配列要素のメモリアドレスを得る。 • ポインタ変数の宣言 型名 *変数名, *変数名, … ; ポインタ変数には記憶場所のアドレスを 保存できる。 • 間接演算子 * *ポインタ変数 *アドレス式 ポインタ変数が指しているアドレスに保存 されている値を得る。 int *pa, *pb; 9 ポインタ 配列とポインタ、アドレス式 int x=10, y; int a[10] = { 1,1,2,3,5,8,13,21,34,55 }; int *px, *pa3; a[0] px a[1] px = &x; a[2] x 10 a[3] pa3 = &a[3]; a[4] pa3 y= *px + *pa3; /* 代入文 */ *px = -12; /* 代入文 */ 10 a[5] a[6] a[7] a[8] a[9] int a[10]= { 1,1,2,3,5,8,13,21,34,55 }; int *pa=&a[0]; 1 1 2 3 5 8 13 21 34 55 11 pa の値は配列要素 a[0]のアドレスである。 このとき 式 pa+1 の値は a[1] のアドレスになる。 i が整数値のとき pa + i の値は a[i] のアドレスに なる。 式 x[y] は *(x+(y)) と同等であると規定されている。 12 2 アドレス式、ポインタの演算 アドレス式と多次元配列 int x, y, i, a[10]; char s[100]; int *px = &x, *pa = &a[0]; char *ps = s; int b[4][3], i, j; *(&b[0][0]) は b[0][0] と同じ。 *(&b[0][0]+3) は b[1][0] と同じ。 *(&b[0][0]+3*i+j) は b[i][j] と同じ。 式 pa+i は &pa[i] と、 ps+i は &ps[i] と等しい。 *(pa+i) は pa[i] と、 *(ps+i) は ps[i] と同じ。 配列要素の占めるメモリサイズによらない。 13 配列とポインタ 14 const 修飾子 int a[10], i; int *pa = &a[0]; このとき a[i] と pa[i] のどちらも同等である。 C言語では、配列名は先頭要素のアドレスを値 とするポインタ変数として使ってもよい。 a[3] = 3*(*(pa+2)); *(a+4) = pa[2]/4; M, Nが整定数のとき int bb[M][N], i, j; では、 *(&bb[0][0]+N*i+j) は bb[i][j] と同じ。 b[0][0] b[0][1] b[0][2] b[1][0] b[1][1] b[1][2] b[2][0] b[2][1] b[2][2] b[3][0] b[3][1] b[3][2] const int m=3; const int *pm; pm = &m; /* 変数 m の値は不変 */ /* const 変数へのポインタ */ /* pm は不変ではない */ int n=10; int *const pn = &n; /* 不変のポインタ変数 */ *pn=20; /* 参照先の値を変えてもよい */ /* 代入文 a[3] = 3*a[2] と同じ*/ /* 代入文 a[4] = a[2]/4 と同じ */ 15 16 配列名とポインタ変数 scanf関数 int a[10]; int *const pa = a; これで、 a と pa とがほぼ同等に使える。 C言語では、1次元配列の配列名は、先頭要素 への const ポインタとほとんど同等である。 int x, y, z[3]; int *px=&x, *py=&y; scanf( "%d %d", &x, &y ); scanf( "%d %d %d", &z[0], &z[1], &z[2] ); sizeof a と sizeof pa とは同じにならない。 C++言語ではほかの意味をもたせることができる。 2次元以上の配列では扱いが複雑になる。 scanf( "%d %d", px, py ); scanf( "%d %d %d", z, z+1, z+2 ); 17 18 3 ポインタの配列 int *p[10]; ポインタ配列の初期化 char s[]="Sunday"; char *s="Monday"; char *w[]={"Tuesday", "Wednesday"}; char v[][10]={"Thursday", "Friday", "Saturday"}; char **u=v; p[0] p[1] p[2] p[0], p[1], ..., p[9] の10個のポインタ p[9] 19 int main( int argc, char *argv[] ) { ... } • Visual Studio .NETからの実行 メニュー「プロジェクト」− 「<プロジェクト名>のプロパティ」 構成プロパティ⇒デバッグ コマンド引数の欄 • コマンド プロンプトからの実行 コマンド行に、スペースで区切って続ける または int main( int argc, char **argv ) { ... } argc : コマンド行パラメータの数 argv[i] : パラメータ文字列へのポインタ 例: > echo hello, world [Enter] 3 argv[0] argv[1] argv[2] argv[3] NULL 20 (参考) main関数へのパラメータ main関数の引数 argc 以下は誤り char x[][]={"Tuesday", "Wednesday"}; char **y={"Thursday", "Friday", "Saturday"}; echo¥0 hello,¥0 world¥0 21 配列へのポインタ(2次元配列) int (*pb)[3]; /* 要素数3のint配列へのポインタ */ int b[4][3]; /* 「要素数3のint配列」の配列 */ pb=&b[0]; /* pb=b と同じ*/ これで、pb は b と同等に使える。 b[i][j], pb[i][j], *(pb+i)[j], *(*(pb+i)+j) はすべて同 じ要素を指す。 b[i][j] を *(b+i)[j] や *(*(b+i)+j) と書いてもよい。 (注意) 多次元配列の配列名は、配列の先頭要素へのポインタではない。 23 22 typedef • 型名を定義する typedef int* pint; int a[10]; pint pa = a; /* int* pa = a; と同じ */ typedef int B[4][3]; /* B が型名になる */ B b, v, w[2]; /* int b[4][3], v[4][3], w[2][4][3]; と同じ */ 24 4 void* NULL • 総称的なポインタ型 void*型の変数には、どんなアドレス値でも保 存できる。 void*型を他のポインタ型に型変換(キャスト) してもアドレス情報は失われない。 { void* px; int x[100]; double z; px=x+3; *(int *)px=10; x[0]=*(int *)px; px=&z; *(double*)px=3.14; } • 特殊なポインタ値(空ポインタ定数) • ポインタ変数が何も指していないことを示す ために、初期値や関数の実行時エラーを返 す戻り値として使われる。 • #include <stddef.h> • C++では、整数値0を空ポインタ定数とする。 (注) NULL : ポインタ NUL : 文字定数 25 26 malloc(), calloc() realloc(), free() • 記憶場所を動的に割り当てる。 • #include <stdlib.h> • void* malloc( int n ); nバイトの記憶場所を割り当て、先頭のアドレスを 返す。失敗したときはNULLを返す。 • void* calloc( int n, int size ); sizeバイトの要素n個分の配列のための記憶場所 を割り当て、先頭のアドレスを返す。失敗したときは NULLを返す。記憶場所はゼロで初期化される。 • 動的に割り当てた記憶場所のサイズを変更する。 • #include <stdlib.h> • void* realloc( void *p, int n ); 割り当てた記憶場所のサイズをnバイトに変更し、 新しい記憶場所の先頭のアドレスを返す。失敗した ときはNULLを返す。元の内容は残っている。 • 動的に割り当てた記憶場所を解放する。 • #include <stdlib.h> • void free( void* p ); pを先頭アドレスとする記憶場所を解放する。 27 配列要素の動的割り当て(例) { sizeof 演算子 int n, i; double *x; scanf( "%d", &n ) • 要素が占めるバイト数が得られる • sizeof( 型名 ) /* データの個数を入力 */ x = (double*)malloc( n*sizeof(double) ); /* 配列要素の記憶域の確保 */ if ( x==NULL ) exit(-1); /* 失敗したらエラー終了 */ for ( i=0; i<n; i++ ) scanf( "%lf", &x[i] ); /* 個々のデータの値を入力 */ – sizeof( 構造体名 ) • sizeof オブジェクト – sizeof 変数名 – sizeof 式 – sizeof 配列名 /* データ処理(省略) */ free(x); 28 /* 記憶域の解放 */ } 29 30 5