静岡理工科大学 菅沼ホーム 全体目次 演習解答例 付録 索引

第8章 構造体と共用体(Javaを除く)

  1. 8.1 構造体
    1. (プログラム例 8.1 ) 構造体(定義と参照)
    2. (プログラム例 8.2 ) 構造体(ネスト,方法 1 )
    3. (プログラム例 8.3 ) 構造体(ネスト,方法 2 )
    4. (プログラム例 8.4 ) 構造体(関数の引数)
    5. (プログラム例 8.5 ) 構造体(リスト構造)
  2. 8.2 共用体
    1. (プログラム例 8.6 ) 共用体
  3. 演習問題8

8.1 構造体

  構造体は,複数の異なった型のデータをひとまとめにして扱いたいときに利用されます.構造体を宣言する一般形式は以下の通りです.なお,構造体の中に構造体を定義することも可能です(構造体のネスト).構造体は,C++ の中では,クラスの特別な形とみなされます.そのような意味で,構造体を使用するなら,クラス(クラスに関しては,第 10 章以降を参照してください)を使用した方が良いと思います.したがって,この節における説明も最小限にとどめたいと思います.
	[記憶クラス] struct [構造体名] [{メンバー}] [変数名の並び];
		
  構造体名とは,1 つの構造体に付けられた名前です.変数名の並びにあげられた各変数は,この構造体名で指定された構造を持つことになります.メンバーのリストと変数名が記述され,かつ,その構造を示す構造体名を他の場所で参照することがない場合は省略できます.なお,記憶クラスに関しては,7.2 節を参照してください.

  メンバーmember )は,構造体の構造を記述する変数の並びからなります.すでに他の場所で宣言された構造体名を参照して定義する場合は省略できます.

  変数名の並びとは,構造体名で定義された構造体型を持つ変数のリストです.配列変数を指定することも可能です.これを省略した場合は,構造が明確になるだけであり,その構造を持つ変数は定義されません.実際にこの構造を持つ変数を使用する場合は,次のように,具体的な変数を宣言する必要があります.
	[記憶クラス] struct 構造体名 [変数名の並び];
		
  以下,構造体の様々な定義方法の例を挙げます.例えば,次のような宣言方法があります.この構造体は,char 型の配列変数 name,sei と int 型変数 kokugo,sansu からなっています.変数 x が構造体名 student タイプの構造体であることを宣言すると共に,初期設定をしています.
	struct student
	{
		char name[20];
		char sei[3];
		int kokugo;
		int sansu;
	}  x = {"山田太郎", "男", 100, 80};
		
また,構造体の宣言部分で変数名の並びを省略し,次のように書くこともできます.
	struct student
	{
		char name[20];
		char sei[3];
		int kokugo;
		int sansu;
	};
	struct student x = {"山田太郎", "男", 100, 80};
		
  構造体内のメンバー値を参照・修正するには 2 つの方法があります.1 つは,「.」演算子を使用し,直接的に参照する方法です.例えば,上の構造体 x において name を参照するためには,
	x.name
		
と書きます.あと 1 つは,構造体を指すポインタ変数を定義し,そのポインタ変数に参照したい構造体のアドレスを代入した後,「->」または「.」演算子を使用して,間接的に参照する方法です.例えば,上と同じデータを参照するために,
	struct student *pt;
	pt = &x;
		
の宣言,代入を行った後,
	pt->name または (*pt).name
		
と書きます.

  次の例は,変数に配列を使用した場合で,その初期化も行っています(最初の 3 個だけが初期設定されます).
	struct student
	{
		char name[20];
		char sei[3];
		int kokugo;
		int sansu;
	};
	struct student x[50] = {
		{"山田太郎", "男", 100, 80},
		{"鈴木次郎", "男", 90, 100},
		{"齋藤花子", "女", 90, 90}
	}
		
------------------(C++)構造体宣言--------------------

  C++ においては,構造体名は,新しいデータ型の名前として,int や char と同じように使用できます.つまり,「 struct 」を省略できます.例えば,
	struct data {
		char *name;
		int nen;
	};
		
のような宣言をしてあれば,
	data man;
		
と書くだけで,変数 man は data 型の構造体とみなされます.

----------------(C++)構造体宣言終わり-----------------

(プログラム例 8.1 ) 構造体(定義と参照)

/****************************/
/* 構造体(定義と参照)     */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
/*
	 構造体の定義と初期化
*/
	struct student
	{
		char name[20];
		char sei[3];
		int kokugo;
		int sansu;
	};
	struct student x = {"山田太郎", "男", 100, 80};
	struct student y = {"山田花子", "女", 95, 90};
	struct student *z;
/*
	 直接的参照
*/
	printf("%s %s %d %d\n", x.name, x.sei, x.kokugo, x.sansu);
	printf("%s %s %d %d\n", y.name, y.sei, y.kokugo, y.sansu);
/*
	 間接的参照
*/
	z = &x;
	printf("%s %s %d %d\n", z->name, z->sei, z->kokugo, z->sansu);
	z = &y;
	printf("%s %s %d %d\n", (*z).name, (*z).sei, (*z).kokugo, (*z).sansu);

	return 0;
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
山田太郎 男 100 80
山田花子 女 95 90
山田太郎 男 100 80
山田花子 女 95 90
		

(プログラム例 8.2 ) 構造体(ネスト,方法 1 )

  構造体のネスト(構造体の中に構造体を定義)の例です.

/****************************/
/* 構造体(ネスト,方法1) */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	struct student
	{
		char name[20];
		char sei[3];
		struct shiken
		{
			int kokugo;
			int sansu;
		} kekka;
	};
	struct student x = {"山田太郎", "男", {100, 80}};

	printf("名前 %s 性 %s 国語 %d 算数 %d\n",
		   x.name, x.sei, x.kekka.kokugo, x.kekka.sansu);

	return 0;
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
名前 山田太郎 性 男 国語 100 算数 80
		

(プログラム例 8.3 ) 構造体(ネスト,方法 2 )

  プログラム例 8.2 は,次のように書くこともできます.

/****************************/
/* 構造体(ネスト,方法2) */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>

int main()
{
	struct shiken
	{
		int kokugo;
		int sansu;
	};
	struct student
	{
		char name[20];
		char sei[3];
		struct shiken kekka;
	};
	struct student x = {"山田太郎", "男", {100, 80}};

	printf("名前 %s 性 %s 国語 %d 算数 %d\n",
		   x.name, x.sei, x.kekka.kokugo, x.kekka.sansu);

	return 0;
}
		

(プログラム例 8.4 ) 構造体(関数の引数)

  構造体も,他の変数と同様に,例えば,この例のような方法で,関数の引数として引き渡すことができます.関数 print1 へは構造体 x のコピーが渡されるため,関数 print1 の内部で構造体の値を変更しても main 関数の x には影響しません.しかし,関数 print2 では構造体 x のアドレスが渡されるため,main 関数における x と同じものを参照することになり,関数 print2 の内部で構造体の値を変更すると,main 関数における x の値も変化します.

  しかし,関数 print1 のような方法を使用すると,構造体のコピーが渡されるため,サイズが大きい構造体の場合は,実行時間的にも,メモリ的にも問題となります.また,関数 print2 のように,構造体のアドレスを渡す方法を採用すると,参照方法が面倒になります.そこで,関数 print3 のように,参照渡し(実際的には,アドレスを渡す場合と同じ)を行うことを勧めます.なお,この例においては,C++ の方法に従い,student の前の struct を省略してあります.

/****************************/
/* 構造体(関数の引数)     */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>
#include <string.h>

struct student
{
	char name[20];
	char sei[3];
	int kokugo;
	int sansu;
};

void print1(student x)   // 構造体のコピーが渡される
{
	x.sansu = 50;
	strcpy(x.name, "山田次郎");
	printf("   print1 %s %s %d %d\n", x.name, x.sei, x.kokugo, x.sansu);
}

void print2(student *x)   // 構造体のアドレスが渡される
{
	x->sansu = 100;
	strcpy(x->name, "山田三郎");
	printf("   print2 %s %s %d %d\n", x->name, x->sei, x->kokugo, x->sansu);
}

void print3(student &x)   // 参照渡し
{
	x.sansu = 70;
	strcpy(x.name, "山田四郎");
	printf("   print3 %s %s %d %d\n", x.name, x.sei, x.kokugo, x.sansu);
}

int main()
{
	student x = {"山田太郎", "男", 100, 80};
	printf("main %s %s %d %d\n", x.name, x.sei, x.kokugo, x.sansu);
	print1(x);
	printf("main %s %s %d %d\n", x.name, x.sei, x.kokugo, x.sansu);
	print2(&x);
	printf("main %s %s %d %d\n", x.name, x.sei, x.kokugo, x.sansu);
	print3(x);
	printf("main %s %s %d %d\n", x.name, x.sei, x.kokugo, x.sansu);
}
		

  このプログラムを実行すると,以下に示すような出力が得られます.
main 山田太郎 男 100 80
   print1 山田次郎 男 100 50
main 山田太郎 男 100 80
   print2 山田三郎 男 100 100
main 山田三郎 男 100 100
   print3 山田四郎 男 100 70
main 山田四郎 男 100 70
		
なお,関数 print3 の内部で値を変更することを許さない場合は,
	void print3(const student &x)   // 参照渡し
		
のような指定を行っておくべきです.

(プログラム例 8.5 ) 構造体(リスト構造)

  この例は,図 8.1 に示すようなリスト構造を生成するプログラムです.データの追加,削除,および,出力の機能を持っています.追加されたデータは,アルファベット順に適当な位置に付け加えられます.ただし,既に存在するデータか否かのチェックは行っていません.

/****************************/
/* リスト処理               */
/*      coded by Y.Suganuma */
/****************************/
#include <stdio.h>
#include <string.h>

/****************/
/* 構造体の定義 */
/****************/
struct List
{
	char *st;
	struct List *next;
};

/****************************************/
/* データの追加                         */
/*      base : 先頭の構造体へのアドレス */
/*      dt : 追加する構造体へのアドレス */
/****************************************/
void add(struct List *base, struct List *dt)
{
	struct List *lt1, *lt2 = base;
	int k, sw = 1;

	while (sw > 0) {
					// 最後に追加
		if (lt2->next == NULL) {
			lt2->next = dt;
			sw        = 0;
		}
					// 比較し,途中に追加
		else {
			lt1 = lt2;
			lt2 = lt2->next;
			k   = strcmp(dt->st, lt2->st);   // 比較
			if (k < 0) {                     // 追加
				dt->next  = lt2;
				lt1->next = dt;
				sw        = 0;
			}
		}
	}
}

/****************************************/
/* データの削除                         */
/*      base : 先頭の構造体へのアドレス */
/*      st1 : 文字列                    */
/****************************************/
void del(struct List *base, char *st1)
{
	struct List *lt1, *lt2 = base;
	int k, sw = 1;

	while (sw > 0) {
					// データが存在しない場合
		if (lt2->next == NULL) {
			printf("      指定されたデータがありません!\n");
			sw = 0;
		}
					// 比較し,削除
		else {
			lt1 = lt2;
			lt2 = lt2->next;
			k   = strcmp(st1, lt2->st);     // 比較
			if (k == 0) {                   // 削除
				lt1->next = lt2->next;
				sw        = 0;
			}
		}
	}
}

/****************************************/
/* リストデータの出力                   */
/*      base : 先頭の構造体へのアドレス */
/****************************************/
void output(struct List *base)
{
	struct List *nt = base->next;

	while (nt != NULL) {
		printf("   data = %s\n", nt->st);
		nt = nt->next;
	}
}

/****************/
/* main program */
/****************/
int main ()
{
	int sw = 1;
	char st[100];
	struct List base, *lt;

	base.next = NULL;

	while (sw > 0) {
		printf("1:追加,2:削除,3:出力,0:終了? ");
		scanf("%d", &sw);
		switch (sw) {
			case 1:   // 追加
				printf("   データを入力してください ");
				scanf("%s", st);
				lt       = new struct List;
				lt->next = NULL;
				lt->st   = new char [strlen(st)+1];
				strcpy(lt->st, st);
				add(&base, lt);
				break;
			case 2:   // 削除
				printf("   データを入力してください ");
				scanf("%s", st);
				del(&base, st);
				break;
			case 3:   // 出力
				output(&base);
				break;
			default :
				sw = 0;
				break;
		}
	}

	return 0;
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
1:追加,2:削除,3:出力,0:終了? 1
   データを入力してください 190
1:追加,2:削除,3:出力,0:終了? 1
   データを入力してください 100
1:追加,2:削除,3:出力,0:終了? 1
   データを入力してください 10
1:追加,2:削除,3:出力,0:終了? 2
   データを入力してください 100
1:追加,2:削除,3:出力,0:終了? 3
   data = 10
   data = 190
1:追加,2:削除,3:出力,0:終了? 0
		

  この例に見るように,リスト構造を生成するプログラムを書くのはかなり面倒です.後の述べる STL を使用すれば,以下に示すように,同じ機能を簡単に実現できます.

/****************************/
/* リスト処理               */
/*      coded by Y.Suganuma */
/****************************/
#include <iostream>
#include <set>
#include <string>

using namespace std;

/**********************/
/* リストデータの出力 */
/*      lt : リスト   */
/**********************/
void output(set<string> &s) {
	set<string>::iterator it;
	cout << "要素数: " << s.size() << "\n";
	for (it = s.begin(); it != s.end(); it++)
		cout << "  " << *it;
	cout << "\n";
}

/****************/
/* main program */
/****************/
int main ()
{
	int sw = 1;
	string str;
	set <string> s;

	while (sw > 0) {
		cout << "1:追加,2:削除,3:出力,0:終了? ";
		cin >> sw;
		switch (sw) {
			case 1:   // 追加
				cout << "   データを入力してください ";
				cin >> str;
				s.insert(str);
				break;
			case 2:   // 削除
				cout << "   データを入力してください ";
				cin >> str;
				s.erase(str);
				break;
			case 3:   // 出力
				output(s);
				break;
			default :
				sw = 0;
				break;
		}
	}

	return 0;
}
		

8.2 共用体

  共用体宣言は,同じメモリー領域を異なる型の別の変数で操作できるようにするための宣言です.その宣言方法・参照等の方法は,下に示すように,構造体とほぼ同じです.ただし,共用体の初期設定は,その最初のメンバーmember )値としてなされなければなりません.
	union [共用体名] {メンバーのリスト} [変数名の並び];
		
(プログラム例 8.6 ) 共用体

  次の例は,同じ記憶域を,char,long,及び,double で共用した例です.この例からも明らかなように,ある型の値を収納してそれを別の型で参照した場合,結果は信頼できないものとなります.

/*****************************/
/* 共用体                    */
/*      coded by Y.Suganuma  */
/*****************************/
#include <stdio.h>

int main()
{
	int i1;
/*
	 共用体の定義(初期設定は文字)
*/
	union smp
	{
		char ch[8];
		long lg[2];
		double db;
	} uni = {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'};
					/* 文字として記憶 */
	printf("-----char----------\n");
	for (i1 = 0; i1 < 8; i1++)
		printf("%c ", uni.ch[i1]);
	printf("\n");
	printf("%5ld %5ld\n", uni.lg[0], uni.lg[1]);
	printf("%10.1f\n", uni.db);
					/* long型整数として記憶 */
	printf("-----long----------\n");
	uni.lg[0] = 10;
	uni.lg[1] = 20;
	for (i1 = 0; i1 < 8; i1++)
		printf("%c ", uni.ch[i1]);
	printf("\n");
	printf("%5ld %5ld\n", uni.lg[0], uni.lg[1]);
	printf("%10.1f\n", uni.db);
					/* double型として記憶 */
	printf("-----double----------\n");
	uni.db = 50.0;
	for (i1 = 0; i1 < 8; i1++)
		printf("%c ", uni.ch[i1]);
	printf("\n");
	printf("%5ld %5ld\n", uni.lg[0], uni.lg[1]);
	printf("%10.1f\n", uni.db);

	return 0;
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
-----char----------
a b c d e f g h
1684234849 1751606885
854088322303612440000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000
000000000000000000000000000000000.0
-----long----------

       
   10    20
       0.0
-----double----------
            I @
    0 1078525952
      50.0
		

演習問題8

[問1] n (入力)人の名前( char ),給与( int ),及び,年齢( int )を入力し,出力するプログラムを書け(各人のデータを構造体で記述せよ).

[問2] n (入力)個の 3 次元空間の点の座標を入力した後,原点からの平均距離を計算し,平均距離以上離れた点の個数を出力するプログラムを書け( 3 次元空間の 1 つの点の座標を構造体で記述せよ)

[問3]自分の名前,住所(県,市,番地),年齢,及び,性別を構造体で記述し,その構造体を関数に引数として受け渡し,内容を出力するプログラムを書け.ただし,住所は,上の構造体に含まれる別の構造体として記述する事(構造体のネスト).

[問4]名前,住所,電話番号を構造体で記述し,名前を入力すると電話番号を出力するプログラムを書け.

[問5]英語の単語を入力し,それを 2 進木として記憶するプログラムを構造体を利用して書け.ただし,新しい単語が入力されたとき,記憶されている単語よりアルファベット順で前にあるなら左,そうでなければ右の枝をたどるものとする.例えば,11 個の単語が,「 network parameters are optimized from empirical data and the optimal number 」という順に入力された場合は以下のようになる.

静岡理工科大学 菅沼ホーム 全体目次 演習解答例 付録 索引