C++算法学习-创建新项目工具

C++算法学习

这是一个记录C++算法学习的博客,记录一些算法的代码实现,算法的思路,算法的时间复杂度,算法的空间复杂度,算法的稳定性等等。
该项目使用VScode编写,使用C++编写,使用CMake构建项目,使用Git进行版本控制,使用GitHub进行代码托管。

点击前往CPlusAlgo

目录

创建新项目工具

简介

这是一个用于创建新项目的工具,它可以帮助你快速地创建一个新的项目,并且可以自动生成项目的目录结构,包括源代码文件、头文件、测试文件等等。

使用方法

  1. 首先使用cmake生成项目文件

    1
    2
    3
    cd 000-CreateNewProject
    cmake -G "MinGW Makefiles" -B build -S .
    cd build && cmake --build .
  2. 运行生成的000-CreateNewProject.exe文件。

    1
    .\build\000-CreateNewProject\000-CreateNewProject.exe

    目录结构如下:

    1
    2
    3
    4
    5
    000-CreateNewProject
    ├── build
    │ └── 000-CreateNewProject
    │ ├── 000-CreateNewProject.exe

  3. 根据提示输入项目名称,例如000-CreateNewProject

    • ps:项目名称不能包含空格,不能包含特殊字符,不能包含中文。
      程序演示

代码解析

  1. bool nameCheck(const string &name)函数

    • 该函数用于检查项目名称是否合法,如果项目名称包含空格、特殊字符或者中文,则返回false,否则返回true。
    • name : 项目名称
    • 该函数的实现如下:
    1. 判断项目名称是否为空,如果为空,则返回false。
    1
    2
    3
    4
    5
    if (name.empty())
    {
    std::cout << "文件夹名称不能为空!\n";
    return false;
    }
    • name.empty()函数用于判断字符串是否为空,如果为空,则返回true,否则返回false。
    1. 检查非法字符,如果项目名称包含非法字符,则返回false。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const std::string invalid_chars = "<>:\"/\\|?*";
    // 遍历name中的每个字符
    for (char c : name)
    {
    if (invalid_chars.find(c) != std::string::npos)
    {
    std::cout << "名称包含非法字符: '" << c << "'\n";
    return false;
    }

    // 控制字符检查
    if (std::iscntrl(static_cast<unsigned char>(c)))
    {
    std::cout << "名称包含控制字符!\n";
    return false;
    }
    }
    • const std::string invalid_chars = "<>:\"/\\|?*" 定义了一个字符串,包含了所有非法字符。
    • for (char c : name){} 是一个范围for循环,用于遍历name中的每个字符c
    • invalid_chars.find(c) != std::string::npos
      • invalid_chars.find(c)在非法字符集合中搜索当前字符 c
      • != std::string::npos 如果找到了,则返回字符在字符串中的位置,否则返回std::string::npos
        • std::string::npos 是一个特殊的值,表示字符串中没有找到指定的字符。
        • ex.name包含非法字符*则输出:名称包含非法字符: '*'并返回false。
    • std::iscntrl(static_cast<unsigned char>(c))用于判断字符c是否为控制字符,如果是,则返回true,否则返回false。
      • std::iscntrl函数用于判断字符是否为控制字符,如果是,则返回true,否则返回false。
      • static_cast<unsigned char>(c)将字符c转换为无符号字符。
        • static_cast是C++中的类型转换运算符,用于将一种类型转换为另一种类型。
        • unsigned char是无符号字符类型,用于表示无符号字符。
    1. 检查项目名称是否是系统保留名称
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 检查保留名称 (Windows)
    const std::vector<std::string> reserved_names = {
    "CON", "PRN", "AUX", "NUL",
    "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
    "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"};

    // 转换为大写以检查保留名称
    std::string upper_name = name;
    std::transform(upper_name.begin(), upper_name.end(), upper_name.begin(), ::toupper);

    for (const auto &reserved : reserved_names)
    {
    if (upper_name == reserved)
    {
    std::cout << "名称 '" << name << "' 是系统保留名称!\n";
    return false;
    }
    }
    • const std::vector<std::string> reserved_names = {"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"}; 定义了一个字符串向量,包含了所有系统保留名称。
    • std::string upper_name = name; 将项目名称转换为小写。
    • std::transform(upper_name.begin(), upper_name.end(), upper_name.begin(), ::toupper); 将项目名称中的每个字符转换为大写。
      • std::transform()函数用于将一个范围内的元素转换为另一个值。
      • upper_name.begin()upper_name.end()是迭代器,表示upper_name的开始和结束位置。
      • ::toupper是一个函数对象,用于将字符转换为大写。
    • for (const auto &reserved : reserved_names){} 是一个范围for循环,用于遍历reserved_names中的每个字符串reserved
    • if (upper_name == reserved){} 判断项目名称是否是系统保留名称,如果是,则返回false。
    1. 检查项目名称开头和结尾是否
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // 检查开头和结尾的非法字符
    if (name.front() == ' ' || name.front() == '.')
    {
    std::cout << "名称不能以空格或点开头!\n";
    return false;
    }

    if (name.back() == ' ' || name.back() == '.')
    {
    std::cout << "名称不能以空格或点结尾!\n";
    return false;
    }
    • name.front()name.back()分别表示项目名称的第一个字符和最后一个字符。
    1. 检查名称长度
    1
    2
    3
    4
    5
    6
    // 检查名称长度
    if (name.length() > 255)
    {
    std::cout << "名称过长(最大255字符)!\n";
    return false;
    }
    • name.length()函数用于获取项目名称的长度。
  2. bool createFolder(const fs::path &parent_path, const string &folderName);函数

    • 该函数用于创建文件夹,如果文件夹创建失败返回false,否则返回true。

    • parent_path : 父路径

    • folderName : 文件夹名称

    • 该函数的实现如下:

      1. 拼接父路径和文件夹名称,得到创建路径,如果文件夹已存在,输出提示信息并返回false。
      1
      2
      3
      4
      5
      6
      7
      8
      // 拼接父路径和文件夹名称,得到创建路径
      fs::path createPath = parent_path / folderName;
      // 如果文件夹已存在,输出提示信息并返回false
      if (fs::exists(createPath))
      {
      cout << "文件夹已存在" << endl;
      return false;
      }
      • fs::path是C++17中引入的文件系统库中的类,用于表示文件路径。
      • parent_path / folderName表示将父路径和文件夹名称拼接起来,得到创建路径。
      • fs::exists(createPath)函数用于判断路径是否存在,如果存在返回true,否则返回false。
      1. 尝试创建文件夹,如果创建文件夹成功,输出成功信息并返回true,否则输出失败信息并返回false。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      // 如果创建文件夹成功,输出成功信息并返回true
      if (fs::create_directory(createPath))
      {
      cout << "成功创建文件夹: " << createPath << "\n";
      return true;
      }
      // 如果创建文件夹失败,输出失败信息并返回false
      cerr << "无法创建文件夹: " << createPath << "\n";
      return false;
      • fs::create_directory(createPath)函数用于创建文件夹,如果创建成功返回true,否则返回false。
      • cerr是C++标准库中的错误输出流,用于输出错误信息。
      1. 捕获文件系统错误,输出错误信息并返回false。
      1
      2
      3
      4
      5
      6
      // 捕获文件系统错误,输出错误信息并返回false
      catch (const fs::filesystem_error &e)
      {
      cerr << "文件系统错误: " << e.what() << "\n";
      return false;
      }
      • catch (const fs::filesystem_error &e)用于捕获文件系统错误,e.what()函数用于获取错误信息。
  3. optional<fs::path> findAimFolder(const fs::path &start_path, const string &target_name, bool find_all = false, bool stop_at_first = false);函数

    • 该函数用于查找目标文件夹,如果找到目标文件夹,返回目标文件夹的路径,否则返回空值。

    • start_path : 查找的起始路径

    • target_name : 目标文件夹名称

    • find_all : 是否查找所有目标文件夹,默认为false

    • stop_at_first : 是否在找到第一个目标文件夹后停止查找,默认为false

    • 该函数的实现如下:

      1. 先创建存储文件路径的动态数组容器,然后获取当前路径的绝对路径,记录根路径用于终止循环,限制最大向上搜索层数,初始化当前搜索层数。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // 存储所有匹配路径的容器
      vector<fs::path> matches;

      // 获取绝对路径确保路径解析正确
      fs::path current = fs::absolute(start_path);

      // 记录根路径用于终止循环
      const fs::path root = current.root_path();

      // 最大向上搜索层数,限制深度
      int max_depth = 5;
      int current_depth = 0;
      • vector<fs::path> matches;创建了一个动态数组容器,用于存储所有匹配的路径。
        • vector<元素类型> 向量名 C++ 的动态数组容器,可自动扩容,元素类型指的是数组中元素的类型,向量名是数组的名字。
      • fs::path current = fs::absolute(start_path);获取当前路径的绝对路径,确保路径解析正确。
        • fs::absolute()函数用于获取路径的绝对路径。
      • const fs::path root = current.root_path();记录根路径用于终止循环。
        • root_path()函数用于获取路径的根路径。
      1. 循环向上遍历目录树,检查当前目录名是否匹配目标,如果匹配则将匹配路径添加到结果集,如果设置了找到即停止且不需要所有结果,则返回第一个匹配项,到达根目录时停止遍历,如果无法继续向上,则停止遍历,移动到父目录继续搜索。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
       // 循环向上遍历目录树
      while (current_depth++ < max_depth && current != root)
      {
      // 调试输出当前检查的路径 (实际使用时可以移除)
      // std::cout << "检查: " << current << std::endl;

      // 检查当前目录名是否匹配目标
      if (current.filename() == target_name)
      {
      // 将匹配路径添加到结果集
      matches.push_back(current);

      // 如果设置了找到即停止且不需要所有结果
      if (stop_at_first && !matches.empty())
      {
      return matches.front(); // 返回第一个匹配项
      }
      }
      // 到达根目录时停止遍历 (防止无限循环)
      if (current == root)
      {
      break;
      }

      // 获取父目录 (当已经是根目录时,parent_path()返回自身)
      fs::path parent = current.parent_path();

      // 检查是否无法继续向上 (文件系统保护机制)
      if (parent == current)
      {
      break;
      }

      // 移动到父目录继续搜索
      current = parent;
      }
      • while (current_depth++ < max_depth && current != root)循环向上遍历目录树,直到到达根目录或达到最大搜索层数。
      • if (current.filename() == target_name)检查当前目录名是否匹配目标。
        • filename()函数用于获取路径的文件名。
        • matches.push_back(current)将匹配路径添加到结果集。
      • if (stop_at_first && !matches.empty())如果设置了找到即停止且不需要所有结果,则返回第一个匹配项。
        • !matches.empty()判断结果集是否为空。
      • if (current == root)到达根目录时停止遍历。
      • fs::path parent = current.parent_path()获取父目录。
        • parent_path()函数用于获取路径的父目录。
      • if (parent == current)检查是否无法继续向上。
        • parent == current判断父目录是否与当前目录相同,如果相同则表示无法继续向上。
      • current = parent移动到父目录继续搜索。
      1. 根据查找结果返回相应的值。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      // 处理查找结果
      if (matches.empty())
      {
      // 没有找到匹配项
      return std::nullopt;
      }
      else if (find_all)
      {
      // 返回所有匹配路径 (实际实现需要修改返回类型)
      // 此处简化处理返回最近的一个匹配项
      return matches.back(); // 最近的祖先目录
      }
      else
      {
      // 默认返回最近(最上层)的匹配项
      return matches.back();
      }
      • if (matches.empty())如果没有找到匹配项,则返回空值。
        • std::nullopt表示空值。
      • else if (find_all)如果设置了查找所有目标文件夹,则返回所有匹配路径。
        • matches.back()返回最近的一个匹配项。
      • else默认返回最近(最上层)的匹配项。
        • matches.back()返回最近的一个匹配项。
  4. void copyFiles(const fs::path &src, const fs::path &dst)函数

    • 该函数用于复制文件,将源文件复制到目标文件夹。
    • src : 源文件路径
    • dst : 目标文件夹路径
    • 该函数的实现如下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    std::vector<std::future<void>> futures; // 存储异步任务的结果

    // 原子计数器:用于线程安全的进度跟踪
    std::atomic<int> filesCopied = 0; // 已复制的文件数
    std::atomic<int> directoriesCopied = 0; // 已复制的目录数
    std::atomic<int> tasksCompleted = 0; // 新增:跟踪完成的任务数(含失败)
    std::atomic<int> copyErrors = 0; // 新增:错误计数器
    std::mutex errorMutex; // 新增:保护错误输出

    // 获取开始时间(用于计算速度)
    auto startTime = std::chrono::steady_clock::now();

    auto showProgress = [&]()
    {
    const int totalTasks = futures.size();
    const int completed = tasksCompleted; // 使用tasksCompleted替代filesCopied+directoriesCopied

    auto duration = std::chrono::steady_clock::now() - startTime;
    double seconds = std::chrono::duration<double>(duration).count();
    double progress = (totalTasks > 0) ? (100.0 * completed / totalTasks) : 0.0;
    double speed = (seconds > 0.1) ? (filesCopied / seconds) : 0.0;

    std::cout << "\r进度: " << std::fixed << std::setprecision(1)
    << progress << "% | "
    << "文件: " << filesCopied
    << " | 目录: " << directoriesCopied
    << " | 错误: " << copyErrors // 显示错误计数
    << " | 速度: " << std::setprecision(1) << speed << " 文件/秒"
    << " | 任务: " << completed << "/" << totalTasks
    << std::flush;
    };

    // 确保目标目录存在(加入异常处理)
    try
    {
    fs::create_directories(dst); // 创建目标目录
    }
    catch (const std::exception &e)
    {
    std::cerr << "创建目标目录失败: " << dst << " - " << e.what() << std::endl;
    return;
    }

    for (const auto &entry : fs::directory_iterator(src))
    {
    auto target = dst / entry.path().filename(); // 目标路径

    if (fs::is_directory(entry))
    {
    futures.push_back(std::async(std::launch::async, [=, &directoriesCopied, &tasksCompleted, &copyErrors, &errorMutex]
    {
    try
    {
    // 尝试创建子目录
    fs::create_directories(target);
    // 递归复制(可能抛出异常)
    fs::copy(entry, target, fs::copy_options::recursive | fs::copy_options::overwrite_existing);
    directoriesCopied++;
    }
    catch (const std::exception &e)
    {
    std::lock_guard<std::mutex> lock(errorMutex);
    std::cerr << "\n目录复制错误: " << entry.path() << " -> " << target
    << " - " << e.what() << std::endl;
    copyErrors++;
    }
    tasksCompleted++; // 无论成功失败都标记任务完成
    }));
    }
    else if (fs::is_regular_file(entry))
    {
    futures.push_back(std::async(std::launch::async, [=, &filesCopied, &tasksCompleted, &copyErrors, &errorMutex]
    {
    try
    {
    // 尝试复制文件
    fs::copy_file(entry, target, fs::copy_options::overwrite_existing);
    filesCopied++;
    }
    catch (const std::exception &e)
    {
    std::lock_guard<std::mutex> lock(errorMutex);
    std::cerr << "\n文件复制错误: " << entry.path() << " -> " << target
    << " - " << e.what() << std::endl;
    copyErrors++;
    }
    tasksCompleted++; // 无论成功失败都标记任务完成
    }));
    }
    }

    std::cout << "开始复制,总任务数: " << futures.size() << "\n";

    // 修改循环条件:基于tasksCompleted而非成功计数
    while (tasksCompleted < static_cast<int>(futures.size()))
    {
    showProgress();
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    }

    showProgress();
    std::cout << "\n复制完成! ";

    // 处理异步任务中的异常(防止未捕获异常传播)
    for (auto &f : futures)
    {
    try
    {
    f.get(); // 获取异步结果(可能重新抛出异常)
    }
    catch (const std::exception &e)
    {
    // 此处异常已在任务内处理过,此处仅确保不会终止程序
    }
    }

    auto endTime = std::chrono::steady_clock::now();
    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
    std::cout << "总耗时: " << duration.count() / 1000.0 << " 秒 | 错误总数: " << copyErrors << "\n";
    • std::vector<std::future<void>> futures;用于存储异步任务的结果。
      • std::future<void>表示一个异步操作的状态。
    • std::atomic<int> filesCopied = 0;:用于记录已复制的文件数。
    • std::atomic<int> directoriesCopied = 0;:用于记录已复制的目录数。
    • std::atomic<int> tasksCompleted = 0;:跟踪已完成的任务数(包含失败的任务数)。
      • ps.使用atomic(原子操作)可以避免多线程同时修改时的数据竞争。
    • std::mutex errorMutex;:用于保护错误计数器,防止多线程同时修改时出现数据竞争。
    • auto startTime = std::chrono::steady_clock::now();获取开始时间(用于计算速度)。
    • auto showProgress = [&]() {...};进度显示函数,用于显示复制进度。
      • const int totalTasks = futures.size();获取总任务数。

      • const int completed = tasksCompleted;获取已完成任务数。

      • auto duration = std::chrono::steady_clock::now() - startTime;计算耗时。

      • double seconds = std::chrono::duration<double>(duration).count();将耗时转换为秒。

        • duration模板的基本结构:

          1
          2
          template <class Rep, class Period = std::ratio<1>>
          class duration;
          • Rep:数值类型(如int, double等)

          • Period:时间单位(默认秒),使用std::ratio表示分数单位

          常用类型 等价表示 说明
          std::ratio<1> 1秒 默认单位
          std::ratio<1,1000> 1毫秒 千分之一秒
          std::ratio<1,1000000> 1微秒 百万分之一秒
        • std::chrono::duration<double>类型部分

          1. 这是模板类的类型声明
          2. double表示内部存储类型为双精度浮点数
          3. 时间单位为秒(默认std::ratio<1>)
          4. ps.可以通过修改durationmilliseconds来使用毫秒模板。
          • ex. std::chrono::milliseconds
        • (duration)变量部分

          1. 这是一个已经存在的duration对象
          2. 实际要转换的时间间隔值
          3. 通常通过时间点计算得到
        • std::chrono::duration<double>(duration)属于类型转换部分

          1. 创建新的duration对象
          2. 指定内部存储类型为double(浮点数)
          3. 自动转换为以秒为单位的持续时间
        • .count() 提取数值

          1. 返回duration对象内部存储的数值
          2. 由于指定为double类型,返回值为双精度浮点数
      • double progress = (totalTasks > 0) ? (100.0 * completed / totalTasks) : 0.0; 当任务数大于0时,计算进度百分比,否则进度为0。

      • double speed = (seconds > 0.1) ? (filesCopied / seconds) : 0.0; :当耗时超过0.1秒时,计算平均速度,否则速度为0。

      • std::cout << "\r进度: " << std::fixed << std::setprecision(1)

        • \r 回车符,使光标回到行首,用于覆盖之前的内容。
        • std::fixed 设置浮点数输出为固定小数点格式。
        • std::setprecision(1) 设置输出精度为1位小数。
      • << std::flush;

        • std::flush 强制刷新输出缓冲区,确保输出立即显示,而不是等待缓冲区满或程序结束。
      • 创建目录异常处理

        1
        2
        3
        4
        5
        6
        7
        8
        9
         try
        {
        fs::create_directories(dst);
        }
        catch (const std::exception &e)
        {
        std::cerr << "创建目标目录失败: " << dst << " - " << e.what() << std::endl;
        return;
        }
        • fs::create_directories(dst); 创建目标目录
        • catch (const std::exception &e) 捕获异常
        • std::cerr << "创建目标目录失败: " << dst << " - " << e.what() << std::endl; 输出错误信息
      • for (const auto &entry : fs::directory_iterator(src))

        • fs::directory_iterator(src) 创建一个迭代器,用于遍历源目录中的所有条目。
        • for (const auto &entry : ...) 遍历每个条目。
        • const auto &entry 使用常量引用,避免不必要的拷贝,同时保证条目不会被修改。
      • auto target = dst / entry.path().filename();

        • dst / entry.path().filename() 使用/运算符将目标目录与条目的文件名连接起来,生成目标路径。
        • entry.path().filename() 获取条目的文件名或目录名。
      • fs::is_directory(entry)

        • 判断条目是否为目录。
      • fs::create_directories(target);

        • 创建目标目录
      • 创建异步任务复制子目录

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        futures.push_back(std::async(std::launch::async, [=, &directoriesCopied, &tasksCompleted, &copyErrors, &errorMutex]
        {
        try
        {
        // 尝试创建子目录
        fs::create_directories(target);
        // 递归复制(可能抛出异常)
        fs::copy(entry, target, fs::copy_options::recursive | fs::copy_options::overwrite_existing);
        directoriesCopied++;
        }
        catch (const std::exception &e)
        {
        std::lock_guard<std::mutex> lock(errorMutex);
        std::cerr << "\n目录复制错误: " << entry.path() << " -> " << target
        << " - " << e.what() << std::endl;
        copyErrors++;
        }
        tasksCompleted++; // 无论成功失败都标记任务完成
        }));
        • futures.push_back(...); 将异步任务的结果存储在futures向量中,以便稍后处理。
        • std::async(std::launch::async, [=, &directoriesCopied] {...}); 创建一个异步任务
          • std::async: 异步任务创建函数
          • std::launch::async: 启动策略,表示任务将在另一个线程中异步执行。
          • [=, &directoriesCopied]: 捕获列表,按值捕获所有变量,按引用捕获directoriesCopied
          • {...}: 异步任务的内容,使用lambda表达式定义。
        • try {...} catch (const std::exception &e) {...} 异常处理
          • try {...} 尝试创建子目录并复制整个子目录,递归复制,覆盖已存在的文件。
          • catch (const std::exception &e) {...} 捕获异常
            • std::lock_guard<std::mutex> lock(errorMutex); 创建互斥锁,保证在多线程环境下安全地访问共享资源。
            • std::cerr << "\n目录复制错误: " << entry.path() << " -> " << target << " - " << e.what() << std::endl; 输出错误信息
        • fs::copy(entry, target, fs::copy_options::recursive | fs::copy_options::overwrite_existing); 复制整个子目录,递归复制,覆盖已存在的文件。
          • fs::copy: 复制函数
          • entry: 源路径
          • target: 目标路径
          • fs::copy_options::recursive: 递归复制子目录
          • fs::copy_options::overwrite_existing: 覆盖已存在的文件
        • directoriesCopied++; 原子递增目录计数器。
      • else if (fs::is_regular_file(entry))
        -判断条目是否为普通文件。

      • 创建异步任务复制文件

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        futures.push_back(std::async(std::launch::async, [=, &filesCopied, &tasksCompleted, &copyErrors, &errorMutex]
        {
        try
        {
        fs::copy_file(entry, target, fs::copy_options::overwrite_existing);
        filesCopied++;
        }
        catch (const std::exception &e)
        {
        std::lock_guard<std::mutex> lock(errorMutex);
        std::cerr << "\n文件复制错误: " << entry.path() << " -> " << target
        << " - " << e.what() << std::endl;
        copyErrors++;
        }
        tasksCompleted++; // 无论成功失败都标记任务完成
        }));
        • futures.push_back(...); 将异步任务的结果存储在futures向量中,以便稍后处理。
        • std::async(std::launch::async, [=, &filesCopied] {...}); 创建一个异步任务
          • std::launch::async: 启动策略,表示任务将在另一个线程中异步执行。
          • [=, &filesCopied]: 捕获列表,按值捕获所有变量,按引用捕获filesCopied
          • {...}: 异步任务的内容,使用lambda表达式定义。
        • try {...} catch (const std::exception &e) {...} 异常处理
          • try {...} 尝试复制文件,覆盖已存在的文件。
          • catch (const std::exception &e) {...} 捕获异常
            • std::lock_guard<std::mutex> lock(errorMutex); 创建互斥锁,保证在多线程环境下安全地访问共享资源。
        • fs::copy_file(entry, target, fs::copy_options::overwrite_existing); 复制单个文件,覆盖已存在的文件。
          • fs::copy_file(): 复制文件函数
          • entry: 源路径
          • target: 目标路径
          • fs::copy_options::overwrite_existing: 覆盖已存在的文件
        • filesCopied++; 原子递增文件计数器。
      • std::cout << "开始复制,总任务数: " << futures.size() << "\n";

        • futures.size(): 异步任务的数量
      • 等待异步任务完成并且更新进度

        1
        2
        3
        4
        5
        6
            // 等待所有异步任务完成,并显示进度
        while (filesCopied + directoriesCopied < static_cast<int>(futures.size()))
        {
        showProgress();
        std::this_thread::sleep_for(std::chrono::milliseconds(200)); // 每200ms更新一次
        }
        • while (filesCopied + directoriesCopied < static_cast<int>(futures.size())) 当文件和目录复制完成数量小于异步任务数量时,继续循环。
          • ```filesCopied + directoriesCopied`: 文件和目录复制完成数量。
          • static_cast<int>(): 将异步任务数量显式转换为整数。
            • ps.显式转换是一种由程序员手动进行的类型转换,它告诉编译器将一个类型的值转换为另一个类型。与隐式转换不同,显式转换需要程序员明确指定转换的类型,因此也被称为“显式类型转换”或“强制类型转换”。
          • futures.size(): 异步任务数量。
        • showProgress(); 更新进度
        • std::this_thread::sleep_for(std::chrono::milliseconds(200)); 每隔200毫秒更新一次进度。
      • 等待所有任务完成

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        for (auto &f : futures)
        {
        try
        {
        f.get(); // 获取异步结果(可能重新抛出异常)
        }
        catch (const std::exception &e)
        {
        // 此处异常已在任务内处理过,此处仅确保不会终止程序
        }
        }
      • for (auto &f : futures) 遍历所有异步任务

        • futures: 异步任务向量
      • f.get(); 获取异步任务的结果,可能重新抛出异常。

      • catch (const std::exception &e) 捕获异常

        • catch (const std::exception &e) {...} 捕获异常
      • auto endTime = std::chrono::steady_clock::now();

        • std::chrono::steady_clock::now(): 获取当前时间点。
      • auto duration = std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime);

        • std::chrono::duration_cast<std::chrono::seconds>(endTime - startTime): 计算时间间隔,将时间间隔转换为秒。
          • chrono::duration_cast<>: 时间间隔转换函数
          • std::chrono::seconds: 时间间隔类型,表示秒。
          • endTime - startTime: 时间间隔,表示从开始时间到结束时间的间隔。
  5. void createFile(const fs::path &path, const std::string &content)函数

    • 该函数用于创建一个文件,并将指定的内容写入该文件。
    • const fs::path &path: 源路径
    • const std::string &content: 文件内容
    • 该函数实现如下:
      1
      2
      3
      4
      5
      6
      7
      ofstream file(path);
      if (!file)
      {
      cerr << "Error: 无法在 " << path << "下创建文件" << endl;
      return;
      }
      file << content;
    • ofstream file(path): 创建一个输出文件流,用于写入文件。使用std::ofstream 在离开作用域时自动调用析构函数关闭文件。
  6. void createCMakeLists(const fs::path &parent_path, const string &projectName)函数

    • 该函数用于在指定的父目录下创建一个CMakeLists.txt文件,并写入指定的项目名称。

    • const fs::path &parent_path: 父目录路径

    • const string &projectName: 项目名称

    • 该函数实现如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
        const string cmakeTemplate =
      R"(project()" + projectName + R"()

      # 添加包含目录
      include_directories(
      ${CMAKE_CURRENT_SOURCE_DIR}/include
      )

      # 收集所有源文件
      file(GLOB SOURCES "src/*.cpp")

      # 创建可执行文件
      add_executable(${PROJECT_NAME} ${SOURCES})

      # 设置目标属性
      set_target_properties(${PROJECT_NAME} PROPERTIES
      OUTPUT_NAME "${PROJECT_NAME}"
      RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/${PROJECT_NAME}"
      )
      )";
      createFile(parent_path / "CMakeLists.txt", cmakeTemplate);
    • const string cmakeTemplate: CMakeLists.txt文件内容模板

    • R"(project()" + projectName + R"()): 使用原始字符串字面值,将项目名称插入到模板中。

    • createFile(parent_path / "CMakeLists.txt", cmakeTemplate): 调用createFile()函数,在父目录下创建CMakeLists.txt文件,并将模板内容写入该文件。

  7. void createReadme(const fs::path &parent_path, const string &projectName)函数

    • 该函数用于在指定的父目录下创建一个README.md文件,并写入指定的项目名称。
    • const fs::path &parent_path: 父目录路径
    • const string &projectName: 项目名称
    • 该函数实现如下:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
          const std::string content =
      R"(# )" + projectName + R"(

      ## 简介

      ## 需求

      ## 练习
      )";

      createFile(parent_path / "readme.md", content);
    • const std::string content: README.md文件内容
    • R"(# )" + projectName + R"(...): 使用原始字符串字面值,将项目名称插入到模板中。
    • createFile(parent_path / "readme.md", content): 调用createFile()函数,在父目录下创建README.md文件,并将模板内容写入该文件。

C++算法学习-创建新项目工具
http://ankali-aylina.github.io/2025/07/22/C-算法学习/
作者
Ankali-Aylina
发布于
2025年7月22日
更新于
2025年7月24日
许可协议