····要编写ls命令,首先要了解它能做什么,完成了什么工作,是如何完成这些工作的····
一、ls命令能做什么?
 
        我们在命令行输入ls,ls默认找出当前目录中所有文件的文件名,并且按照字典序排序后输出。ls命令有很多参数选项,可以决定ls的输出内容。如果参数是目录,ls列出目录的内容,如果参数是文件,ls列出文件名和属性。
	  例如:ls -l  
 
           
        (每行7个字段,mode+links+owers+group+size+last-modified+name)
问题在于:
1.如何列出目录的内容。
2.如何读取并显示文件的属性。
3.给出一个名字,如何判断出它是目录还是文件。
二、ls命令是如何工作的?
文件和目录被组成一棵目录树结构。目录是一种特殊的文件,其内容是文件和目录的名字。目录是记录的序列,每条记录对应一个文件或子目录,通过readdir来读取目录中的记录,readdir返回一个指向目录的当前记录的指针,记录的类型是struct_dirent。
三、如何编写ls命令?
  框架:
    mian()
	    opendir
	    while(readdir)
      print d_name
	    closedir
几个结构体的解析:
1、结构体__dirstream:
struct __dirstream{
         void* __fd;
         char* __data;
         int __entry_data;
         char* __ptr;
         int __entry_ptr;
         size_t __allocation;
         size_t __size;
         __libc_lock_define(,__lock)
};
typedef struct __dirstream DIR;
       
        DIR结构体类似于FILE,是一个内部结构,以下几个函数用这个内部结构保存当前正在被读取的目录的有关信息。
  Eg: DIR *opendir(const char 
      *parhname);打开文件目录,返回指向DIR 结构体的指针。
	     struct dirent *readdir(DIR 
      *dp);
	     void rewinddir(DIR 
      *dp);
	     int closedir(DIR 
      *dp);
	     long telldir(DIR 
      *dp);
	     void seekdir(DIR 
      *dp,long loc);
2、结构体 dirent:
struct dirent { 
      ino_t d_ino; /* inode number 索引节点号*/ 
      off_t d_off; /* offset to the next dirent 在目录文件中的偏移*/ 
      unsigned short d_reclen; /* length of this record 文件名长*/ 
      unsigned char d_type; /* type of file; not supported by all file system types文件类型 */ 
      char d_name[256]; /* filename 文件名,最长255字符*/ 
}; 
         目录文件(directory 
      file):这种文件包含了其他文件的名字以及指向与这些文件有关的信息的指针。从此可以看出,dirent不仅仅指向目录,还指向目录中的具体文件。(readdir函数同样也可以读取目录下的文件,这就是证据)
	  从上述的定义我们可以看出dirent结构体中存储的有关文件的信息太少,不能满足我们的需求,即dirent只是起到了索引的作用。所以我们要想获得ls 
      -l类似的信息,必须通过stat函数获取。
	通过readdir函数读取到文件名并存储到结构体dirent的d_name成员中,然后通过int 
      stat(const *file_name,struct stat 
      *buf)获取文件名为d_name的文件的详细信息,并存储在stat结构体中。
3、结构体 stat:
struct stat { 
     dev_t st_dev; /* ID of device containing file 文件使用的设备号*/ 
     ino_t st_ino; /* inode number 索引节点号*/ 
     mode_t st_mode; /* protection 文件访问权限*/ 
     nlink_t st_nlink; /* number of hard links 文件的硬链接数*/ 
     uid_t st_uid; /* user ID of owner 所有者用户识别号*/ 
     gid_t st_gid; /* group ID of owner 组识别号*/ 
     dev_t st_rdev; /* device ID (if special file) 设备文件的设备号*/ 
     off_t st_size; /* total size, in bytes 以字节为单位的文件容量*/ 
     blksize_t st_blksize; /* blocksize for file system I/O 包含该文件的磁盘块的大小*/ 
     blkcnt_t st_blocks; /* number of 512B blocks allocated 该文件所占的磁盘块*/ 
     time_t st_atime; /* time of last access 最后一次访问该文件的时间*/ 
     time_t st_mtime; /* time of last modification 最后一次修改该文件的时间*/ 
     time_t st_ctime; /* time of last status change 最后一次改变文件状态的时间*/ 
};
      struct stat中的st_mode值各个位代表的含义:
The following flags are defined for the st_mode field:
      /*  是什么类型的文件  */
           S_IFMT     0170000   bit mask for the file type bit fields
           S_IFSOCK   0140000   socket
           S_IFLNK    0120000   symbolic link
           S_IFREG    0100000   regular file
           S_IFBLK    0060000   block device
           S_IFDIR    0040000   directory
           S_IFCHR    0020000   character device
           S_IFIFO    0010000   FIFO
           S_ISUID    0004000   set UID bit
           S_ISGID    0002000   set-group-ID bit (see below)
           S_ISVTX    0001000   sticky bit (see below)
      /*  是否有可读写权限  */
           S_IRWXU    00700     mask for file owner permissions
           S_IRUSR    00400     owner has read permission
           S_IWUSR    00200     owner has write permission
           S_IXUSR    00100     owner has execute permission
           S_IRWXG    00070     mask for group permissions
           S_IRGRP    00040     group has read permission
           S_IWGRP    00020     group has write permission
           S_IXGRP    00010     group has execute permission
           S_IRWXO    00007     mask for permissions for others (not in group)
           S_IROTH    00004     others have read permission
           S_IWOTH    00002     others have write permission
           S_IXOTH    00001     others have execute permission
      4、结构体passwd :
/* The passwd structure.  */
struct passwd
{
  char *pw_name;          /* Username.  */
  char *pw_passwd;        /* Password.  */
  __uid_t pw_uid;         /* User ID.  */
  __gid_t pw_gid;         /* Group ID.  */
  char *pw_gecos;         /* Real name.  */
  char *pw_dir;           /* Home directory.  */
  char *pw_shell;         /* Shell program.  */
};
      5、结构体group:
/* The group structure.     */
struct group
  {
    char *gr_name;         /* Group name.    */
    char *gr_passwd;       /* Password.    */
    __gid_t gr_gid;        /* Group ID.    */
    char **gr_mem;         /* Member list.    */
  };
      6、结构体tm:
/* Used by other time functions.  */
struct tm
{
  int tm_sec;            /* Seconds.    [0-60] (1 leap second) */
  int tm_min;            /* Minutes.    [0-59] */
  int tm_hour;            /* Hours.    [0-23] */
  int tm_mday;            /* Day.        [1-31] */
  int tm_mon;            /* Month.    [0-11] */
  int tm_year;            /* Year    - 1900.  */
  int tm_wday;            /* Day of week.    [0-6] */
  int tm_yday;            /* Days in year.[0-365]    */
  int tm_isdst;            /* DST.        [-1/0/1]*/
#ifdef    __USE_BSD
  long int tm_gmtoff;        /* Seconds east of UTC.  */
  __const char *tm_zone;    /* Timezone abbreviation.  */
#else
  long int __tm_gmtoff;        /* Seconds east of UTC.  */
  __const char *__tm_zone;    /* Timezone abbreviation.  */
#endif
};
      sunmmary:
 
      如果想获取某目录下(eg:目录a)b文件的详细信息,我们该怎么做?
  1、使用opendir函数打开目录a,返回指向目录a的DIR的结构体c。
  2、调用readdir(c)函数读取目录a下所有的文件(包括目录),返回指向目录a下所有文件的dirent结构体d。
  3、遍历d,调用stat(d->name,stat 
      *e)函数来获取每个文件的详细信息,并存储在stat结构体e中。
知识点解释:
1、stat得到文件属性:
    int result = stat(char * fname , struct stat 
      * bufp)
    包含在# include 
      <sys/stat.h>,把文件fname的信息复制到指针bufp所指的结构中。
2、将mode模式字段转换成字符:
    st_mode是一个16位的二进制数,文件类型和权限被编码在这个数中。
    字段编码——整数是bit组成的序列,使用八进制简化表示。
    解码——掩码技术(与0做位于)。
3、将用户/组ID转换成字符串:
	    etc/passwd文件中包含用户列表(搜索文件繁琐),而且其并没有包含所有的用户(网络计算系统,所有主机通过NIS进行用户身份验证)。
	    通过getpwuid来得到完整的用户列表——通过库函数getpwuid来访问用户信息,如果用户信息保存在/etc/passwd中,那么getpwuid就会查找/etc/passwd的内容,如果用户信息在NIS中,getpwuid会从NIS中获取信息。
	    getpwuid需要uid作为参数,返回一个指向struct 
      passwd的指针。	
	    return 
      getpwuid(uid)->pw_name
同理:/etc/group是组列表,通过getgrgid访问组列表。
4、用ctime将time_t类型的修改时间转换成字符串格式:
    char *ctime(const time_t 
      *time);包含于time.h中
5、参数(目录OR某个文件)判断:
	    if((st.st_mode & S_IFMT) == 
      S_IFDIR)成立则是目录。
6、排序后输出:
	    qsort()函数使用快速排序的例程进行排序,包含于stdlib.h中。
	    void qsort(void *base,int nelem,int 
      width,int (*fcmp)(const void *,const void *));
    参数解析: 
         *base:待排序数组首地址
		 
                nelem:数组中待排序元素数目
		 
                width:各元素占用的空间大小
		 
                *:指向函数的指针,用于确定排序的顺存
qsort参考链接:http://baike.baidu.com/view/982231.htm?fr=aladdin
7、高亮显示:
 文件按照属性显示颜色
        033[mode;foreground;backgroundmhello\033[0m
mode为显示模式:
        0、1、22、4、24、5、25、7、27,分别表示设定颜色、黑体、非黑体、下画线、非下画线、闪烁、非闪烁、翻转、非翻转。
foreground为前景颜色:
        30 
      (黑色)、31 (红色)、32 (绿色)、33 (黄色)、34 (蓝色)、35 ( 紫红色)、36 (青色)和37 (白色)
background为背景颜色:
        40 
      (黑色)、41 (红色)、42 (绿色)、43 (黄色)、44 (蓝色)、45 ( 紫红色)、46 (青色)和47 (白色)
      1 样式: 2 00 — Normal (no color, no bold) 3 01 — Bold //粗体 4 5 文字颜色 6 30 — Black //黑色 7 31 — Red //红色 8 32 — Green //绿色 9 33 — Yellow //黄色 10 34 — Blue //蓝色 11 35 — Magenta //洋红色 12 36 — Cyan //蓝绿色 13 37 — White //白色 14 15 背景颜色 16 40 — Black 17 41 — Red 18 42 — Green 19 43 — Yellow 20 44 — Blue 21 45 — Magenta 22 46 — Cyan 23 47 – White 24 25 白色: 表示普通文件 26 蓝色: 表示目录 27 绿色: 表示可执行文件 28 红色: 表示压缩文件 29 蓝绿色: 链接文件 30 红色闪烁:表示链接的文件有问题 31 黄色: 表示设备文件 32 灰色: 表示其他文件
8、获取符号链接:
readlink读取符号链接内容:
表头文件: #include <unistd.h>
定义函数:ssize_t readlink(const char *path, char *buf, size_t bufsiz);
          path为符号链接路径
          buf为符号链接内容
          bufsize为要获得内容长度
函数说明:readlink()会将参数path的符号链接内容存储到参数buf所指的内存空间,返回的内容不是以\000作字符串结尾,但会将字符串的字符数返回,这使得添加\000变得简单。若参数bufsiz小于符号连接的内容长度,过长的内容会被截断,如果 readlink 第一个参数指向一个文件而不是符号链接时,readlink 设 置errno 为 EINVAL 并返回 -1。 readlink()函数组合了open()、read()和close()的所有操作。
待完成工作:
  1、对于指定目录下信息的显示。
  2、支持参数选项:-R,-u等。
附注:
      1 /* ls2_3.c 2 * -r -l 3 */ 4 #include <stdio.h> 5 #include <stdlib.h> 6 #include <string.h> 7 #include <unistd.h> 8 #include <sys/types.h> 9 #include <dirent.h> 10 #include <sys/stat.h> 11 #include <time.h> 12 #include <grp.h> 13 #include <pwd.h> 14 15 void do_ls_ls(char[]); //No para 16 void do_ls(char[],int); //para 17 void dostat(char *); //file status 18 void show_file_info( char *, struct stat *); 19 void mode_to_letters( int , char [] ); 20 char *uid_to_name( uid_t ); 21 char *gid_to_name( gid_t ); 22 23 void *emalloc(unsigned),*erealloc(void *, unsigned); 24 char **namelist = NULL; 25 int listlen = 0; 26 int taken = 0; 27 28 //设置显示颜色 29 int background = 1; 30 int foreground = 0; 31 int colormode = 0; 32 33 #define CHUNKLEN 100 34 35 main(int ac, char *av[]) 36 { 37 int r_flag = 0; 38 int anyfile = 0; 39 40 struct stat st; 41 42 if ( ac == 1 ) //没有参数 43 { 44 //检查 45 printf("hello---ac=1\n\n"); 46 do_ls_ls("."); 47 printf("hello----ac=1结束\n\n"); 48 } 49 else 50 while ( --ac ){ 51 *av++; 52 if(strcmp("-l",*av) == 0) //参数为-l,正序输出 53 { 54 r_flag = 0; 55 //检查 56 printf("hello---参数为l\n\n"); 57 do_ls( "." ,r_flag); 58 printf("hello---参数为l结束\n\n"); 59 } 60 else if(strcmp("-r",*av) == 0) //参数为-r,倒序输出 61 { 62 //检查 63 printf("hello----参数为r\n\n"); 64 r_flag = 1; 65 do_ls( "." ,r_flag); 66 printf("hello---参数为r结束\n\n"); 67 } 68 else if(stat(*av, &st) < 0) 69 { 70 //检查 71 printf("hello----参数\n\n"); 72 perror("stat"); 73 printf("hello---参数:结束\n\n"); 74 } 75 else if((st.st_mode & S_IFMT) == S_IFDIR) 76 { 77 //检查 78 printf("hello---参数为目录 \n\n"); 79 display_dir(*av); 80 printf("hello---参数为目录结束\n\n"); 81 } 82 else 83 { 84 //检查 85 printf("hello---参数为某个文件\n\n"); 86 dostat(*av); 87 printf("hello---参数为某个文件结束\n\n"); 88 } 89 anyfile = 1; 90 } 91 } 92 93 void do_ls_ls( char dirname[] ) 94 { 95 int i; 96 DIR *dir_ptr; /* the directory */ 97 struct dirent *direntp; /* each entry */ 98 99 list_init(); 100 101 if ( ( dir_ptr = opendir( dirname ) ) == NULL ) 102 fprintf(stderr,"ls2: cannot open %s\n", dirname); 103 else 104 { 105 while ( ( direntp = readdir( dir_ptr ) ) != NULL ) 106 { 107 list_append(direntp->d_name); 108 list_sort(0); 109 // printf("%s\n", direntp->d_name ); 110 } 111 for(i=0;i<taken;i++) 112 printf("%s\n", namelist[i]); 113 // dostat(namelist[i]); 114 closedir(dir_ptr); 115 } 116 list_finish(); 117 } 118 119 int display_dir(char *dirname) 120 { 121 DIR *dir; 122 struct dirent *dirent; 123 struct stat st; 124 char buf[1024]; 125 126 dir = opendir(dirname); 127 128 //检查 129 printf("hello---display_dir\n\n"); 130 while((dirent = readdir(dir)) != NULL) 131 { 132 strcpy(buf, dirname); 133 strcat(buf, "/"); 134 strcat(buf, dirent->d_name); 135 if(stat(buf, &st)) 136 { 137 perror("stat"); 138 return -1; 139 } 140 if(dirent->d_name[0] != ‘.‘) 141 display_file(buf, dirent->d_name); 142 } 143 printf("\nhello---display_dir结束\n\n"); 144 closedir(dir); 145 } 146 147 int display_file(char *path, char *filename) 148 { 149 struct stat st; 150 int i; 151 struct passwd *pw; 152 struct group *gr; 153 struct tm *tm; 154 stat(path, &st); 155 dostat(filename); 156 return 0; 157 } 158 159 list_init() 160 { 161 namelist = (char **)emalloc(CHUNKLEN * sizeof(char *)); 162 listlen = CHUNKLEN; 163 taken = 0; 164 } 165 166 list_append(char *str) 167 { 168 char *newstring(char *); 169 char *name = newstring(str); 170 171 if(taken == listlen){ 172 listlen += CHUNKLEN; 173 namelist = (char **)erealloc(namelist,listlen); 174 } 175 namelist[taken++] = name ; 176 177 } 178 179 list_finish() 180 { 181 int i; 182 for (i=0;i<taken;i++) 183 free(namelist[i]); 184 free(namelist); 185 taken = listlen = 0; 186 } 187 188 int comp_increasing(char **p1,char **p2) 189 { 190 return strcmp((char *) *p1,(char *) *p2); 191 } 192 193 int comp_decreasing(char **p1,char **p2) 194 { 195 return strcmp((char *) *p2,(char *) *p1); 196 } 197 198 list_sort(int revflag) 199 { 200 qsort((void *)namelist,(size_t)taken,sizeof(char *),(__compar_fn_t)(revflag?comp_decreasing:comp_increasing)); 201 202 } 203 204 void do_ls( char dirname[] , int reverse ) 205 { 206 int i; 207 DIR *dir_ptr; /* the directory */ 208 struct dirent *direntp; /* each entry */ 209 210 list_init(); 211 212 if ( ( dir_ptr = opendir( dirname ) ) == NULL ) 213 fprintf(stderr,"ls2: cannot open %s\n", dirname); 214 else 215 { 216 while ( ( direntp = readdir( dir_ptr ) ) != NULL ) 217 { 218 list_append(direntp->d_name); 219 list_sort(reverse); 220 } 221 222 for(i=0;i<taken;i++) 223 dostat( namelist[i] ); 224 225 closedir(dir_ptr); 226 } 227 list_finish(); 228 } 229 230 char *newstring(char *s) 231 { 232 char *rv = emalloc(1+strlen(s)); 233 strcpy(rv,s); 234 return rv; 235 } 236 237 void *emalloc(unsigned n) 238 { 239 void *rv = malloc(n); 240 if(rv) 241 return rv; 242 fprintf(stderr, "ls2:%s/%s\n","out of memory",""); 243 } 244 245 void *erealloc(void *p,unsigned n) 246 { 247 void *rv = realloc(p,n); 248 if(rv) 249 return rv; 250 fprintf(stderr, "ls2:%s/%s\n","out of memory",""); 251 } 252 253 /* 254 * 获得文件的属性 255 */ 256 void dostat( char *filename ) 257 { 258 struct stat info; 259 260 if ( stat(filename, &info) == -1 ) /* cannot stat */ 261 perror( filename ); /* say why */ 262 else /* else show info */ 263 show_file_info( filename, &info ); 264 } 265 266 void show_file_info( char *filename, struct stat *info_p ) 267 { 268 char *uid_to_name(), *ctime(), *gid_to_name(), *filemode(); 269 void mode_to_letters(); 270 char modestr[11]; 271 272 mode_to_letters( info_p->st_mode, modestr); 273 274 printf( "%7d " , info_p->st_ino ); //i节点号 275 printf( "%s" , modestr ); 276 printf( "%4d " , (int) info_p->st_nlink); 277 printf( "%-8s " , uid_to_name(info_p->st_uid) ); 278 printf( "%-8s " , gid_to_name(info_p->st_gid) ); 279 printf( "%8ld " , (long)info_p->st_size); 280 printf( "%.12s ", 4+ctime(&info_p->st_mtime)); 281 printf( "\033[%d;%d;%dm%s\033[0m\n" , foreground, background, colormode, filename ); 282 283 } 284 285 void mode_to_letters( int mode, char str[]) 286 { 287 strcpy( str, "----------" ); /* default=no perms */ 288 289 if ( S_ISDIR(mode) ) {foreground = 34; str[0] = ‘d‘;} /* directory? */ 290 if ( S_ISCHR(mode) ) {colormode = 1; foreground = 33; background = 40; str[0] = ‘c‘;} /* char devices */ 291 if ( S_ISBLK(mode) ) {colormode = 1; foreground = 33; background = 40; str[0] = ‘b‘;} /* block device */ 292 293 if ( mode & S_IRUSR ) str[1] = ‘r‘; /* 3 bits for user */ 294 if ( mode & S_IWUSR ) str[2] = ‘w‘; 295 if ( mode & S_IXUSR ) { if(foreground == 37) foreground = 32; str[3] = ‘x‘;} 296 297 if ( mode & S_IRGRP ) str[4] = ‘r‘; /* 3 bits for group */ 298 if ( mode & S_IWGRP ) str[5] = ‘w‘; 299 if ( mode & S_IXGRP ) { if(foreground == 37) foreground = 32; str[6] = ‘x‘;} 300 301 if ( mode & S_IROTH ) str[7] = ‘r‘; /* 3 bits for other */ 302 if ( mode & S_IWOTH ) str[8] = ‘w‘; 303 if ( mode & S_IXOTH ) { if(foreground == 37) foreground = 32; str[9] = ‘x‘;} 304 } 305 306 char *uid_to_name( uid_t uid ) 307 { 308 struct passwd *getpwuid(), *pw_ptr; 309 static char numstr[10]; 310 311 if ( ( pw_ptr = getpwuid( uid ) ) == NULL ){ 312 sprintf(numstr,"%d", uid); 313 return numstr; 314 } 315 else 316 return pw_ptr->pw_name ; 317 } 318 319 char *gid_to_name( gid_t gid ) 320 { 321 struct group *getgrgid(), *grp_ptr; 322 static char numstr[10]; 323 324 if ( ( grp_ptr = getgrgid(gid) ) == NULL ){ 325 sprintf(numstr,"%d", gid); 326 return numstr; 327 } 328 else 329 return grp_ptr->gr_name; 330 }
        1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <dirent.h>
  4 #include <sys/stat.h>
  5 #include <string.h>
  6 #include <unistd.h>
  7 #include <grp.h>
  8 #include <pwd.h>
  9 #include <time.h>
 10 
 11 int display_file(char *path, char *filename)
 12 {
 13     struct stat st;
 14     int i;
 15     struct passwd *pw;
 16     struct group *gr;
 17     struct tm *tm;
 18     stat(path, &st);
 19 
 20     switch(st.st_mode & S_IFMT)    
 21     {
 22     case S_IFREG:  printf("-");    break;
 23     case S_IFDIR:  printf("d");    break;
 24     case S_IFLNK:  printf("l");    break;
 25     case S_IFBLK:  printf("b");    break;
 26     case S_IFCHR:  printf("c");    break;
 27     case S_IFIFO:  printf("p");    break;
 28     case S_IFSOCK: printf("s");    break;
 29     }
 30 
 31     for(i = 8; i >= 0; i--)
 32     {    
 33         if(st.st_mode & (1 << i))
 34         {
 35             switch(i%3)
 36             {
 37             case 2: printf("r"); break;
 38             case 1: printf("w"); break;
 39             case 0: printf("x"); break;
 40             }
 41         }
 42         else
 43             printf("-");
 44     }
 45 
 46     pw = getpwuid(st.st_uid);
 47     gr = getgrgid(st.st_gid);
 48 
 49     printf("%2d %s %s %4ld", st.st_nlink, pw->pw_name, gr->gr_name, st.st_size);
 50 
 51     tm = localtime(&st.st_ctime);
 52     printf(" %04d-%02d-%02d %02d:%02d",tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min);
 53 
 54     printf(" %s\n", filename);
 55 
 56     return 0;
 57 }
 58 
 59 int display_dir(char *dirname)
 60 {
 61     DIR *dir;
 62     struct dirent *dirent;
 63     struct stat st;
 64     char buf[1024];
 65 
 66     dir = opendir(dirname);
 67     while((dirent = readdir(dir)) != NULL)
 68     {
 69         strcpy(buf, dirname);
 70         strcat(buf, "/");
 71         strcat(buf, dirent->d_name);
 72         if(stat(buf, &st))
 73         {
 74             perror("stat");
 75             return -1;
 76         }
 77 
 78         if(dirent->d_name[0] != ‘.‘)
 79             display_file(buf, dirent->d_name);
 80     }
 81 }
 82 
 83 int main(int argc, char **argv)
 84 {
 85     struct stat st;
 86     char buf[1024];
 87 
 88     if(stat(argv[1], &st) < 0)
 89     {
 90         perror("stat");
 91         return -1;
 92     }
 93 
 94     if((st.st_mode & S_IFMT) == S_IFDIR)
 95         display_dir(argv[1]);
 96     else
 97         display_file(argv[1], argv[1]);
 98 
 99     return 0;
100 }
      链接:
ubuntu 编写自己的ls命令:http://www.cnblogs.com/lr-ting/archive/2012/06/19/2555207.html
高亮显示:http://www.embedu.org/Column/Column341.htm
原文:http://www.cnblogs.com/xymqx/p/3714798.html