[前へ]   [目次へ]   [次へ]

構造体型オブジェクトの使い方(2)(C/C++)


今回は構造体型オブジェクトの役に立つ使い方についてです。

構造体型は型の一種として定義されるため、
配列を作ることも、ポインタを作ることも、関数の引数や戻り値に使うこともできます。

ただし、関数の引数や戻り値に使用する場合、
構造体型オブジェクトを直接使うと都度コピーされてしまい、処理が重くなるため注意が必要です。

また、前々回に説明した通り、構造体の「定義内容」は原則として、その時できる変数宣言と同じものが使えます。
つまり、構造体の中に構造体型オブジェクトを宣言することができます。
さらに、「定義中の構造体」は既に不完全型として登録されているため、
「定義中の構造体型へのポインタ」は「定義内容」に使うことができます。
この「自分の型へのポインタ」が使えることは重要な意味がありますので覚えておいてください。


さて、これらのことを考慮して、少し複雑なデータ集合を扱うプログラムを考えてみます。
扱う対象は「本棚」として、「中にある本」を「書名と読んだ回数」で表現してみます。
また、本棚には名前を付けられるようにして、複数扱えるようにしてみます。

本棚は一個につき20冊収納できることにし、
「入っている本を一覧」「本を入れる」「本を出す」「本を読む」の4つの操作ができるようにしてみます。
また、書名のない本は「存在しない」という扱いとします。

まずは、構造体を使わずに作成してみます。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
< 26>
< 27>
< 28>
< 29>
< 30>
< 31>
< 32>
< 33>
< 34>
< 35>
< 36>
< 37>
< 38>
< 39>
< 40>
< 41>
< 42>
< 43>
< 44>
< 45>
< 46>
< 47>
< 48>
< 49>
< 50>
< 51>
< 52>
< 53>
< 54>
< 55>
< 56>
< 57>
< 58>
< 59>
< 60>
< 61>
< 62>
< 63>
< 64>
< 65>
< 66>
< 67>
< 68>
< 69>
< 70>
< 71>
< 72>
< 73>
< 74>
< 75>
< 76>
< 77>
< 78>
< 79>
< 80>
< 81>
< 82>
< 83>
< 84>
< 85>
< 86>
< 87>
< 88>
< 89>
< 90>
< 91>
< 92>
< 93>
< 94>
< 95>
< 96>
< 97>
< 98>
< 99>
<100>
<101>
<102>
<103>
<104>
<105>
<106>
<107>
<108>
<109>
<110>
<111>
<112>
<113>
<114>
<115>
<116>
<117>
<118>
<119>
<120>
<121>
<122>
<123>
<124>
<125>
<126>
<127>
<128>
<129>
<130>
<131>
<132>
<133>
<134>
<135>
<136>
<137>
<138>
<139>
<140>
<141>
<142>
<143>
<144>
<145>
<146>
<147>
<148>
<149>
<150>
<151>
<152>
<153>
<154>
<155>
<156>
<157>
<158>
<159>
<160>
<161>
<162>
<163>
<164>
<165>
<166>
<167>
<168>
<169>
<170>
<171>
<172>
<173>
<174>
<175>
<176>
<177>
<178>
<179>
<180>
<181>
<182>
<183>
<184>
<185>
<186>
<187>
<188>
<189>
#include <stdio.h>
#include <string.h>

void ViewBookList(const char *shelfname,const char booknames[20][32],const int readcnts[20]){
   
/****************************
   本棚の内容を表示します。
   
   const char *shelfname:本棚の名前
   const char booknames[20][32]:本棚の書名の配列
   const int readcnts[20]:本棚の読んだ数の配列
   ****************************/
   
   
int i,freecnt=0;
   
   printf(
"本棚名:%s\n%32s 読数\n",shelfname,"書名");
   
for(i=0;i<20;i++){
      
if(booknames[i][0]!='\0')printf("%32s:%4d\n",booknames[i],readcnts[i]);
      
else freecnt++;
   }
   printf(
"あと%d冊収納することができます。\n\n",freecnt);
}

int AddBook(const char *shelfname,char booknames[20][32],int readcnts[20],const char *bookname,int newreadcnt){
   
/*************************
   本棚に本を入れる
   
   戻り値:入れられた場合1、入れられない場合0
   
   const char *shelfname:本棚の名前
   char booknames[20][32]:[入出力]本を入れる本棚の書名の配列
   int readcnts[20]:[入出力]本を入れる本棚の読んだ数の配列
   const char *bookname:新しく入れる本の書名
   int newreadcnt:新しく入れる本の読んだ数
   *************************/
   
   
int i,len;
   
   len=strlen(bookname);
   
if((len==0)||(len>=31)){
      puts(
"書名が無効です。");
      
return 0;
   }
   
for(i=0;i<20;i++){
      
if(booknames[i][0]=='\0'){
         strcpy(booknames[i],bookname);
         readcnts[i]=newreadcnt;
         printf(
"「%s」の本棚に「%s」を入れました。\n",shelfname,bookname);
         
return 1;
      }
   }
   puts(
"本棚がいっぱいです。");
   
return 0;
}

int RemoveBook(const char *shelfname,char booknames[20][32],const char *bookname){
   
/*************************
   本棚から本を出す(本棚から消す)
   
   戻り値:出した場合1、出せない場合0
   
   const char *shelfname:本棚の名前
   char booknames[20][32]:[入出力]本を出す本棚の書名の配列
   const char *bookname:出す本の書名
   *************************/
   
   
int i;
   
   
if(bookname[0]=='\0'){
      puts(
"書名が無効です。");
      
return 0;
   }
   
for(i=0;i<20;i++){
      
if(!strcmp(booknames[i],bookname)){
         booknames[i][0]=
'\0';
         printf(
"「%s」の本棚から「%s」を出しました。\n",shelfname,bookname);
         
return 1;
      }
   }
   printf(
"「%s」の本棚には「%s」がありません。\n",shelfname,bookname);
   
return 0;
}

int ReadBook(const char *shelfname,const char booknames[20][32],int readcnts[20],const char *bookname){
   
/****************************
   本棚の本を読みます。(読んだ数を+1します)
   
   戻り値:読んだ数を増やせた場合は1、増やせなかった場合は0
   
   const char *shelfname:本棚の名前
   const char booknames[20][32]:本棚の書名の配列
   int readcnts[20]:[入出力]本棚の読んだ数の配列
   const char *bookname:本の名前
   ****************************/
   
   
int i;
   
   
if(bookname[0]=='\0'){
      puts(
"書名が無効です。");
      
return 0;
   }
   
for(i=0;i<20;i++){
      
if(!strcmp(booknames[i],bookname)){
         readcnts[i]++;
         printf(
"「%s」の本棚にある「%s」を読みました。\n",shelfname,bookname);
         
return 1;
      }
   }
   printf(
"「%s」の本棚には「%s」がありません。\n",shelfname,bookname);
   
return 0;
}

void ResetBookList(char booknames[20][32],int readcnts[20]){
   
/*************
   本棚を初期化します
   
   char booknames[20][32]:[出力]本棚の書名の配列
   int readcnts[20]:[出力]本棚の読んだ数の配列
   *************/
   
   
int i;
   
   
for(i=0;i<20;i++){
      booknames[i][0]=
'\0';
      readcnts[i]=0;
   }
}

int main(void){
   
char listname[3][32];
   
char booknames[3][20][32];
   
int readcnts[3][20];
   
int i;
   
   strcpy(listname[0],
"寿司ネタ");
   strcpy(listname[1],
"教科書");
   strcpy(listname[2],
"色");
   
for(i=0;i<3;i++)ResetBookList(booknames[i],readcnts[i]);
   
   
for(i=0;i<3;i++)ViewBookList(listname[i],booknames[i],readcnts[i]);
   getchar();
   
   AddBook(listname[0],booknames[0],readcnts[0],
"タマゴ",1);
   AddBook(listname[0],booknames[0],readcnts[0],
"マグロ",3);
   AddBook(listname[0],booknames[0],readcnts[0],
"タイ",5);
   AddBook(listname[0],booknames[0],readcnts[0],
"うに",7);
   AddBook(listname[0],booknames[0],readcnts[0],
"とろ",9);
   
   AddBook(listname[1],booknames[1],readcnts[1],
"国語",1);
   AddBook(listname[1],booknames[1],readcnts[1],
"算数",3);
   AddBook(listname[1],booknames[1],readcnts[1],
"理科",5);
   AddBook(listname[1],booknames[1],readcnts[1],
"社会",7);
   AddBook(listname[1],booknames[1],readcnts[1],
"情報",9);
   
   AddBook(listname[2],booknames[2],readcnts[2],
"赤",1);
   AddBook(listname[2],booknames[2],readcnts[2],
"白",3);
   AddBook(listname[2],booknames[2],readcnts[2],
"黄色",5);
   AddBook(listname[2],booknames[2],readcnts[2],
"青",7);
   AddBook(listname[2],booknames[2],readcnts[2],
"紫",9);
   
   
for(i=0;i<3;i++)ViewBookList(listname[i],booknames[i],readcnts[i]);
   getchar();
   
   RemoveBook(listname[0],booknames[0],
"タマゴ");
   RemoveBook(listname[0],booknames[0],
"とろ");
   RemoveBook(listname[0],booknames[0],
"赤");
   
   RemoveBook(listname[1],booknames[1],
"社会");
   RemoveBook(listname[1],booknames[1],
"赤");
   
   RemoveBook(listname[2],booknames[2],
"白");
   RemoveBook(listname[2],booknames[2],
"紫");
   RemoveBook(listname[2],booknames[2],
"赤");
   
   
for(i=0;i<3;i++)ViewBookList(listname[i],booknames[i],readcnts[i]);
   getchar();
   
   ReadBook(listname[0],booknames[0],readcnts[0],
"マグロ");
   ReadBook(listname[0],booknames[0],readcnts[0],
"とろ");
   
   ReadBook(listname[1],booknames[1],readcnts[1],
"情報");
   
   ReadBook(listname[2],booknames[2],readcnts[2],
"青");
   ReadBook(listname[2],booknames[2],readcnts[2],
"青");
   
   
for(i=0;i<3;i++)ViewBookList(listname[i],booknames[i],readcnts[i]);
   
   getchar();
   
return 0;


次に、これを構造体を使って書いてみます。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
< 26>
< 27>
< 28>
< 29>
< 30>
< 31>
< 32>
< 33>
< 34>
< 35>
< 36>
< 37>
< 38>
< 39>
< 40>
< 41>
< 42>
< 43>
< 44>
< 45>
< 46>
< 47>
< 48>
< 49>
< 50>
< 51>
< 52>
< 53>
< 54>
< 55>
< 56>
< 57>
< 58>
< 59>
< 60>
< 61>
< 62>
< 63>
< 64>
< 65>
< 66>
< 67>
< 68>
< 69>
< 70>
< 71>
< 72>
< 73>
< 74>
< 75>
< 76>
< 77>
< 78>
< 79>
< 80>
< 81>
< 82>
< 83>
< 84>
< 85>
< 86>
< 87>
< 88>
< 89>
< 90>
< 91>
< 92>
< 93>
< 94>
< 95>
< 96>
< 97>
< 98>
< 99>
<100>
<101>
<102>
<103>
<104>
<105>
<106>
<107>
<108>
<109>
<110>
<111>
<112>
<113>
<114>
<115>
<116>
<117>
<118>
<119>
<120>
<121>
<122>
<123>
<124>
<125>
<126>
<127>
<128>
<129>
<130>
<131>
<132>
<133>
<134>
<135>
<136>
<137>
<138>
<139>
<140>
<141>
<142>
<143>
<144>
<145>
<146>
<147>
<148>
<149>
<150>
<151>
<152>
<153>
<154>
<155>
<156>
<157>
<158>
<159>
<160>
<161>
<162>
<163>
<164>
<165>
<166>
<167>
<168>
<169>
<170>
<171>
<172>
<173>
<174>
<175>
<176>
<177>
<178>
<179>
<180>
<181>
<182>
<183>
<184>
<185>
<186>
<187>
<188>
<189>
<190>
<191>
<192>
#include <stdio.h>
#include <string.h>

typedef struct{
   
char shelfname[32];
   
char booknames[20][32];
   
int readcnts[20];
}Bookshelf;

void ViewBookList(const Bookshelf *shelf){
   
/****************************
   本棚の内容を表示します。
   
   const Bookshelf *shelf:本棚オブジェクトへのポインタ
   ****************************/
   
   
int i,freecnt=0;
   
   printf(
"本棚名:%s\n%32s 読数\n",shelf->shelfname,"書名");
   
for(i=0;i<20;i++){
      
if(shelf->booknames[i][0]!='\0'){
         printf(
"%32s:%4d\n",shelf->booknames[i],shelf->readcnts[i]);
      }
      
else freecnt++;
   }
   printf(
"あと%d冊収納することができます。\n\n",freecnt);
}

int AddBook(Bookshelf *shelf,const char *bookname,int newreadcnt){
   
/*************************
   本棚に本を入れる
   
   戻り値:入れられた場合1、入れられない場合0
   
   Bookshelf *shelf:[入出力]本を入れる本棚オブジェクトへのポインタ
   const char *bookname:新しく入れる本の書名
   int newreadcnt:新しく入れる本の読んだ数
   *************************/
   
   
int i,len;
   
   len=strlen(bookname);
   
if((len==0)||(len>=31)){
      puts(
"書名が無効です。");
      
return 0;
   }
   
for(i=0;i<20;i++){
      
if(shelf->booknames[i][0]=='\0'){
         strcpy(shelf->booknames[i],bookname);
         shelf->readcnts[i]=newreadcnt;
         printf(
"「%s」の本棚に「%s」を入れました。\n",shelf->shelfname,bookname);
         
return 1;
      }
   }
   puts(
"本棚がいっぱいです。");
   
return 0;
}

int RemoveBook(Bookshelf *shelf,const char *bookname){
   
/*************************
   本棚から本を出す(本棚から消す)
   
   戻り値:出した場合1、出せない場合0
   
   Bookshelf *shelf:[入出力]本を出す本棚オブジェクトへのポインタ
   const char *bookname:出す本の書名
   *************************/
   
   
int i;
   
   
if(bookname[0]=='\0'){
      puts(
"書名が無効です。");
      
return 0;
   }
   
for(i=0;i<20;i++){
      
if(!strcmp(shelf->booknames[i],bookname)){
         shelf->booknames[i][0]=
'\0';
         printf(
"「%s」の本棚から「%s」を出しました。\n",shelf->shelfname,bookname);
         
return 1;
      }
   }
   printf(
"「%s」の本棚には「%s」がありません。\n",shelf->shelfname,bookname);
   
return 0;
}

int ReadBook(Bookshelf *shelf,const char *bookname){
   
/****************************
   本棚の本を読みます。(読んだ数を+1します)
   
   戻り値:読んだ数を増やせた場合は1、増やせなかった場合は0
   
   Bookshelf *shelf:[入出力]読む本が入っている本棚オブジェクトへのポインタ
   const char *bookname:本の名前
   ****************************/
   
   
int i;
   
   
if(bookname[0]=='\0'){
      puts(
"書名が無効です。");
      
return 0;
   }
   
for(i=0;i<20;i++){
      
if(!strcmp(shelf->booknames[i],bookname)){
         shelf->readcnts[i]++;
         printf(
"「%s」の本棚にある「%s」を読みました。\n",shelf->shelfname,bookname);
         
return 1;
      }
   }
   printf(
"「%s」の本棚には「%s」がありません。\n",shelf->shelfname,bookname);
   
return 0;
}

void ResetBookList(Bookshelf *shelf,const char *shelfname){
   
/*************
   本棚を初期化します
   
   Bookshelf *shelf:[出力]初期化する本棚オブジェクトへのポインタ
   const char *shelfname:本棚オブジェクトの新しい名前
   *************/
   
   
int i;
   
   
for(i=0;i<20;i++){
      shelf->booknames[i][0]=
'\0';
      shelf->readcnts[i]=0;
   }
   
if(strlen(shelfname)>30){
      puts(
"本棚の名前が長すぎます。");
      strcpy(shelf->shelfname,
"新しい本棚");
   }
   
else strcpy(shelf->shelfname,shelfname);
}

int main(void){
   Bookshelf shelves[3];
   
int i;
   
   ResetBookList(&shelves[0],
"寿司ネタ");
   ResetBookList(&shelves[1],
"教科書");
   ResetBookList(&shelves[2],
"色");
   
   
for(i=0;i<3;i++)ViewBookList(&shelves[i]);
   getchar();
   
   AddBook(&shelves[0],
"タマゴ",1);
   AddBook(&shelves[0],
"マグロ",3);
   AddBook(&shelves[0],
"タイ",5);
   AddBook(&shelves[0],
"うに",7);
   AddBook(&shelves[0],
"とろ",9);
   
   AddBook(&shelves[1],
"国語",1);
   AddBook(&shelves[1],
"算数",3);
   AddBook(&shelves[1],
"理科",5);
   AddBook(&shelves[1],
"社会",7);
   AddBook(&shelves[1],
"情報",9);
   
   AddBook(&shelves[2],
"赤",1);
   AddBook(&shelves[2],
"白",3);
   AddBook(&shelves[2],
"黄色",5);
   AddBook(&shelves[2],
"青",7);
   AddBook(&shelves[2],
"紫",9);
   
   
for(i=0;i<3;i++)ViewBookList(&shelves[i]);
   getchar();
   
   RemoveBook(&shelves[0],
"タマゴ");
   RemoveBook(&shelves[0],
"とろ");
   RemoveBook(&shelves[0],
"赤");
   
   RemoveBook(&shelves[1],
"社会");
   RemoveBook(&shelves[1],
"赤");
   
   RemoveBook(&shelves[2],
"白");
   RemoveBook(&shelves[2],
"紫");
   RemoveBook(&shelves[2],
"赤");
   
   
for(i=0;i<3;i++)ViewBookList(&shelves[i]);
   getchar();
   
   ReadBook(&shelves[0],
"マグロ");
   ReadBook(&shelves[0],
"とろ");
   
   ReadBook(&shelves[1],
"情報");
   
   ReadBook(&shelves[2],
"青");
   ReadBook(&shelves[2],
"青");
   
   
for(i=0;i<3;i++)ViewBookList(&shelves[i]);
   
   getchar();
   
return 0;

これらのプログラムの実行結果:
本棚名:寿司ネタ
                            書名 読数
あと20冊収納することができます。

本棚名:教科書
                            書名 読数
あと20冊収納することができます。

本棚名:色
                            書名 読数
あと20冊収納することができます。


「寿司ネタ」の本棚に「タマゴ」を入れました。
「寿司ネタ」の本棚に「マグロ」を入れました。
「寿司ネタ」の本棚に「タイ」を入れました。
「寿司ネタ」の本棚に「うに」を入れました。
「寿司ネタ」の本棚に「とろ」を入れました。
「教科書」の本棚に「国語」を入れました。
「教科書」の本棚に「算数」を入れました。
「教科書」の本棚に「理科」を入れました。
「教科書」の本棚に「社会」を入れました。
「教科書」の本棚に「情報」を入れました。
「色」の本棚に「赤」を入れました。
「色」の本棚に「白」を入れました。
「色」の本棚に「黄色」を入れました。
「色」の本棚に「青」を入れました。
「色」の本棚に「紫」を入れました。
本棚名:寿司ネタ
                            書名 読数
                          タマゴ:   1
                          マグロ:   3
                            タイ:   5
                            うに:   7
                            とろ:   9
あと15冊収納することができます。

本棚名:教科書
                            書名 読数
                            国語:   1
                            算数:   3
                            理科:   5
                            社会:   7
                            情報:   9
あと15冊収納することができます。

本棚名:色
                            書名 読数
                              赤:   1
                              白:   3
                            黄色:   5
                              青:   7
                              紫:   9
あと15冊収納することができます。


「寿司ネタ」の本棚から「タマゴ」を出しました。
「寿司ネタ」の本棚から「とろ」を出しました。
「寿司ネタ」の本棚には「赤」がありません。
「教科書」の本棚から「社会」を出しました。
「教科書」の本棚には「赤」がありません。
「色」の本棚から「白」を出しました。
「色」の本棚から「紫」を出しました。
「色」の本棚から「赤」を出しました。
本棚名:寿司ネタ
                            書名 読数
                          マグロ:   3
                            タイ:   5
                            うに:   7
あと17冊収納することができます。

本棚名:教科書
                            書名 読数
                            国語:   1
                            算数:   3
                            理科:   5
                            情報:   9
あと16冊収納することができます。

本棚名:色
                            書名 読数
                            黄色:   5
                              青:   7
あと18冊収納することができます。


「寿司ネタ」の本棚にある「マグロ」を読みました。
「寿司ネタ」の本棚には「とろ」がありません。
「教科書」の本棚にある「情報」を読みました。
「色」の本棚にある「青」を読みました。
「色」の本棚にある「青」を読みました。
本棚名:寿司ネタ
                            書名 読数
                          マグロ:   4
                            タイ:   5
                            うに:   7
あと17冊収納することができます。

本棚名:教科書
                            書名 読数
                            国語:   1
                            算数:   3
                            理科:   5
                            情報:  10
あと16冊収納することができます。

本棚名:色
                            書名 読数
                            黄色:   5
                              青:   9
あと18冊収納することができます。

明らかに本のタイトルじゃないものが入ってるのはスルーしてください(笑)

この二つのプログラムの実行結果は全く同じになります。
行数としては大差ありませんが、注目してほしいのは main 関数の内容です。
構造体を使わない版では3個の配列をそれぞれ個別に渡す必要があるため、
本棚の操作をする関数の引数が多く、面倒かつ冗長な書き方になっています。
また、それぞれの配列を渡し間違えたり、配列の要素数を間違えたりなどバグを埋め込みやすい状態にもなっています。

それに対して構造体を使う版では BookShelf 型のオブジェクト1つが本棚1つに対応するため、
本棚を操作する関数の引数がスッキリし、必要最低限の引数で呼び出せるようになっています。
また、オブジェクト1つにまとめられたため、 main 関数から利用する分には必要な配列の種類や要素数を
あまり気にしなくてもよくなっています。(このプログラムでは文字数上限については気にする必要がありますが)

「この程度のことなら大した問題ではない」と思った人もいるかもしれませんが、
これが中に変数を100個ぐらい持っているようなオブジェクトだったらどうでしょうか?
100個とまではいかなくても、数十の変数を持っている構造体は世の中にそこそこあります。
それらを使うのにいちいち全て引数経由で渡そうなどと考えたら大変なことになると思いませんか?

中身をあまり気にしなくても使えるようにすることこそ、構造体の本当の価値なのです。
なお、この「中身を気にしなくても済む」という特性は「カプセル化」と呼ばれ
オブジェクト指向プログラミングにおけるキーポイントの一つとなっています。

次回は構造体型オブジェクトの便利な用法と注意点についての予定です。

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2012/07/20