当前位置: 首页 > news >正文

C++学习:六个月从基础到就业——C++17:string_view与filesystem

C++学习:六个月从基础到就业——C++17:string_view与filesystem

本文是我C++学习之旅系列的第四十八篇技术文章,也是第三阶段"现代C++特性"的第十篇,主要介绍C++17引入的两个强大功能:string_view和filesystem库。查看完整系列目录了解更多内容。

引言

C++17标准引入了许多实用的新特性,其中std::string_viewstd::filesystem库尤为显著。这两个特性解决了C++程序员长期面临的实际问题:字符串处理的性能开销和跨平台文件系统操作的复杂性。

std::string_view提供了一种轻量级、非拥有型的字符串引用方式,可以显著减少不必要的字符串复制和内存分配,提高性能。而std::filesystem库则提供了一套统一且跨平台的文件和目录操作接口,简化了以往需要依赖操作系统特定API或第三方库才能完成的文件操作。

本文将探讨这两个特性的设计理念、使用方法、性能优势以及最佳实践,帮助你在实际项目中充分利用它们提升代码质量和性能。

目录

  • std::string_view
    • 基本概念与动机
    • 创建和使用string_view
    • 与std::string的对比
    • 注意事项与生命周期管理
    • 实际应用场景
  • std::filesystem库
    • 背景与概述
    • 路径操作
    • 文件状态查询
    • 目录遍历
    • 文件操作
    • 空间与权限管理
    • 错误处理
  • 实际应用示例
    • 文件内容分析工具
    • 文件系统监控
  • 性能考量与最佳实践
    • 使用指南
    • 常见陷阱与避坑
  • 总结

std::string_view

基本概念与动机

std::string_view是C++17引入的一个轻量级、非拥有型(non-owning)的字符串引用类型,定义在<string_view>头文件中。它表示字符数组的一个视图(view),不拥有也不会复制这些字符,只是引用已存在的字符序列。

设计std::string_view的主要动机是解决以下问题:

  1. 减少不必要的内存分配和字符串复制:在许多只读访问字符串的场景中,传统的std::string会导致不必要的内存分配和数据复制。
  2. 统一接口:提供一个统一的接口来处理各种形式的字符串,如C风格字符串、std::string、字符数组等。
  3. 提高性能:通过避免复制来提高字符串处理的性能,特别是在频繁进行字符串传递但不修改内容的场景中。

std::string_view可以看作是对字符序列的一个"窗口",它不拥有底层数据,因此是只读的、轻量级的。

创建和使用string_view

以下是创建和使用std::string_view的基本示例:

#include <iostream>
#include <string>
#include <string_view>int main() {// 从C风格字符串创建std::string_view sv1 = "Hello, string_view!";// 从std::string创建std::string s = "Hello from string";std::string_view sv2 = s;// 从字符数组创建char arr[] = {'A', 'r', 'r', 'a', 'y', '\0'};std::string_view sv3(arr, 5);  // 显式指定长度,不依赖'\0'// 基本用法std::cout << "sv1: " << sv1 << std::endl;std::cout << "sv2: " << sv2 << std::endl;std::cout << "sv3: " << sv3 << std::endl;// 获取长度和判断是否为空std::cout << "sv1 length: " << sv1.length() << std::endl;std::cout << "sv1 empty: " << std::boolalpha << sv1.empty() << std::endl;// 访问单个字符std::cout << "sv1[7]: " << sv1[7] << std::endl;// 修改视图范围(注意:这不会改变底层数据)std::string_view sv4 = sv1;sv4.remove_prefix(7);  // 移除前7个字符sv4.remove_suffix(1);  // 移除最后1个字符std::cout << "sv4 after remove: " << sv4 << std::endl;std::cout << "original sv1: " << sv1 << std::endl;  // sv1不受影响// 子串操作std::string_view sv5 = sv1.substr(7, 6);  // 从索引7开始的6个字符std::cout << "sv5 (substring): " << sv5 << std::endl;return 0;
}

与std::string的对比

std::string_viewstd::string的主要区别:

  1. 所有权模型

    • std::string拥有并管理自己的内存
    • std::string_view不拥有内存,只是一个引用
  2. 修改能力

    • std::string可以修改字符串内容
    • std::string_view只能读取,不能修改字符串内容(除了调整视图范围)
  3. 性能特性

    • std::string需要分配内存并复制数据
    • std::string_view不分配内存,不复制数据,只存储指针和长度

下面是一个简单的性能对比:

#include <iostream>
#include <string>
#include <string_view>
#include <chrono>// 测量函数执行时间的辅助函数
template <typename Func>
double measureTime(Func func, int iterations = 1000000) {auto start = std::chrono::high_resolution_clock::now();for (int i = 0; i < iterations; ++i) {func();}auto end = std::chrono::high_resolution_clock::now();std::chrono::duration<double, std::milli> elapsed = end - start;return elapsed.count();
}// 使用string的函数
bool containsString(const std::string& s, const std::string& substr) {return s.find(substr) != std::string::npos;
}// 使用string_view的函数
bool containsStringView(std::string_view s, std::string_view substr) {return s.find(substr) != std::string_view::npos;
}int main() {std::string str = "This is a test string for performance comparison";std::string substr = "test";// 测量string版本auto stringTime = measureTime([&]() {containsString(str, substr);});// 测量string_view版本auto stringViewTime = measureTime([&]() {containsStringView(str, substr);});std::cout << "String version took: " << stringTime << " ms" << std::endl;std::cout << "String_view version took: " << stringViewTime << " ms" << std::endl;std::cout << "String_view is " << (stringTime / stringViewTime) << " times faster" << std::endl;return 0;
}

注意事项与生命周期管理

使用std::string_view时最重要的是注意生命周期问题。由于它只是引用而不拥有底层数据,如果底层数据被销毁,那么string_view将变成悬空引用(dangling reference):

#include <iostream>
#include <string>
#include <string_view>// 危险的函数:返回局部字符串的string_view
std::string_view dangerousFunction() {std::string localString = "This string will be destroyed!";return localString;  // 错误:返回了一个即将被销毁对象的视图
}// 安全的函数:参数string_view不会延长其引用对象的生命周期
void safeFunction(std::string_view sv) {std::cout << "Safely using: " << sv << std::endl;
}int main() {// 正确用法std::string s1 = "Hello";std::string_view sv1 = s1;safeFunction(sv1);  // 安全:s1在此处仍然存在// 危险用法:使用已销毁字符串的视图{std::string s2 = "Temporary";sv1 = s2;}  // s2被销毁// 使用sv1是危险的,因为它引用的s2已经被销毁// 安全用法:引用字符串字面量std::string_view sv4 = "String literal";  // 安全,字符串字面量的生命周期是整个程序std::cout << "String literal view: " << sv4 << std::endl;return 0;
}

需要特别注意的其他事项:

  1. 不要从临时字符串创建长生命周期的string_view
  2. string_view不保证以null结尾
  3. 避免多次不必要的转换

实际应用场景

std::string_view在以下场景特别有用:

  1. 函数参数:当函数只需要读取字符串而不修改时:

    bool isValidEmail(std::string_view email) {return email.find('@') != std::string_view::npos &&email.find('.', email.find('@')) != std::string_view::npos;
    }
    
  2. 解析和分词:处理大文本时的子串操作:

    std::vector<std::string_view> split(std::string_view str, char delimiter) {std::vector<std::string_view> result;size_t start = 0;size_t end = str.find(delimiter);while (end != std::string_view::npos) {result.push_back(str.substr(start, end - start));start = end + 1;end = str.find(delimiter, start);}result.push_back(str.substr(start));return result;
    }
    
  3. 配置处理和键值查找

    class ConfigParser {
    private:std::map<std::string, std::string> configData;public:void parse(std::string_view configText) {auto lines = split(configText, '\n');for (auto line : lines) {// 处理配置行...}}std::string_view get(std::string_view key) const {auto it = configData.find(std::string(key));if (it != configData.end()) {return it->second;}return "";}
    };
    

std::filesystem库

背景与概述

C++17引入的std::filesystem库是基于Boost.Filesystem库的标准化版本,它提供了一套跨平台的文件系统操作接口。在C++17之前,C++标准库并没有提供直接操作文件系统的功能,开发者不得不依赖平台特定的API或第三方库。

std::filesystem库定义在<filesystem>头文件中,包含在std::filesystem命名空间内。它的主要功能包括:

  1. 路径表示和操作:提供了std::filesystem::path类来表示和操作文件路径
  2. 文件状态查询:检查文件是否存在、类型、大小等
  3. 目录遍历:迭代目录内容
  4. 文件操作:创建、删除、重命名文件等
  5. 权限管理:查询和修改文件权限
  6. 符号链接处理:创建和解析符号链接
  7. 空间查询:检查磁盘空间信息

这个库的最大优势是提供了统一的跨平台接口,不管是Windows、Linux、macOS还是其他支持C++17的平台,都可以使用相同的代码进行文件系统操作。

路径操作

std::filesystem::pathstd::filesystem库的核心类,用于表示和操作文件路径:

#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;int main() {// 创建路径对象fs::path p1 = "C:/Users/username/Documents/file.txt";  // Windows风格fs::path p2 = "/home/username/Documents/file.txt";     // POSIX风格// 路径组合fs::path dir = "C:/Users/username";fs::path subdir = "Documents";fs::path file = "file.txt";fs::path combined = dir / subdir / file;  // 使用/运算符组合路径std::cout << "Combined path: " << combined << std::endl;// 路径分解std::cout << "Filename: " << combined.filename() << std::endl;std::cout << "Stem: " << combined.stem() << std::endl;std::cout << "Extension: " << combined.extension() << std::endl;std::cout << "Parent path: " << combined.parent_path() << std::endl;// 路径修改fs::path p3 = combined;p3.replace_filename("newfile.docx");p3.replace_extension(".pdf");// 相对路径和绝对路径fs::path rel = "Documents/file.txt";fs::path abs = fs::absolute(rel);return 0;
}

文件状态查询

std::filesystem提供了多种查询文件状态的函数:

#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;void checkFileStatus(const fs::path& path) {std::cout << "File: " << path << std::endl;// 检查文件是否存在bool exists = fs::exists(path);std::cout << "  Exists: " << std::boolalpha << exists << std::endl;if (!exists) return;// 检查文件类型std::cout << "  Is regular file: " << fs::is_regular_file(path) << std::endl;std::cout << "  Is directory: " << fs::is_directory(path) << std::endl;std::cout << "  Is symlink: " << fs::is_symlink(path) << std::endl;// 获取文件大小if (fs::is_regular_file(path)) {auto size = fs::file_size(path);std::cout << "  Size: " << size << " bytes" << std::endl;}// 获取最后修改时间auto lastWriteTime = fs::last_write_time(path);auto systemTime = std::chrono::time_point_cast<std::chrono::system_clock::duration>(lastWriteTime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());std::time_t tt = std::chrono::system_clock::to_time_t(systemTime);std::cout << "  Last modified: " << std::ctime(&tt);
}int main() {fs::path currentPath = fs::current_path();checkFileStatus(currentPath);return 0;
}

目录遍历

std::filesystem提供了两种遍历目录的方式:

  1. directory_iterator:迭代目录中的直接条目
  2. recursive_directory_iterator:递归迭代目录及其所有子目录
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;// 简单目录遍历
void listDirectory(const fs::path& path) {std::cout << "Contents of: " << path << std::endl;try {if (!fs::exists(path) || !fs::is_directory(path)) {std::cout << "Path is not a valid directory" << std::endl;return;}// 遍历目录内容for (const auto& entry : fs::directory_iterator(path)) {std::cout << "  " << entry.path().filename();if (fs::is_directory(entry)) {std::cout << " (directory)";} else if (fs::is_regular_file(entry)) {std::cout << " (file, " << fs::file_size(entry) << " bytes)";}std::cout << std::endl;}} catch (const fs::filesystem_error& e) {std::cerr << "Error: " << e.what() << std::endl;}
}// 递归目录遍历
void listDirectoryRecursive(const fs::path& path) {try {// 遍历目录及其所有子目录for (const auto& entry : fs::recursive_directory_iterator(path, fs::directory_options::skip_permission_denied)) {// 输出缩进for (int i = 0; i < entry.depth(); ++i) {std::cout << "  ";}std::cout << entry.path().filename();if (fs::is_directory(entry)) {std::cout << " (directory)";} else if (fs::is_regular_file(entry)) {std::cout << " (file)";}std::cout << std::endl;}} catch (const fs::filesystem_error& e) {std::cerr << "Error: " << e.what() << std::endl;}
}int main() {fs::path currentDir = fs::current_path();listDirectory(currentDir);return 0;
}

文件操作

std::filesystem提供了多种文件和目录操作函数:

#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;void demonstrateFileOperations() {// 创建临时测试目录fs::path testDir = fs::temp_directory_path() / "filesystem_test";// 删除目录(如果已存在)if (fs::exists(testDir)) {fs::remove_all(testDir);}// 创建目录fs::create_directory(testDir);fs::path subDir = testDir / "subdir";fs::create_directory(subDir);// 创建文件fs::path testFile = testDir / "test.txt";{std::ofstream file(testFile);file << "This is a test file content.";}// 复制文件fs::path copyFile = testDir / "test_copy.txt";fs::copy_file(testFile, copyFile);// 重命名/移动文件fs::path renamedFile = testDir / "test_renamed.txt";fs::rename(copyFile, renamedFile);// 列出目录内容std::cout << "Contents of test directory:" << std::endl;for (const auto& entry : fs::directory_iterator(testDir)) {std::cout << "  " << entry.path().filename() << std::endl;}// 删除文件和目录fs::remove(testFile);fs::remove_all(testDir);
}int main() {demonstrateFileOperations();return 0;
}

空间与权限管理

std::filesystem提供了查询磁盘空间信息和文件权限管理的功能:

#include <iostream>
#include <filesystem>
#include <iomanip>
namespace fs = std::filesystem;void checkDiskSpace(const fs::path& path) {try {fs::space_info space = fs::space(path);std::cout << "Disk space for path: " << path << std::endl;std::cout << "  Capacity: " << space.capacity << " bytes" << std::endl;std::cout << "  Free: " << space.free << " bytes" << std::endl;std::cout << "  Available: " << space.available << " bytes" << std::endl;} catch (const fs::filesystem_error& e) {std::cerr << "Error: " << e.what() << std::endl;}
}void printPermissions(fs::perms p) {std::cout << "Permission bits: " << std::oct << static_cast<int>(p) << std::dec << std::endl;std::cout << "User: ";std::cout << ((p & fs::perms::owner_read) != fs::perms::none ? "r" : "-");std::cout << ((p & fs::perms::owner_write) != fs::perms::none ? "w" : "-");std::cout << ((p & fs::perms::owner_exec) != fs::perms::none ? "x" : "-");std::cout << " Group: ";std::cout << ((p & fs::perms::group_read) != fs::perms::none ? "r" : "-");std::cout << ((p & fs::perms::group_write) != fs::perms::none ? "w" : "-");std::cout << ((p & fs::perms::group_exec) != fs::perms::none ? "x" : "-");std::cout << " Others: ";std::cout << ((p & fs::perms::others_read) != fs::perms::none ? "r" : "-");std::cout << ((p & fs::perms::others_write) != fs::perms::none ? "w" : "-");std::cout << ((p & fs::perms::others_exec) != fs::perms::none ? "x" : "-");std::cout << std::endl;
}int main() {fs::path currentPath = fs::current_path();checkDiskSpace(currentPath);// 获取文件权限fs::perms currentPerms = fs::status(currentPath).permissions();printPermissions(currentPerms);return 0;
}

错误处理

std::filesystem函数在遇到错误时可能会抛出std::filesystem::filesystem_error异常,或者返回错误码:

#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;void demonstrateErrorHandling() {// 使用异常处理文件系统错误try {fs::path nonExistentFile = "/path/to/nonexistent/file.txt";auto fileSize = fs::file_size(nonExistentFile);} catch (const fs::filesystem_error& e) {std::cerr << "Filesystem error: " << e.what() << std::endl;std::cerr << "Error code: " << e.code().value() << " - " << e.code().message() << std::endl;}// 使用错误码处理文件系统错误fs::path nonExistentFile = "/path/to/nonexistent/file.txt";std::error_code ec;bool exists = fs::exists(nonExistentFile, ec);if (ec) {std::cerr << "Error checking if file exists: " << ec.message() << std::endl;} else {std::cout << "File " << (exists ? "exists" : "does not exist") << std::endl;}
}int main() {demonstrateErrorHandling();return 0;
}

实际应用示例

文件内容分析工具

结合std::string_viewstd::filesystem可以创建高效的文件内容分析工具:

#include <iostream>
#include <filesystem>
#include <fstream>
#include <string>
#include <string_view>
#include <vector>
#include <algorithm>
namespace fs = std::filesystem;class FileAnalyzer {
private:std::string fileContent;std::vector<std::string_view> lines;public:bool loadFile(const fs::path& filePath) {if (!fs::exists(filePath) || !fs::is_regular_file(filePath)) {return false;}std::ifstream file(filePath, std::ios::binary);if (!file) return false;// 读取整个文件内容fileContent = std::string(std::istreambuf_iterator<char>(file),std::istreambuf_iterator<char>());// 分割成行splitLines();return true;}size_t countOccurrences(std::string_view pattern) const {size_t count = 0;size_t pos = 0;while ((pos = fileContent.find(pattern.data(), pos, pattern.length())) != std::string::npos) {++count;pos += pattern.length();}return count;}std::vector<std::string_view> findLines(std::string_view pattern) const {std::vector<std::string_view> result;for (const auto& line : lines) {if (line.find(pattern) != std::string_view::npos) {result.push_back(line);}}return result;}private:void splitLines() {lines.clear();size_t start = 0;size_t end = fileContent.find('\n');while (end != std::string::npos) {std::string_view line(&fileContent[start], end - start);if (!line.empty() && line.back() == '\r') {line.remove_suffix(1);  // 处理CRLF行尾}lines.push_back(line);start = end + 1;end = fileContent.find('\n', start);}// 处理最后一行if (start < fileContent.size()) {std::string_view line(&fileContent[start], fileContent.size() - start);if (!line.empty() && line.back() == '\r') {line.remove_suffix(1);}lines.push_back(line);}}
};int main() {FileAnalyzer analyzer;// 分析当前目录中的所有.cpp文件fs::path currentPath = fs::current_path();for (const auto& entry : fs::recursive_directory_iterator(currentPath)) {if (fs::is_regular_file(entry) && entry.path().extension() == ".cpp") {std::cout << "Analyzing file: " << entry.path().filename() << std::endl;if (analyzer.loadFile(entry.path())) {// 统计关键字出现次数std::cout << "  'auto' occurrences: " << analyzer.countOccurrences("auto") << std::endl;std::cout << "  'const' occurrences: " << analyzer.countOccurrences("const") << std::endl;// 查找包含特定模式的行auto matchingLines = analyzer.findLines("main");std::cout << "  Lines containing 'main': " << matchingLines.size() << std::endl;// 显示前3行匹配内容for (size_t i = 0; i < std::min(matchingLines.size(), size_t(3)); ++i) {std::cout << "    " << matchingLines[i] << std::endl;}}std::cout << std::endl;}}return 0;
}

文件系统监控

std::filesystem可以用来实现简单的文件系统监控工具:

#include <iostream>
#include <filesystem>
#include <map>
#include <chrono>
#include <thread>
namespace fs = std::filesystem;class FileSystemMonitor {
private:fs::path monitorPath;std::map<fs::path, fs::file_time_type> fileTimestamps;bool running = false;public:FileSystemMonitor(const fs::path& path) : monitorPath(path) {}void initialize() {if (!fs::exists(monitorPath) || !fs::is_directory(monitorPath)) {throw std::runtime_error("Invalid directory path");}// 初始化文件时间戳记录for (const auto& entry : fs::recursive_directory_iterator(monitorPath)) {if (fs::is_regular_file(entry)) {fileTimestamps[entry.path()] = fs::last_write_time(entry.path());}}std::cout << "Monitoring " << fileTimestamps.size() << " files in " << monitorPath << std::endl;}void start() {running = true;while (running) {checkForChanges();std::this_thread::sleep_for(std::chrono::seconds(2));}}void stop() {running = false;}private:void checkForChanges() {// 检查现有文件的变化for (auto it = fileTimestamps.begin(); it != fileTimestamps.end(); ) {const fs::path& path = it->first;if (!fs::exists(path)) {std::cout << "File deleted: " << path.filename() << std::endl;it = fileTimestamps.erase(it);} else {auto lastWriteTime = fs::last_write_time(path);if (lastWriteTime != it->second) {std::cout << "File modified: " << path.filename() << std::endl;it->second = lastWriteTime;}++it;}}// 检查新文件for (const auto& entry : fs::recursive_directory_iterator(monitorPath)) {if (fs::is_regular_file(entry)) {const fs::path& path = entry.path();if (fileTimestamps.find(path) == fileTimestamps.end()) {std::cout << "New file: " << path.filename() << std::endl;fileTimestamps[path] = fs::last_write_time(path);}}}}
};int main() {try {FileSystemMonitor monitor(fs::current_path());monitor.initialize();std::cout << "Starting file system monitor. Press Ctrl+C to stop." << std::endl;monitor.start();} catch (const std::exception& e) {std::cerr << "Error: " << e.what() << std::endl;return 1;}return 0;
}

性能考量与最佳实践

使用指南

string_view最佳实践
  1. 将函数参数从const std::string&改为std::string_view

    // 改前
    void process(const std::string& str);// 改后
    void process(std::string_view str);
    
  2. 注意生命周期:确保string_view引用的字符串在使用期间保持有效

  3. 避免不必要的转换

    // 不好的做法
    std::string_view sv = text;
    std::string s = std::string(sv);  // 不必要的复制// 好的做法
    void processString(std::string_view sv) {// 直接使用sv
    }
    
  4. 使用string_view::data()时注意结尾:string_view不保证以null结尾

filesystem最佳实践
  1. 使用错误码而不是异常处理频繁操作

    std::error_code ec;
    for (const auto& file : files) {fs::remove(file, ec);if (ec) {// 处理错误ec.clear();}
    }
    
  2. 避免递归遍历大型目录树:可能导致性能问题或栈溢出

  3. 路径操作中使用operator/:使用/运算符组合路径而不是字符串连接

  4. 使用相对路径时注意当前目录:相对路径始终基于当前工作目录

常见陷阱与避坑

string_view陷阱
  1. 悬空引用:引用已销毁的字符串
  2. 从临时string创建string_view:临时string会立即销毁
  3. 修改string_view引用的字符串:可能导致意外行为
filesystem陷阱
  1. 路径分隔符混用:在不同操作系统上可能导致问题
  2. 权限问题:某些操作可能需要管理员权限
  3. 符号链接循环:递归遍历时可能导致无限循环
  4. 大文件操作:某些操作在大文件上可能表现不佳

总结

C++17引入的std::string_viewstd::filesystem库是现代C++程序员工具箱中的重要组成部分。

std::string_view提供了一种高效的方式来处理字符串数据,通过避免不必要的复制和内存分配,显著提高了性能。然而,使用时必须特别注意生命周期问题,以避免悬空引用带来的未定义行为。

std::filesystem库则填补了C++标准库中文件系统操作的空白,提供了跨平台的统一接口,大大简化了文件和目录操作的代码编写。它涵盖了从路径操作到文件状态查询、目录遍历和文件操作等全面的功能。

通过合理结合这两个强大的特性,我们可以编写更加高效、可维护的文件处理代码。无论是文本处理、配置管理、日志分析还是文件系统监控,这些工具都能帮助我们简化代码并提高性能。

在下一篇文章中,我们将开始探索C++20的新特性,首先介绍概念(Concepts),这是C++20引入的一个重要模板编程增强功能,它如何改变我们编写和使用模板的方式。


这是我C++学习之旅系列的第四十八篇技术文章。查看完整系列目录了解更多内容。

http://www.xdnf.cn/news/519265.html

相关文章:

  • Vue3前端xlsx导出
  • 微服务项目->在线oj系统(Java版 - 3)
  • 王树森推荐系统公开课 排序02:Multi-gate Mixture-of-Experts (MMoE)
  • 【AI面试秘籍】| 第15期:大模型如何稳定输出合法JSON?
  • 【Linux笔记】——线程同步条件变量与生产者消费者模型的实现
  • GEE谷歌地球引擎批量下载逐日ERA5气象数据的方法
  • 等于和绝对等于的区别
  • LeetCode 394. 字符串解码详解:Java栈实现与逐行解析
  • 第5章 监控与回归测试:日志收集 · 代码覆盖率 · 静态分析 · 质量门
  • Python爬虫实战:通过PyExecJS库实现逆向解密
  • 院士方复全数学命题证明采用预期理由和循环论证以及类比的错误方法
  • web页面布局基础
  • 【动态规划】路径问题
  • STM32八股【9】-----volatile关键字
  • vim - v
  • Python数据可视化 - Pyecharts绘图示例
  • 中级统计师-统计学基础知识-第三章 参数估计
  • 【Linux】命令行参数和环境变量
  • 【PyQt5实战】五大对话框控件详解:从文件选择到消息弹窗
  • 【typenum】 11 私有模块(private.rs)
  • 【Redis实战篇】Redis消息队列
  • 10.9 LangChain LCEL革命:43%性能提升+声明式语法,AI开发效率飙升实战指南
  • 深入理解递归算法:Go语言实现指南
  • C44-练习
  • 全基因组关联研究揭示了脑淋巴活动的机制
  • Rstudio换皮:自定义彩虹括号与缩进线
  • Python Requests库完全指南:从入门到精通
  • 《C语言中的传值调用与传址调用》
  • 多头自注意力机制—Transformer模型的并行特征捕获引擎
  • 如何畅通需求收集渠道,获取用户反馈?