コンピュータ初級 (クラス2)

IT入門B2
ー 行列とポインタ ー
授 業 内 容
• 配列とポインタ
• 行列とポインタ
• 動的なメモリの確保
• 演習
配列とポインタ
n次元ベクトルxをプログラム上で表現する際は,配列を利用.
double x[n];
と宣言することで,double型でn個分の記憶領域がメモリ上に
連続して確保される:
0xbfff660 0xbfff668 0xbfff670
…
x[0]
x[1]
x[2]
…
…
x[n-1]
…
メモリには住所のように番地(アドレス)が振られている.
各要素のアドレスはアドレス演算子(&)を用いて,
&x[0], &x[1], &x[2], … , &x[n-1]
により参照できる.
配列とポインタ
要素x[i]のアドレスは &x[i] により参照できる.
&x[2]
&x[0]
0xbfff660 0xbfff668 0xbfff670
…
x[0]
x[1]
x[2]
…
…
x[n-1]
…
一方,配列名xは先頭要素へのポインタとして利用できる.
ポインタの演算を用いることにより,各要素のアドレスは
x+i(i=0,1,2,…,n-1) でも参照できる.
x
x+2
x+1
x+(n-1)
…
x[0]
x[1]
x[2]
…
x[n-1]
…
配列とポインタ
ポインタに対して間接参照演算子(*)を適用することにより,
ポインタの指すアドレスに格納された値を参照できる.
ポインタ x+i に対しては,*(x+i) とすることで各要素の値
を参照できる.
アドレスの参照
x
…
*x
x+2
x+1
x[0]
x[1]
*(x+1)
x[2]
*(x+2)
値の参照
x+(n-1)
…
x[n-1]
*(x+(n-1))
…
行列とポインタ
行列をプログラム上で表現する場合は,2次元配列を利用する.
例えば,n次の正方行列Aに対しては
double a[n][n];
と宣言することで,double型でn×n個分の記憶領域がメモリ
上に確保される:
a :
a[0][0]
a[0][1]
a[0][2]
…
a[0][n-1]
a[1][0]
a[1][1]
a[1][2]
…
a[1][n-1]
a[2][0]
a[2][1]
a[2][1]
…
a[2][n-1]
…
a[n-1][0] a[n-1][1] a[n-1][2]
…
a[n-1][n-1]
行列とポインタ
メモリ
…
double a[n][n];
a[0] →
このときaのn×n個の要素の領域は,
右のようにメモリ上に連続して確保
される.
aは,
a[0], a[1], … , a[n-1]
から構成される.
a[1] →
各要素a[i] (i=0,1,…,n-1) は
第i行の先頭要素を指すポインタ
a[n-1] →
である.
a[0][0]
a[0][1]
…
第0行
a[0][n-1]
a[1][0]
a[1][1]
…
第1行
a[1][n-1]
…
a[n-1][0]
…
a[n-1][n-1]
…
第
n-1
行
行列とポインタ
メモリ
double a[n][n];
…
行列aの第i行j列の要素(a[i][j])
を,ポインタと間接参照演算子を
用いて参照する場合は,
*(a[i] + j)
となる.
a[i][j]
⇔
*(a[i] + j)
*(a[0])
a[0][0]
*(a[0]+1)
a[0][1]
…
*(a[0]+n-1)
a[0][n-1]
…
*(a[i]+0)
a[i][0]
*(a[i]+1)
a[i][1]
…
*(a[i]+j)
a[i][j]
…
行列とポインタ
メモリ
double a[n][n];
に対してポインタ変数 ai を
導入する:
…
*ai
a[0][0]
*(ai+1)
a[0][1]
double *ai;
配列名aは先頭要素へのポイン *(ai+n-1)
タとして利用できるから,
ai = a;
により,aの第0行の各要素が
*ai, *(ai+1), …, *(ai+n-1)
で参照できる.
ポインタ
ai
+1
+1
…
a[0][n-1]
a[1][0]
a[1][1]
…
a[1][n-1]
a[2][0]
…
+1
行列とポインタ
メモリ
次の行に処理が移るときは,
…
a[0][0]
ai += n;
a[0][1]
によりポインタaiを更新すれば,
aiは常に
「第i行の先頭要素a[i][0]へ
のポインタ」
+n
…
a[0][n-1]
*ai
a[1][0]
として機能し,第i行の各要素が *(ai+1)
a[1][1]
*ai, *(ai+1), …, *(ai+n-1)
で参照できる.
ポインタ
ai
…
*(ai+n-1)
a[1][n-1]
a[2][0]
…
+1
+1
+n
(行列)×(ベクトル)の計算
以前作成した(行列)×(ベクトル)を計算するプログラム
を,ポインタの表現を用いて書き直して見よう.
double x[n];
double a[n][n];
double *ai;
ベクトルxの各要素:
*(x), *(x + 1), … , *(x + j), … , *(x + (n-1))
行列aの各要素:
aiが第i行の先頭要素へのポインタであるとして,
*(ai + 0),*(ai + 1), … ,*(ai + j), … ,*(ai + (n-1))
次の行(第i+1行)へ移るときには,
ai += n;
(行列)×(ベクトル)の計算
2次元配列の表現を用いた
プログラム例
ポインタの表現を用いた
プログラム例
for(i = 0; i < n; i++){
y[i] = 0.0;
for(j = 0; j < n; j++){
y[i] += a[i][j]*x[j];
}
}
ai = a;
for(i = 0; i < n; i++){
*(y+i) = 0.0;
for(j = 0; j < n; j++){
*(y+i) += *(ai+j) * *(x+j);
}
ai += n;
}
演 習
第2回の授業で作成した行列 A とベクトル x の積( A x )を計
算するプログラムを,ポインタの表現を用いたプログラムに
書き直し,以下の場合について積を計算してみよう:
2 4 6 
  33
A   3 8 7 , x   9 
 5 7 21
 6 




(行列)×(ベクトル)の計算
これまでのプログラムでは,
double a[3][3], x[3];
のように配列の大きさを宣言
してから利用.
• 次元nに依存している.
• 次元nや行列A, ベクトルxの
内容が変わるたびにプログ
ラムの修正,コンパイル,
実行,を行わなければなら
ない.
#include <stdio.h>
int main(void)
{
int i, j;
double x[3] = {-33.0, 9.0, 6.0};
double y[3];
double a[3][3] = {{2.0, 4.0, 6.0},
{3.0, 8.0, 7.0},
{5.0, 7.0, 21.0}};
…
}
次元nや行列A, ベクトルxの内容に依存しないプログラムに変
更してみよう.
(行列)×(ベクトル)の計算
行列Aやベクトルxの内容変更への対応
ここでは,scanf()などの関数を利用して,実行時に行列,ベ
クトルの各要素をキーボードから入力することにする.
次元nの変更への対応
動的なメモリ確保を行う関数malloc()を利用する.
プログラムの実行中に行列やベクトルの大きさに応じたメモ
リ領域を確保することができる.
動的なメモリ確保
malloc(size) :
• 引数sizeで指定されたサイズのメモリ領域を確保して,
その先頭へのポインタを返す関数.
• メモリの確保に失敗した場合は,NULLを返す.
• malloc()により確保したメモリ領域は,使い終わったら
関数free()により開放する必要がある.
• malloc()を利用するにはヘッダファイル stdlib.h を
インクルードする必要がある.
動的なメモリ確保
基本的な使い方:
double *x;
x = (double *)malloc(n*sizeof(double));
メモリ
…
x
double型
double型
double型のサイズのn個分の領域を確保し,
その領域の先頭へのポインタをxに代入.
double型
n個
…
sizeof演算子
いろいろな型のサイズ(バイト数)を求める
ことができる.
sizeof(double) → 8 (バイト)
sizeof(int)
→ 4 (バイト)
sizeof(char)
→ 1 (バイト)
double型
double型
double型
…
動的なメモリ確保
基本的な使い方:
x = (double *)malloc(n*sizeof(double));
malloc()により得られたデータ型をdoubleへのポインタ型に変換する.
キャスト演算子
(型名)式 : 式で与えられているデータ型を型名へ変換する.
例
int a, b;
double c, d;
a
b
c
d
=
=
=
=
1.0;
3.0;
a / b;
→ 0.000000
(double)a / b; → 0.333333
動的なメモリ確保
基本的な使い方:
x = (double *)malloc(n*sizeof(double));
• メモリの確保に失敗した場合は,以降の処理を中止する必要がある.
• 確保した領域は,使い終わった段階で開放する必要がある.
x = (double *)malloc(n*sizeof(double));
if (x == NULL) {
printf(Cant allocate memory.\n);
exit(1);
}
/* 必要な処理 */
free(x);
動的なメモリ確保
メモリ
…
double *x;
x = (double *)malloc(n*sizeof(double));
x
double型
double型
double型
ポインタxがdouble型の要素をn個持つような
配列名と同じように扱うことができる.
n個
…
double型
x + i
double型
→ i番目の要素へのポインタ
*(x + i) → i番目の要素
scanf("%lf", x + i);
double型
…
→ i番目の要素をキーボードから入力
printf("%.2f\n", *(x + i)); → i番目の要素を出力
動的なメモリ確保
メモリ
行列の場合
double *a, *ai;
…
a
a = (double *)malloc(n*n*sizeof(double));
if (a==NULL) {
printf("Can't allocate memory.\n");
exit(1);
}
ポインタaがdouble型の要素をn×n個持つような
配列名と同じように扱うことができる.
double
double
double
n×n個
…
double
double
double
ポインタ変数aiを導入し,
…
ai = a;
とすると,ポインタの表現による行列の場合と同様に第0行の各要素が
*(ai + j)で参照でき,次の行へ処理が移るときに
ai += n;
とすれば,aiが常に行列の第i行の先頭へのポインタとして機能する.
動的なメモリ確保
行列の場合のプログラムの流れ
double *a, *ai;
a = (double *)malloc(n*n*sizeof(double));
if (a==NULL) {
printf("Can't allocate memory.\n");
exit(1);
}
ai = a;
for (i=0; i<n; i++) {
for(j=0; j<n; j++) {
printf("a[%d][%d] = ", i, j);
scanf("%lf", ai + j);
}
ai += n;
}
行列aの各要素にキーボードから
データを入力
演 習
キーボードから次元nを入力した後,n×n行列Aとn次元ベクトルxのデータ
を要素ごとにキーボードから入力し,A x を計算するプログラムを完成さ
せよう.
$ ./a.out
n = 3
a[0][0] = 2.0
a[0][1] = 4.0
a[0][2] = 6.0
a[1][0] = 3.0
…
x[2] = 6.0
A*x =
6.0
15.0
24.0
まとめ
以上から,プログラムの実行時にメモリを動的に確保することで,行列や
ベクトルの次元に依存しないプログラムを作成することができた.
実際には,行列やベクトルのデータの入出力を行う部分について,行列,
ベクトルのデータを記述したファイルを用意しておき,
・ このファイルからデータを読み込む
・ 計算結果を別のファイルに書き込む
といったファイル入出力の関数を利用することにより,より実用的なプロ
グラムが作成できる.