scandir函數

         在C語言課程的後端,講完指針和標準文件IO處理,我會做出一個難度較大練習,題目就是,利用標準的目錄處理函數 opendir/readdir/closedir實現類似於 scandir的功能。其中接口要scandir 函數一致

  這個題目看起來簡單,實現難度相當大,主要採用複雜指針的操作。我第一次拿出來測試,全班大約只一二名實現80%的功能,其餘很多覺得無從下手。程序很容易就會出現段錯誤。基本上短時間內正確的做出來的人可以劃歸專業級的程度了。有興趣的人可以先不看後面內容,自行實現一下

首先看一下man的scandir 接口定義

int scandir(const char *dir, struct dirent ***namelist,
              int(*filter)(const struct dirent *),
              int(*compar)(const struct dirent **, const struct dirent **));

,從定義來看就不是一個簡單的函數,形參裏,出現一個三級指針,二個函數指針。它的功能是,掃描名字爲dir的目錄,把滿足filter函數的過濾條件(即filter執行爲非0值)的目錄項加入到一維指針數組namelist.數組的總長度爲返回值n,如果compar不爲空,則最終輸出結果還要調用qsort來對數組進行排序後再輸出。

從scandir的演示代碼,我們可以推算出namelist是一個指向一維指針數組的指針。(一維指針數組等同於 struct dirent ** namelist,這裏寫在三級指針是因爲要從函數裏改變namelist的值,必須再多做一級)原因可以參考我的函數傳值類型的說明

#include <sys/types.h>
#include <dirent.h>

#include <sys/stat.h>
#include <unistd.h>

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

//掃描所有的lib打頭的文件

 int filter_fn(const struct dirent * ent)
 {
   if(ent->d_type != DT_REG)
     return 0;
     
   return (strncmp(ent->d_name,"lib",3) == 0);
 }


void scan_lib(char * dir_name)
{
  int n;
   struct dirent **namelist; // struct dirent * namelist[];

   n = scandir(dir_name, &namelist, filter_fn, alphasort);
   if (n < 0)
        perror("scandir");
   else {
               while(n--) {
                   printf("%s\n", namelist[n]->d_name);
                   free(namelist[n]);
               }
               free(namelist);
           }
}

int main(int argc ,char * argv[])
{
   scan_lib("/usr/lib");
}

最後:

/*
 * Author : Andrew Huang
 *
*/
#define MAX_DIR_ENT 1024

typedef int(*qsort_compar)(const void *, const void *);

int hxy_scandir(const char *dir, struct dirent ***namelist,
              int(*filter)(const struct dirent *),
              int(*compar)(const struct dirent **, const struct dirent **))
{
  DIR * od;
  int n = 0;
  struct dirent ** list = NULL;
  struct dirent * ent ,* p;
  
  if((dir == NULL) || (namelist == NULL))
    return -1;
    
  od = opendir(dir);
  if(od == NULL)
    return -1;
    
   /* 分配一個最大數組 */
  list = (struct dirent **)malloc(MAX_DIR_ENT*sizeof(struct dirent *));
  
  
   while(( ent = readdir(od)) != NULL)
    {
       if( filter && !filter(ent))
          continue;
          
        
          p = (struct dirent *)malloc(sizeof(struct dirent));
          
          memcpy((void *)p,(void *)ent,sizeof(struct dirent));
          list[n] = p;
          
          n++;
          if(n >= MAX_DIR_ENT)
            break;
       
    }
       
    closedir(od);
   
    /* 改變返回數組大小*/
    *namelist = realloc((void *)list,n*sizeof(struct dirent *));
     if(*namelist == NULL)
        *namelist = list;
  

    /* 數組排序*/
    if(compar)
       qsort((void *)*namelist,n,sizeof(struct dirent *),(qsort_compar)compar);
     
    return n;
   
}

 

程序分析

1.這一個程序的第一個難點是 namelist個數不確定的.是根據掃描目錄的結果來確定,並且通過返回值告訴調用者.一種辦法是做兩次循環,先掃描一次readdir從頭讀一次,確定個數,然後再重新讀一次讀入內容,這樣結果是準確了,但是效率極低.另外一種方法讀入時採用是使用鏈表緩存,然後最後一次性存入數組.這樣代碼過於複雜了.

  最後採用一個折中的辦法,即開始一次性分配最大值(1024)的數組,在讀入時直接對數組操作.這樣代碼處理簡單,絕大部分情況能正確運行.萬一有超過1024,一種是簡單丟棄多餘,二是擴大最大值.這個方法是在效率和正確性採用一個折衷。

 

2.最後輸出時,可以用realloc調整namelist大小再輸出,這樣可以節約堆空間。

3.關於最後的數組的排序,scandir文檔明確告之是採用qsort進行排序,因此最後需要進行這一步,關鍵是參數怎麼填寫。

4.這個函數內部的指針操作相當複雜,象三級指針namelist最好不要直接使用,而是要在函數用一箇中間指針變量struct dirent ** list 來簡化。而且在函數是直接將其作爲數組

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章