配列とポインタ

配列とポインタ、参考書
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