C言語関係掲示板

過去ログ

No.1011 qsortの引数について

[戻る] [ホームページ]
No.1202

qsortの引数について
投稿者---KAZ(2004/02/02 01:08:08)


またお世話になります。今日は過去ログを参照し、qsortについて勉強していたのですが、qsortのしくみが理解できません。以下に実験用に作製したプログラムを示します。

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

int counter=0;

struct node{
  char filename[100];
  struct node *next;
};

int list_empty(struct node *first)
{
  return (first == NULL);
}

int list_length(struct node *first)
{
  int length=0;
  struct node *n;
  for(n = first; n != NULL; n=n->next)
    length++;
  return length;
}

void
list_print(struct node *first)
{
  struct node *n=first;
  while(n != NULL){
    printf("%s\n",n->filename);    
    n = n->next;
  }
 

}
struct node *
list_append(struct node *first, char *str)
{
  struct node *n;
  struct node *new=(struct node *)malloc(sizeof(struct node));
  
  strcpy(new->filename,str);
  new->next = NULL;

  if (first == NULL)
    return new;
  else{
    for(n=first; n->next != NULL; n=n->next);

    n->next = new;
    return first;
  }
}

int n_cmp(const struct node *x, const struct node *y)
{
  return strcmp(x->filename, y->filename);
}

int main(int argc, char *argv[])
{
  int i,len,n;
  struct node *list=NULL;
  
  len = list_length(list);

  list = list_append(list,"isvalid.c");
  list = list_append(list,"node.c");
  list = list_append(list,"haskell.c");
  list = list_append(list,"matrix.c");
  list = list_append(list,"converge.c");
  list = list_append(list,"divergent.c");
  
  list_print(list);

  //n = sizeof(list)/sizeof(*list);
  
  qsort(list,len,sizeof(struct node),(int(*)(const void*, const void*))n_cmp);

  printf("\nupdated:\n");
  list_print(list);
  
  return 0;

}



どこがまちがってるんでしょうか? qsortは

void qsort(void *base, size_t n, size_t size, int (*cmp)(const void *,const void *))

と定義されていて、調べたところ

base 配列の先頭アドレス
n 配列の長さ
size 配列の1つのバイト数
cmp 比較関数

となっているようなのですが、私の質問は

1.
私のプログラムはどこがまちがっているんでしょうか?(笑)

2.
過去ログをみていたら、よく配列の長さをとるのに
例) int se[]={1,2,3,4,5,6,7};
int length;
sizeof(se)/sizeof(se[0]);

としているのをみるのですが、これが理解できません。seは最初のアドレスをさすのでそれのサイズとなるとこの場合、アドレスの入ったポインターのサイズですよね、それをse[0]、1が入っている配列のサイズでわるとなぜ配列の長さがでるんでしょうか?

3
最後の引数に比較関数をいれるようなんですが、その関数の前にある

int(*)(const void*, const void*)

これは何をしているんでしょうか?また、比較関数(私のプログラムではn_cmp)の引数はどのようにわたされているのでしょうか?

どうか教えてください。よろしくおねがいします。

No.1203

Re:qsortの引数について
投稿者---YuO(2004/02/02 08:59:01)


>私のプログラムはどこがまちがっているんでしょうか?(笑)

・配列でないものにqsortを使っている
・qsortの比較関数として,int ()(const void *, const void *)でないものを強引に使っている

とりあえず,パッと見た感じでの問題点はこんなところです。


>過去ログをみていたら、よく配列の長さをとるのに
>例) int se[]={1,2,3,4,5,6,7};
> int length;
> sizeof(se)/sizeof(se[0]);
>としているのをみるのですが、これが理解できません。seは最初のアドレスをさすのでそれのサイズとなるとこの場合、アドレスの入ったポインターのサイズですよね、それをse[0]、1が入っている配列のサイズでわるとなぜ配列の長さがでるんでしょうか?

sizeof(se)はseというオブジェクト全体のサイズを返します。
sizeof演算子や単項の&演算子を適用する場合,
「配列を先頭の要素を指すポインタに変換する」という変換は働きません。

結果として,sizeof(se)は(sizeof(int) * 7)に相当する値になります。


>最後の引数に比較関数をいれるようなんですが、その関数の前にある
>int(*)(const void*, const void*)
>これは何をしているんでしょうか?また、比較関数(私のプログラムではn_cmp)の引数はどのようにわたされているのでしょうか?

「const void *の引数を二つとって,intを返す関数」へのポインタ型を意味しています。
引数は,n_cmpがqsort内部で呼び出されるときに普通に渡されます。

ちなみに,n_cmpは
int n_cmp(const void *, const void *);
という宣言でないといけません。
int n_cmp(const struct node *, const struct node *);
でも,キャストすればコンパイルは通るでしょうけれど,コンパイルが通る程度の安全性しかありません。


----関数ポインタのお試し----
#include <stdio.h>

void add (int a, int b)
{
    printf("%i + %i = %i\n", a, b, a + b);
}

void sub (int a, int b)
{
    printf("%i - %i = %i\n", a, b, a - b);
}

void mul (int a, int b)
{
    printf("%i * %i = %i\n", a, b, a * b);
}

void div (int a, int b)
{
    printf("%i / %i = %i\n", a, b, a / b);
}

void mod (int a, int b)
{
    printf("%i %% %i = %i\n", a, b, a % b);
}

int main (void)
{
    void (*func[])(int, int) = { add, sub, mul, div, mod };
    int i, a, b;

    puts("input number");
    scanf("%i%i", &a, &b);

    for (i = 0; i < sizeof(func)/ sizeof(func[0]); ++i) {
        func[i](a, b);
    }

    return 0;
}


No.1204

Re:qsortの引数について
投稿者---たか(2004/02/02 10:54:35)


私がぱっと見た感じでは、YuOさんとほとんど同じ印象を受けましたが、
まずリスト構造にqsort()を適用するのはまず間違いだと思って下さい。
ポインタがバラバラにばらけます。

リスト構造にはmerge_sort()が一番最適ではないかと思います。しかし
これも双方向リストでなくて単方向リストで制限が強いので部分リスト
を先頭から走査する事が何回も発生しますのでスピードは遅いでしょう。

後ろ方向への走査は楽なので単純選択ソートがアルゴリズム的には一番
簡単になります。速度は遅いですが。

No.1206

Re:qsortの引数について
投稿者---たか(2004/02/02 14:25:47)


単純選択ソートの例です。この版はポインタを付け替えてないので、
構造体の中身が増えると遅いです。ポインタを付け替えるにはどうしても
双方向リストの方が便利です。手持ちのサンプルを探してみましたが、
ポインタを付け替える版は失念してしまいました。この場合は一つ手前
のノードへのポインタと、交換しようとするノードへのポインタのそれ
ぞれ一つ前までを覚えておき、ポインタを付け替えるというとても面倒
な物になっていました。

双方向リストにするとquick_sort()も作ろうと思えば、作れます。但し
標準関数のqsort()はどちらにしろ使えません。

無理矢理qsort()を使おうとすれば、各ノードを指すポインタ配列を作って
おき、それをqsort()して、その後先頭から一気にポインタを付け替えれば
いいのですが、ポインタ配列という無駄なスペースが必要になります。
確かに速い事は速いですけど。

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

struct node {
  char filename[100];
  struct node *next;
};

int list_length(struct node *n)
{
  int length = 0;

  for (; n; n = n->next, length++);

  return length;
}

void list_print(struct node *n)
{
  while (n) {
    puts(n->filename);    
    n = n->next;
  }
}
struct node *list_append(struct node *first, char *str)
{
  struct node *n, *newn;
  
  if ((newn = (struct node *)malloc(sizeof(struct node))) == NULL) exit(1);
  
  strcpy(newn->filename, str);
  newn->next = NULL;

  if (!first)
    return newn;
  else {
    for (n = first; n->next; n = n->next);
    n->next = newn;
    return first;
  }
}

struct node *list_selection_sort(struct node *s)
{
  struct node *top = s, *t;
  char ft[100];
  
  if (s == NULL || s->next == NULL) return s;
  
  for (; s->next; s = s->next)
    for (t = s->next; t; t = t->next)
      if (strcmp(s->filename, t->filename) > 0) {
        strcpy(ft, s->filename);
        strcpy(s->filename, t->filename);
        strcpy(t->filename, ft);
      }

  return top;
}

void list_delete(struct node *s)
{
  struct node *t;
  
  while (s) {
    t = s;
    s = s->next;
    free(t);
  }
}

int main(void)
{
  int len;
  struct node *list = NULL;
  
  list = list_append(list, "isvalid.c");
  list = list_append(list, "node.c");
  list = list_append(list, "haskell.c");
  list = list_append(list, "matrix.c");
  list = list_append(list, "converge.c");
  list = list_append(list, "divergent.c");
  
  len = list_length(list);
  printf("List Length = %d\n", len);

  printf("\n--- Before Updated ---\n");
  list_print(list);

  list = list_selection_sort(list);

  //n = sizeof(list)/sizeof(*list);
  //qsort(list,len,sizeof(struct node),(int(*)(const void*, const void*))n_cmp);

  printf("\n--- After Updated ---\n");
  list_print(list);
  
  // 後始末
  list_delete(list);
  
  return 0;
}


No.1207

Re:qsortの引数について
投稿者---たか(2004/02/02 14:27:42)


今「双方向リストのランダム並び替え」というスレッドを見ましたが、
これと同じ方法でノードをそのまま配列にするとqsort()も使えますね。
但しこの方法はインチキっぽいです。リスト構造にする意味がありませ
んし。

No.1213

qsortについて
投稿者---KAZ(2004/02/02 17:14:40)


みなさん親切なレス有難うございます。
qsortはそもそも配列に使うものなんですね。知りませんでした。
YUOさんsizeofについての説明有難うございます。理解することができました。
ですが、もうひとつの質問に答えていただいたほうが理解できていません。

>ちなみに,n_cmpは

>int n_cmp(const void *, const void *);

>という宣言でないといけません。

>int n_cmp(const struct node *, const struct node *);

>でも,キャストすればコンパイルは通るでしょうけれど,コンパイルが通>る程度の安全性しかありません。

と答えていただいたのですが、

int n_cmp(const void *, const void *);

で宣言したときにこのファンクションの中を作る方法がわかりません。
私が前に示したリンクリストのプログラムは下のインターネットで見つけたプログラムを参考につくったものです。

/*
    qsort関数を用いて配列をソート
*/

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

typedef struct {
    char  name[10];     /* 名前 */
    int   height;       /* 身長 */
    int   weight;       /* 体重 */
} person;

/*--- person型オブジェクトの比較関数(名前昇順) ---*/
int npcmp(const person *x, const person *y)
{
    return (strcmp(x->name, y->name));
}

/*--- person型オブジェクトの比較関数(身長昇順) ---*/
int hpcmp(const person *x, const person *y)
{
    return (x->height < y->height ? -1 :
            x->height < y->height ?  1 : 0);
}

/*--- person型オブジェクトの比較関数(体重降順) ---*/
int wdcmp(const person *x, const person *y)
{
    return (x->height < y->height ?  1 :
            x->height < y->height ? -1 : 0);/* 降順 */
}

/*--- 一人分のデータを表示 ---*/
void print_person(person x)
{
    printf("%-10.10s %dcm %dkg\n", x.name, x.height, x.weight);
}

int main(void)
{
    int  i;
    person x[]= {{"Shibata", 170, 52},
                 {"Takaoka", 180, 70},
                 {"Nangoh",  172, 63},
                };
    int  nx = sizeof(x) / sizeof(x[0]);     /* 配列xの要素数 */

    puts("ソート前");
    for (i = 0; i < nx; i++)
        print_person(x[i]);

    /* 名前昇順にソート */
    qsort(x, nx, sizeof(person), (int(*)(const void*, const void*))npcmp);

    puts("\n名前昇順ソート後");
    for (i = 0; i < nx; i++)
        print_person(x[i]);

    /* 身長昇順にソート */
    qsort(x, nx, sizeof(person), (int(*)(const void*, const void*))hpcmp);

    puts("\n身長昇順ソート後");
    for (i = 0; i < nx; i++)
        print_person(x[i]);

    /* 体重降順にソート */
    qsort(x, nx, sizeof(person), (int(*)(const void*, const void*))wdcmp);

    puts("\n体重降順ソート後");
    for (i = 0; i < nx; i++)
        print_person(x[i]);

    return (0);
}

ここではnpcmpの引数にstructをつかっているようなのですが、これはあまりよくないのでしょうか?
今回、私はqsortの勉強のためにディレクトリにあるファイルの名前をアルファベット順にならべてみようと思い構造体のソートに挑戦しているんですが、
qsortは配列に使うものということなので構造体をリンクリストから配列に直してみました。(これではファイルの数に制限ができてしまいますが。。。今回はqsortの使い方を覚えたいので。)
ですが、やはりうまくいきません。以下にプログラムを示します。
どうか間違いを指摘ください。(構造体内のファイルの名前が格納される文字
列の変数のなまえがpathとなっていますが、いずれここにpathをいれるつも
りなので現段階ではそれをつかってます。)

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>


struct File_info{
  char path[500];
  int size;
};


void file_list(char *dir, struct File_info *file_info);
int n_cmp(const struct File_info *x, const struct File_info *y);

int counter=0,num=0;

int main(int argc, char *argv[])
{
  struct File_info file_info[100];
  int k;
  char dir_name[100];

  strcpy(dir_name,argv[1]);

  file_list(dir_name,file_info);

  for(k=0; k<counter; k++){
  printf("file path is %s and its size is %d.\n", 
     file_info[k].path,file_info[k].size);
  }

  int n = sizeof(file_info) / sizeof(file_info[0]);

  qsort(file_info, n, sizeof(struct File_info),
    (int(*)(const void*,const void*))n_cmp);

  printf("Updated:\n");
  for(k=0; k<counter; k++){
  printf("file path is %s and its size is %d.\n", 
     file_info[k].path,file_info[k].size);
  }

  return 0;
}

int n_cmp(const struct File_info *x, const struct File_info *y)
{
    return (strcmp(x->path, y->path));
}


void file_list(char *dir, struct File_info *file_info)
{
  struct dirent *entry;  
  struct stat statbuf;  
  char *tail;
  DIR *dp;
  
 
  printf("%s/\n",dir);

  dp = opendir(dir);
  if (dp == NULL) { 
    perror("Can not open the directory.\n"); 
    exit(1); 
  }
  printf("dir is %s\n",dir);
  tail = dir + strlen(dir);
  *tail++ = '/';

  while ((entry = readdir(dp)) != NULL) {
    strcpy(tail, entry->d_name);
  
    stat(dir, &statbuf);
    if (!S_ISDIR(statbuf.st_mode)){
      int n = strlen(entry->d_name);
      if ((n > 2 && strcmp(entry->d_name + n - 2, ".c") == 0)){
    strcpy(file_info[num].path,entry->d_name);
    file_info[num].size = statbuf.st_size + 50;
    num++;
    counter++;
    printf("  %s\n", entry->d_name);
      }        
    }
  }
  rewinddir(dp);
  while ((entry = readdir(dp)) != NULL) {
    if (strcmp(entry->d_name, ".") == 0) continue;
    if (strcmp(entry->d_name, "..") == 0) continue;
    strcpy(tail, entry->d_name);
    stat(dir, &statbuf);
    if (S_ISDIR(statbuf.st_mode)){
      file_list(dir,file_info);
    }
  }
  closedir(dp);
}





No.1214

Re:qsortについて
投稿者---たか(2004/02/02 18:03:28)


私の環境ではこのプログラムはコンパイルできるものの実行できません
ので、qsort()用の比較関数の書き方だけ修正した物をUPします。
済みませんがUnixやLinuxが使える環境にある方、御検証願えないで
しょうか。

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>


struct File_info{
  char path[500];
  int size;
};


void file_list(char *dir, struct File_info *file_info);
int n_cmp(const void *, const void *);

int counter=0,num=0;

int main(int argc, char *argv[])
{
  struct File_info file_info[100];
  int n, k;
  char dir_name[100];

  strcpy(dir_name,argv[1]);

  file_list(dir_name,file_info);

  for(k=0; k<counter; k++){
  printf("file path is %s and its size is %d.\n", 
     file_info[k].path,file_info[k].size);
  }

  n = sizeof(file_info) / sizeof(file_info[0]);

  qsort(file_info, n, sizeof(struct File_info), n_cmp);

  printf("Updated:\n");
  for(k=0; k<counter; k++){
  printf("file path is %s and its size is %d.\n", 
     file_info[k].path,file_info[k].size);
  }

  return 0;
}

int n_cmp(const void *x, const void *y)
{
  return strcmp(((struct File_info *)x)->path, ((struct File_info *)y)->path);
}


void file_list(char *dir, struct File_info *file_info)
{
  struct dirent *entry;  
  struct stat statbuf;  
  char *tail;
  DIR *dp;
  
 
  printf("%s/\n",dir);

  dp = opendir(dir);
  if (dp == NULL) { 
    perror("Can not open the directory.\n"); 
    exit(1); 
  }
  printf("dir is %s\n",dir);
  tail = dir + strlen(dir);
  *tail++ = '/';

  while ((entry = readdir(dp)) != NULL) {
    strcpy(tail, entry->d_name);
  
    stat(dir, &statbuf);
    if (!S_ISDIR(statbuf.st_mode)){
      int n = strlen(entry->d_name);
      if ((n > 2 && strcmp(entry->d_name + n - 2, ".c") == 0)){
    strcpy(file_info[num].path,entry->d_name);
    file_info[num].size = statbuf.st_size + 50;
    num++;
    counter++;
    printf("  %s\n", entry->d_name);
      }        
    }
  }
  rewinddir(dp);
  while ((entry = readdir(dp)) != NULL) {
    if (strcmp(entry->d_name, ".") == 0) continue;
    if (strcmp(entry->d_name, "..") == 0) continue;
    strcpy(tail, entry->d_name);
    stat(dir, &statbuf);
    if (S_ISDIR(statbuf.st_mode)){
      file_list(dir,file_info);
    }
  }
  closedir(dp);
}


No.1217

Re:qsortについて
投稿者---YuO(2004/02/02 19:28:52)


> ここではnpcmpの引数にstructをつかっているようなのですが、これはあまりよくないのでしょうか?

だめです。
関数の定義された型と,呼び出し時の型が違った場合,その動作は未定義とされています。
qsortやbsearchで利用する場合,関数内で引数を本来の型に戻して利用します。

元々,コンパイラがわかる形でvoid *とT *(Tはオブジェクト型または不完全型)の間で変換する分には問題ないのですが,
コンパイラが与り知らぬところでvoid *とT *を変換しようとしても,状況次第で失敗します。

そもそも,「明示的なキャストが必要であること」は基本的に「よくないこと」だと思ってください。
今回もそうですが,たぶんコンパイラが警告かエラーを発したのでキャストが付けられたのだと思いますが,
そのようなキャストの使い方は最悪です。


> ですが、やはりうまくいきません。以下にプログラムを示します。

うまくいかない,というのはあなたが思っていることです。
コンピュータはまず間違いなくあなたが書いたとおりのことを行っています。

デバッガを使ったり,printfを散りばめたりして,
どこで予想と違う動きをしているのかを探し出してみるとよいでしょう。

No.1219

Re:qsortについて
投稿者---KAZ(2004/02/02 22:22:06)


たかさん、YUOさんアドバイスありがとうございます。たかさんが書いてくれた比較関数と同じものを私も過去ログで見つけ、試してみました。
結果は

qsort後:
Updated:
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.

とこのような結果になりました。そこでqsort後の結果をすべての配列を表示させてみたところ、

Updated:
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 0.
file path is and its size is 448693.
file path is and its size is 0.
file path is and its size is 134515006.
file path is ,&#65533;&#65533;U and its size is 0.
file path is N&#65533; and its size is 1236992.
file path is ask.c and its size is 2097.
file path is backup.c and its size is 2188.
file path is backup_2.c and its size is 2176.
file path is bms.c and its size is 76643.
file path is dir.c and its size is 630.
file path is dir3.c and its size is 1480.
file path is dir4.c and its size is 50.
file path is direct.c and its size is 780.
file path is filelist.c and its size is 2047.
file path is filesize.c and its size is 296.
file path is kk.c and its size is 50.
file path is look.c and its size is 313.
file path is qs.c and its size is 1857.
file path is rec.c and its size is 1165.
file path is test.c and its size is 314.

とこのように配列の最後のほうにくっついてしまっています。なぜでしょうか? このプログラムを簡単にするのに、いったん他に配列をつくってそこにファイル名をいれて、それにqsortを実行するのが簡単なのかもしれませんが、それでは構造体に対するqsortが学べないのでこの方法でやってみたいのですが。
この原因をさぐるためには、比較関数について理解しなくてはいけないと
思い、ずっと調べてはいるのですが、どうしても理解できません。どうかご教授願います。

int n_cmp(const void *x, const void *y)
{
return strcmp(((struct File_info *)x)->path, ((struct File_info *)y)->path);
}

ここで

((struct File_info *)x)->path

としているのですが、なぜ
単純に

x->path

ではだめなのでしょうか?おかしなことをきいているかもしれませんがどうかよろしくお願いします。

>そもそも,「明示的なキャストが必要であること」は基本的に「よくない>こと」だと思ってください。
>今回もそうですが,たぶんコンパイラが警告かエラーを発したのでキャス>トが付けられたのだと思いますが,
>そのようなキャストの使い方は最悪です。

なるほどです。

>デバッガを使ったり,printfを散りばめたりして,
>どこで予想と違う動きをしているのかを探し出してみるとよいでしょう。

私はプログラミングをはじめて一ヶ月半のビギナーです。デバッガをつかうなんて十年はやいと思ってます。(笑)
YUOさんのおっしゃるとおりプログラムは私が書いたようにしか動きませんよね。毎日プログラムを書いてはやく上達したいと思い、やっているのですが、それでも理解できないことがたくさんでてきて、ここの掲示板で答えてもらえてすごくありがたいと思っています。いつか自分がここで他の人に
教えれるようになれればいいんですが。。それまでは教えてください。(笑)

No.1220

Re:qsortについて
投稿者---かずま(2004/02/03 02:41:05)


> とこのように配列の最後のほうにくっついてしまっています。なぜでしょうか?

qsort(file_info, n, sizeof(struct File_info), n_cmp); の n がおかしい。
num または counter にしてみてください。


> ここで
>
> ((struct File_info *)x)->path
>
> としているのですが、なぜ
> 単純に
>
> x->path
>
> ではだめなのでしょうか?

x は const void へのポインタです。const void は構造体ではないので
path というメンバは存在しません。

struct File_info へのポインタに変換すれば、メンバの path を参照できます。

No.1221

できました!! ですが、理解できないところが。。
投稿者---KAZ(2004/02/03 02:51:39)


うおー、6時間かけて今日はこのqsortのプログラムを何個かいたことか。。
ようやく正しく動くものができました。とりあえずソースをみていただけますか?

#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>


struct File_info{
  char path[500];
  int size;
};


void file_list(char *dir, struct File_info *file_info);
int n_cmp(const void *, const void *);

int counter=0,num=0;

int main(int argc, char *argv[])
{
  struct File_info file_info[100];
  int n, k;
  char dir_name[100];

  strcpy(dir_name,argv[1]);

  file_list(dir_name,file_info);

  for(k=0; k<counter; k++){
  printf("file path is %s and its size is %d.\n", 
     file_info[k].path,file_info[k].size);
  }

  n = sizeof(file_info) /sizeof(file_info[0]);
  
  printf("\nn is %d.\n\n",n);
  qsort(file_info, counter, sizeof(struct File_info), n_cmp);

  printf("Updated:\n");
  for(k=0; k<counter; k++){
  printf("file path is %s and its size is %d.\n", 
     file_info[k].path,file_info[k].size);
  }

  return 0;
}

int n_cmp(const void *x, const void *y)
{
  return strcmp((char *)((struct File_info *)x)->path, (char *)((struct File_info *)y)->path);
}


void file_list(char *dir, struct File_info *file_info)
{
  struct dirent *entry;  
  struct stat statbuf;  
  char *tail;
  DIR *dp;
  
 
  printf("%s/\n",dir);

  dp = opendir(dir);
  if (dp == NULL) { 
    perror("Can not open the directory.\n"); 
    exit(1); 
  }
  printf("dir is %s\n",dir);
  tail = dir + strlen(dir);
  *tail++ = '/';

  while ((entry = readdir(dp)) != NULL) {
    strcpy(tail, entry->d_name);
  
    stat(dir, &statbuf);
    if (!S_ISDIR(statbuf.st_mode)){
      int n = strlen(entry->d_name);
      if ((n > 2 && strcmp(entry->d_name + n - 2, ".c") == 0)){
    strcpy(file_info[num].path,entry->d_name);
    file_info[num].size = statbuf.st_size + 50;
    num++;
    counter++;
    printf("  %s\n", entry->d_name);
      }        
    }
  }
  rewinddir(dp);
  while ((entry = readdir(dp)) != NULL) {
    if (strcmp(entry->d_name, ".") == 0) continue;
    if (strcmp(entry->d_name, "..") == 0) continue;
    strcpy(tail, entry->d_name);
    stat(dir, &statbuf);
    if (S_ISDIR(statbuf.st_mode)){
      file_list(dir,file_info);
    }
  }
  closedir(dp);
}

実行結果です。

file path is bms.c and its size is 76643.
file path is look.c and its size is 313.
file path is backup.c and its size is 2188.
file path is dir3.c and its size is 1480.
file path is backup_2.c and its size is 2176.
file path is filelist.c and its size is 2047.
file path is dir4.c and its size is 50.
file path is test.c and its size is 314.
file path is filesize.c and its size is 296.
file path is ask.c and its size is 2148.
file path is qs.c and its size is 1857.
file path is direct.c and its size is 780.
file path is rec.c and its size is 1165.
file path is dir.c and its size is 630.
file path is kk.c and its size is 50.

n is 100.

Updated:
file path is ask.c and its size is 2148.
file path is backup.c and its size is 2188.
file path is backup_2.c and its size is 2176.
file path is bms.c and its size is 76643.
file path is dir.c and its size is 630.
file path is dir3.c and its size is 1480.
file path is dir4.c and its size is 50.
file path is direct.c and its size is 780.
file path is filelist.c and its size is 2047.
file path is filesize.c and its size is 296.
file path is kk.c and its size is 50.
file path is look.c and its size is 313.
file path is qs.c and its size is 1857.
file path is rec.c and its size is 1165.
file path is test.c and its size is 314.

ようやく正しく動くものができたのですが、完全に理解できてません。
この一行で配列の要素数をとっているのですが、

 n = sizeof(file_info) /sizeof(file_info[0]);
  
nを表示させたところ、

n=100

とでました。これは配列の要素数ですよね。でもこれをqsortにわたしたら以前示した結果がでました。qsortにわたす配列の要素数というのはデータが格納されているものの要素数なのでしょうか? このプログラムではデータのはいった配列の数をわたすことによって自分が思っているようにうごいてく
れました。配列にフルにデータがはいってないとこの

 n = sizeof(file_info) /sizeof(file_info[0]);

テクニックはつかえないんでしょうか?

もうひとつクリアにならない部分があります。この前の書き込みにも書いてるんですが、

return strcmp((char *)((struct File_info *)x)->path, (char *)((struct File_info *)y)->path);

比較関数の作り方です。int型、char型、構造体、すべての配列でqsortのプログラムをつくってみました。そこで共通しているのは比較関数でのキャストです。これが理解できません。なぜ単純に

return strcmp(x->path,y->path);

とできないのでしょうか?どうか教えてください。


No.1222

Re:できました!! ですが、理解できないところが。。
投稿者---YuO(2004/02/03 09:56:40)


> void file_list(char *dir, struct File_info *file_info);
> int counter=0,num=0;

numもcounterも一つでよい上に,わざわざ大域変数にする必要はないです。
file_listの戻り値としてcounterの値を返せばよいのですから。

大域変数にする前に,本当に大域変数にする必要があるのか検討を加える必要があります。
引数や戻り値でよいものを大域変数にすると,思わぬところで書き変わったり,
逆に書き変わっていなかったりしてバグの元になります。


> qsortにわたす配列の要素数というのはデータが格納されているものの要素数なのでしょうか?

そういうことです。
どのデータが有効かを判断できるのは,プログラマのみであることをお忘れなく。


> 配列にフルにデータがはいってないとこの
> n = sizeof(file_info) /sizeof(file_info[0]);
> テクニックはつかえないんでしょうか?

一般に,配列の要素数≠有効な値の入っている要素数です。
配列サイズを要素サイズで割って得られるのは配列の要素数になります。

書き込み可能な最大数を与えるためにこの方法を利用することは可能ですが,
書き込まれた要素の数を得ることはできません。
そもそも,一般にこの値はコンパイル時の定数になります。


> return strcmp((char *)((struct File_info *)x)->path, (char *)((struct File_info *)y)->path);

char *へのキャストが不要です。


> int型、char型、構造体、すべての配列でqsortのプログラムをつくってみました。
> そこで共通しているのは比較関数でのキャストです。これが理解できません。なぜ単純に
> return strcmp(x->path,y->path);
> とできないのでしょうか?どうか教えてください。

xとは何ですか?pathとは何ですか?それをCコンパイラの立場から考えてみて下さい。
#型をちゃんと意識してみる必要があります。


No.1228

Re:できました!! ですが、理解できないところが。。
投稿者---たか(2004/02/03 22:25:12)


>> 配列にフルにデータがはいってないとこの
>> n = sizeof(file_info) /sizeof(file_info[0]);
>> テクニックはつかえないんでしょうか?

ありゃりゃりゃ、ここを直すのを忘れていました。nは読み込んだファイル
名と一致していないと余分な配列(いじっていない配列)をソートの対象に
してしまいますね。

>> int型、char型、構造体、すべての配列でqsortのプログラムをつくってみました。
>> そこで共通しているのは比較関数でのキャストです。これが理解できません。なぜ単純に
>> return strcmp(x->path,y->path);
>> とできないのでしょうか?どうか教えてください。

void * 型というのはコンパイラにとってはどんな型をも指せる特別な
ポインタです。逆に言うとキャストして型をはっきりコンパイラに伝え
ないと、コンパイラはこのポインタが指しているオブジェクトのサイズ
(sizeof)もオフセットも決定できず、従って通常のポインタ演算はおろ
か、構造体のメンバを指すこともできません。

アセンブリでインデックスレジスタがどこかのアドレスを指していたと
しても、それだけではインデックスレジスタが指すアドレスの中身の情報
は何も得られないのと同じです。

No.1229

みなさん、ありがとうございました。
投稿者---KAZ(2004/02/03 23:13:25)


みなさんの丁寧な解説のおかげで比較関数のなかのキャストのしくみなどが見えてきました。すごく勉強になりました。ありがとうございます。YUOさんにいただいたアドバイス


> void file_list(char *dir, struct File_info *file_info);
> int counter=0,num=0;

>numもcounterも一つでよい上に,わざわざ大域変数にする必要はないで>す。
>file_listの戻り値としてcounterの値を返せばよいのですから。

たしかにcounterは1つで十分ですね。グローバル変数を使うと簡単に出来ることがおおいのでたよってしましがちですが、よく考えて使うべきなんですね。ただYUOさんのコメント

>file_listの戻り値としてcounterの値を返せばよいのですから。

の意味がわかりませんでした。file_listは再帰的関数なんですが、この戻り値を使うとはどういう意味でしょうか?

No.1230

Re:みなさん、ありがとうございました。
投稿者---YuO(2004/02/03 23:36:56)


>>file_listの戻り値としてcounterの値を返せばよいのですから。
>の意味がわかりませんでした。file_listは再帰的関数なんですが、この戻り値を使うとはどういう意味でしょうか?

1221のプログラムに関して,
void file_list(char *dir, struct File_info *file_info)
int file_list(char *dir, struct File_info *file_info)

に,
  struct dirent *entry;  
  struct stat statbuf;  
  char *tail;
  DIR *dp;
  struct dirent *entry;  
  struct stat statbuf;  
  char *tail;
  DIR *dp;
  int num = 0;

に,
      file_list(dir,file_info);
      num += file_list(dir, file_info + num); /* a */

に,
}
  return num; /* b */
}

に,それぞれ書き換えると,戻り値として格納した数が返ります。
#最初の前方宣言も書き換える必要がありますが。

aの前後及びbの直前にnumを出力するようなコードを挿入してnumの値の変化を調べてみたり,
file_listが呼び出されるときのfile_infoの変化を調べてみると,
何をやっているのかがわかると思います。