修改后的函数没有递归无法正常工作

如何解决修改后的函数没有递归无法正常工作

我有一个递归函数,它通过目录树列出位于其中的文件名进行迭代。

功能如下:

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);
此时

包含有效数据的hFindFindClose(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;
}
,

从递归中获得的关键要素之一是,每次调用递归函数时,都需要单独的一组局部变量。当函数调用自身并在递归调用中修改局部变量时,这些局部变量更改不会(直接)影响调用方的局部变量。在您的原始程序中,这适用于变量hFindFindDataSearchNameFullPath

如果要在函数的非递归版本中使用类似的行为,则需要手动保留当您下降到另一级时遍历树的状态。 goto语句不执行任何操作,它只是重定向程序的控制流。尽管C中有goto的一些好用例,但它们并不常见,您也不是其中之一。

有几种方法可以实现手动保存状态,但我建议

  1. 创建一种结构类型,在其中存储那些表征特定级别遍历状态的数据。那些似乎只是hFindFindData-看起来不需要保留其他本地人。也许是这样,然后:

    struct dir_state {
        HANDLE hFind;
        WIN32_FIND_DATA FindData;
    };
    
  2. 动态分配该类型的结构数组。

    unsigned depth_limit = DEFAULT_DEPTH_LIMIT;
    struct dir_state *traversal_states
            = malloc(depth_limit * sizeof(*traversal_states));
    if (traversal_states == NULL) // ... handle allocation error ...
    
  3. 使用数组元素(其索引是该目录的相对深度)跟踪树遍历的深度以及处理的每个目录。

    // For example:
    traversal_states[depth].hFind
            = FindFirstFile(SearchName,&traversal_states[depth].FindData);
    // etc.
    
  4. 记住数组的大小,以便在遍历下降到其当前大小过深时能够更大地重新分配它。

    // 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;
    }
    

此外,请使用普通的forwhiledo循环,而不要后退goto。有一些细节需要解决,以跟踪何时使用FindFirstFile和何时使用FindNextFilegoto仍然会使用),但是我敢肯定您可以对它进行梳理

细节留作练习。

,

除非由于内存或处理方面的限制或无限递归尾部条件而导致不必要的复杂化,否则实际上并不需要在这里使用递归,因为这样会导致可读性和优雅的解决方案。

我还想指出,在“现代” C语言中,任何使用GOTO的解决方案都可能不是您想要的解决方案,因为它们经常使人困惑,并导致内存问题(我们现在有循环程序可以使所有这么简单)。

我建议不要使用GOTO来实现目录的堆栈。将打印逻辑打包一会或一会儿,然后在遍历文件时将所有目录添加到堆栈中。在每次新的迭代中,弹出并遍历堆栈顶部的目录。循环条件只需要在继续执行其块之前检查目录堆栈是否为空。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-