- PR -

C言語の構造体を配列の様に見る事は可能?

投稿者投稿内容
通りすがり
会議室デビュー日: 2004/07/02
投稿数: 8
投稿日時: 2004-08-17 08:31
初歩的な質問で申し訳ありません、C初心者です。
研修課題として、CSV形式でIDを検索して表示をする物をCで組む問題が出されました。
CSVから読み込んでカンマ区切りでスプリットした後、ポインタの構造体に
内容を格納しました。

・CSVの中身には、「本のID」「本の名前」「著者名」「貸し出しフラグ」
がそれぞれカンマ区切りされています。

コンパイラはBCCを使っています。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define SIZE 2040
int intCountP;

struct book{
   char title[41];
   char author[21];
   int bookid;
   int abaiable;
   struct book *next; /* 次のデータへのポインタ */
};


//グローバル変数
struct book *head,*p; /* 先頭ポインタ */

//関数の使用宣言
void initdata(void);
void show_list(struct book *p);
void cleanupdata(void);


//bookid順にソート
void sortbook(int bottom,int top){

}

//バイナリサーチ
//int searchbook(book *books[],int size,int key){


//}



int main(void){
   int Number = 0;

   //CSVからのリスト取得
   initdata();

   //ソートの処理
   //sortbook(intCountP,top)
   printf("%d\n",intCountP);

   printf("検索するほんの管理番号を入力して下さい(0で終了)>");
   while (scanf("%d", &Number) != 0) {

     //終了処理
     if(Number==0){
      break;
     }

     //検索


     //リストの表示
     show_list(head);

     //printf("タイトル");
     //printf("著者");
     //printf("管理番号");
     //printf("この本は貸し出し中です");
     //printf("お探しの本は見つかりませんでした。");



     //二回目以降の表示
     printf("検索するほんの管理番号を入力して下さい(0で終了)>");

   }

   //メモリ開放処理
   cleanupdata();

   return(0);
}

//書庫データ初期化
void initdata(void){

   head = NULL;


   //ファイル関連
   char filename[]="db.csv";
   char buffer[SIZE]="";
   int intlen;
   int intChkflg;
   char *BookTitle;
   char *NameValue;
   char *RentFlg;

   intChkflg = 0;

   FILE *fp;
   //ファイルが開けなかった時の処理
   if((fp=fopen(filename,"r"))==NULL){
     fprintf(stderr,"%s ファイルが開けませんでした。\n",filename);
     exit(1);
   }

   //ファイルが開けた時の処理
   while(fgets(buffer,SIZE,fp)){
     //一行づつの文字数
     //
     intlen = strlen(buffer);

     buffer[intlen - 1] = 0;

     for(int i = 0; i < intlen; i++){
       if(buffer[i]==','){
        buffer[i]=0;
        intChkflg++;

        if(intChkflg==1){
         BookTitle = &buffer[i+1];
        }
        if(intChkflg==2){
         NameValue = &buffer[i+1];
        }
        if(intChkflg==3){
         RentFlg = &buffer[i+1];
        }
       }
     }

     /* 記憶領域の確保 */
     if ((p = (struct book *) malloc(sizeof(struct book))) == NULL) {
          printf("malloc error\n");
          exit(1);
     }


     /* リストにデータを登録 */
     p->bookid = atoi(buffer);
     strcpy(p->title, BookTitle);
     strcpy(p->author, NameValue);
     p->abaiable = atoi(RentFlg);


     /* ポインタのつなぎ換え */
     p->next = head; /* 今までの先頭ポインタを次ポインタに */
     head = p; /* 新たな領域を先頭ポインタに */

     //ポインタのカウント
     intCountP++;
     //スプリットカウントを初期化
     intChkflg=0;
   }


     fclose(fp);
}

/*** リストの表示 ***/
void show_list(struct book *p)
{
     while (p != NULL) { /* 次ポインタがNULLまで処理 */
       p = head;
       printf("%d %s %s %d\n", *(*n+2)bookid, p->title, p->author, p->abaiable);
       p = p->next;
     }
}

//メモリー解放
void cleanupdata(void){
     struct book *p2;

     while (p != NULL) { /* 次ポインタがNULLまで処理 */
       p2 = p->next;
       free(p);
       p = p2;
     }
}
例えば、以上のソースでソートを行なう為にはポインタアドレスの
ジャンプが必要で実現させる為にfor を使ってp->nextを繰り返す関数を作ります。
void jump(int intloop){
  p=head;
  for(int i=1;i<intloop;i++){
   p->next;
  }
}
で、行移動して欲しい中身を抜く目的は達成出来そうなのですが、
現存のソースを改変してmallocなりを使って動的に配列を作り
例えば p[i]->title の様な感じで直接、移動できる手法は可能なのでしょうか?

実は研修自体、課題を渡されて達成可否のみの判断で
ソースの善し悪しは指摘されない為にスキルアップが図れません。
どうか御教授をお願いいたします。

[ メッセージ編集済み 編集者: 通りすがり 編集日時 2004-08-17 08:33 ]

[ メッセージ編集済み 編集者: 通りすがり 編集日時 2004-08-17 08:40 ]

[ メッセージ編集済み 編集者: 通りすがり 編集日時 2004-08-17 08:46 ]

[ メッセージ編集済み 編集者: 通りすがり 編集日時 2004-08-17 08:51 ]
burton999
ぬし
会議室デビュー日: 2003/10/06
投稿数: 898
お住まい・勤務地: 東京
投稿日時: 2004-08-17 10:20
mallocだと連続した領域を確保してるわけではないので
reallocを使えば実現できると思います。
.NETとは全く関係ないですけど。。。
たーぞう
ぬし
会議室デビュー日: 2003/08/08
投稿数: 317
お住まい・勤務地: お花畑
投稿日時: 2004-08-17 12:01
引用:

通りすがりさんの書き込み (2004-08-17 08:31) より:
現存のソースを改変してmallocなりを使って動的に配列を作り
例えば p[i]->title の様な感じで直接、移動できる手法は可能なのでしょうか?


勿論可能です。

 1.mallocで、必要なだけ構造体配列の領域を確保し、
   領域がデータでいっぱいになったらreallocで必要
   なだけ構造体配列の領域を確保し直す

 2.戻り値は(void *)なので、それを構造体ポインタに
   キャストしてポインタ変数に設定する

で実現できます。

初期割り当て値および増分値をどうするかでパフォーマンスが変わってきます。
この2つの値を簡単に変更できるようにしておくとよいでしょう。


# ここは.NETの会議室です。C言語に関しては、@ITクラブ Cafe 会議室に投稿されるといいですね。

[ メッセージ編集済み 編集者: たーぞう 編集日時 2004-08-17 12:07 ]
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2004-08-17 12:59
ちょっと強引かも知れませんが、

コード:
//グローバル変数


struct book *head;
struct book *p[2048]; /* ユーザー定義型ポインタの配列 2048個分 */

void initdata(void){
.
.
省略
.
.
while(fgets(buffer,SIZE,fp)){
.
.
省略
.
.
if((p[i] = (struct book *)malloc(sizeof(struct book))) == NULL){
printf("malloc error\n");
exit(1);
}
.
.
省略
.
.
}
.
.
省略
.
.
}



こんなんはあきませんか?

[ メッセージ編集済み 編集者: コブラ 編集日時 2004-08-17 13:06 ]

[ メッセージ編集済み 編集者: コブラ 編集日時 2004-08-17 13:12 ]
たーぞう
ぬし
会議室デビュー日: 2003/08/08
投稿数: 317
お住まい・勤務地: お花畑
投稿日時: 2004-08-17 14:25
> コブラ殿

ん〜あきまへん。
それやとデータ件数が2048件までに限定されてしまいます。
やっぱりrealloc使うて配列のサイズ増やしていきませんと。
Jitta
ぬし
会議室デビュー日: 2002/07/05
投稿数: 6267
お住まい・勤務地: 兵庫県・海手
投稿日時: 2004-08-18 08:40
引用:

通りすがりさんの書き込み (2004-08-17 08:31) より:

例えば、以上のソースでソートを行なう為にはポインタアドレスの
ジャンプが必要で実現させる為にfor を使ってp->nextを繰り返す関数を作ります。
で、行移動して欲しい中身を抜く目的は達成出来そうなのですが、
現存のソースを改変してmallocなりを使って動的に配列を作り
例えば p[ i ]->title の様な感じで直接、移動できる手法は可能なのでしょうか?


 私なら今のままか、ポインタの配列を格納する領域を別に作るか、かな?

 まず、charで確保する領域ですが、40バイト+nullで41とか、21ですか?しかし、sizeofで、配列のサイズを出してみてください。宣言しているバイト数をそのまま計算したのとは違うと思います。内部で最小単位に丸められます。8バイト単位にしておきましょう。
コード:

struct book{
struct book *next; /* 次のデータへのポインタ←慣例的に一番前 */
char title[48];
char author[24];
int bookid;
int abaiable;
};




 今の、構造体の中に次に確保した領域へのポインタを置く方法を、チェーンといいます。チェーンにすると、検索したり解放したりするのが少々面倒になるのですが、連続した領域が不要というメリットがあります。
 どういうことかというと、構造体のサイズが10だとします。この構造体の100個の配列を確保しようとすると、1000の連続した空き領域が必要になります。99個の配列があって、100個に拡張する場合は、一時的に1990か、現在確保している配列の領域の直後に10の空きが必要です。
 対してチェーンだと、どこかに10の空きが100個あればよいことになります。連続している必要はありません。
 連続している必要がない、というのは、フラグメンテーションの話しです。確保/解放が頻繁に行われ、確保の逆順に解放が行われるのでないなら、フラグメンテーションが発生します。すると、1000の空きはあるけれど、連続した空きはない、という場合が発生します。チェーンだと、こういう場合にも領域を確保しやすい、というメリットがあります。


 ポインタの配列を別に確保するというのは、例えば、今のプログラムはそのままで、
struct book* arrayAccess[n];
を用意します。
コード:

typedef struct bookArray {
struct book *p[0];
int count;
int capacity;
} BookArray;
BookArray books;


initData内でbook構造体を確保するごとに、books.p[count+1]にも、データを格納します。すると、books.pで、配列として扱うことができます。
 capacityは、配列として扱う領域がいくつあるかです、宣言では0なので、50くらいに初期化します。その後、“countがcapacityの9割を超えるならcapacityを1.5倍する”、などの処理を入れて、capacityを増やします。常にcount < capacityとなるようにします。

[ メッセージ編集済み 編集者: Jitta 編集日時 2004-08-18 08:41 ]
コブラ
ぬし
会議室デビュー日: 2003/07/18
投稿数: 1038
お住まい・勤務地: 神奈川
投稿日時: 2004-08-18 13:14
コード:
#define MAX 65536


//グローバル変数
struct book *head;
struct book *p[MAX]; /* ユーザー定義型ポインタの配列 2^16個分 */

void initdata(void)
{
int i=0;
.
.
省略
.
.
while(fgets(buffer,SIZE,fp)){
.
.
省略
.
.
if((p[i] = (struct book *)malloc(sizeof(struct book))) == NULL){
printf("malloc error\n");
exit(1);
}
.
.
省略
.
.
i++;
}
.
.
省略
.
.
}



足りんと言うなら足りるようにするまで。

[ メッセージ編集済み 編集者: コブラ 編集日時 2004-08-18 13:19 ]
たーぞう
ぬし
会議室デビュー日: 2003/08/08
投稿数: 317
お住まい・勤務地: お花畑
投稿日時: 2004-08-18 14:20
なるほど。
うまく動作しなかったらプログラムを修正して再実行すればいいわけですな。

今度、契約書にそう書いてみるか。

スキルアップ/キャリアアップ(JOB@IT)