8.構造体
「構造体」を用いると、幾つかの異なる型のデータをまとめて一つのデータ型として扱うことができます。
8.1 構造体
<例8−1>
#include <stdio.h>
struct student { /* 構造体studentの定義 */
int
no; /*
1つめのメンバ */
char
name[20]; /*
2つめのメンバ */
double
average; /*
3つめのメンバ */
};
int main(void) {
/*
構造体studentのオブジェクトseito1を宣言し、各メンバ変数を初期化 */
struct
student seito1={5, "SUZUKI", 64.8};
/*
構造体オブジェクトseito1の各メンバ変数の値を表示 */
printf("%d
%s %5.1f\n", seito1.no, seito1.name, seito1.average);
return
0;
}
この例では、学生の情報を格納するため、int型の学生番号と、char型配列の氏名と、double型の平均点をまとめて、studentという構造体を作っています。
mainでは、この構造体のオブジェクトseito1を宣言した上、初期化しています。構造体の個々の「メンバ」にはオブジェクト名とメンバ名の間にピリオド(.)をつけることでアクセスできます。
8.2 構造体の配列、構造体へのポインタ
当然、同じ構造体の複数のオブジェクトをまとめて扱う時には、構造体の配列を用います。
<例8−2>
#include <stdio.h>
#define N 3
/* 構造体data_recordを定義し、あらたにそれをRECORD型として定義 */
typedef struct data_record { /* double型データを格納するための構造体 */
int
id; /*
識別番号 */
int
length; /*
格納データの長さ */
double
data[10]; /*
データ格納用配列 */
} RECORD;
int main(void) {
/*
RECORD型構造体の配列を宣言し、各要素の各メンバを初期化 */
RECORD
tbl[N] = {{1, 5, {0.0, 1.1, 2.2, 3.3, 4.4}},
{2, 3, {12.3, 23.4, 34.5}},
{3, 2, {0.987, 0.654}}};
int
i, j;
/*
配列tblの各要素(FILE型構造体オブジェクト)のメンバ変数data(配列)の各要素(double型実数)の値を表示 */
for
(i = 0; i < N; i++) {
printf("record
#%d\n", tbl[i].id);
for
(j = 0; j < tbl[i].length; j++)
printf("%5.3f
", tbl[i].data[j]);
printf("\n");
}
return
0;
}
[解説] typedef
上の例では、データのID番号id、データの長さlengthと実際のデータ配列dataをメンバに持つ構造体を定義し、main内ではその構造体の配列を使っています。
通常の配列と同様に、配列要素にポインタを使ってアクセスすることもできます。
【課題8−1】(プログラムを提出)
<例8−2>を、構造体の配列tblの個々の要素に「構造体へのポインタ」(RECORD *tptr;)を使ってアクセスするように書き換える。
[解説] 構造体へのポインタの使い方
《プログラム例》
8.3 構造体オブジェクトを動的に生成する
<例8−3>
#include <stdio.h>
/* 構造体data_recordを定義し、あらたにそれをRECORD型として定義 */
typedef struct data_record { /* ある長さのデータを格納するための構造体 */
int
id; /*
識別番号 */
char
unit[10]; /*
格納データの単位(文字列) */
long
length; /*
格納データの長さ */
double
*data; /* データ格納用バッファ(へのポインタ)*/
} RECORD;
RECORD *create_record(void); /* 関数create_recordのプロトタイプ宣言 */
int main(void) {
RECORD
*tbl; /* RECORD型構造体へのポインタ */
/*
関数create_recordを呼び出し、返却値(新たに作られたFILE型構造体へのポインタ)をtblに格納 */
/*
返却値がNULLだったらエラーとして処理 */
if
((tbl=create_record())==NULL) {
printf("Failed
to create new record.\n");
return
1;
};
/*
create_recordで作られたRECORD型構造体の各メンバ変数の表示 */
printf("ID : %d\n", tbl->id);
printf("unit : %s\n", tbl->unit);
printf("length:
%d\n", tbl->length);
printf("address
of data: %p\n", tbl->data); /* データバッファのアドレスの表示 */
free(tbl->data); /* メンバ変数dataに割り当てられたメモリの解放 */
free(tbl); /*
tblが指す構造体に割り当てられたメモリの解放 */
return
0;
}
/* 関数create_recordの定義 */
/* RECORD型構造体オブジェクトを1つ動的に生成し、そのメンバ変数を設定 */
/* 生成した構造体オブジェクトを指すポインタを返す */
RECORD *create_record(void) {
RECORD
*rec; /* RECORD型構造体へのポインタ */
/*
1つのRECORD型構造体オブジェクトのためのメモリ領域を確保し、 */
/*
それを指すポインタをrecに格納 */
if
((rec=(RECORD *)malloc(sizeof(RECORD)))==NULL) {
return
NULL;
}
/*
キーボードからの入力で、生成した構造体オブジェクトの各メンバを初期化 */
printf("Input
data ID: ");
scanf("%d",
&(rec->id));
printf("Input
unit: ");
scanf("%9s",
rec->unit);
printf("Input
data length: ");
scanf("%d",
&(rec->length));
/*
lengthで指定された数のdouble型実数を格納するためのメモリ領域を確保 */
if
((rec->data=(double *)malloc(rec->length * sizeof(double)))==NULL) {
/*
メモリ確保に失敗したら、上で生成した構造体オブジェクトのメモリを解放するのを忘れずに */
free(rec);
return
NULL;
}
return
rec; /* 生成した構造体オブジェクトへのポインタを返す */
}
(実行結果例)
Input data ID: 1<enter>
Input unit: kg<enter>
Input data length: 100<enter>
ID : 1
unit : kg
length: 100
address of data: 0x2800400
この例では、関数create_recordの中で構造体オブジェクトのために必要なメモリを動的に確保し、そのアドレスを構造体へのポインタtblに格納しています。構造体へのポインタの使い方については【課題8−1】の[解説]を参照して下さい。
if ((rec=(RECORD
*)malloc(sizeof(RECORD)))==NULL)
この時点で各メンバに必要なメモリはすべて割り当てられています。ただし、データを格納するための領域は、そこを指すためのポインタ変数(data)が作られただけです。入力されたlengthの値に従って、必要なだけのメモリをmallocする必要があります。
if ((rec->data=(double
*)malloc(rec->length * sizeof(double)))==NULL)
create_record関数は、構造体オブジェクトの生成に成功したらそれを指すポインタを、失敗したらNULLを返します。main側ではそれをtblというポインタで受け取り、内容を表示しています。(ただしこの例では、dataの中身には何も入れられていませんので、とりあえずアドレスを表示しています)
上の例では、あえて構造体オブジェクトを動的に生成する意味もありませんが、多数のオブジェクトを扱うプログラムでは必要になってきます。
【課題8−2】(プログラムを提出)
<例8−3>を、「構造体へのポインタの配列」を用いて、複数のRECORD型オブジェクトを動的に作成するプログラムに書き換える。
【余力のある人は...】
同じことを、「構造体へのポインタへのポインタ」を使ってやってみる。
《プログラム例》