ページ 11

リスト構造によるアドレス帳の作成

Posted: 2011年12月11日(日) 12:01
by astrea
この掲示板を始めて利用させていただきます。
C言語の勉強をしているのですが、アドレス帳を作るプログラムで詰まってしまいました。
課題丸投げという風になってしまって恐縮ですがよろしくお願いします。

プログラムは、リスト構造を使って名前・住所・電話番号・メールアドレスのデータを持った構造体のアドレス帳を作ること。
その際、txtなどのファイルから読み込み、データの追加・削除・参照・ソート(名前か電話番号を選んでソートする)ができる。
そして、データを書き換えた場合は新しいファイルに書き込むというものです。

こちらが途中のプログラムになります。

コード:

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

struct address{
  //アドレス帳のデータ
  char name[32];
  char addre[128];
  char number[12];
  char mail[64];
  
  struct address* next; //次の要素へのポインタ
};

/*プロトタイプ宣言*/
void data_show(struct address* head);
void data_add(struct address*head);
void data_delete(struct address*head);
void data_sort(struct address*head);
void data_write(struct address* head);

int main(void){
  struct address* head=NULL,*p;
  FILE* fp;
  int c;
  
  fp = fopen("address.csv","r");
  if(fp==NULL){
    printf("file not open\n");
    exit(1);
  }
  
  char ch[512];
  while(fscanf(fp,"%s",ch)!=EOF){
    p = (struct address*)malloc(sizeof(struct address));

    char* token=",";
    
    strcpy(p->name ,strtok(ch,token));	
    strcpy(p->addre ,strtok(NULL,token));	
    strcpy(p->number ,strtok(NULL,token));	
    strcpy(p->mail ,strtok(NULL,token));	
     
    p->next = head;
    head = p;
  }
  
  p = head;
  printf("\n");
	
  fclose(fp);
  
  int select;
  while(select != 0){
    printf("1:ソート 2:削除 3:追加 4:参照 0:終了\nメニューを選択してください:");
    scanf("%d",&select);
    switch(select){
    case 1: 
      data_sort(head);
      break;
    case 2:
      data_delete(head);
      break;
    case 3:
      data_add(head);
      break;
    case 4: 
      data_show(head);
      break;
    case 0: 
      printf("終了します\n");
      break;
    default:
      printf("もう一度選択してください\n"); 
      break;
    }
  }
  return 0;
}

//データの追加する関数
void data_add(struct address* head){
  struct address* p, *q, *new;
  char new_name[32];
  char new_addre[128];
  char new_number[12];
  char new_mail[64];

  printf("追加する要素を入力してください");
  printf("名前:"); scanf("%s",new_name);
  printf("住所:"); scanf("%s",new_addre);
  printf("電話番号:"); scanf("%s",new_number);
  printf("メールアドレス:"); scanf("%s",new_mail);

  p = head->next;
  q = head;

  while(p != NULL){
    q = p;
    p = p->next;
  }

  new = (struct address*)malloc(sizeof(struct address));

  strcpy(new->name,new_name);	
  strcpy(new->addre,new_addre);	
  strcpy(new->number,new_number);	
  strcpy(new->mail,new_mail);
  new->next = p;
  q->next = new;
  q = head;
  data_write(q);
}


void data_delete(struct address* head){
  char name[32];
  struct address* p, *before;

  printf("アドレスを削除します\n名前を入力してください:");
  scanf("%s", &name);
  p = head;
  int flag = 0;
  while(p != NULL && strcmp(p->name,name)!=0){
    before = p;
    p = p->next;
    flag = 1;
    printf("a\n");
  }
  printf("%s\n",p->name);
  if(flag==0 && strcmp(p->name,name) == 0){
    before = p;
    p = p->next;
    before = NULL;
  }
  else if(p == NULL){
    printf("アドレスに登録されていません\n");
  }else{
    before->next = p->next;
    p = head;
    printf("アドレスを削除しました\n\n");
    data_write(p);
  }
}

//データを参照する関数
void data_show(struct address* head){
  struct address* p = head;
  while(p){
    printf("%s,%s,%s,%s\n",p->name,p->addre,p->number,p->mail);
    p = p->next;
  }
  printf("\n");
}

//データソート関数
void data_sort(struct address* head){
  struct address* p,*before,*after;
  p = head;
  after = head->next;

  if(strcmp(p->name,after->name) > 0){
    p = after;
    p->next = head;
    p->next->next = after->next;
  }

  before = p;
  p = p->next;
  after = p->next;
  
  while(before){
    before = p;
    p = p->next;
    after = p->next;
      
  }
  printf("ソートしました\n");
}

//ファイルに書き込む関数
void data_write(struct address* p){
  FILE* fp2;
  struct address* tmp;

  fp2 = fopen("new_address.csv","w");
  if(fp2==NULL){
    printf("file not open\n");
    exit(1);
  }

  while(p){
    fprintf(fp2,"%s,%s,%s,%s\n",p->name,p->addre,p->number,p->mail);
    p = p->next;
  }
  fclose(fp2);
}



/*address.csvの中身*/
sato,tokyo,02018179484,hogehoge@docomo
suzuki,saitama,02037829163,prog@docomo
saito,yamanashi,03019295433,ruby_love@docomo
kato,kanagawa,02012345678,java@docomo


最終的には関数それぞれ別ファイルにわけたいので、先頭アドレスのheadを引数にしました。
追加と参照は出きるのですが、削除が上手くいかないのとソートの実装が分からない状況です。
双方向リスト構造というものを使うべきなのでしょうが、よく分からず詰まってしまいました。

雑なコードに長文失礼しました。
アドバイス等よろしくお願いします。できればソースを載せていただくと大変ありがたいです。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月11日(日) 13:11
by non
まず、削除から行きましょう。
削除では、どのようなときにうまくいかないのか説明してください。

なお、双方向リストにしなくても作れます。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月11日(日) 13:21
by astrea
nonさん、お早い返事ありがとうございます。
一番下のデータが上手く削除されないようです。
address.csvのkatoにあたります。
何度やっても削除されなかったり、無限ループに陥ったりしました。
なので、双方向リストにして前後をリスト構造にしなければならないのかと思ったのですが、
単方向でもできるとのことなのでご教授お願いします。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月11日(日) 14:08
by non
リストの先頭の削除ですよね。

コード:

  if(flag==0 && strcmp(p->name,name) == 0){
    before = p;
    p = p->next;
    before = NULL;
  }
この部分になります。
先頭を削除するのだから、headが指しているノードが変わるはずですよね。
もう一度
ここだけ考え直してください。
消したいノードはpが指しています。
削除前はheadはpを指しています。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月11日(日) 16:41
by astrea

コード:

 if(flag==0 && strcmp(p->name,name) == 0){
    head = p->next;
    p = head;
    printf("アドレスを削除しました\n");
    data_write(p);
  }
>nonさん
このようにしてみて最初は削除できたのですが、
もう一度他のを削除すると先頭が削除されていないことになっていました。
いろいろ試してみましたがわかりませんでした。
どうすればいいですか?

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月11日(日) 17:41
by non
考え方は良いのですが。。。
値渡しと参照渡しという言葉を聞いたことがありますか?
data_delete(head);
headはポインタです。そのポインタの値(すなわちノードのアドレス)を関数に渡してます。
すなわち、値渡し。
関数の中で、headに格納されているアドレスを変更しても、main側のheadのアドレスが
変わるわけではありません。
で、どうすればよいか。
3つの方法があります。
1 参照渡しにする。
   data_delete(&head);

2 returnでアドレスを返し、headに代入する。
   head=data_delete(head);

3 headをグローバルにする。

どれでもお好きな方法を。
なお、一番簡単なのは、3ですね。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月11日(日) 18:06
by astrea
おおお!
2のreturnで返す方法でできました!
なるほど参照渡しと値渡しについてまったく考えていませんでした。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月11日(日) 18:53
by beatle
もっと現実的なコードにするなら、headでもってリスト全体を表すのではなくて

コード:

struct LinkedList {
    struct address* head; // 先頭要素
    size_t size; // 要素数
};
のような、リンクリスト全体を表す構造体を作るのがいいのではないでしょうか。
data_xxx系の関数は

コード:

void data_xxx(struct LinkedList* list);
のようにします。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月11日(日) 19:01
by non
astrea さんが書きました:おおお!
2のreturnで返す方法でできました!
はいおめでとう。
では、次に、削除したノードはfreeするのが決まりです。
いらなくなるノードをポインタで指しておいて、つなぎかえた後、free しましょう。

そこまで、できたら、一度プログラムを載せてください。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月12日(月) 02:08
by astrea

コード:

struct address* data_delete(struct address* head){
  char name[32];
  struct address* p, *before;

  printf("アドレスを削除します\n名前を入力してください:");
  scanf("%s",name);
  p = head;
  int flag = 0;

  while(p != NULL && strcmp(p->name,name)!=0){
    before = p;
    p = p->next;
    flag = 1;
  }
  
if(flag==0 && strcmp(p->name,name) == 0){
    head = p->next;
    free(p);
    p = head;
    printf("アドレスを削除しました\n\n");
    data_write(p);
  }else{
    before->next = p->next;
    free(p);
    p = head;
    printf("アドレスを削除しました\n\n");
    data_write(p);
  }
  if(p == NULL){
    printf("アドレスに登録されていません\n\n");
  }

  return p;
 }
>nonさん
data_delete関数のプログラムです。
このような感じでいいでしょうか。
一応プログラム上では思ったとおりに動いてくれています。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月12日(月) 10:02
by non
削除したい名前が見つからない場合のチェックはしましたか?

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月12日(月) 11:01
by astrea
>nonさん
一致しなかった場合pがNULLになるので、

if(p == NULL){
printf("アドレスに登録されていません\n\n");
}

でNULLになったら、登録されていないと出るようにしました。
こういうことではないのでしょうか?

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月12日(月) 11:04
by non
実行してみましたか?

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月12日(月) 11:57
by astrea
>nonさん
すいません、勘違いしてました・・・。
segmentation faultがでたので

if(p == NULL){
printf("アドレスに登録されていません\n\n");
return head;
}

returnでheadを返しました。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月12日(月) 12:04
by non
それもありますが、それ以前に、今のままだと必ず削除に行くのでは?
最初の頃のプログラムは
else if で判断されていましたが・・・

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月12日(月) 13:19
by astrea

コード:

 if(flag==0 && strcmp(p->name,name) == 0){
    head = p->next;
    p = head;
    printf("アドレスを削除しました\n\n");
    data_write(p);
  }else if(p == NULL){
    printf("アドレスに登録されていません\n\n");
    data_write(head);
    return head;
  }else{
    before->next = p->next;
    p = head;
    printf("アドレスを削除しました\n\n");
    data_write(p);  
  } 
このように直しました。
これで大丈夫でしょうか?

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月12日(月) 13:33
by astrea

コード:

if(flag==0 && strcmp(p->name,name) == 0){
    head = p->next;
    free(p);
    p = head;
    printf("アドレスを削除しました\n\n");
    data_write(p);
  }else if(p == NULL){
    printf("アドレスに登録されていません\n\n");
    data_write(head);
    return head;
  }else{
    before->next = p->next;
    free(p);
    p = head;
    printf("アドレスを削除しました\n\n");
    data_write(p);  
  } 
freeを書き忘れてました。

Re: リスト構造によるアドレス帳の作成

Posted: 2011年12月12日(月) 13:39
by non
p=headにわざわざ入れなくても、data_write(head);
でよいので、数行は短くなると思いますが、まぁ、いいでしょう。

さて、ソートですが・・・・
一般的にリスト構造では、並び替えるのではなく、リスト構造を作るときに、昇順、または降順につなぎながら作成します。

今、astreaさんがやろうとしているのは、面倒なのでよく見ませんでしたが、一度できているリスト構造をつなぎ替えようと
しているように見えますが、そうですか?そうしなさいという課題なのでしょうか?

もし、読み込むのは今行われているように、常に先頭に追加する方法で行い、そのあと並び替えなければいけないなら、
今あるリストから1こずつ取り出し、別のリスト構造を作っていくという方法をとります。

どの方法でいきますか?

Re: リスト構造によるアドレス帳の作成

Posted: 2012年2月13日(月) 02:40
by QZaw55cn4c
こちらにコード例をあげておきました。
>一般的にリスト構造では、並び替えるのではなく、
というのはそのとおりだとは思いますが、この例ではリスト構造を直接並び替えています。
http://toro.2ch.net/test/read.cgi/tech/1328276597/243
ご参考になれば幸いです。

Re: リスト構造によるアドレス帳の作成

Posted: 2012年2月13日(月) 16:08
by ISLe
アドレス帳が空のときに削除を選ぶとヤバイのでは…。