多指触控中的检测问题

首先介绍一下具体的触屏识别情况,在多指触控时,手指不可能同时落到屏幕上,比如三个手指落到屏幕上,屏幕上的touchPoints的个数就是0 –> 1 -> 2 –> 3。同样的,如果三个手指从屏幕上拿掉,个数便会是3 –> 2 –> 1 –> 0的过程。这一点随便写一个Demo应该就能够很好的验证。

接下来引入一个场景,就是如果说当前的这个程序设计在单指的情况下有一个操作(比如选中),在双指的情况下会有另外的操作(比如拖动或是缩放 Pinch)。那么在做双指动作的时候自然会先做单指动作,那如何防止做出类似选中操作呢。类似的还有,单击和双击的区分?

解决的方法也是很简单的,就是通过两次接触的时间差,因为这种时间间隔非常短,可能是0.1s左右。具体的方法是,通过定时器(比如Qt中的QTimer),在TouchEvent变化的时候,判断统计每次的TouchPoints的个数,如果同之前的个数不同,那就启动或重启计时器;如果同之前的个数相同且计时器已经Timeout,那么就可以做出具体的手势操作。

当然,这样操作会有一个小bug,就是当单指的操作时间非常短的时候会有问题。具体的情况比如,单指迅速点击的选中,可能计时器还没结束就停止触摸操作了,这一点特殊处理一下就好了。

[C++] 右值引用

根据我个人的理解,引用应当是是C++为了实现与C中的指针相同功能的一种封装。右值引用,就是为了完整实现原有C中的“指针功能”。
传统的引用往往就是左值引用,比如:

int gen = 1;
int& ref_gen = gen;
const int& conref_gen = gen;

左值引用(int& ),一般用于需要在函数内成员对象,比如std::swap。
常量左值引用(const int &),一般用于传入一个较大的数据的时候,比如一个巨大的结构体,比如在传入的时候的拷贝工作。

但是,右值引用解决的一种具体的情形是这样的,如果一个临时的变量(传完即销毁)传入其中,那么就不需要再拷贝一次内存进行赋值,只需要把指针指向临时变量的地址即可。这种,用指针非常容易实现,但是如果使用引用的方式就非常难,因此右值引用就可以避免一次拷贝的过程,最直接的影响就是可以加快程序的运行速度。

class Info
{
private:
    char* _data;
public:
    Info(const char* str = 0)
    {
        _data = new char[strlen(str) + 1];
        strcpy(_data, str);
    }
    Info(const Info& cur)
    {
        _data = new char[strlen(cur._data) + 1];
        strcpy(_data, cur._data);
    }
    Info(Info&& cur) noexcept
    {
        _data = cur._data;
        cur._data = nullptr;
    }
};
int main()
{
    Timer CurTimer;
    CurTimer.StartTimer();
    for (int i=0;i<100000;i++)
    {
        Info cur("10000000086");
        Info l(cur);
    }
    CurTimer.StopTimer();
    std::cout << "left: " << CurTimer.GetTimerMilliSec() << std::endl;
    CurTimer.StartTimer();
    for (int i = 0; i < 100000; i++)
    {
        Info cur("10000000086");
        Info r(std::move(cur));
    }
    CurTimer.StopTimer();
    std::cout << "right: " << CurTimer.GetTimerMilliSec() << std::endl;
    return 0;
}

通用引用
当右值引用和模板类型和auto搭配的时候,T&&不一定代表右值引用,也可能是一个左值引用。具体的类型跟初始化的内容有关,初始化是左值引用,那么之后的类型就是左值引用。(以下的程序说明,左值引用和原值的地址,而右值引用的内容是暂时存储到了一个新的地址里面。

template<typename T>
void func(T&& foo) 
{
    std::cout << &foo << std::endl;
}

int main()
{
    int value = 10;
    int& ref = value;
    int&& lef = value * 1;
    func(ref);
    func(lef);
    std::cout << &value << std::endl;
}
/*
output:
000000E3425EF664
000000E3425EF6C4
000000E3425EF664
*/

完美转发
如果将一个参数交给另外一个函数处理,那么那么另一个函数会不知道这个是左值还是右值。int && a并不是一个右值,而是一个绑定了右值对象的左值。因此,需要有一个函数使得传递的函数常数的类型。(以下的程序可以很好的说明参数在传递过程的类型变化。

#include <iostream>

template<typename T>
void typeprint(T& _) 
{
    std::cout << "left value" << std::endl;
}

template<typename T>
void typeprint(T&& _) 
{
    std::cout << "right value" << std::endl;
}

template<typename T>
void testForward(T&& v) 
{
    typeprint(v);
    typeprint(std::forward<T>(v));
    typeprint(std::move(v));
}

int main()
{
    testForward(1);
    std::cout << std::endl;
    int x = 1;
    testForward(x);
}

/*
output:
left value
right value
right value

left value
left value
right value
*/

STL中也使用右值引用作为参数来加速,比如emplace_back,根据cppreference可以看到,当然构造函数也是需要经过处理的,就跟给出的例子一样。

std::move 是将原本的左值临时显式的变为右值,之后原本的左值应当应当被销毁。
std::forward 用于完美转发的函数。

关于软件工程的一些想法

 

这学期选了软件工程,也就是更快速而有效的进行协作。

人工智能课程做了一个军旗AI,说是AI,不过是用Alpha-Beta剪枝,然后做暴力搜索,再加上一些优化,其实到最后比的就是谁的算法优化的更好博弈树搜索的越深,以及更好的布局(军旗的布局影响挺大的)。当时我就想,如何我写好了一个框架,剩下的人在里面优化一些函数就好了,最后的结果其实并不好。一个同学STL,git都不太会,可能就无法上手了,另一个同学在原本的框架下,又重写了半个框架(这个问题我也不知道如何解决,不知道是不是我的注释写的不够清楚),因此大部分都被我删掉了。最后其实是相当于和室友两个人完成的,毕竟调参优化什么还是挺费时间的。不过感觉这种算法类的项目,的确挺难分工的,但最后拿了rank1还是挺能够开心的。

在做软件工程的项目也会遇到这个问题,原来想是PHP的,毕竟简单好用,结果发前后端比较难分工,之后开始用flask开发做前后端协作,最后好在做出了个还算不错的成品,我主要做的就是OJ的判题内核

总的来说,敏捷开发感觉太业务了,每一次的Sprint都是为了业务,为了新的功能点,如果说是想进行大规模的重构基本是不可能的。稍微有点深度的项目感觉很难开发,敏捷开发感觉还是适合外包开发业务的简单情形,比如做个选课网平台(学校请的外包公司似乎写的bug挺多),我觉得倒是适合的。

软件工程里的测试驱动开发倒是挺有道理的,毕竟我在写OJ判题内核的时候用的就是TTD,当时写的时候其实并没有想到这个概念,之后发现还是蛮有有效的的。TTD最简单的例子就是渣哥高程的某些作业,再者比如CS231n里的作业也是。最主要的特点就是目标清晰,只要考虑接口内部实现即可。其实这就挺适合分工的,先把接口和测试写好,每个人写各自的接口就好了。

不过,最重要的还是,组内的人水平差不多,这样无论是交流还是做Code Review都能方便很多。

 

 

 

SSE/AVX SIMD初步尝试.

SSE/AVX 是其实就是SIMD指令集的主要应用(MMX太过远古现在基本不用了,AVX512现在桌面级还不支持),使用时只需要#include <immintrin.h>即可,至于具体的函数可以查阅Intel的官网 ,官网的对每个指令都非常详细的demo.

想到这个是因为,做计组作业的时候被问到一个问题,感觉挺怪的,我只能拿奔腾4和龙芯做对比了..

回到正题,初步用SIMD做了一下memset的操作,确实有比较大的提速,AVX和SSE的差距貌似并不是特别大,可能主要的时间浪费在IO上了,不过gcc里面的memset确实非常快,似乎是CPU内部对这个做了专门的提速,外加上我没有考虑上字节对齐的因素。

一句题外话,for(Loop)循环会比用指针(Pointer)做迭代慢,因为在汇编上,多了一个赋值给i的操作,但是影响貌似并不大。

最后代码里用了WIN32的API做的Timer来实现更精确的计数.

#include <iostream>
#include <windows.h>
#include <immintrin.h>
#include <ctime>

//======================Precise Timer================================
class Timer
{
private:
  unsigned long long _start_count;
  unsigned long long _stop_count;
  double _millisec_percount;
  bool _timer_state;
public:
  Timer();
  void StartTimer();
  void StopTimer();
  double GetTimerMilliSec() const;
  double GetTimerSec() const;
};

Timer::Timer()
{
  _start_count = _stop_count = 0;
  _timer_state = false;
  unsigned long long frequency;
  QueryPerformanceFrequency((LARGE_INTEGER*)&frequency);
  _millisec_percount = (double)1.0 / ((double)frequency / 1000.0);
}

void Timer::StartTimer()
{
  _timer_state = true;
  QueryPerformanceCounter((LARGE_INTEGER*)&_start_count);
}

void Timer::StopTimer()
{
  QueryPerformanceCounter((LARGE_INTEGER*)&_stop_count);
  _timer_state = false;
}

double Timer::GetTimerSec() const
{
  return GetTimerMilliSec() / (double)1000.0;
}

double Timer::GetTimerMilliSec() const
{
  return (float)(_stop_count - _start_count) * _millisec_percount;
}
//===================================================================


void Memset_Loop(int* a, int size)
{
  for (int i = 0; i < size; i++)
    a[i] = 0;
}
void Memset_Pointer(int* a, int size)
{
  for (int* i = &a[0], *end = &a[size]; i < end; i++)
    *i = 0;
}


void Memset_SSE(int* a, int size)
{
  __m128i zero = _mm_setzero_si128();
  for (__m128i* i = reinterpret_cast<__m128i*>(a), *end = reinterpret_cast<__m128i*>(&a[size]); i < end; i++)
    _mm_store_si128(i, zero);
}

void Memset_AVX(int* a, int size)
{
  __m256i zero = _mm256_setzero_si256();
  for (__m256i* i = reinterpret_cast<__m256i*>(a), *end = reinterpret_cast<__m256i*>(&a[size]); i < end; i++)
    _mm256_store_si256(i, zero);
}

void Memset_SYS(int* a, int size)
{
  memset(a, 0, sizeof(a));
}
/*
unsupported AVX512

void Memset_AVX512(int* a, int size)
{
  __m512i zero = _mm512_setzero_si512();
  for (__m512i* i = reinterpret_cast<__m512i*>(a), *end = reinterpret_cast<__m512i*>(&a[size]); i < end; i++)
    _mm512_store_si512(i, zero);
}
*/
const int MYSIZE = 100000000;

int main()
{
  int* a = new int[MYSIZE];
  for (int i = 0; i < MYSIZE; i++)
    a[i] = rand();


  std::cout << "Start!!!" << std::endl;
  Timer CurTimer;
  CurTimer.StartTimer();
  Memset_Pointer(a, MYSIZE);
  CurTimer.StopTimer();
  std::cout << "Loop   : " << CurTimer.GetTimerMilliSec() << std::endl;

  CurTimer.StartTimer();
  Memset_Pointer(a, MYSIZE);
  CurTimer.StopTimer();
  std::cout << "Pointer: " << CurTimer.GetTimerMilliSec() << std::endl;

  CurTimer.StartTimer();
  Memset_SSE(a, MYSIZE);
  CurTimer.StopTimer();
  std::cout << "SSE    : " << CurTimer.GetTimerMilliSec() << std::endl;

  CurTimer.StartTimer();
  Memset_AVX(a, MYSIZE);
  CurTimer.StopTimer();
  std::cout << "AVX    : " << CurTimer.GetTimerMilliSec() << std::endl;

  CurTimer.StartTimer();
  Memset_SYS(a, MYSIZE);
  CurTimer.StopTimer();
  std::cout << "SYS    : " << CurTimer.GetTimerMilliSec() << std::endl;
  return 0;
}

 

[C] 关于goto的想法

goto似乎一直都不推荐使用,但是我在处理动态内存申请时候,感觉不用goto挺难维护代码。比如在一个函数有多个出口的时候,函数内有动态内存申请,那么退出的时候必然要去free,但如果每个return之前都要写free,这样在修改的时候很复杂。

void func()
{
  malloc();
  for	(..)
  {
    if (..)
    {
      free(...);
      return;
    }
    if (..)
    {
      free(...);
      return;
    }
  }
  free(...)
  return;
}

如果使用Goto的话,所有的动态内存释放都放在一个地方管理,而且代码似乎更加简洁了。不过C++就不需要考虑这个问题了。

void func()
{
  malloc();
  for	(..)
  {
    if (..)
    {
      goto CLEAR;
    }
    if (..)
    {
      goto CLEAR;
    }
  }
  
CLEAR:
  free(...);
  return;
}

网上还有一些关于Goto的应用比如:
Ragel State Machine Compiler 有限状态机编译器,可以直接转混成C和C++源码

C里面还有setjmp和longjmp来完成goto的操作。

#include <stdio.h>
#include <setjmp.h>
int main()
{
  jmp_buf env;
  int ret = 0;
  ret = setjmp(env);
  printf("ret=%d\n", ret);
  if (ret == 0)
    longjmp(env, 1);
  if (ret == 1)
    longjmp(env, 2);
  return 0;
}
//	output:
//	ret = 0
//	ret = 1
//	ret = 2

[C++] 内嵌汇编和调用汇编函数

内嵌汇编

在VS中可以加入__asm来内在VS中的测试。

#include <iostream>
#include <ctime>
using namespace std;

int main()
{
  clock_t start, end;
  int Sum=0,Count=100000000;
  int Block = 20000000;
  start = clock();

  __asm 
  {
    MOV ECX, Count
  L1: PUSH ECX
    MOV EAX, 1
    MOV ECX, 20
  L2: SHL EAX, 1
    INC Sum
    LOOP L2
    POP  ECX
    LOOP L1
  }
  
  cout << Sum << endl;
  end = clock();
  cout << ((double)end - (double)start) / CLOCKS_PER_SEC << endl;
  
  start = clock();
  Sum = 0;
  for (int i = 0; i < Count; i++)
  {
    int Ax = 1;
    for (int j = 0; j < 20; j++)
    {
      Ax = Ax << 1;
      Sum++;
    }
  }
  cout << Sum << endl;
  end = clock();
  cout << ((double)end - (double)start) / CLOCKS_PER_SEC << endl;
  return 0;
}

但貌似还是C++源码快(逃。可能是现代编译器太强了。

C++调用汇编函数

TITLE AsmFindArray Procedure
.586
.model flat,C

AsmFindArray PROTO,
  Val:DWORD,Array:PTR DWORD,N:DWORD
.code
AsmFindArray PROC USES edi,
  Val:DWORD,Array:PTR DWORD,N:DWORD
  true=1
  false=0

  mov		eax,Val
  mov		ecx,N
  mov		edi,Array

  repne	scasd
  jz		rT
rF:
  mov		al,false
  jmp		short exit
rT:
  mov		al,true
exit:
  ret
AsmFindArray	ENDP
END
#include <iostream>
#include <ctime>
using namespace std;

extern "C" bool AsmFindArray(int Val, int Array[], int N);
bool FindArray(int Val, int Array[], int N)
{
  for (int i = 0; i < N; i++)
    if (Array[i] == Val)
      return true;
  return false;
}
int Num = 100000;
int A[100010];
int main()
{
  for (int i = 0; i < Num; i++)
    A[i] = i;
  int Sum = 0;

  clock_t start, end;

  start = clock();
  for (int i = 0; i <= Num * 2; i++)
    Sum += AsmFindArray(i, A, Num);
  end = clock();
  cout << Sum << endl;
  cout << "ASM:" << ((double)end - (double)start) / CLOCKS_PER_SEC << endl;

  Sum = 0;
  start = clock();
  for (int i = 0; i <= Num * 2; i++)
    Sum += FindArray(i, A, Num);
  end = clock();
  cout << Sum << endl;
  cout << "C++:" << ((double)end - (double)start) / CLOCKS_PER_SEC << endl;
  return 0;
}

VS中汇编的调用需要一些设定,用的时候可以百度一下,结果的优化还是挺明显的。

不过还是比不过开了O2优化的代码。
所以感觉一般汇编优化还是在一些不能开O优化的场合用比较好。

参考文献:Intel汇编程序设计

[C] C Traps and Pitfalls 读书记录

使用errno.h检测错误

当库文件调失败的时候,比如fopen没有相应文件,malloc没有内存等等时,就会设置errno。引用errno.h(cerrno)的时候就会有一个全局变量errno。 还可以perror,strerror使用。我们可以先判断错误之后,在检查errno找出原因。

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
int main(void)
{
    /*调用errno之前必须先将其清零*/
    errno=0;
    FILE *fp = fopen("test.txt","r");
    if(fp==NULL)
    {
  perror("Error");
        printf("errno值: %d\n",errno);
        printf("错误信息: %s\n",strerror(errno));
    }
}

使用Signal检查

信号是某种意义上的异步,可以在程序执行期间的任何一个时刻发生。有SIGABRT,SIGINT等等,可以去查询,也可以自定义信号。下面程序可以检测在程序被Ctrl+C中断。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <csignal>
#include <unistd.h>

void sighandler(int signum)
{
   printf("Caught signal %d, coming out...\n", signum);
   exit(1);
}

int main()
{
   signal(SIGINT, sighandler);
   while(1) 
   {
      printf("Going to sleep for a second...\n");
      sleep(1);
   }
   return 0;
}

 

getchar()实际上是#define getchar() getc(stdin)的宏定义。当然很多编译器也写了getchar()的函数。当然putchar也有宏定义。

宏定义在类型定义时出现问题

#define ElemType struct foo
ElemType a,b;

但是当为指针时,会变成一个指针和一个变量。

#define ElemType struct foo *
ElemType a,b;
struct foo *a,b;

词法分析中的贪心法

在C中有/,*,=等单字符符号,也有/*,==等多字符符号,词法会选择当前情况下能够组合的最长的组合情况,因此a—b与表达式 a– -b的含义相同。

 

[C++]String

COW ——共享内存,通过计数器来统计相同的字符串的个数。

SSO ——28个字节中如果短的字符串自身存储,长的字符串再内存申请。
VS下sizeof(string)=28
如果长度不超过16个字符则存在*(p+1)~*(p+4)之间,*(p+5)表示的是string的长度,如果长度超过16则会在*(p+1)存储指向该字符串的指针。

一行写法?(stringstream() << str1 << str2).str()

linux下的opendir,readdir,closedir

 

enum
{
  DT_UNKNOWN = 0,
#define DT_UNKNOWN DT_UNKNOWN  //未知的类型
  DT_FIFO = 1,
#define DT_FIFO DT_FIFO        //命名管道或FIFO
  DT_CHR = 2,
#define DT_CHR DT_CHR          //字符设备文件
  DT_DIR = 4,
#define DT_DIR DT_DIR          //普通目录
  DT_BLK = 6,
#define DT_BLK DT_BLK          //块设备文件
  DT_REG = 8,
#define DT_REG DT_REG          //普通文件
  DT_LNK = 10,
#define DT_LNK DT_LNK          //快捷方式
  DT_SOCK = 12,
#define DT_SOCK DT_SOCK        //本地套接口
  DT_WHT = 14
#define DT_WHT DT_WHT          //whiteout
};
/*
whiteout 概念存在于联合文件系统(UnionFS)中,代表某一类占位符形态的特殊文件,
当用户文件夹与系统文件夹的共通部分联合到一个目录时(例如 bin 目录),
用户可删除归属于自己的某些系统文件副本,但归属于系统级的原件仍存留于同一个联合目录中,
此时系统将产生一份 whiteout 文件,表示该文件在当前用户目录中已删除,但系统目录中仍然保留。
*/

可以这些写一个类似于Linux中Tree类似功能的程序,而且貌似st_mode比dtype要判断的精确,误差更小。
stat  返回lnk所指向的文件信息
lstat 返回lnk的信息
其他都一样

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


#define PATH_LENGTH 2000
#define FILE_MAXN 1000
#define NAME_LENGTH 255

struct OPT
{
  int Show_Hidden_File;
  int Show_File;
};
void Print_TAB(int Dep)
{
  for (int i = 1; i < Dep; i++)
    printf("|   ");
  printf("|-- ");
}
int Is_Hidden_File(const char * Str)
{
  if (Str[0] == '.')
    return 1;
  return 0;
}
//int Reg = 0, Chr = 0, Blk = 0, Sock = 0, Fifo = 0;

int Is_File(char * File_Path)
{
  struct stat S_Buf;
  if (lstat(File_Path, &S_Buf) < 0)
    return 1;
#if 0
  if (S_ISREG(S_Buf.st_mode))
    Reg++;
  if (S_ISCHR(S_Buf.st_mode))
    Chr++;
  if (S_ISBLK(S_Buf.st_mode))
    Blk++;
  if (S_ISSOCK(S_Buf.st_mode))
    Sock++;
  if (S_ISFIFO(S_Buf.st_mode))
    Fifo++;
#endif
    if (S_ISREG(S_Buf.st_mode) || S_ISCHR(S_Buf.st_mode) || S_ISBLK(S_Buf.st_mode) || S_ISFIFO(S_Buf.st_mode) || S_ISSOCK(S_Buf.st_mode))
    return 1;
  if (S_ISLNK(S_Buf.st_mode))
  {
    if (stat(File_Path, &S_Buf) < 0)
      return 1;
#if 0
    if (S_ISREG(S_Buf.st_mode))
      Reg++;
    if (S_ISCHR(S_Buf.st_mode))
      Chr++;
    if (S_ISBLK(S_Buf.st_mode))
      Blk++;
    if (S_ISSOCK(S_Buf.st_mode))
      Sock++;
    if (S_ISFIFO(S_Buf.st_mode))
      Fifo++;
#endif
    if (S_ISREG(S_Buf.st_mode) || S_ISCHR(S_Buf.st_mode) || S_ISBLK(S_Buf.st_mode) || S_ISFIFO(S_Buf.st_mode) || S_ISSOCK(S_Buf.st_mode))
      return 1;
  }
  return 0;
}

int Is_Dir(char * File_Path)
{
  struct stat S_Buf;
  stat(File_Path, &S_Buf);

  if (S_ISDIR(S_Buf.st_mode) || S_ISSOCK(S_Buf.st_mode))
    return 1;
  return 0;
}

int Is_Lnk(char * File_Path)
{
  struct stat S_Buf;
  lstat(File_Path, &S_Buf);
  //!! use lstat 
  if (S_ISLNK(S_Buf.st_mode))
    return 1;
  return 0;
}

int Cmp(const char * Str1, const char * Str2)
{
  for (int i = 0; Str1[i] && Str2[i]; i++)
  {
    if (Str1[i] > Str2[i])
      return 1;
    if (Str1[i] < Str2[i])
      return 0;
  }
  return 1;
}
/*
  用stat之后结果会准确很多,但是在/proc/self/task这个文件夹下面有个fd的文件夹和一个fdinfo的文件夹,
  里面分别有两个软连接,tree只会打开其中一个,我的两个无法打开
*/
void Tree_Maker(const char * Cur_Path, int Depth, OPT Opt, int * Dir_Num, int * File_Num)
{
  
  DIR            *dir;
  struct dirent  *node;

  if ((dir = opendir(Cur_Path)) == NULL)
    return;
  /*
  char File[FILE_MAXN][NAME_LENGTH];
  int Cnt = 0;
  while ((node = readdir(dir)) != NULL)
    strcpy(File[++Cnt], node->d_name);
  closedir(dir);
  
  for (int i = 1; i <= Cnt; i++)
    for (int j = i + 1; j <= Cnt; j++)
      if (Cmp(File[i], File[j]))
      {
        char Tmp[PATH_LENGTH];
        strcpy(Tmp, File[i]);
        strcpy(File[i], File[j]);
        strcpy(File[j], Tmp);
      }
  */

  while ((node = readdir(dir)) != NULL)
  {
    char File_Path[PATH_LENGTH];
    
    strcpy(File_Path, Cur_Path);
    strcat(File_Path, "/");
    strcat(File_Path, node->d_name);
    int Sym_File = Is_File(File_Path);
    if (strcmp(node->d_name, ".") == 0 || strcmp(node->d_name, "..") == 0)
      continue;
    //Ignore dot and dot-dot
    if (Is_Hidden_File(node->d_name) && !Opt.Show_Hidden_File)
      continue;
    if (Sym_File && !Opt.Show_File)
      continue;

    Print_TAB(Depth);
    printf("%s", node->d_name);

    if (Sym_File)
      (*File_Num)++;
    else
    {
      (*Dir_Num)++;
      //printf("!!!");
    }
    putchar('\n');
    if (Is_Dir(File_Path) && !Is_Lnk(File_Path))
      Tree_Maker(File_Path, Depth + 1, Opt, Dir_Num, File_Num);
  }
  closedir(dir);
}

int main(int argc, char* argv[])
{
  if (argc > 3)
  {
    puts("参数过多");
    return -1;
  }
  OPT Opt = { 0,1 };
  char Path[PATH_LENGTH] = ".";
  if (argc == 2)
  {
    if (argv[1][0] == '-')
    {
      if (strcmp(argv[1], "-a") == 0)
        Opt.Show_Hidden_File = 1;
      if (strcmp(argv[1], "-d") == 0)
        Opt.Show_File = 0;
    }
    else
      strcpy(Path, argv[1]);
  }
  if (argc == 3)
  {
    if (strcmp(argv[1], "-a") == 0)
      Opt.Show_Hidden_File = 1;
    if (strcmp(argv[1], "-d") == 0)
      Opt.Show_File = 0;
    strcpy(Path, argv[2]);
  }

  int Dir_Num = 0, File_Num = 0;
  puts(Path);
  Tree_Maker(Path, 1, Opt, &Dir_Num, &File_Num);
  printf("\n%d directories, %d files\n", Dir_Num, File_Num);
  //printf("Reg: %d Chr: %d Blk: %d Sock: %d Fifo: %d\n", Reg, Chr, Blk, Sock, Fifo);
  return 0;
}