計数工学プログラミング演習 ( 第 3 回 ) 2017/04/25 DEPARTMENT OF MATHEMATICAL INFORMATICS 1
内容 ポインタの続き 引数の値渡しと参照渡し 構造体 2
ポインタで指されるメモリへのアクセス double **R; 型 R[i] と *(R+i) は同じ意味 意味 R double ** ポインタの配列 ( の先頭 ) へのポインタ R[i] double * i 行目を表す配列 ( の先頭 ) へのポインタ R[i][j] double i 行 j 列の要素 i は負の整数でもいいが, メモリを破壊しないように注意する double *X; は実数へのポインタだが, 同時に配列の先頭へのポインタとも解釈できるので紛らわしいので注意 3
ポインタの演算 整数へのポインタを 1 増やすと, アドレスが整数 1 個分増える 配列の要素は連続したアドレスに格納されるので, ポインタを 1 ずつ増やすと配列の要素に順番にアクセスできる 配列の長さを超えてもポインタは増やせるのでバグに注意 コンパイルはできるが実行するとエラー int A[10]; for (i=0; i<10; i++) A[i] = 0; int *p, A[10]; p = A; for (i=0; i<10; i++) *p = 0; p += 1; 4 *p++ = 0; とも書ける
変数を格納するメモリ領域 /* test4.c */ #include<stdio.h> #include<stdlib.h> int a[10]; // グローバル変数 ( どの関数からもアクセス可 ) int main() int b[10]; // ローカル変数 ( この関数内からのみアクセス可 ) int *c; // 動的に確保する領域へのポインタ int i; printf("address of a = %p, size of a[i] = %d bytes n", a, (int)sizeof(a[0])); for (i=0; i<10; i++) printf("address of a[%d] = %p n", i, &a[i]); printf("address of b = %p, size of b[i] = %d bytes n", b, (int)sizeof(b[0])); for (i=0; i<10; i++) printf("address of b[%d] = %p n", i, &b[i]); c = (int *)malloc(10*sizeof(*c)); // ヒープにメモリを確保 printf("address of c = %p, size of c[i] = %d bytes n", c, (int)sizeof(c[0])); for (i=0; i<10; i++) printf("address of c[%d] = %p n", i, &c[i]); printf("address of &a = %p, sizeof(&a) = %d bytes n", &a, (int)sizeof(&a)); printf("address of &b = %p, sizeof(&b) = %d bytes n", &b, (int)sizeof(&b)); printf("address of &c = %p, sizeof(&c) = %d bytes n", &c, (int)sizeof(&c)); free(c); return 0; 5
実行例 %./a.out Address of a = 0x601060, size of a[i] = 4 bytes Address of a[0] = 0x601060 Address of a[1] = 0x601064 Address of b = 0x7fff0347a1a0, size of b[i] = 4 bytes Address of b[0] = 0x7fff0347a1a0 Address of b[1] = 0x7fff0347a1a4 Address of c = 0x143d010, size of c[i] = 4 bytes Address of c[0] = 0x143d010 Address of c[1] = 0x143d014 Address of &a = 0x601060, sizeof(&a) = 8 bytes Address of &b = 0x7fff0347a1a0, sizeof(&b) = 8 bytes Address of &c = 0x7fff0347a198, sizeof(&c) = 8 bytes 実行中に確保される領域 ( サイズ不変 ) 0x7fff0347a1a0 0x7fff0347a198 実行中に確保される領域 ( サイズ可変 ) 0x143d010 実行前に確保される領域 ( サイズ不変 ) 0x601060 b[1] b[0] &c c[1] c[0] a[1] a[0] 6
配列を関数の引数にする #include<stdio.h> double sum(int n, double A[]) // 配列の長さは与えない ( 与えても無意味 ) // double *A でも同じ int i, x; x = 0; for (i=0; i<n; i++) x += A[i]; // x = x + A[i] の意味 return x; int main() double x; double B[3] = 1.0, 2.0, 3.0; x = sum(3, B); printf( x = %lf n,x); return 0; 7
関数でスカラー値以外を返したい場合 #include<stdio.h> int main() double x; int i; double B[3] = 1.0, 2.0, 3.0; double A[3]; for (i=0; i<3; i++) A[i] = -B[i]; for (i=0; i<3; i++) printf( %lf n, A[i]); return 0; 8
関数でスカラー値以外を返したい場合 #include<stdio.h> void invert(int n, double X[], double Y[]) // void は値を返さない関数の意味 int i; for (i=0; i<n; i++) Y[i] = -X[i]; int main() double x; double B[3] = 1.0, 2.0, 3.0; double A[3]; invert(3, B, A); for (i=0; i<3; i++) printf( %lf n, A[i]); return 0; 9
引数の値渡しと参照渡し 値渡し (call by value) 値をコピーしてサブルーチンに渡す サブルーチン内で値を変更しても元の値は変化しない 参照渡し (call by reference) 変数へのポインタをサブルーチンに渡す サブルーチン内で値を変更すると, 元の値も変化する C 言語は値渡し,Fortranは参照渡し C 言語で参照渡しをするときはポインタを引数にする 配列は参照渡しになっている 10
#include <stdio.h> #include <stdlib.h> void sub_value(int x) x = x + 1; printf("sub_value: x = %d n", x); void sub_reference(int x[]) x[0] = x[0] + 1; printf("sub_reference: x[0] = %d n", x[0]); void main(void) int x, y[1]; x = 1; y[0] = 1; printf("main: x = %d n", x); sub_value(x); printf("main: x = %d n", x); printf( main: y[0] = %d n, y[0]); 実行結果 main: x = 1 sub_value: x = 2 main: x = 1 main: y[0] = 1 sub_reference: x[0] = 2 main: y[0] = 2 sub_reference(y); // [] は付けない値ではなく y のポインタを渡している printf("main: y[0] = %d n", y[0]); 11
関数呼び出し #include <stdio.h> #include <stdlib.h> void sub_value(int x) x = x + 1; printf("sub_value: x = %d n", x); void main(void) int x; x = 1; printf("main: x = %d n", x); sub_value(x); printf("main: x = %d n", x); 実行結果 main: x = 1 sub_value: x = 2 main: x = 1 12
引数がポインタの場合 x は整数変数へのポインタ void sub_reference(int *x) *x = *x + 1; ポインタの中身を参照 printf("sub_reference: x = %d n", *x); void main(void) int y; y = 1; printf("main: y = %d n", y); yのアドレス sub_reference(&y); printf("main: y = %d n", y); 実行結果 main: y = 1 sub_reference: x = 2 main: y = 2 13
引数が配列の場合 配列は参照渡しになる void sub_reference(int x[]) x[0] = x[0] + 1; printf("sub_reference: x = %d n", x[0]); void main(void) int y[1]; y[0] = 1; printf("main: y = %d n", y[0]); sub_reference(y); printf("main: y = %d n", y[0]); 実は int *x と書いても同じ 実行結果 main: y = 1 sub_reference: x = 2 main: y = 2 14
struct ( 構造体 )1 複数の変数をまとめたもの struct matrix int n, m; // 行数, 列数 double **A; // 行列の中身 ; 15
struct ( 構造体 )2 typedef で構造体に名前をつけると便利 typedef struct int n, m; // 行数, 列数 double **A; // 行列の中身 matrix; matrix *X, *Y; // 行列の構造体へのポインタの宣言 X = malloc(sizeof(matrix)); // 構造体を格納するメモリを確保 X-> n = 3; X->m = 2; // 行数, 列数を構造体に書き込む 16
struct ( 構造体 )3 注意 : ポインタの代入では中身は複製されない int main() matrix *X, *Y; X = malloc(sizeof(matrix)); X->n = 3; Y = X; // ポインタの代入 Y->n = 4; 実行結果 X->n = 4 Y->n = 4 printf( X->n = %d n", X->n); printf( Y->n = %d n", Y->n); int main() matrix *X, *Y; X = malloc(sizeof(matrix)); X->n = 3; Y = malloc(sizeof(matrix)); // 新しい領域の確保 Y->n = 4; printf( X->n = %d n", X->n); printf( Y->n = %d n", Y->n); 実行結果 X->n = 3 Y->n = 4 17
struct ( 構造体 )4 関数の返り値を構造体へのポインタにする matrix *create_matrix(int n, int m) // 新しい零行列を作る関数 int i, j; matrix *X; X = malloc(sizeof(matrix)); // 構造体を格納するメモリを確保 X-> n = n; X->m = m; // 行数, 列数を構造体に代入 X->A = malloc(n*sizeof(double *)); // 行ベクトルの配列のメモリを確保して構造体に代入 // ここで列ベクトルのメモリを確保 for (i=0; i<n; i++) for (j=0; j<m; j++) X->A[i][j] = 0.0; // 値を初期化 return X; int main() matrix *A; A = create_matrix(3, 2); 18
第 3 回課題 : 2 つの行列の積を計算するプログラム 入力行列は標準入力から与えられる 1 行目は行列の行数と列数 1 つ目の行列のデータの直後に 2 つ目の行列のデータが来る 標準入力から続けて読めばいい 結果は標準出力に 入力と同じ形式 積を計算できない場合は 0 行 0 列の行列を出力する 出力は 0 0 の 1 行だけ 行列の形式 2 3 // 行数, 列数 1.0 2.0 3.0 // 1 行目 4.0 5.0 6.0 // 2 行目 main 関数の最後は return 0; をつけておく 提出するソースコードの中に, 学生証番号, 氏名, メールアドレスをコメントとして書いておく 締切 : 2017 年 4 月 25 日 ( 火 ) 17:35 ( 演習終了時 ) http://133.11.136.29/domjudge/ 19
ヒント : 以下の関数を順番に作っていけばいい matrix *create_matrix(int n, int m) n 行 m 列の零行列のメモリを確保する matrix *read_matrix() 標準入力から行列のデータを読み, 新しく行列を作る 行列のメモリはこの関数内で確保する void print_matrix(matrix *A) 行列を指定された形式で標準出力に書く matrix *product(matrix *A, matrix *B) 行列積を求め, 新しい行列を作る void free_matrix(matrix *X) 行列のメモリを開放する ( 作らなくても動くが ) 20
Windows 10 を使いたい人は Bash on Windows を入れる bash を起動し,make, gcc, gdb を入れる sudo apt-get install gcc sudo apt-get install make sudo apt-get install gdb valgrind を入れる 参考 http://blog.wagavulin.jp/entry/2016/08/30/230429 21