2.9.1 行列を作成・削除する関数 前のページ「2.8 動的配列」のプログラム matrix06.cpp を関数を使って表現し ましょう。行列を動的に作成し、表示、そして行列領域を解放する。この3つの動作 を関数にします。次のプログラムを見てください。
// lst02_13.cpp // 行列を動的に作成 // 関数版 #include <iostream.h> int **matrix_new(int, int); // 行列の動的作成 void matrix_del(int **, int); // 行列の解放 void print(int **, int, int); // 行列の表示 int main() { int row, col; // 行と列の大きさ cout << "行の大きさを入力してください: "; cin >> row; cout << "列の大きさを入力してください: "; cin >> col; int **mat; mat = matrix_new(row, col); // 行列の作成 // 行列の値を設定 for(int i = 0; i < row; i++) for(int j = 0; j < col; j++) mat[i][j] = (i+1)*10 + (j + 1); print(mat, row, col); matrix_del(mat, row); return 0; } // 行列の動的作成 int **matrix_new(int row, int col) { int **a = new int *[row]; // 行を作る for(int i = 0; i < row; i++) // 列を作る a[i] = new int[col]; return a; // アドレスを返す } //行列の解放 void matrix_del(int **a, int row) { for(int i = 0; i < row; i++) delete [] a[i]; // 列を解放 delete [] a; // 行を解放 } void print(int **a, int row, int col) { for(int i = 0; i < row; i++){ for(int j = 0; j < col; j++) cout << " " << a[i][j]; cout << endl; //行の終わりに改行 } } |
行の大きさを入力してください: 5 列の大きさを入力してください: 8 11 12 13 14 15 16 17 18 21 22 23 24 25 26 27 28 31 32 33 34 35 36 37 38 41 42 43 44 45 46 47 48 51 52 53 54 55 56 57 58 |
2.9.2 使いやすい関数の工夫 行列操作関数を使いやすくするために、いくつかの工夫を行いましょう。ここでは行列 をポインタへのポインタとして表現しています。浮動小数点型の行列の場合は、 double **mat; となります。これを独自のデータ型で定義しましょう。C/C++ではtypedef文を使って自 作のデータ型を定義できます。次のようにすると typedef double **Matrix; 新しい自作の型 Matrix が定義できます。これは(double **)型と同じです。この型定 義はキーワードtypedefを除けば、変数の宣言と同じです。typedefを付けると変数名 ではなく型名を定義します。次の宣言 Matrix a; は、次の宣言と同じです。 double **a; C言語では新しい構造体を定義するのにtypedefをよく使います。 次に、行列領域を解放する関数 matrix_del() を検討しましょう。この関数は行の数 を渡す必要があり、バランスの悪い格好をしています。できれば次のように void matrix_del(Matrix a); としたいものです。これだと関数の利用者は「行数を渡す(列数は不要)」といった奇妙 な関数の使い方(仕様)を覚える必要はありません。 「行数を渡す」必要があるのは、ポインタを関数に渡すと配列の大きさについての情報 が失われるためでした。ところが、配列の大きさについての情報を与える巧い方法があり ます。このアイデアは『奥村晴彦著:C言語による最新アルゴリズム辞典、技術評論社』 にあります。 まず行列領域を作成するときに行のポインタ配列を1つ余分に作り、行の最後を示す印 としてヌルポインタを入れておきます。 Matrix matrix_new(int row, int col) { Matrix a = new double *[row + 1]; // 行を作る(1つ余分に作る) for(int i = 0; i < row; i++) // 列を作る a[i] = new double[col]; a[row] = 0; // ヌルポインタを指す return a; // アドレスを返す } ヌルポインタは何も指していないことを意味しますから、これを目印にして領域を解放す ることができます。 void matrix_del(Matrix a) { Matrix b = a; while(*b != 0) delete [] *b++; // 列を解放 delete [] a; // 行を解放 } ここで列を解放する(つまり行ベクトルを解放する)のに、一時的なポインタbを使って いることに注意しましょう。bは行ベクトルを指す配列ポインタの先頭を指していますか ら、*bは1行目の行ベクトル(横ベクトル)のアドレスです。*bの値がヌルポインタ になるまで、ポインタbをインクリメントして列を解放して行きます。このwhile文は次 の様に書いたものと同じです。 while(*b != 0){ delete [] *b; // 列を解放 ++b; // 行を1つ進める } ポインタbはインクリメントされる度に値が変わっていますので、もはや行列の先頭を指 してはいません。そして最後に元のポインタaで行を解放します。 [注]このトリックは、文字列操作では普通に使われています。文字列は最後の要素にヌル 文字('\0')を挿入した(1個余分にとった)配列ですから、文字列の長さを返すC言語 の関数 strlen() はおよそ次のような方法でヌル文字を目印にして文字数を求めます。 // 文字列 sの長さを返す int strlen(char *s) { char *p = s; while( *p != '\0') p++; return ( p - s ); } 以上より、 Matrix a = matrix_new(row, col); で行列を作成して、 matrix_del(a); で行列を解放できるようになりました。 【演習問題 2.12】プログラム lst02_13.cpp を上の方法を使って書き換えなさい。 (回答プログラム lst02_14.cpp)
2.9.3 分割コンパイル 行列を操作する関数ライブラリを作ります。プログラムを複数のファイルに分けて、関 数のプロトタイプ宣言だけを書いたファイルをヘッダファイル matrix09.h として作り、 関数実装を書いたものを別のファイル matrix09.cpp として作ります。
// ヘッダファイル matrix09.h // 行列操作ライブラリ インターフェイス部 #ifndef MATRIX09_H #define MATRIX09_H typedef double **Matrix; Matrix matrix_new(int, int); void matrix_del(Matrix); void read(Matrix, int, int); void print(Matrix, int, int); #endif |
// matrix09.cpp // 行列操作ライブラリ 関数定義部 #include <iostream.h> #include "matrix09.h" // 行列操作ライブラリのヘッダファイル // 行列の動的作成 Matrix matrix_new(int row, int col) { Matrix a = new double *[row + 1]; // 行を作る for(int i = 0; i < row; i++) // 列を作る a[i] = new double[col]; a[row] = 0; return a; } //行列の解放 void matrix_del(Matrix a) { Matrix b = a; while(*b != 0) delete [] *b++; // 列を解放 delete [] a; // 行を解放 } // 行列要素の読み込み void read(Matrix a, int row, int col) { cout << row << " 行" << col << " 列の行列要素を入力してください" << endl; for(int i = 0; i < row; i++) for(int j = 0; j < col; j++){ cout << (i+1) << " 行" << (j+1) << " 列: "; cin >> a[i][j]; } } // 行列要素をプリント void print(Matrix a, int row, int col) { cout.setf(ios::scientific); // 科学表記法 for(int i = 0; i < row; i++){ for(int j = 0; j < col; j++) cout << a[i][j] << '\t'; cout << endl; //行の終わりに改行 } } |
// lst02_15.cpp // lst02_15.cpp // ドライバプログラム #include <iostream.h> #include "matrix09.h" int main() { int row, col; // 行と列の大きさ cout << "行の大きさを入力してください: "; cin >> row; cout << "列の大きさを入力してください: "; cin >> col; Matrix mat = matrix_new(row, col); read(mat, row, col); cout << "行列要素の表示:" << endl; print(mat, row, col); matrix_del(mat); return 0; } |
C:\Source>bcc32 lst02_15.cpp matrix09.cpp Borland C++ 5.3 for Win32 Copyright (c) 1993, 1998 Borland International lst02_15.cpp: matrix09.cpp: Turbo Incremental Link 3.01 Copyright (c) 1997, 1998 Borland International C:\Source> |
C:\Source>lst02_15 行の大きさを入力してください: 3 列の大きさを入力してください: 3 3 行3 列の行列要素を入力してください 1 行1 列: 123.456 1 行2 列: 234.567 1 行3 列: 30 2 行1 列: .456789 2 行2 列: 543.2 2 行3 列: 90.9 3 行1 列: 32.12 3 行2 列: 3.456789 3 行3 列: 123.5 行列要素の表示: 1.234560e+02 2.345670e+02 3.000000e+01 4.567890e-01 5.432000e+02 9.090000e+01 3.212000e+01 3.456789e+00 1.235000e+02 C:\Source> |
C:\Source>bcc32 -c matrix09.cpp Borland C++ 5.3 for Win32 Copyright (c) 1993, 1998 Borland International matrix09.cpp: C:\Source> |
C:\Source>bcc32 lst02_15.cpp matrix09.obj Borland C++ 5.3 for Win32 Copyright (c) 1993, 1998 Borland International lst02_15.cpp: Turbo Incremental Link 3.01 Copyright (c) 1997, 1998 Borland International C:\Source> |
2.9.4 プリプロセッサ(前処理プログラム)
プログラム・ファイルの先頭でヘッダファイルをインクルードする命令はプリプロセッ
サ命令といいます。プリプロセッサは前処理プログラムのことで、コンパイラはプログラ
ムをコンパイルする前にプリプロセッサを起動させます。プリプロセッサ命令はC/C++の
プログラミング言語ではありません。従って、C/C++言語とは異なる構文規則に従います
(つまりC/C++の構文を理解しません)。すべてのプリプロセッサ命令は先頭にハッシュ
記号(#)が付きます。
#includeプリプロセッサ命令は、指定したファイルのコピーをプログラムの中に取り込
み(インクルード)ます。これには lst02_15.cpp にあるように2つの形式があります。
#include <iostream.h>
#include "matrix09.h"
ヘッダファイルを括弧(<と>)で囲んだ場合、これは標準ライブラリに対して使われ、プ
リプロセッサは既定のディレクトリからファイルを検索します。またファイル名が二重引
用符("")で囲まれている場合は、コンパイルされるファイルのあるディレクトリを検索
し、そこにないときは既定のディレクトリを検索します。インクルードするのはプログラ
ムファイルでも構いません。従って、関数プロトタイプ宣言と関数定義を1つのファイル
matrix09.h にしてこれをインクルードすることもできます。この場合、このページの最
初のプログラム lst02_13.cpp と同じ、すべてを1つのファイルとしてコンパイルされま
す。
プリプロセッサが行う処理をファイルに書き出すと、これが何を行っているのかよく分
かります。
cpp32 lst02_15.cpp
と実行すると、拡張子.Iのファイルが作成されます。テキストエディタでこのファイルを
見ると、末尾の部分が次のようになっています(これは大きなファイルとなっているはず
です。ファイルのほとんどは iostream.h のインクルード部分で占められています)。
typedef double **Matrix; Matrix matrix_new(int, int); void matrix_del( Matrix ); void read(Matrix, int, int); void print(Matrix, int, int); int main() { int row, col; cout << "行の大きさを入力してください: "; cin >> row; cout << "列の大きさを入力してください: "; cin >> col; Matrix mat = matrix_new(row, col); read(mat, row, col); cout << "行列要素の表示:" << endl; print(mat, row, col); matrix_del(mat); return 0; } |
Copyright(c) 1999 Yamada,K