ce-8. 構造体,レコードデータ
ファイル
1
金子邦彦
C プログラミング応用)(全14回)
URL: https://www.kkaneko.jp/pro/c/index.html
C言語における構造体の定義と利用、バイナリファイル
形式でのデータ読み書き
学習内容の構成
1. 構造体の基礎:複数のデータを一つの型としてまと
める仕組み
2. バイナリファイル操作fread/fwriteによる構造体
データの読み書き
3. メモリ配置:構造体配列のメモリアドレスと実際の
配置の確認
4. 応用例Windowsビットマップファイルの読み出し
と画像処理
前提:C言語の基本文法、配列、ポインタの理解
意義:実用的なファイル処理とデータ構造の設計能
の習得
2
例題1.バイナリファイル形式のファイルか
らのデータ読み込み
次のような名簿ファイル(バイナリファイル形
式)を読み込んで,画面に表示するプログラムを
作る
3
名簿ファイル
name age address
Ken 20 NewYork
Bill 32 HongKong
Mike 35 Paris
4
#include "stdio.h"
#include <math.h>
#pragma warning(disable:4996)
struct Person {
char name[20];
int age;
char address[20];
};
int main()
{
const int max_lines = 100;
const char file_name[] = "z:¥¥PersonData.bin";
struct Person a[max_lines];
FILE *fp;
int n;
int i;
int ch;
// データファイル読み込み
fp = fopen( file_name, "r" );
if ( fp == NULL ) {
fprintf( stderr, "ファイル %s のオープンに失敗しました" );
return -1;
}
n = 0;
while( 1 ) {
if ( ( fread( &(a[n]), sizeof(struct Person), 1, fp ) == 0 )
|| ( n >= max_lines ) ) {
break;
}
n = n + 1;
}
fclose( fp );
// 画面表示
for( i=0; i<n; i++ ) {
printf( "name: %s, age: %d, address: %s¥n",
a[i].name, a[i].age, a[i].address );
}
printf( "Enter キーを1,2回押してください. プログラムを終了します¥n");
ch = getchar();
ch = getchar();
return 0;
}
この fread はバイナリファイル形式のファイルを読み出す
Person構造体を,
1度に1つ
5
ビルド後の画面
ビルドが正常終了したこと
を示すメッセージ
ビルドの手順:
「ビルド」「○○のビル
ド」
「1.正常終了」を
確認
6
実行中の画面
実行の手順:
「デバッグ」
「実行」
実行ウインドウが現れる
7
実行結果の例
例題1のプログラムが
行っていること
8
データファイル
(バイナリファイル形式)
プログラムが使う
メモリ空間
fread
読み出し
fread
3回実行
読み出しが終わったら
実行ウインドウに表示
例題1では
3人分のデータ
name ag
e
address
Ken 20 NewYork
Bill 32 HongKon
g
Mike 35 Paris
9
name ag
e
address
Ken 20 NewYork
Bill 32 HongKon
g
Mike 35 Paris
name, age, address の3つの「メンバ」から構成される
構造体 Person の配列 a
a[0]
a[1]
a[2]
1つの
ファイル
(132バイト)
10
実際のメモリの中身
元々のファイルサイズ: 132バイト
実メモリでのサイズ: 132バイト
name age address
Ken 20 NewYork
Bill 32 HongKong
Mike 35 Paris
バイナリファイル形式の性質
11
実際のメモリの中身
バイナリファイル形式の性質
a[0]
a[1]
a[2]
a[3]
a[4]
元々のファイルサイズ: 132バイト
実メモリでのサイズ: 132バイト
name age address
Ken 20 NewYork
Bill 32 HongKong
Mike 35 Paris
12
int main()
{
const int max_lines = 100;
const char file_name[] = "z:¥¥PersonData.bin";
struct Person a[max_lines];
FILE *fp;
int n;
int i;
int ch;
// データファイル読み込み
fp = fopen( file_name, "r" );
if ( fp == NULL ) {
fprintf( stderr, "ファイル %s のオープンに失敗しました" );
return -1;
}
n = 0;
while( 1 ) {
if ( ( fread( &(a[n]), sizeof(struct Person), 1, fp ) == 0 )
|| ( n >= max_lines ) ) {
break;
}
n = n + 1;
}
fclose( fp );
// 画面表示
for( i=0; i<n; i++ ) {
printf( "name: %s, age: %d, address: %s¥n",
a[i].name, a[i].age, a[i].address );
}
printf( "Enter キーを1,2回押してください. プログラムを終了します¥n");
ch = getchar();
ch = getchar();
return 0;
}
#include "stdio.h"
#include <math.h>
#pragma warning(disable:4996)
struct Person {
char name[20];
int age;
char address[20];
};
構造体 Person の定義
配列とメモリアドレス
13
配列 a
(サイズは100
a[0]
a[1]
a[2]
メモリアドレス
0012ED80
0012EDAC
0012EDD8
プログラムが使う
メモリ空間
構造体の配列
構造体(struct
データの集合を一つのデータ型として扱う仕組み。
新たな型名を付けて登録することができる。
構造体の要素をメンバと呼び、それぞれに型を指
定し、名前を付ける。
構造体のメンバとして他の構造体を含むことが許
される。特に、同じ構造体をメンバとするもの
(再帰的構造体)も許される。
14
構造体の例
15
name
age
address
例題1の
構造体
これで1つの
データ
演算子 . の意味
構造体のメンバを指定
例題1では
a[i].name
a[i].age
a[i].address
16
演算子 -> の意味
ポインタの指す構造体のメンバを指定
z->re_part = x;
z struct complex なる構造体を指すポインタで
あるとして、
そのメンバ re_part x の値を代入。
17
例題2.構造体データの
メモリ配置を見る
下記の構造体 ImaginaryNumber について,メモリ
アドレスを表示してみる
1つの構造体は 16 バイト
18
real_part imaginary_pa
rt
3.1 -2.4
4.5 5.1
-2.4 6.3
8バイト
8バイト
#include "stdio.h"
#include <math.h>
struct ImaginaryNumber {
double real_part;
double imaginary_part;
};
int main()
{
struct ImaginaryNumber a[] = {{3.1, -2.4},
{4.5, 5.1},
{-2.4, 6.3}};
int i;
int ch;
for (i=0; i<3; i++ ) {
printf( "複素数 %3.1f + %3.1f i¥n", a[i].real_part, a[i].imaginary_part );
}
for (i=0; i<3; i++ ) {
printf( "address(a[%d]) = %p¥n", i, &(a[i]) );
}
printf( "Enter キーを1,2回押してください. プログラムを終了します¥n");
ch = getchar();
ch = getchar();
return 0;
}
19
&」はメモリアドレス
の取得
%p」はメモリアドレス
の表示
%3.1f」は, 小数点以上は最大3桁,
小数点以下は最大1桁の表示
a[0] 3.1, -2.4
a[1] 4.5, 5.1
a[2] -2.4, 6.3 をセット
メモリアドレス表示
20
実行結果の例
表示された
メモリアドレス*
メモリアドレスの値が
ここでの「例」と違っている
ことはある(動作は正しい)
配列とメモリアドレス
21
配列 a
(サイズは3)
a[0]
a[1]
a[2]
メモリアドレス
0012FEA8
0012FEB8
0012FEC8
プログラムが使う
メモリ空間
構造体の配列
3.1 -2.4
4.5 5.1
-2.4 6.3
22
実際のメモリの中身
double 型の変数は
8 バイトになっている
メモリアドレスの値が
ここでの「例」と違っている
ことはある
#include "stdio.h"
#include <math.h>
struct ImaginaryNumber {
double real_part;
double imaginary_part;
};
int main()
{
struct ImaginaryNumber a[] = {{3.1, -2.4},
{4.5, 5.1},
{-2.4, 6.3}};
int i;
int ch;
for (i=0; i<3; i++ ) {
printf( "複素数 %3.1f + %3.1f i¥n", a[i].real_part, a[i].imaginary_part );
}
for (i=0; i<3; i++ ) {
printf( "address(a[%d]) = %p¥n", i, &(a[i]) );
}
printf( "Enter キーを1,2回押してください. プログラムを終了します¥n");
ch = getchar();
ch = getchar();
return 0;
}
23
構造体 ImaginaryNumber
の型宣言
構造体の配列 a
の宣言と初期化
構造体のメンバ
構造体の型宣言
構造体には,名前がある
それぞれのデータ(メンバという)は,名前と型
(データの種類のこと)がある.
24
struct Person {
char name[20];
int age;
char address[20];
};
名前
メンバ
参考.バイナリファイル形式の
ファイル読み出し,書き込み
最初にファイルの書き込みを行い,次に,書き込
んだファイルの読み出しを行うプログラム
練習用の見本として示す
25
最初は書き込み
26
データファイル
(バイナリファイル形式)
プログラムが使う
メモリ空間
fwrite
書き込み
fwrite
3回実行
3人分のデータを
書き込む
変数 orig
次は読み出し
27
データファイル
(バイナリファイル形式)
プログラムが使う
メモリ空間
fread
読み出し
例題1では
3人分のデータを書き込む
変数 a
28
#include "stdio.h"
#include "stdio.h"
#include <math.h>
#pragma warning(disable:4996)
struct Person {
char name[20];
int age;
char address[20];
};
int main()
{
const int max_lines = 100;
const char file_name[] = "z:¥¥PersonData.bin";
struct Person a[max_lines];
FILE *fp;
int n;
int i;
int ch;
struct Person orig[3] = { {"kaneko", 38, "hazozaki"}, {"ken",
20, "kaizuka"}, {"mike", 30, "tenjin" } };
printf( "書きます.ファイル名は %s¥n", file_name );
// データファイル書き込み
fp = fopen( file_name, "w" );
if ( fp == NULL ) {
fprintf( stderr, "ファイル %s のオープンに失敗しました" );
return -1;
}
// 書き出すのは3個(i = 0, 1, 2)
for ( i = 0; i < 3; i++ ) {
fwrite( &(orig[i]), sizeof(struct Person), 1, fp );
}
fclose( fp );
printf( "読みます.ファイル名は %s¥n", file_name );
// データファイル読み出し
fp = fopen( file_name, "r" );
if ( fp == NULL ) {
fprintf( stderr, "ファイル %s のオープンに失敗しました" );
return -1;
}
n = 0;
while( 1 ) {
if ( ( fread( &(a[n]), sizeof(struct Person), 1, fp ) == 0 )
|| ( n >= max_lines ) ) {
break;
}
n = n + 1;
}
fclose( fp );
// 画面表示
for( i=0; i<n; i++ ) {
printf( "name: %s, age: %d, address: %s¥n",
a[i].name, a[i].age, a[i].address );
}
printf( "Enter キーを1,2回押してください. プログラムを終了します¥n");
ch = getchar();
ch = getchar();
return 0;
}
最初は書き込み
次は読み出し
例題3.Windows ビットマップファイルの読
み出し,書き込み
24ビットカラーの Windows ビットマップファイ
ルについて,簡単な計算を行ってみる
29
30
実行結果の例
z:¥Mandrill.bmp
z:¥done.bmp
新しく生成されるファイル
画素の r, g, b
31
r'(x, y) = | r(x,y) - r(x-1, y) |
g'(x, y) = | g(x,y) - g(x-1, y) |
b'(x, y) = | b(x,y) - b(x-1, y) |
x > 0 に対して
r'(x, y) = 0
g'(x, y) = 0
b'(x, y) = 0
x = 0 に対して
横方向の差分
(差分の量が大きい
ほど,画素は「明るく」
なる)
例題3のプログラムが
行っていること
32
Windows ビットマップ
ファイル
プログラムが使う
メモリ空間
fread
3回実行
ファイルヘッダ
ヘッダ
本体
例題3のプログラムが
行っていること
33
Windows ビットマップ
ファイル
プログラムが使う
メモリ空間
ファイルヘッダ
ヘッダ
本体
計算
例題3のプログラムが
行っていること
34
Windows ビットマップ
ファイル
プログラムが使う
メモリ空間
ファイルヘッダ
ヘッダ
本体
画像本体の実メモリ上での配置
35
24ビットカラーの Windows ビットマップ
実メモリ上では,バイト列のデータ
画像本体の実メモリ上での配置
36
24ビットカラーの Windows ビットマップ
実メモリ上では,バイト列のデータ
画素 (x,y) のデータは,先頭から 3xy バイト目にある
画像本体の実メモリ上での配置
37
24ビットカラーの Windows ビットマップ
ファイルヘッダ
ヘッダ
typedef struct tagBITMAPFILEHEADER {
unsigned short bfType;
unsigned long bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned long bfOffBits;
} BITMAPFILEHEADER
typedef struct tagBITMAPINFOHEADER{
unsigned long biSize;
long biWidth;
long biHeight;
unsigned short biPlanes;
unsigned short biBitCount;
unsigned long biCompression;
unsigned long biSizeImage;
long biXPixPerMeter;
long biYPixPerMeter;
unsigned long biClrUsed;
unsigned long biClrImporant;
} BITMAPINFOHEADER;