如何解决修改后的函数没有递归无法正常工作
我有一个递归函数,它通过目录树列出位于其中的文件名进行迭代。
功能如下:
void WINAPI SearchFile(PSTR Directory)
{
HANDLE hFind;
WIN32_FIND_DATA FindData;
char SearchName[1024],FullPath[1024];
memset(SearchName,sizeof(SearchName));
memset(&FindData,sizeof(WIN32_FIND_DATA));
sprintf(SearchName,"%s\\*",Directory);
hFind=FindFirstFile(SearchName,&FindData);
if(hFind!=INVALID_HANDLE_VALUE)
{
while(FindNextFile(hFind,&FindData))
{
if(FindData.cFileName[0]=='.')
{
continue;
}
memset(FullPath,sizeof(FullPath));
sprintf(FullPath,"%s\\%s",Directory,FindData.cFileName);
if(FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
MessageBoxA(NULL,FullPath,"Directory",MB_OK);
SearchFile(FullPath);
}
else
{
MessageBoxA(NULL,"File",MB_OK);
}
}
FindClose(hFind);
}
}
我需要用一个非递归的函数代替它,我玩过其他循环功能的方法,例如标签,但是似乎总是在结束前只列出一个目录树。
这是我修改的代码:
char* Directory = "D:\\test_location";
label:
HANDLE hFind;
WIN32_FIND_DATA FindData;
char SearchName[1024],FullPath[1024];
memset(SearchName,sizeof(SearchName));
memset(&FindData,sizeof(WIN32_FIND_DATA));
sprintf(SearchName,Directory);
hFind = FindFirstFile(SearchName,&FindData);
if (hFind != INVALID_HANDLE_VALUE)
{
while (FindNextFile(hFind,&FindData))
{
if (FindData.cFileName[0] == '.')
{
continue;
}
memset(FullPath,sizeof(FullPath));
sprintf(FullPath,FindData.cFileName);
//MessageBoxA(NULL,"FullPath",MB_OK);
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
MessageBoxA(NULL,MB_OK);
char cArray[1024];
memset(cArray,sizeof(cArray));
sprintf(cArray,"%s",FullPath);
Directory = cArray;
goto label;
//SearchFile(FullPath);
}
else
{
MessageBoxA(NULL,MB_OK);
}
}
FindClose(hFind);
}
两个函数之间显然存在差异,但是我不明白是什么使它们的行为有所不同。有谁知道我为什么遇到这个问题?
解决方法
差异的原因实际上是goto label
给您带来的困惑。如果您使用的是递归版本,则在递归执行完成后,它将返回到递归位置继续执行。
在您的代码中,您继续执行while (FindNextFile(hFind,&FindData))
,但是当您使用goto label
时,它将跳出原始循环并从标签重新启动程序,这将导致您所说的{ {1}}
如果将修改后的代码修改为以下迭代版本,则可以理解为什么会出现这种问题。
list a single directory tree before ending.
因此,使用void fun()
{
char* Directory = "D:\\test";
HANDLE hFind;
WIN32_FIND_DATA FindData;
char SearchName[1024],FullPath[1024];
char LastName[1024] = "";
while (1)
{
memset(SearchName,sizeof(SearchName));
memset(&FindData,sizeof(WIN32_FIND_DATA));
sprintf(SearchName,"%s\\*",Directory);
if (strcmp(SearchName,LastName) == 0)
{
return;
}
strcpy(LastName,SearchName);
hFind = FindFirstFile(SearchName,&FindData);
if (hFind != INVALID_HANDLE_VALUE)
{
while (FindNextFile(hFind,&FindData))
{
if (FindData.cFileName[0] == '.')
{
continue;
}
memset(FullPath,sizeof(FullPath));
sprintf(FullPath,"%s\\%s",Directory,FindData.cFileName);
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
MessageBoxA(NULL,"Directory",MB_OK);
char cArray[1024];
memset(cArray,sizeof(cArray));
sprintf(cArray,"%s",FullPath);
Directory = cArray;
break;
}
else
{
MessageBoxA(NULL,FullPath,"File",MB_OK);
}
}
FindClose(hFind);
}
}
}
不能达到与递归相同的目的,在这里只能使用递归。当然,我提供了一种使用队列非递归遍历目录的方法,这是一种更科学的方法。
要快速理解错误,请寻找行
goto label;
//SearchFile(FullPath);
此时包含有效数据的hFind
和FindClose(hFind);
需要被调用。但是执行goto label;
之后-用hFind
覆盖 hFind = FindFirstFile(SearchName,&FindData);
-这样,您就永远不会关闭原始hFind
,再也无法返回到迭代文件夹了转到子文件夹。这是关键点-需要保存原始hFind
,然后再进入子目录,然后再还原。当您执行递归函数调用时-这是自动完成的-因为在这种情况下,每个子目录都是在自堆栈框架中枚举的,它们具有单独的hFind
。这是本地解决方案,请在此处使用递归。
,但可能将递归转换为循环,因为我们总是从单个位置调用self,然后将其称为单个位置。因此我们不能将返回地址保存在堆栈中,而是无条件跳转(转到)到已知位置。
然后代码有一些额外的错误,您永远不会检查字符串缓冲区是否溢出,为什么当文件路径最多可以达到32768个字符时,为什么硬编码最大长度为1024,所以不检查重解析点,因为结果可能会进入无限循环,请使用FindFirstFile
代替FindFirstFileEx
等。
下一步可能是枚举循环中子文件夹的正确代码
void DoEnum(PCWSTR pcszRoot)
{
SIZE_T FileNameLength = wcslen(pcszRoot);
// initial check for . and ..
switch (FileNameLength)
{
case 2:
if (pcszRoot[1] != '.') break;
case 1:
if (pcszRoot[0] == '.') return;
}
static const WCHAR mask[] = L"\\*";
WCHAR FileName[MAXSHORT + 1];
if (_countof(FileName) < FileNameLength + _countof(mask))
{
return;
}
ULONG dwError;
HANDLE hFindFile = 0;
WIN32_FIND_DATA FindData{};
enum { MaxDeep = 0x200 };
//++ stack
HANDLE hFindFileV[MaxDeep];
PWSTR pszV[MaxDeep];
char prefix[MaxDeep+1];
//--stack
ULONG Level = MaxDeep;
memset(prefix,'\t',MaxDeep);
prefix[MaxDeep] = 0;
PWSTR psz = FileName;
goto __enter;
__loop:
hFindFile = FindFirstFileEx(FileName,FindExInfoBasic,&FindData,FindExSearchNameMatch,FIND_FIRST_EX_LARGE_FETCH);
if (hFindFile != INVALID_HANDLE_VALUE)
{
do
{
pcszRoot = FindData.cFileName;
// skip . and ..
switch (FileNameLength = wcslen(pcszRoot))
{
case 2:
if (pcszRoot[1] != '.') break;
case 1:
if (pcszRoot[0] == '.') continue;
}
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if ((SIZE_T)(FileName + _countof(FileName) - psz) < FileNameLength + _countof(mask))
{
continue;
}
__enter:
memcpy(psz,pcszRoot,(1 + FileNameLength) * sizeof(WCHAR));
if (FindData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
{
DbgPrint("%sreparse point: <%S>\n",prefix + Level,pcszRoot);
}
else
{
if (Level)
{
DbgPrint("%s<%S>\n",psz);
hFindFileV[--Level] = hFindFile;
pszV[Level] = psz;
memcpy(psz += FileNameLength,mask,sizeof(mask));
psz++;
goto __loop;
__return:
*--psz = 0;
psz = pszV[Level];
hFindFile = hFindFileV[Level++];
DbgPrint("%s</%S>\n",psz);
}
}
}
else
{
DbgPrint("%s[%u%u] %S\n",FindData.nFileSizeLow,FindData.nFileSizeHigh,pcszRoot);
}
if (!hFindFile)
{
// top level exit
return ;
}
} while (FindNextFile(hFindFile,&FindData));
if ((dwError = GetLastError()) == ERROR_NO_MORE_FILES)
{
dwError = NOERROR;
}
FindClose(hFindFile);
}
else
{
dwError = GetLastError();
}
if (dwError)
{
DbgPrint("<%S> err = %u\n",FileName,dwError);
}
goto __return;
}
,
从递归中获得的关键要素之一是,每次调用递归函数时,都需要单独的一组局部变量。当函数调用自身并在递归调用中修改局部变量时,这些局部变量更改不会(直接)影响调用方的局部变量。在您的原始程序中,这适用于变量hFind
,FindData
,SearchName
和FullPath
。
如果要在函数的非递归版本中使用类似的行为,则需要手动保留当您下降到另一级时遍历树的状态。 goto
语句不执行任何操作,它只是重定向程序的控制流。尽管C中有goto
的一些好用例,但它们并不常见,您也不是其中之一。
有几种方法可以实现手动保存状态,但我建议
-
创建一种结构类型,在其中存储那些表征特定级别遍历状态的数据。那些似乎只是
hFind
和FindData
-看起来不需要保留其他本地人。也许是这样,然后:struct dir_state { HANDLE hFind; WIN32_FIND_DATA FindData; };
-
动态分配该类型的结构数组。
unsigned depth_limit = DEFAULT_DEPTH_LIMIT; struct dir_state *traversal_states = malloc(depth_limit * sizeof(*traversal_states)); if (traversal_states == NULL) // ... handle allocation error ...
-
使用数组元素(其索引是该目录的相对深度)跟踪树遍历的深度以及处理的每个目录。
// For example: traversal_states[depth].hFind = FindFirstFile(SearchName,&traversal_states[depth].FindData); // etc.
-
记住数组的大小,以便在遍历下降到其当前大小过深时能够更大地重新分配它。
// For example: if (depth >= depth_limit) { depth_limit = depth_limit * 3 / 2; struct dir_state *temp = realloc(traversal_states,depth_limit * sizeof(*traversal_states)); if (temp == NULL) { // handle error,discontinuing traversal } traversal_states = temp; }
此外,请使用普通的for
,while
或do
循环,而不要后退goto
。有一些细节需要解决,以跟踪何时使用FindFirstFile
和何时使用FindNextFile
(goto
仍然会使用),但是我敢肯定您可以对它进行梳理
细节留作练习。
,除非由于内存或处理方面的限制或无限递归尾部条件而导致不必要的复杂化,否则实际上并不需要在这里使用递归,因为这样会导致可读性和优雅的解决方案。
我还想指出,在“现代” C语言中,任何使用GOTO的解决方案都可能不是您想要的解决方案,因为它们经常使人困惑,并导致内存问题(我们现在有循环程序可以使所有这么简单)。
我建议不要使用GOTO来实现目录的堆栈。将打印逻辑打包一会或一会儿,然后在遍历文件时将所有目录添加到堆栈中。在每次新的迭代中,弹出并遍历堆栈顶部的目录。循环条件只需要在继续执行其块之前检查目录堆栈是否为空。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。