#2 行列操作ライブラリの設計(1)  1999.03.19(初版)


 前回に続きデータの入れ物つまりデータ構造を扱います。ここでは行列ライブラリを 
設計します。行列は行(row)と列(column)に値を配置した表の構造をしていますから、 
2次元配列で表現できます。 
 2次元配列を使った行列ライブラリを設計する過程で配列とポインタとの密接な関係 
が鮮明になってきます。その知識は連立方程式や固有値問題などの行列計算に限らず、 
エディタなどの文書処理ソフトウェアの作成にも応用することができます。 
 以下(ライブラリの設計(1))ではまず、C++で手続き型(非オブジェクト志向) 
のライブラリを構築します。これは本質的にはC言語プログラミングです。その後設計 
(2)でオブジェクト指向のクラス・ライブラリを構築します。これにより強力で柔軟 
性のある関数ライブラリをつくることができます。関連する基本的概念を整理しながら、 
ゆっくり進みます。 



2.1 配列

2.1.1 配列の宣言  配列は同一型のデータを格納するための連続したメモリ領域です。配列の1つ1つのデ ータ項目を要素、配列の要素数を次元といいます。配列は次のような宣言でメモリ内に確 保されます。 int a[3]; // 宣言 この配列aは3個のint型のデータ(要素)があり、それぞれの要素は int型の別々の変 数a[0],a[1],a[2]として読み書きすることができます。配列の要素の番号(添字)は0 (ゼロ)から始まり(次元−1)で終わります。なぜそうなのかは、後で行うポインタと の関係についての議論から明らかになります。  宣言時に、中括弧{}を使って初期値を並べて与えることで要素を初期化することができ ます。 int a[3] = { 5, 2, 9 }; // a[0]は5、a[1]は2、a[2]は9に初期化 また、初期値の数が配列要素の数より少ない場合は、残りの要素は0に初期化されます。 従って次の場合 int a[10] = {0}; は10個の配列要素の値はすべて0で初期化されます。初期値リストを付けた配列宣言で は配列のサイズを省略できます。 int a[] = { 1, 2, 3, 4, 5}; は、5個の要素の配列を確保します。しかし、次の宣言 int a[3] = { 1, 2, 3, 4}; // エラー は、3要素しかない配列に4個の初期値を指定しているために、構文エラーになります。  ★配列を使った例題

2.1.2 2次元配列  2次元配列は行(row)と列(column)に値を配置した表の構造をしています。その宣言は 2組の角括弧(大括弧)[ ]を使って行と列のサイズを指定します。 int mat[4][5]; は行のサイズが4、列のサイズが5の int型の配列名 mat を意味します。 mat[i][j] はi行j列目の要素でi、jの範囲は0≦i≦3,0≦j≦4となります。プログラム 例を挙げます。
// matrix01.cpp
// 4×5行列を2元配列で表す
// 行列の初期化
#include <iostream.h>

int main()
{
    const int ROW = 4;	//行の次元
    const int COL = 5;	//列の次元

    int mat[][COL] = { {11,12,13,14,15},
                       {21,22,23,24,25},
                       {31,32,33,34,35},
                       {41,42,43,44,45} };      

    cout << "行列の表示\n";

    for(int i = 0; i < ROW; i++){
        for(int j = 0; j < COL; j++)
            cout << " " << mat[i][j];

        cout << endl;   //行の終わりに改行
    }

    return 0;
}

ソースファイル:matrix01.cpp
[実行結果]
行列の表示                
 11 12 13 14 15           
 21 22 23 24 25           
 31 32 33 34 35           
 41 42 43 44 45
 4行5列の2次元配列を表示するプログラムです。このサンプルプログラムの実行結果 で注目することは、初期化リストで与えたデータが行列の要素mat[i][j]にどのように配 置されているかです。  ここで行と列の次元を示す変数ROW、COLの宣言にキーワードconstで修飾しています。 const int ROW = 4; //行の次元 const int COL = 5; //列の次元 これはユーザー定義定数で、変数を定数として扱うことができます。定数ですから代入が できません。従って定数変数は宣言したときに定数式で初期化しなければなりません。そ れ以降は変更できません。定数変数を使って配列を次のように宣言することができます。 int mat[ROW][COL]; // int mat[4][5]; と同じ   [注]C言語ではキーワード const をこの様に使えません。配列の宣言ではマクロ    定義を使うことになります。後ろに;が付かないことに注意しましょう。    #define ROW 4   #define COL 5      main()   { int mat[ROW][COL];    // ......    }    C++では、マクロ定義はなるべく使わないのが原則です。  プログラムで初期化リストの与え方に注目すると、次のように行ごとに中括弧でグルー プ化した2重括弧になっています。 int mat[ROW][COL] = { {.....}, {.....}, {.....}, {.....} }; つまり、配列の配列となっています。ここでは4×5の配列を、要素数が5の配列の配列 として実現されています。  また初期化リストを与えた宣言で最初の添字サイズ(行のサイズROW)は省略できますが 残りの添字サイズ(列のサイズCOL)は省略できません。列のサイズは配列要素のサイズを 表すからです。コンパイラは列のサイズが分かれば2次元元配列の各要素のメモリ位置を決 めることができます。 【演習問題 2.1】配列 mat の宣言で後ろの添字サイズ COL を省略して、コンパイルす  ると構文エラーが起きることを確かめなさい。

2.1.3 アドレス演算子  配列がメモリ内でどのように配置されるかを見ます。変数やデータ型の記憶領域のバイ ト数は sizeof 演算子で知ることができます。 sizeof 単項式 sizeof( 型 ) は使用されるメモリ領域をバイト数で返します。  アドレスはメモリ内の位置(番地)をバイト単位で表したものです。アドレスを知るに はアドレス演算子&を使います。アドレス演算子は、アドレスを返す単項演算子で、変数 の前にアンパーサンド(&)を付けます。 int x = 6; cout << "xのアドレスは:" << &x << endl; は変数xの先頭位置のアドレスを画面に表示します。int型が4バイトとするとそのアド レスから4バイト分の領域に変数xの領域が配置されていることを意味します。
// matrix02.cpp
// 3×4行列のメモリー領域
#include <iostream.h>

int main()
{
    const int ROW = 3;  //行の次元
    const int COL = 4;  //列の次元

    int a[ROW][COL];

    cout << "int型の3×4行列" << endl;

    cout << "行列のサイズ: sizeof(a)      = " << sizeof(a) << endl
         << "列のサイズ:   sizeof(a[0])   = " << sizeof(a[0]) << endl
         << "要素のサイズ: sizeof(a[0][0])= " << sizeof(a[0][0])
         << endl << endl;

    cout << "行列の先頭アドレス: &a[0][0] = " << &a[0][0] << endl;
    cout << "(または (void *)a = " << (void *)a << " でもよい)" << endl;


    for(int i = 0; i < ROW; i++){
        cout << i+1 << " 行目のアドレス: &a[" << i << "]  = "
             << &a[i] << endl;
        for(int j = 0; j < COL; j++){
            cout << "                &a[" << i << "][" << j << "] = "
                 << &a[i][j] << endl;
        }
    }

    return 0;
}

ソースファイル:matrix02.cpp
[実行結果]
int型の3×4行列 
行列のサイズ: sizeof(a)      = 48 
列のサイズ:   sizeof(a[0])   = 16 
要素のサイズ: sizeof(a[0][0])= 4 

行列の先頭アドレス: &a[0][0] = 0x0063FDC8       
(または (void *)a = 0x0063FDC8 でもよい) 
1 行目のアドレス: &a[0]   = 0x0063FDC8 
                &a[0][0] = 0x0063FDC8 
                &a[0][1] = 0x0063FDCC 
                &a[0][2] = 0x0063FDD0 
                &a[0][3] = 0x0063FDD4 
2 行目のアドレス: &a[1]   = 0x0063FDD8 
                &a[1][0] = 0x0063FDD8 
                &a[1][1] = 0x0063FDDC 
                &a[1][2] = 0x0063FDE0 
                &a[1][3] = 0x0063FDE4 
3 行目のアドレス: &a[2]   = 0x0063FDE8 
                &a[2][0] = 0x0063FDE8 
                &a[2][1] = 0x0063FDEC 
                &a[2][2] = 0x0063FDF0 
                &a[2][3] = 0x0063FDF4 
アドレスは16進数で表示されています。アドレス値の先頭の 0x は、16進整数を意味 します。 10進数:0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16進数:0 1 2 3 4 5 6 7 8 9 A B C D E F 16進数でCは12を表します。この実行結果では、int型のサイズは4バイトになって います。実行結果を見ると、1行目から順に行単位で配置されています。行列要素の配置 は、先頭アドレス 0x0063FDC8 に要素a[0][0]があって要素のサイズは4バイトです。つ まりa[0][0]はアドレス 0x0063FDC8〜0x0063FDCB(アドレスの1桁目に注目:8〜11) の4バイトを占めます。次の要素a[0][1]の先頭アドレスは0x0063FDCCに位置し(つまり 配列の先頭から4バイト後ろの位置)、以下順に4バイトごとに連続に占められています。
行列要素のメモリ内での先頭位置  プログラムの中で行列aに添字を1つだけ付けた表現a[0],a[1],a[2]があります。これ らは各行の横の数の並びの1次元配列(つまり行ベクトル)を意味しています。つまり大 きさの関係は、 行列: a   ----> 2次元配列                 (横の並びの配列×行の数=16×3=48バイト) 行 : a[0],a[1],a[2]  ----> 1次元配列                 (横の並び=int型×列の数=4×4=16バイト) 要素:a[0][0],a[0][1],..----> 変数(int型=4バイト) で、 行列aの先頭アドレス = 第1行a[0]の先頭アドレス = 要素a[0][0]の(先頭)アドレス, 第2行a[1]の先頭アドレス = 要素a[1][0]の(先頭)アドレス, 第3行a[2]の先頭アドレス = 要素a[2][0]の(先頭)アドレス, となります。行列aは各行の配列を要素に持つ1次元配列であり、また各行はint型の1次 元配列というデータ構造になっています。数学のことばで言えば、1組の数を並べたものが ベクトル。行ベクトル(横ベクトル)を縦に並べたものが行列。つまり行列はベクトルのベ クトルです。 【演習問題2.2】この配列のメモリ内の位置の様子をできるだけ正確に図に描きなさい。

| 目次 | | 次のページ |

Copyright(c) 1999 Yamada,K