Subsections

12章 構造体

12.1 学習のポイント

以下の事柄を理解すること.

  1. ★ p.272: 構造体の定義 (変数のグループ化) の仕方.
  2. ★ Table 12-1: メンバの参照の仕方. (.演算子)
  3. ★ List 12-4: 初期化.
  4. ★ p.280: 構造体の代入.
  5. ★ Table 12-2: 構造体へのポインタ変数が指す構造体のメンバの参照. (->演算子)
  6. ◎ p.281: 構造体を返す関数, 構造体を受け取る関数.
  7. ○ p.278: 構造体に対する typedef.
  8. △ p.282: 構造体の配列.
  9. △ p.286: メンバとしての構造体.

構造体の例

// 線分
struct SEGMENT {
  double x1, y1;  // 始点
  double x2, y2;  // 終点
  char color[10]; // 色
};

// 始点終点を配列で表現した線分
// 始点を(x[0], y[0]), 終点を(x[1], y[1])とする.
struct SEGMENT {
  double x[2];  // 始点
  double y[2];  // 終点
  char color[10]; // 色
};

// 円
struct CIRCLE {
  double x, y;    // 中心
  double r;       // 半径
  char color[10]; // 色
};

// 四角形
// 頂点は (x[0], y[0]), (x[1], y[1]), (x[2], y[2]), (x[3], y[3]) とする.
struct RECTANGLE {
  double x[4];
  double y[4];
  char color[10];
};

// 本
struct BOOK {
  char title[100];
  char author[50];
  char publisher[50]; // 出版社
  int year;           // 出版年
  int price;          // 価格
};

// 都市
struct CITY {
  char name[50];   // 名前
  double area;     // 面積
  int population;  // 人口
  double altitude; // 標高
};

// 部屋
struct ROOM {
  char building[20]; // 建物
  int number;        // 部屋番号
  int capacity;      // 収容人数
};

12.2 構造体の特徴

配列, 配列でない変数, 構造体を比較し, 整理して理解しておくこと.

12.3 変数の使い方

変数の使い方と比べながら, 構造体の使い方を覚えましょう.

program A (See program 1.)

#include <stdio.h>

// [A] double 型の定義は予めされている.

int main(void)
{
	double x; // [B] double 型の宣言 (記憶域上での領域の確保)

	x = 3; // [C] 代入

	printf("%f\n", x); // [D] 参照
	
	return 0;
}

program B (See (programs 6) and program 7.)

#include <stdio.h>

int main(void)
{
	double x;
	double *y; // ポインタ変数
	
	x = 3;

	y = &x; // xのアドレスを代入

	// [C] y が指すアドレスにある内容を「*」を用いて参照
	printf("%f\n", *y);
	
	return 0;
}

program C (See program 8.)

#include <stdio.h>


// double 型を受け取る関数
void double_print(double a)
{
	printf("%f\n", a);
}

int main(void)
{
	double x = 3;

	double_print(x);
	
	return 0;
}

program D (program 10.)

#include <stdio.h>

// double 型を返す関数
double double_func(void)
{
	double a;

	a = 3;

	return a;
}

int main(void)
{
	double x;

	x = double_func(); // 関数の戻り値をxに代入.

	printf("%f\n", x);
	
	return 0;
}

program E (See (program 13) and program 15.)

#include <stdio.h>


// double 型ポインタ変数 (double型の変数/定数のアドレス) を受け取る関数
void double_func(double *a)
{
	printf("(2) %f\n", *a); // 関数の中.
	*a = 30.0;              // main 中の x の領域に代入.
}

int main(void)
{
	double x = 3.0;

	printf("(1) %f\n", x); // 呼び出し前
	double_func(&x); // アドレスを渡す.
	printf("(3) %f\n", x); // 呼び出し後. 値が変わることに注意.
	
	return 0;
}

12.4 構造体の使い方

12.4.1 構造体を用いるとよい題材

例えば次のようなベクトルを扱うプログラムを考えてみます.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 最大の次元数.
// C99 が使える環境では, ここの#define の行は
// const int nmax = 10;
// のように定数にする方がよい.
#define NMAX 10
#define LABELMAX 100

int main(void)
{
	int i;
	
	int xn;
	double xval[NMAX];
	char xlabel[LABELMAX];

	int yn;
	double yval[NMAX];
	char ylabel[LABELMAX];

	xn = 3; // xの次元. xn は NMAX と同じかまたは小さいこと.
	for(i=0; i<xn; i++) 	// 成分の代入
		xval[i] = (double)i;
	strcpy(xlabel, "x"); // 文字列 xlabel に 文字列 "x" をコピー

	// y に x を代入.
	yn = xn;
	for(i=0; i<yn; i++)
		yval[i] = xval[i] + 10;
	strcpy(ylabel, "y");
	
	// y を表示
	printf("%s = ( ", ylabel);
	for(i=0; i<yn; i++)
		printf("%g ", yval[i]);
	printf(")\n");
	
	return EXIT_SUCCESS;
}

12.4.2 構造体の定義, メンバーの参照, メンバーへの代入

同じことを構造体を用いて表現してみる. 構造体を用いると変数のグループ化ができる. 構造体の定義および宣言にはstructを用いる. x のメンバーn.を用いて x.nと表す.

program 1

#include <stdio.h>
#include <string.h> // strcpyを用いるときインクルードする.

/*
  strcpy は 文字列をコピーする関数
  #include <string.h>
  char destination[100];
  char source[100] = "ABC";
  strcpy(destination, source);
  とすると source の文字列が destination にコピーされる.
*/
 
// [A] 構造体の定義
struct VECTOR {
	int dimension;
	double val[10];
	char label[100];
};

int main(void)
{
	struct VECTOR x; // [B] VECTOR 型の宣言 (記憶域上での領域の確保)

	// [C] 構造体 x の各メンバーへの代入
	x.dimension = 3;                // 整数の場合
	x.val[0] = 10.0;        // 配列の場合
	x.val[1] = 20.0;
	x.val[2] = 30.0;
	strcpy(x.label, "vector x");   // 文字列の場合.
	
	// [D] x  のメンバーの参照
	printf("%s\n", x.label);
	printf("%d\n", x.dimension);
	printf("%f\n", x.val[0]);
	printf("%f\n", x.val[1]);
	printf("%f\n", x.val[2]);
	
	return 0;
}

program 2

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

int main(void)
{
	int i;
	
	struct VECTOR x, y;

	// 構造体の各メンバーへの代入
	x.n = 3;
	strcpy(x.label, "x"); // strcpy は 文字列 x.label に 文字列"x"書き込む関数.
	for(i=0; i<x.n; i++)
		x.val[i] = (double)i;

	// y に x + 10 を代入.
	y.n = x.n;
	strcpy(y.label, "y");
	for(i=0; i<y.n; i++)
		y.val[i] = x.val[i] + 10;
	
	// y を表示
	printf("%s = ( ", y.label);
	for(i=0; i<y.n; i++)
		printf("%g ", y.val[i]);
	printf(")\n");
	
	return EXIT_SUCCESS;
}

12.4.3 初期化

program 3

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

int main(void)
{
	int i;
	// 初期化. メンバーが定義された順に行う.
	struct VECTOR x = {3, {100, 200, 300}, "x"};

	// x を表示
	printf("%s = ( ", x.label);
	for(i=0; i<x.n; i++)
		printf("%g ", x.val[i]);
	printf(")\n");
	
	return EXIT_SUCCESS;
}

program 4

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

int main(void)
{
	int i;
	// 初期化. メンバーが定義された順に行う.
	struct VECTOR x = {3, {100, 200, 300}, "x"};
	struct VECTOR y = {0, {0}, "y"};

	// y に x + 10 を代入.
	y.n = x.n;
	for(i=0; i<y.n; i++)
		y.val[i] = x.val[i] + 10;
	
	// y を表示
	printf("%s = ( ", y.label);
	for(i=0; i<y.n; i++)
		printf("%g ", y.val[i]);
	printf(")\n");
	
	return EXIT_SUCCESS;
}

12.4.4 構造体の代入

配列と異なり, メンバーに配列を含んでいても, 配列の各要素まで代入される.

program 5

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

int main(void)
{
	int i;
	struct VECTOR x = {3, {100, 200, 300}, "x"};
	struct VECTOR y;

	// y に x を代入.
	y = x;
	
	// y を表示
	printf("%s = ( ", y.label);
	for(i=0; i<y.n; i++)
		printf("%g ", y.val[i]);
	printf(")\n");
	
	return EXIT_SUCCESS;
}

12.4.5 ポインタ変数

変数名の前に * をつけることでポインタ変数とすることができる. *.と比べて優先順位が低いので (*y).n のように ( )を付ける. ( ) を付けない場合は, *(y.n)と解釈される.

program 6

#include <stdio.h>
#include <string.h>

struct VECTOR {
	char label[100];
	int dimension;
	double val[10];
};

/*
  ここでは, 各メンバーに代入したが, 構造体は
  struct VECTOR x = {"vector x", 3, {100.0, 200.0, 300.0}};
  のように初期化できる.
*/

int main(void)
{
	struct VECTOR x;
	struct VECTOR *y; // [A] ポインタ変数

	strcpy(x.label, "vector x");
	x.dimension = 3;
	x.val[0] = 100.0;
	x.val[1] = 200.0;
	x.val[2] = 300.0;
	
	y = &x; // [B] xのアドレスを代入
	
	// [C] y が指すアドレスにある内容を「*」を用いて参照
	printf("%s\n", (*y).label); // *は優先順位が低いので()が必要
	printf("%d\n", (*y).dimension);
	printf("%g\n", (*y).val[0]);
	printf("%g\n", (*y).val[1]);
	printf("%g\n", (*y).val[2]);
	
	return 0;
}

12.4.6 ポインタ変数: -> 演算子

(*y).n と書くのは面倒なので y->nという別の表記方法が用意されている.

program 7

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

int main(void)
{
	int i;
	struct VECTOR x = {3, {100, 200, 300}, "x"};
	struct VECTOR *y; // ポインタ変数

	y = &x; // xのアドレスを代入
	
	// y が指すアドレスにある内容を参照
	printf("%s = ( ", y->label); // *は優先順位が低いので()が必要
	for(i=0; i < y->n ; i++)
	  printf("%g ", y->val[i]);
	printf(")\n");
	
	return EXIT_SUCCESS;
}

12.4.7 構造体を受け取る関数

program 8

#include <stdio.h>

struct VECTOR {
	int dimension;
	double val[10];
	char label[100];
};

// 構造体を受け取る関数.
void vector_print(struct VECTOR a)
{
	printf("%s\n", a.label);
	printf("%d\n", a.dimension);
	printf("%f\n", a.val[0]);
	printf("%f\n", a.val[1]);
	printf("%f\n", a.val[2]);
}

int main(void)
{
	struct VECTOR x = {3, {100, 200, 300}, "x"};

	vector_print(x);
	
	return 0;
}

program 9

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

// 構造体を受け取る関数.
void vector_print(struct VECTOR a)
{
	int i;

	printf("%s = ( ", a.label);
	for(i=0; i<a.n; i++)
		printf("%g ", a.val[i]);
	printf(")\n");
}

int main(void)
{
	struct VECTOR x = {3, {100, 200, 300}, "x"};
	struct VECTOR y = {0, {0}, "y"};

	y = x; 	// y に x を代入.
	
	vector_print(y); 	// y を表示
	
	return EXIT_SUCCESS;
}

12.4.8 構造体を返す関数

program 10

#include <stdio.h>
#include <string.h>

struct VECTOR {
	int n;
	double val[10];
	char label[100];
};

// VECTOR型を返す関数.
// 構造体を用いることで, 構造体のメンバー全ての値(複数の値)を返すことができる.
struct VECTOR vector_func(void)
{
	struct VECTOR a;

	strcpy(a.lable, "vector a");
	a.dimension = 3;
	a.val[0] = 100.0;
	a.val[1] = 200.0;
	a.val[2] = 300.0;
	
	return a;
}

int main(void)
{
	struct VECTOR x;

	x = vector_func(); // 関数の戻り値をxに代入.

	printf("%s\n", x.label);
	printf("%d\n", x.dimension);
	printf("%f\n", x.val[0]);
	printf("%f\n", x.val[1]);
	printf("%f\n", x.val[2]);

	return 0;
}

program 11

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

// 構造体を受け取る関数.
void vector_print(struct VECTOR a)
{
	int i;

	printf("%s = ( ", a.label);
	for(i=0; i<a.n; i++)
		printf("%g ", a.val[i]);
	printf(")\n");
}

// 構造体を返す関数.
struct VECTOR vector_add(struct VECTOR a, struct VECTOR b)
{
	int i;
	struct VECTOR tmp;

	// sprintf は printf の結果を文字列 tmp.label に書き込む関数.
	sprintf(tmp.label, "%s + %s", a.label, b.label);
	if ( a.n < b.n )
		tmp.n = a.n;
	else
		tmp.n = b.n;
	for(i=0; i< a.n; i++)
		tmp.val[i] = a.val[i] + b.val[i];
	return tmp; // 構造体を用いることで複数の値を返すことができる.
}

int main(void)
{
	struct VECTOR c10 = {3, {10, 10, 10}, "10"};
	struct VECTOR x = {3, {100, 200, 300}, "x"};
	struct VECTOR y = {0, {0}, "y"};

	y = vector_add(x, c10); // y = x + 10
	
	vector_print(y); 	// y を表示
	
	return EXIT_SUCCESS;
}

呼び出した関数内で引数のメンバーの値を変更しても, 呼び出し側の構造体のメンバーの値は変更されていないことに注意.

program 12

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

void vector_func(struct VECTOR v)
{
	int i;

	// 関数内で引数のメンバーの値を変更.
	for(i=0; i<v.n; i++)
		v.val[i] += 1000.0;
}

void vector_print(struct VECTOR a)
{
	int i;

	printf("%s = ( ", a.label);
	for(i=0; i<a.n; i++)
		printf("%g ", a.val[i]);
	printf(")\n");
}


int main(void)
{
	struct VECTOR x = {3, {100.0, 200.0, 300.0}, "x"};

	vector_print(x);
	vector_func(x);
	vector_print(x); 	// x のメンバーの値は変更されていないことに注意.
	
	return EXIT_SUCCESS;
}

12.4.9 構造体のアドレスを受け取る関数

呼び出した関数内でポインター変数が指す構造体メンバーの値を変更すると, 呼び出し側の構造体のメンバーの値が変更されることに注意. ポインタ変数が指す構造体のメンバーの値は (*v).n のように参照する. *演算子の優先順位が低いため () が必要.

program 13

#include <stdio.h>
#include <string.h>

struct VECTOR {
	int dimension;
	double val[10];
	char label[100];
};


// VECTOR型ポインタ変数 (VECTOR型の変数/定数のアドレス) を受け取る関数
void vector_func(struct VECTOR *a)
{
	// 関数の中
	printf("(2) %s\n", (*a).label);
	printf("(2) %d\n", (*a).dimension);
	printf("(2) %f\n", (*a).val[0]);
	printf("(2) %f\n", (*a).val[1]);
	printf("(2) %f\n", (*a).val[2]);

	// main 中のxに代入.
	strcpy((*a).label, "vector a");
	(*a).dimension = 3;
	(*a).val[0] = 1000.0;
	(*a).val[1] = 2000.0;
	(*a).val[2] = 3000.0;
}

int main(void)
{
	struct VECTOR x = {3, {100.0, 200.0, 300.0}, "x"};

	// 呼び出し前
	printf("(1) %s\n", x.label);
	printf("(1) %d\n", x.dimension);
	printf("(1) %f\n", x.val[0]);
	printf("(1) %f\n", x.val[1]);
	printf("(1) %f\n", x.val[2]);
		
	vector_func(&x);  // アドレスを渡す.

	// 呼び出し後
	printf("(3) %s\n", x.label);
	printf("(3) %d\n", x.dimension);
	printf("(3) %f\n", x.val[0]);
	printf("(3) %f\n", x.val[1]);
	printf("(3) %f\n", x.val[2]);
	
	return 0;
}

program 14

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

// アドレスを受け取る関数.
// v はポインタ変数.
void vector_func(struct VECTOR *v)
{
	int i;

	// 関数内で引数のメンバーの値を変更.
	for ( i = 0; i < (*v).n; i++ )
		(*v).val[i] += 1000.0;
}

void vector_print(struct VECTOR *a)
{
	int i;

	printf("%s = ( ", (*a).label);
	for(i=0; i<(*a).n; i++)
		printf("%g ", (*a).val[i]);
	printf(")\n");
}

int main(void)
{
	struct VECTOR x = {3, {100.0, 200.0, 300.0}, "x"};

	vector_print(&x);
	vector_func(&x);  // アドレスを渡す.
	vector_print(&x); // x のメンバーの値が変更されていることに注意.
	
	return EXIT_SUCCESS;
}

(*v).n と表記する代わりに 矢印(->)を用いてv->nと表記するのが普通の書き方.

program 15

#include <stdio.h>
#include <string.h>

struct VECTOR {
	int dimension;
	double val[10];
	char label[100];
};


// VECTOR型ポインタ変数 (VECTOR型の変数/定数のアドレス) を受け取る関数
void vector_func(struct VECTOR *a)
{
	// 関数の中
	printf("(2) %s\n", a->label);
	printf("(2) %d\n", a->dimension);
	printf("(2) %f\n", a->val[0]);
	printf("(2) %f\n", a->val[1]);
	printf("(2) %f\n", a->val[2]);

	// main 中のxに代入.
	strcpy(a->label, "vector a");
	a->dimension = 3;
	a->val[0] = 1000.0;
	a->val[1] = 2000.0;
	a->val[2] = 3000.0;
}

int main(void)
{
	struct VECTOR x = {3, {100.0, 200.0, 300.0}, "x"};

	// 呼び出し前
	printf("(1) %s\n", x.label);
	printf("(1) %d\n", x.dimension);
	printf("(1) %f\n", x.val[0]);
	printf("(1) %f\n", x.val[1]);
	printf("(1) %f\n", x.val[2]);
		
	vector_func(&x);  // アドレスを渡す.

	// 呼び出し後
	printf("(3) %s\n", x.label);
	printf("(3) %d\n", x.dimension);
	printf("(3) %f\n", x.val[0]);
	printf("(3) %f\n", x.val[1]);
	printf("(3) %f\n", x.val[2]);
	
	return 0;
}

program 16

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

void vector_func(struct VECTOR *v)
{
	int i;

	for ( i = 0; i < v->n; i++ )
		v->val[i] += 1000.0;
}

void vector_print(struct VECTOR *a)
{
	int i;

	printf("%s = ( ", a->label);
	for ( i = 0; i < a->n; i++ )
		printf("%g ", a->val[i]);
	printf(")\n");
}

int main(void)
{
	struct VECTOR x = {3, {100.0, 200.0, 300.0}, "x"};

	vector_print(&x);
	vector_func(&x);
	vector_print(&x);
	
	return EXIT_SUCCESS;
}

練習として, 前述のベクトルの和を計算する関数vector_addを, アドレスを受け取る関数に書き換えると次のようになる.

program 17

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

// v = a + b  を計算する関数.
void vector_add(struct VECTOR *v, struct VECTOR *a, struct VECTOR *b)
{
	
	int i;
	sprintf(v->label, "%s + %s", a->label, b->label);
	if(a->n < b->n)
		v->n = a->n;
	else
		v->n = b->n;

	for ( i = 0; i < v->n; i++ )
		v->val[i] = a->val[i] + b->val[i];
}

void vector_print(struct VECTOR *a)
{
	int i;

	printf("%s = ( ", a->label);
	for ( i = 0; i < a->n; i++ )
		printf("%g ", a->val[i]);
	printf(")\n");
}

int main(void)
{
	struct VECTOR c10 = {3, { 10.0,  10.0,  10.0}, "10"};
	struct VECTOR x   = {3, {100.0, 200.0, 300.0}, "x" };
	struct VECTOR y   = {3, {0.0}                , "y" };

	vector_print(&y);
	vector_add(&y, &x, &c10);
	vector_print(&y);
	
	return EXIT_SUCCESS;
}

12.4.10 構造体のアドレスを返す関数

関数が計算した結果を返すと便利なことが多い. 次にあげるプログラムのように, 1度だけ構造体の領域の確保をしておく. それ以外の部分では全てアドレスにより参照することで演算する. これにより, サイズの大きなデータを扱うときに記憶域の使用領域を最小限にでき, 実行速度も速くなる.

program 18

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

// 構造体のアドレスを返す関数
struct VECTOR *vector_add(struct VECTOR *v, struct VECTOR *a, struct VECTOR *b)
{
	
	int i;
	sprintf(v->label, "%s + %s", a->label, b->label);
	if(a->n < b->n)
		v->n = a->n;
	else
		v->n = b->n;

	for ( i = 0; i < v->n; i++ )
		v->val[i] = a->val[i] + b->val[i];

	return v;
}

void vector_print(struct VECTOR *a)
{
	int i;

	printf("%s = ( ", a->label);
	for ( i = 0; i < a->n; i++ )
		printf("%g ", a->val[i]);
	printf(")\n");
}

int main(void)
{
	struct VECTOR c10 = {3, { 10.0,  10.0,  10.0}, "10"}; // 領域確保
	struct VECTOR x   = {3, {100.0, 200.0, 300.0}, "x" }; // 領域確保
	struct VECTOR y   = {3, {0.0}                , "y" }; // 領域確保
	struct VECTOR *z; // ポインター変数

	z = &y;
	vector_print(z);
	z = vector_add(&y, &x, &c10);
	vector_print(z);
	
	return EXIT_SUCCESS;
}

再度先に挙げたアドレスによる参照をを行わないプログラムについて, 領域確保の視点から, コメントを入れておくので両者を比較してみるとよい.

program 19

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

// 呼び出し時に
// a の領域確保
// a = y の代入が起きる.
void vector_print(struct VECTOR a)
{
	int i;

	printf("%s = ( ", a.label);
	for(i=0; i<a.n; i++)
		printf("%g ", a.val[i]);
	printf(")\n");
}

// 呼び出し時に
// a の領域確保, b の領域確保
// a = x の代入, b = c10 の代入が起きる.
struct VECTOR vector_add(struct VECTOR a, struct VECTOR b)
{
	int i;
	struct VECTOR tmp; // 領域確保

	// sprintf は printf の結果を文字列 tmp.label に書き込む関数.
	sprintf(tmp.label, "%s + %s", a.label, b.label);
	if ( a.n < b.n )
		tmp.n = a.n;
	else
		tmp.n = b.n;
	for(i=0; i< a.n; i++)
		tmp.val[i] = a.val[i] + b.val[i];
	return tmp; // アドレスではなく 複数のメンバーの値が含まれる tmp そのものを返す.
}

int main(void)
{
	struct VECTOR c10 = {3, { 10.0,  10.0,  10.0}, "10"};
	struct VECTOR x   = {3, {100.0, 200.0, 300.0}, "x" };
	struct VECTOR y   = {0, {0.0},                 "y" };

	y = vector_add(x, c10); // y = tmp の代入が起きる.
	
	vector_print(y);
	
	return EXIT_SUCCESS;
}

12.5 実用上の使い方

実用上は, 構造体の実体を初出のブロック内で宣言(確保)し, それをポインタで受け渡して操作する.

program 20

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

// 構造体へのポインタ(構造体のアドレス)を返す関数
struct VECTOR *vector_add(struct VECTOR *v, struct VECTOR *a, struct VECTOR *b)
{
	
	int i;
	sprintf(v->label, "(%s + %s)", a->label, b->label);
	if(a->n < b->n)
		v->n = a->n;
	else
		v->n = b->n;

	for ( i = 0; i < v->n; i++ )
		v->val[i] = a->val[i] + b->val[i];

	return v; // ここで結果を代入した構造体のアドレスを返しておくと便利.
}

// ベクトルa, bで表される2点間の距離を求める関数.
double vector_length(struct VECTOR *a, struct VECTOR *b)
{
  int i;
  double sum = 0.0;

  if ( a->n != b->n ) {
    printf("type mismatch\n");
    exit(0);
  }

  for(i=0; i < a->n; i++)
    sum += (a->val[i] - b->val[i]) * (a->val[i] - b->val[i]);
  return sqrt(sum);
}

void vector_print(struct VECTOR *a)
{
	int i;

	printf("%s = ( ", a->label);
	for ( i = 0; i < a->n; i++ )
		printf("%g ", a->val[i]);
	printf(")\n");
}

int main(void)
{
	struct VECTOR c10 = {3, { 10.0,  10.0,  10.0}, "10"};
	struct VECTOR x   = {3, {100.0, 200.0, 300.0}, "x" };
	//結果を代入するための領域は呼出側で用意する.
	struct VECTOR y   = {3, {0.0}                , "y" };

	vector_print(&y);
	vector_add(&y, &x, &c10); // 呼出側で結果を代入する領域yを用意して渡す.
	vector_print(&y);
	
	// vector_add が結果を代入した構造体のアドレスを返すようにしてある.
	// このようにしておくと, その結果を更に別の関数に渡して使える.
	vector_print(vector_add(&y, &x, &c10));

	return EXIT_SUCCESS;
}

プログラムを改変していくに従って, 関数に渡す引数の数が変わっていく場合や, 関数から複数の値を返したい場合で, あまり大きくない構造体の場合は, 値を渡したり返したりしてもいいかもしれない.

アドレスでなく値を用いるこの書き方の方は, double型変数などと同じ表記をすればよいのでわかりやすい. ただし, 途中経過を記憶する領域を必要とする点, 常に全ての要素の代入が起き, 時間がかかる点で劣る.

program 21

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define NMAX 10
#define LABELMAX 100

struct VECTOR {
	int n;
	double val[NMAX];
	char label[LABELMAX];
};

// 構造体(の値)を受け取り, 構造体(の値)を返す関数
// 関数内で宣言された構造体の変数v は, 通常の変数と同じ 寿命を持ち,
// 返り値としての扱いも同じ.
struct VECTOR vector_add(struct VECTOR a, struct VECTOR b)
{
  struct VECTOR v; //関数内で宣言された変数の寿命は関数が終了するまで.
  int i;
  sprintf(v.label, "(%s + %s)", a.label, b.label);
  if(a.n < b.n)
    v.n = a.n;
  else
    v.n = b.n;

  for ( i = 0; i < v.n; i++ )
    v.val[i] = a.val[i] + b.val[i];

  return v; // 構造体の値を返すことができる.
}

// ベクトルa, bで表される2点間の距離を求める関数.
double vector_length(struct VECTOR a, struct VECTOR b)
{
  int i;
  double sum = 0.0;
  double length;//関数内で宣言された変数の寿命は関数が終了するまで.

  if ( a.n != b.n ) {
    printf("type mismatch\n");
    exit(0);
  }
  
  for(i=0; i < a.n; i++)
    sum += (a.val[i] - b.val[i]) * (a.val[i] - b.val[i]);
  length = sum;

  return length; // double 型変数の値を返すことができる.
}

void vector_print(struct VECTOR a)
{
  int i;

  printf("%s = ( ", a.label);
  for ( i = 0; i < a.n; i++ )
    printf("%g ", a.val[i]);
  printf(")\n");
}

int main(void)
{
  struct VECTOR c10 = {3, { 10.0,  10.0,  10.0}, "10"};
  struct VECTOR x   = {3, {100.0, 200.0, 300.0}, "x" };
  //結果を代入するための領域は呼出側で用意する.
  struct VECTOR y   = {3, {0.0}                , "y" };

  vector_print(y);
  y = vector_add(x, c10); // 呼出側で結果を代入する領域yを用意して代入する.
  vector_print(y);
	
  // vector_add が結果を代入した構造体(の値)を返すようにしてある.
  // このようにしておくと, その結果を更に別の関数に渡して使える.
  vector_print(vector_add(x, c10));

  return EXIT_SUCCESS;
}

12.6 自由課題: 構造体を用いた例: Scalable Vector Graphics

12.6.1 SVGファイル

SVGは Webなどで用いることができる Scalable Vector Grapohics である. jpg, bmp などの画像と異なり, どんなに拡大してもギザギザにならない.

SVGファイルは, 例えば下のような内容を持っている. この内容をを emacs, gedit, メモ帳などのエディタに写し, aaa.svg のように 拡張子として.svgがつく ファイル名で保存してしてみよう.

<?xml version='1.0' encoding='UTF-8' standalone='no'?>
<svg
   xmlns:svg='http://www.w3.org/2000/svg'
   xmlns='http://www.w3.org/2000/svg'
   version='1.0'
   width='700'
   height='1000'>
  <g>
    <path
       d='M 100.0,200.0 L 500.0,500.0'
       style='stroke:#ff0000;stroke-width:10px' />
  </g>
</svg>

下に示すように, このファイルは Firefox などのWebブラウザで「ファイルを開い」たり, Webブラウザにドラッグしたりすることで, 表示できる.

\includegraphics[width=10cm]{svg/src/svg_hello.eps}

再度, エディタなどでファイルを変更し保存した後は, ブラウザの再読み込みで新しい内容を表示し直すことができるブラウザもある.

SVGファイルの構造は次のようになっている.

12.6.2 SVGファイルをC言語を用いて作成する.

上のSVGファイルを出力するするプログラムは以下のようになる.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    printf("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    printf("<svg\n");
    printf("   xmlns:svg='http://www.w3.org/2000/svg'\n");
    printf("   xmlns='http://www.w3.org/2000/svg'\n");
    printf("   version='1.0'\n");
    printf("   width='700'\n");
    printf("   height='1000'>\n");
    printf("  <g>\n");
    printf("    <path\n");
    printf("       d='M 100.0,200.0 L 500.0,500.0'\n");
    printf("       style='stroke:#ff0000;stroke-width:10px' />\n");
    printf("  </g>\n");
    printf("</svg>\n");
	 
    return EXIT_SUCCESS;
}

この結果をファイルに保存するためには, リダイレクトを用いる. そのためには, 後述するように端末の中でプログラムを実行する.

$ ./a.out > bbb.svg

12.6.3 端末の中でコマンドを実行する.

  1. Windowsでは「スタート」$\Rightarrow$ 「アクセサリ」から「コマンドプロンプト」を開く. Linuxでは, GNOME端末, kterm, xterm などを開く.
  2. 実行ファイルのあるディレクトリ(フォルダ)に移動する.

  3. 目的のファイルが存在するフォルダまで cd を繰り返しディレクトリを変更する.

  4. 目的とする実行ファイルがあるディレクトリに移動した後に, 以下のようにしてプログラムを実行する. Windowsの場合
    $ abc.exe
    
    UNIX の場合
    $ ./a.out
    

    出力結果をファイルに保存する場合は,

    Windowsの場合

    $ abc.exe > xyz.svg
    
    UNIX の場合
    $ ./a.out > xyz.svg
    

    出力先のファイルが既に存在する場合は, 上書きされてしまうので十分注意すること.

12.6.4 汎用性の高いCプログラムにする.

変化させたい部分を変数にすると以下のようになる.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    double width = 500.0;          // 画面の幅
    double height = 1000.0;        // 画面の高さ
    double x0 = 100.0, y0 = 200.0; // 直線の始点の座標
    double x1 = 500.0, y1 = 500.0; // 直線の終点の座標
    unsigned stroke = 0x0fff00;    // 直線の色
    unsigned stroke_width = 10;    // 直線の太さ
	 
    printf("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    printf("<svg\n");
    printf("   xmlns:svg='http://www.w3.org/2000/svg'\n");
    printf("   xmlns='http://www.w3.org/2000/svg'\n");
    printf("   version='1.0'\n");
    printf("   width='%g'\n", width);
    printf("   height='%g'>\n", height);
    printf("  <g>\n");
    printf("    <path\n");
    printf("       d='M %g,%g L %g,%g'\n", x0, y0, x1, y1);
    printf("       style='stroke:#%06x;stroke-width:%upx' />\n", stroke, stroke_width);
    printf("  </g>\n");
    printf("</svg>\n");
	 
    return EXIT_SUCCESS;
}

関数化すると見通しがよくなる. 一度開始と終了のための関数を作っておけば, 以降はmain関数の中でこれらを呼ぶだけで済む.

#include <stdio.h>
#include <stdlib.h>

void svg_begin(double w, double h)
{
    printf("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    printf("<svg\n");
    printf("   xmlns:svg='http://www.w3.org/2000/svg'\n");
    printf("   xmlns='http://www.w3.org/2000/svg'\n");
    printf("   version='1.0'\n");
    printf("   width='%g'\n", w);
    printf("   height='%g'>\n", h);
    printf("  <g>\n");

}

void line_print(double p0, double q0, double p1, double q1,
    unsigned s, unsigned w)
{
    printf("    <path\n");
    printf("       d='M %g,%g L %g,%g'\n", p0, q0, p1, q1);
    printf("       style='stroke:#%06x;stroke-width:%upx' />\n", s, w);
}

void svg_end(void)
{
    printf(" </g>\n"); // group 終了
    printf("</svg>\n"); // svg 終了
}

int main(void)
{
    double width = 500.0;          // 画面の幅
    double height = 1000.0;        // 画面の高さ
    double x0 = 100.0, y0 = 500.0; // 直線の始点の座標
    double x1 = 500.0, y1 = 200.0; // 直線の終点の座標
    unsigned stroke = 0x0fff00;    // 直線の色
    unsigned stroke_width = 25;    // 直線の太さ
    
    svg_begin(width, height);      // SVG 開始
    line_print(x0, y0, x1, y1, stroke, stroke_width); // 色々な描画をここに記述.
    svg_end();                     // SVG 終了
    return EXIT_SUCCESS;
}

直線を描画する関数にいろいろな値を入れて呼び出すと, その数だけ直線が描ける. main の部分だけを変えれば自由に描画ができる. line_print関数を呼び出す実引数の部分を変更し, 色々な直線を描いてみましょう.

#include <stdio.h>
#include <stdlib.h>

void svg_begin(double w, double h)
{
    printf("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    printf("<svg\n");
    printf("   xmlns:svg='http://www.w3.org/2000/svg'\n");
    printf("   xmlns='http://www.w3.org/2000/svg'\n");
    printf("   version='1.0'\n");
    printf("   width='%g'\n", w);
    printf("   height='%g'>\n", h);
    printf("  <g>\n");

}

void line_print(double p0, double q0, double p1, double q1,
    unsigned s, double w)
{
    printf("    <path\n");
    printf("       d=\"M %g,%g L %g,%g\"\n", p0, q0, p1, q1);
    printf("       style='stroke:#%06x;stroke-width:%gpx' />\n",	s, w);
}

void svg_end(void)
{
    printf(" </g>\n"); // group 終了
    printf("</svg>\n"); // svg 終了
}

int main(void)
{
    svg_begin(500, 500);                  // SVG 開始
    line_print(100.0, 500.0, 50.0, 300, 0xff0000, 2.0);
    line_print(200.0, 400.0, 60, 400, 0x00ff00, 5.0);
    line_print(300.0, 300.0, 70.0, 500, 0x0000ff, 10.0);
    svg_end();                                 // SVG 終了
    return EXIT_SUCCESS;
}

12.6.5 構造体を用いるとより扱い易くなる.

SVGに関係した部分, 描画部品に関係した部分を struct SVG, struct LINEという構造体でまとめると 以下のようになる. 変数のグループ化ができている点に注意すること.
#include <stdio.h>
#include <stdlib.h>

struct SVG {
    double width;  // 画面の幅
    double height; // 画面の高さ
};

struct LINE {
    double x0; // 始点のx座標
    double y0; // 始点のy座標
    double x1; // 終点のx座標
    double y1; // 終点のy座標
    unsigned int stroke; // 線の色 0xrrggbb
    double stroke_width;
};

void svg_begin(struct SVG s)
{
    printf("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    printf("<svg\n");
    printf("   xmlns:svg='http://www.w3.org/2000/svg'\n");
    printf("   xmlns='http://www.w3.org/2000/svg'\n");
    printf("   version='1.0'\n");
    printf("   width='%g'\n", s.width);
    printf("   height='%g'>\n", s.height);
    printf("  <g>\n");
}

void line_print(struct LINE p)
{
    printf("    <path\n");
    printf("       d='M %g,%g L %g,%g'\n", p.x0, p.y0, p.x1, p.y1);
    printf("       style='stroke:#%06x;stroke-width:%gpx' />\n",
	p.stroke, p.stroke_width);
}

void svg_end(void)
{
    printf(" </g>\n"); // group 終了
    printf("</svg>\n"); // svg 終了
}

int main(void)
{
    struct SVG svg;
    struct LINE line;
	 
    svg.width = 500.0;   // 構造体メンバへの代入.
    svg.height = 1000.0;

    line.x0 = 10.0;
    line.y0 = 100.0;
    line.x1 = 300.0;
    line.y1 = 700.0;
    line.stroke = 0xff00ff;
    line.stroke_width = 4.0;
	 
    svg_begin(svg);    // 構造体を関数に渡す.
    line_print(line);
    svg_end();         // SVG 終了
    return EXIT_SUCCESS;
}

構造体の初期化. 上のプログラムと比べmain関数の部分のみを変更.

#include <stdio.h>
#include <stdlib.h>

struct SVG {
    double width;  // 画面の幅
    double height; // 画面の高さ
};

struct LINE {
    double x0; // 始点のx座標
    double y0; // 始点のy座標
    double x1; // 終点のx座標
    double y1; // 終点のy座標
    unsigned int stroke; // 線の色 0xrrggbb
    double stroke_width;
};

void svg_begin(struct SVG s)
{
    printf("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    printf("<svg\n");
    printf("   xmlns:svg='http://www.w3.org/2000/svg'\n");
    printf("   xmlns='http://www.w3.org/2000/svg'\n");
    printf("   version='1.0'\n");
    printf("   width='%g'\n", s.width);
    printf("   height='%g'>\n", s.height);
    printf("  <g>\n");
}

void line_print(struct LINE p)
{
    printf("    <path\n");
    printf("       d='M %g,%g L %g,%g'\n", p.x0, p.y0, p.x1, p.y1);
    printf("       style='stroke:#%06x;stroke-width:%gpx' />\n",
	p.stroke, p.stroke_width);
}

void svg_end(void)
{
    printf(" </g>\n"); // group 終了
    printf("</svg>\n"); // svg 終了
}

int main(void)
{
    // 構造体の初期化. 定義した順に並べる.
    struct SVG svg = {500.0, 1000.0};
    struct LINE line = {10.0, 100.0, 300.0, 700.0, 0xff0000, 4.0};
	 
    svg_begin(svg);    // 構造体を関数に渡す.
    line_print(line);
    svg_end();         // SVG 終了
    return EXIT_SUCCESS;
}

構造体の代入. 同様に main 関数の部分のみを変更. 構造体の代入では, 代入元の各メンバーの値が, 代入先の各メンバーに代入される.

#include <stdio.h>
#include <stdlib.h>

struct SVG {
    double width;  // 画面の幅
    double height; // 画面の高さ
};

struct LINE {
    double x0; // 始点のx座標
    double y0; // 始点のy座標
    double x1; // 終点のx座標
    double y1; // 終点のy座標
    unsigned int stroke; // 線の色 0xrrggbb
    double stroke_width;
};

void svg_begin(struct SVG s)
{
    printf("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    printf("<svg\n");
    printf("   xmlns:svg='http://www.w3.org/2000/svg'\n");
    printf("   xmlns='http://www.w3.org/2000/svg'\n");
    printf("   version='1.0'\n");
    printf("   width='%g'\n", s.width);
    printf("   height='%g'>\n", s.height);
    printf("  <g>\n");
}

void line_print(struct LINE p)
{
    printf("    <path\n");
    printf("       d='M %g,%g L %g,%g'\n", p.x0, p.y0, p.x1, p.y1);
    printf("       style='stroke:#%06x;stroke-width:%gpx' />\n",
	p.stroke, p.stroke_width);
}

void svg_end(void)
{
    printf(" </g>\n"); // group 終了
    printf("</svg>\n"); // svg 終了
}

int main(void)
{
    struct SVG svg = {500.0, 1000.0};
    struct LINE line = {10.0, 100.0, 300.0, 700.0, 0xff00ff, 4.0};
    struct LINE line2, line3;

    line2 = line; // line の各メンバーの値を line2 の各メンバーに代入.
    line3 = line; // line の各メンバーの値を line2 の各メンバーに代入.

    line2.x0 = 100.0; // line2.x0 のみ変更
    line3.x0 = 200.0; // line3.x0 のみ変更
		  
    svg_begin(svg);
    line_print(line);
    line_print(line2);
    line_print(line3);
    svg_end();
    return EXIT_SUCCESS;
}

関数の戻り値とすることができる. (配列では各要素を返して代入することはできない.) 新しく struct LINE line_default(void) 関数を作成した. この関数の構造体としての戻り値を, 別の構造体 line2 および line3 に代入している.

#include <stdio.h>
#include <stdlib.h>

struct SVG {
    double width;  // 画面の幅
    double height; // 画面の高さ
};

struct LINE {
    double x0; // 始点のx座標
    double y0; // 始点のy座標
    double x1; // 終点のx座標
    double y1; // 終点のy座標
    unsigned int stroke; // 線の色 0xrrggbb
    double stroke_width;
};

void svg_begin(struct SVG s)
{
    printf("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    printf("<svg\n");
    printf("   xmlns:svg='http://www.w3.org/2000/svg'\n");
    printf("   xmlns='http://www.w3.org/2000/svg'\n");
    printf("   version='1.0'\n");
    printf("   width='%g'\n", s.width);
    printf("   height='%g'>\n", s.height);
    printf("  <g>\n");
}

struct LINE line_default(void)
{
    struct LINE line = {10.0, 100.0, 300.0, 700.0, 0xff00ff, 4.0};
    return line;
}

void line_print(struct LINE p)
{
    printf("    <path\n");
    printf("       d='M %g,%g L %g,%g'\n", p.x0, p.y0, p.x1, p.y1);
    printf("       style='stroke:#%06x;stroke-width:%gpx' />\n",
	p.stroke, p.stroke_width);
}

void svg_end(void)
{
    printf(" </g>\n"); // group 終了
    printf("</svg>\n"); // svg 終了
}

int main(void)
{
    struct SVG svg = {500.0, 1000.0};
    struct LINE line2, line3;

    // line_default 関数は struct LINE を返す.
    // その各メンバーの値を line2, line3 の各メンバーに代入.
    line2 = line_default();
    line3 = line_default();

    line2.x0 = 100.0; // line2.x0 のみ変更
    line3.x0 = 200.0; // line3.x0 のみ変更

    svg_begin(svg);
    line_print(line2);
    line_print(line3);
    svg_end();
    return EXIT_SUCCESS;
}

構造体の配列. 配列要素の後ろにメンバーの指定をする.

#include <stdio.h>
#include <stdlib.h>

struct SVG {
    double width;  // 画面の幅
    double height; // 画面の高さ
};

struct LINE {
    double x0; // 始点のx座標
    double y0; // 始点のy座標
    double x1; // 終点のx座標
    double y1; // 終点のy座標
    unsigned int stroke; // 線の色 0xrrggbb
    double stroke_width;
};

void svg_begin(struct SVG s)
{
    printf("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    printf("<svg\n");
    printf("   xmlns:svg='http://www.w3.org/2000/svg'\n");
    printf("   xmlns='http://www.w3.org/2000/svg'\n");
    printf("   version='1.0'\n");
    printf("   width='%g'\n", s.width);
    printf("   height='%g'>\n", s.height);
    printf("  <g>\n");
}

void line_print(struct LINE p)
{
    printf("    <path\n");
    printf("       d='M %g,%g L %g,%g'\n", p.x0, p.y0, p.x1, p.y1);
    printf("       style='stroke:#%06x;stroke-width:%gpx' />\n",
	p.stroke, p.stroke_width);
}

void svg_end(void)
{
    printf(" </g>\n"); // group 終了
    printf("</svg>\n"); // svg 終了
}

#define NLINES 10

int main(void)
{
    int i;
    struct SVG svg = {500.0, 1000.0};
    struct LINE line0 = {10.0, 100.0, 300.0, 700.0, 0xff00ff, 4.0};
    struct LINE lines[NLINES];
	 
    for(i=0; i<NLINES; i++)
	lines[i] = line0; // lines の各要素を line0 で初期化.
	 
    for(i=0; i<NLINES; i++)
	lines[i].x0 = i*20.0; // lines の各要素の x0 を変更.
    for(i=0; i<NLINES; i++)
	lines[i].stroke =0x808000 + i*0x1010; // lines の各要素の stroke を変更.
		  
    svg_begin(svg);
    for(i=0; i<NLINES; i++)
	line_print(lines[i]);
    svg_end();
    return EXIT_SUCCESS;
}

構造体へのポインタを関数に渡す. 以下の点に注意.

関数に渡す場合は次のようになる. 構造体を受け取る関数を全て書き換えた.

#include <stdio.h>
#include <stdlib.h>

struct SVG {
    double width;  // 画面の幅
    double height; // 画面の高さ
};

struct LINE {
    double x0; // 始点のx座標
    double y0; // 始点のy座標
    double x1; // 終点のx座標
    double y1; // 終点のy座標
    unsigned int stroke; // 線の色 0xrrggbb
    double stroke_width;
};

void svg_begin(struct SVG *s)
{
    printf("<?xml version='1.0' encoding='UTF-8' standalone='no'?>\n");
    printf("<svg\n");
    printf("   xmlns:svg='http://www.w3.org/2000/svg'\n");
    printf("   xmlns='http://www.w3.org/2000/svg'\n");
    printf("   version='1.0'\n");
    printf("   width='%g'\n", s->width);
    printf("   height='%g'>\n", s->height);
    printf("  <g>\n");
}

void line_print(struct LINE *p)
{
    printf("    <path\n");
    printf("       d='M %g,%g L %g,%g'\n", p->x0, p->y0, p->x1, p->y1);
    printf("       style='stroke:#%06x;stroke-width:%gpx' />\n",
	p->stroke, p->stroke_width);
}

void svg_end(void)
{
    printf(" </g>\n"); // group 終了
    printf("</svg>\n"); // svg 終了
}

int main(void)
{
    struct SVG svg = {500.0, 1000.0};
    struct LINE line = {10.0, 100.0, 300.0, 700.0, 0xff00ff, 4.0};
    struct LINE line2, line3;

    line2 = line; // line の各メンバーの値を line2 の各メンバーに代入.
    line3 = line; // line の各メンバーの値を line2 の各メンバーに代入.

    line2.x0 = 100.0; // line2.x0 のみ変更
    line3.x0 = 200.0; // line3.x0 のみ変更
		  
    svg_begin(&svg);
    line_print(&line);
    line_print(&line2);
    line_print(&line3);
    svg_end();
    return EXIT_SUCCESS;
}

main関数の中で, line3 を line を指すポインタとすることにした. line3が指すアドレスの内容を変更すると, line1 のメンバが変更されることに注意. また, 関数に渡すとき, &line1 のようにline1にはアドレス演算子が必要であるし, line3には, line3のように付けないことに注意すること.

#include <stdio.h>
#include <stdlib.h>

struct SVG {
	 double width;  // 画面の幅
	 double height; // 画面の高さ
};

struct LINE {
	 double x0; // 始点のx座標
	 double y0; // 始点のy座標
	 double x1; // 終点のx座標
	 double y1; // 終点のy座標
	 unsigned int stroke; // 線の色 0xrrggbb
	 double stroke_width;
};

void svg_begin(struct SVG *s)
{
	 printf("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
	 printf("<svg\n");
	 printf("   xmlns:svg=\"http://www.w3.org/2000/svg\"\n");
	 printf("   xmlns=\"http://www.w3.org/2000/svg\"\n");
	 printf("   version=\"1.0\"\n");
	 printf("   width=\"%g\"\n", s->width);
	 printf("   height=\"%g\">\n", s->height);
	 printf("  <g>\n");

}

void line_print(struct LINE *p)
{
	 printf("    <path\n");
	 printf("       d=\"M %g,%g L %g,%g\"\n", p->x0, p->y0, p->x1, p->y1);
	 printf("       style=\"stroke:#%06x;stroke-width:%gpx\" />\n",
			p->stroke, p->stroke_width);
}

void svg_end(void)
{
	 printf(" </g>\n"); // group 終了
	 printf("</svg>\n"); // svg 終了
}

int main(void)
{
	 struct SVG svg = {500.0, 1000.0};
	 struct LINE line = {10.0, 100.0, 300.0, 700.0, 0xff00ff, 4.0};
	 struct LINE line2, *line3;

	 line2 = line; // line の各メンバーの値を line2 の各メンバーに代入.
	 line3 = &line; // line3 は lineを指す.

	 line2.x0 = 100.0; // line2.x0 のみ変更
	 line3->x0 = 200.0; // line1.x0 が変更される.
		  
	 svg_begin(&svg);
	 line_print(&line); // アドレスを渡す. &lineがアドレス.
	 line_print(&line2);
	 line_print(line3); // アドレスを渡す. line3 がアドレス.
	 svg_end();
	 return EXIT_SUCCESS;
}

配列名はアドレスを保持することに注意すると, 仮引数がポインタ変数のときには, 構造体の配列を渡すときは以下のようになる. あらたに void lines_print(struct LINE *p, int n)関数を作成した.

#include <stdio.h>
#include <stdlib.h>

struct SVG {
	 double width;  // 画面の幅
	 double height; // 画面の高さ
};

struct LINE {
	 double x0; // 始点のx座標
	 double y0; // 始点のy座標
	 double x1; // 終点のx座標
	 double y1; // 終点のy座標
	 unsigned int stroke; // 線の色 0xrrggbb
	 double stroke_width;
};

void svg_begin(struct SVG *s)
{
	 printf("<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
	 printf("<svg\n");
	 printf("   xmlns:svg=\"http://www.w3.org/2000/svg\"\n");
	 printf("   xmlns=\"http://www.w3.org/2000/svg\"\n");
	 printf("   version=\"1.0\"\n");
	 printf("   width=\"%g\"\n", s->width);
	 printf("   height=\"%g\">\n", s->height);
	 printf("  <g>\n");

}

struct LINE line_default(void)
{
	 struct LINE line = {10.0, 100.0, 300.0, 700.0, 0xff00ff, 4.0};
	 return line;
}

void line_print(struct LINE *p)
{
	 printf("    <path\n");
	 printf("       d=\"M %g,%g L %g,%g\"\n", p->x0, p->y0, p->x1, p->y1);
	 printf("       style=\"stroke:#%06x;stroke-width:%gpx\" />\n",
			p->stroke, p->stroke_width);
}

void lines_print(struct LINE *p, int n)
{
	 int i;

	 for(i=0; i<n; i++){
		  printf("    <path\n");
		  printf("       d=\"M %g,%g L %g,%g\"\n",
				 p[i].x0, p[i].y0, p[i].x1, p[i].y1);
		  printf("       style=\"stroke:#%06x;stroke-width:%gpx\" />\n",
				 p[i].stroke, p[i].stroke_width);
	 }
}

void svg_end(void)
{
	 printf(" </g>\n"); // group 終了
	 printf("</svg>\n"); // svg 終了
}

#define NLINES 10

int main(void)
{
	 int i;
	 struct SVG svg = {500.0, 1000.0};
	 struct LINE lines[NLINES];

	 for(i=0; i<NLINES; i++)
		  lines[i] = line_default();
	 for(i=0; i<NLINES; i++)
		  lines[i].x0 = i*20.0; // 各要素の x0 を変更.
	 for(i=0; i<NLINES; i++)
		  lines[i].stroke =0x808000 + i*0x1010; // 各要素の stroke を変更.

	 svg_begin(&svg);
	 lines_print(lines, NLINES);
	 svg_end();
	 return EXIT_SUCCESS;
}

(c)1999-2013 Tetsuya Makimura