第33回
データ構造(12)~構造体の利用例と共用体

構造体の利用例

構造体の利用例を、いくつか紹介しておきます。特に、メンバに自分自身へのポインタを加えた自己参照構造体は、たくさんの複合型データをつないで操作できるため、利用範囲は多岐にわたります。

データベースの行を表現する

前回紹介したサンプルでは配列に保存した値を使いましたが、実務処理ではデータベースから読み出した複数のデータ(行、レコード)を構造体に格納して操作することが多いでしょう。データベースの行を構成する列の構造はそのまま構造体で表現できます。

たとえば、以下のような列構造の行を持つ表(テーブル)があるとします。
列名	型
----------------
商品ID	整数
品名	文字列
単価	通貨(整数)

このような構成のデータベースの表からデータを読み出したり、あるいはXMLファイルなどからデータを読み込んでそれをデータベースに書き込むような処理では、以下のような構造体を定義して一方向のリンク構造を作ると便利です。
typedef struct __item{
  unsigned long int id;
  char name[256 + 1];
  unsigned long int price;
  struct __item next;
} _Item;

これによって画面上でそれぞれのデータを順次確認したり、帳票として印刷したりできます。データを書き込む場合は、前回のサンプルでprintf関数によって画面に表示したのと同じようにwhileループを使い、先頭から順に書き込み処理を行います。

点と点を結ぶ

グラフィックスを使って画面上に図形を描く場合、座標を指定してそれらを順に直線でつないでいくことがあります。その場合には、各座標を以下のような構造体として管理します。
typedef struct __point{
  struct __point *prev;    /* 1つ前の点 */
  signed short int X;      /* X座標値 */
  signed short int Y;      /* Y座標値 */
  struct __point *next;    /* 次の点 */
} _Point;

このようにしておけばある座標を示す点からその前後へと自在に行き来できるため、点と点を結んで直線を描いたり、操作をやり直して元に戻したり(アンドゥ)──という処理が簡単に作れます。

構造体のネスト

構造体のメンバに、別の構造体を入れてネスト(入れ子)にすることもできます。

1つの点の座標は、以下のような構造体で表せます。

・1個の点を表す構造体
typedef struct {
  signed short int X;
  signed short int Y;
} _pos;

これを4個集めた構造体を定義すれば、矩形(四角形)が表現できます。

・4個の点で矩形を表す構造体
typedef struct {
  _pos topleft;        /* 左上角 */
  _pos topright;       /* 右上角 */
  _pos bottomleft;     /* 左下角 */
  _pos bottomright;    /* 右下角 */
} _rectangle;

値の整列を行う構造体~二分探索木

自己参照構造体では、自分自身へのポインタをメンバにしました。その場合、自分自身へのポインタは『1つ前と次』を示していましたが、それを『1つ前の値より大きいか小さいか』という基準で分けるようにすれば、ランダムに読み込んだ値を昇順や降順に並べ替えることができます。

typedef struct __item{
  unsigned long int id;      /* 商品ID */
  char name[256 + 1];        /* 商品名 */
  unsigned long int price;   /* 価格 */
  struct __item left;        /* priceの値が小さい場合は左へ */
  struct __item right;       /* priceの値が大きい場合は右へ */
} _Item;

値を比較する項目はいろいろ考えられますが、たとえば上のようにprice(価格)を基準に並べ替えるなら、
次に受け取ったデータのpriceの値が
現在の構造体のpriceより小さければleft(左)に
大きければright(右)に
……と構造体を振り分けていけば、二分探索木というデータ構造が作れます。

これを先頭から左に左に(leftの側に)操作していけば昇順に、右に右に(rightの側に)操作していけば降順に、構造体の値を並べることが可能です。

これは、Cのプログラミングとは少し方向が異なってくるため、詳しくはアルゴリズムをテーマとしたときに紹介します。

状態の保存と復元

プログラムの現在の状態を保存しておき、処理後に元に戻す──という目的で構造体を利用することもよくあります。

たとえば、Wimndowsアプリケーションで子ウィンドウのサイズや位置をユーザーが変更した場合に、それを既定のサイズに戻す──といった処理では、既定の位置とサイズを以下のような構造体に保存しておきます。
typedef struct {
  unsigned short int x;      /* 左上角点のx座標 */
  unsigned short int y;      /* 左上角点のy座標 */
  unsigned short int width;  /* 幅 */
  unsigned short int height; /* 高さ */
} _ChiledWindow;

こうしておいて、アプリケーションの開始時に既定のサイズを構造体の各メンバに代入しておき、元の位置とサイズに戻すときにそれらを参照します(リスト1)。ClientWidthとClientHeightは、システムから取得したアプリケーションのウィンドウ(クライアントウィンドウ)の幅と高さとします。SetWindowPosはWindowsに用意されているAPI関数で、ウィンドウハンドル(個々のウィンドウを特定するためのID)で示すウィンドウの位置とサイズを変更します。

リスト1:子ウィンドウの状態を保存して必要なときに復元する処理
#define ChildWidth  600  /* 子ウィンドウの幅 */
#define ChildHeight 400  /* 子ウィンドウの高さ */

void init_child _window(void);
void reset_child_window(void);

/* 構造体を生成 */
_ChildWindow cw;
(開始時)
        :
      (処理)
        :
(既定の位置とサイズに戻す)
  reset_child_window();

/* 既定の位置とサイズを構造体に保存 */
void init_child _window()
{
  cw.x = (ClientWidth - ChildWidth) / 2;
  cw.y = (ClientWidth - ChildHeight) / 2;
  cw.width = ChildWidth;
  cw.Height = ChildHeight;
}

void reset_child_window()
{
  SetWindowPos(
    hWnd = xxxx;    ---------------- ウィンドウハンドル
    hWndInstertAfter = xxxx; ------- 配置の順序を示すハンドル
    X = cw.x;
    Y = cw.y;
    cx = cw.width;
    cy = cw.height;
  );
}