一、嵌套命名空间
在C++17之前,我们想要定义多层命名空间,只能像下面这样定义:
cpp
namespace A
{
namespace B
{
namespace C
{
void foo()
{
std::cout << "Old nested namespace style" << std::endl;
}
}
}
}
C++17引入了简化的嵌套命名空间定义语法,使得定义多层嵌套的命名空间更加简洁方便。
cpp
namespace A::B::C
{
void bar()
{
std::cout << "New nested namespace style" << std::endl;
}
}
同时,为了使用时更加方便,我们也可以给嵌套命名空间定义别名:
cpp
namespace abc = A::B::C;
int main()
{
abc::bar();
}
二、__has_include
__has_include 是 C++17 引⼊的⼀个特殊预处理器功能,⽤于在编译时检查某个头⽂件是否可以被包含。__has_include (<头文件名>) 或 **__has_include ("头文件名")**返回⼀个可以被预处理器解释的整型常量表达式,1 表⽰头⽂件存在且可被包含, 0 表⽰头⽂件不可⽤。
cpp
// 示例:检查是否⽀持⽂件系统相关库
#if __has_include(<filesystem>)
#include <filesystem>
namespace fs = std::filesystem;
#elif __has_include(<experimental/filesystem>)
#include <experimental/filesystem>
namespace fs = std::experimental::filesystem;
#else
#error "需要 filesystem ⽀持"
#endif
三、std::string_view
std::string_view 是 C++17 标准库中引入的⼀个非拥有的字符串视图类,它提供了⼀种轻量级的方式来"查看"⼀个已有的字符串(或字符数组),无需复制其内容,因此它本身不分配或管理内存。
std::string_view 提供和std::string完全类似的接口,主要提供string读相关的接口,不提供写相关的接口,另外它自身还支持⼀些移动视图位置的接口。
你可以把std::string_view想象成⼀个指向现有字符串的"望远镜"或"观察窗⼝",它不拥有空间和数据,底层不分配内存,只保存原始字符串的指针和⻓度;⽆所有权,不负责字符串的⽣命周期管理。
cpp
#include <iostream>
#include <string>
#include <string_view>
int main()
{
// 从C⻛格字符串构造
std::string_view sv1("Hello, world!");
std::cout << sv1 << std::endl;
// 从std::string构造
std::string str = "C++17 string_view";
std::string_view sv2(str);
std::cout << sv2 << std::endl;
// 从部分字符串构造
std::string_view sv3(str.c_str() + 6, 6);
std::cout << sv3 << std::endl;
return 0;
}
std::string_view⼏乎是零开销,它不会复制字符串数据,构造和析构成本极低。并且提供了与std::string ⾮常相似的接⼝(⼏乎提供了所有的读相关接⼝),并且兼容性强,它可以⾼效地从std::string 、字符串字⾯量、 char* 等任何字符序列构造⽽来。这样在很多场景下可以减少构造和拷⻉,⼜可以利⽤string类似的相关接⼝处理串。
cpp
#include <iostream>
#include <string_view>
std::string_view extract(std::string_view input, char sep)
{
size_t pos = input.find(sep);
return input.substr(0, pos);
}
int main()
{
const char* str = "hello:world";
std::string_view sv = extract(str, ':');
std::cout << sv << std::endl;
return 0;
}
四、文件系统库
std::filesystem库提供了⼀个现代化的、跨平台的方式来处理⽂件系统和路径。它极大地简化了文件和目录的操作,使得 C++ 程序员不再需要依赖平台特定的API。
⽂件系统库相关的内容都在 std::filesystem 命名空间下⾯,这个命名空间有点太⻓了,我们可以使⽤ namespace fs = std::filesystem 的⽅式,定义⼀下命名空间的别名,使⽤起来更⽅便⼀点。
库的核心是以下几个类:
• fs::path :最重要的类,⽤于表⽰和操作路径。
• fs::directory_entry :表⽰⽬录条⽬(⼀个⽂件或⼦⽬录),它可以缓存⽂件属性以提⾼
效率。
• fs::directory_iterator :⽤于遍历当前⽬录内容的迭代器。
• fs::recursive_directory_iterator :⽤于递归遍历⽬录及其所有⼦⽬录的迭代器。
• fs::file_status :表⽰⽂件的状态信息(类型、权限等)。
4.1 路径相关功能
4.1.1 路径构造
cpp
#include <filesystem>
// 使⽤命名空间别名 fs 来简化代码(常⻅的做法)
namespace fs = std::filesystem;
int main()
{
fs::path p1 = "/usr/local/bin"; // 从字符串隐式构造
fs::path p2 = "C:\\Windows\\System32"; // Windows 路径,注意转义反斜杠
fs::path p3 = R"(C:\Windows\System32)"; // 使用原始字符串字面量避免转义
fs::path p4 = fs::current_path(); // 获取当前工作目录的路径
return 0;
}
4.1.2 路径分解
cpp
int main()
{
fs::path p = R"(C:\Windows\System32\test.cpp)"; // 使⽤原始字符串字⾯量避免转义
std::cout << p << std::endl;
std::cout << p.root_name() << std::endl; // 根名称 (如 "C:")(在POSIX上通常为空)
std::cout << p.root_directory() << std::endl; // 根⽬录 (如 "\\")(在POSIX上通常为 / )
std::cout << p.root_path() << std::endl; // 根路径 (如 "C:\\")(在POSIX上通常为 / )
std::cout << p.relative_path() << std::endl; // 相对路径部分
std::cout << p.parent_path() << std::endl; // ⽗路径
std::cout << p.filename() << std::endl; // ⽂件名部分
std::cout << p.stem() << std::endl; // 不带扩展名的⽂件名
std::cout << p.extension() << std::endl; // 扩展名
return 0;
}
运行结果
4.1.3 路径修改
cpp
int main()
{
fs::path p = "temp";
// 使⽤ /= 运算符追加路径,自动处理分隔符
p /= "subdir";
p /= "file.log";
std::cout << p << std::endl; // p 现在为 "temp/subdir/file.log" (POSIX) 或 "temp\\subdir\\file.log" (Windows)
// 替换最后的文件名部分
p.replace_filename("data.json");
std::cout << p << std::endl; // p 现在为 "temp/subdir/data.json"
// 移除最后的文件名部分
p.remove_filename();
std::cout << p << std::endl; // p 现在为 "temp/subdir/"
return 0;
}
4.2 文件和目录相关功能
4.2.1 文件或目录的状态
fs::status(path); // 获取文件状态
fs::is_directory(path); // 是否为目录
fs::is_regular_file(path); // 是否为普通文件
fs::is_symlink(path); // 是否为符号链接
fs::is_empty(path); // 是否为空(文件或目录)
fs::exists(path); // 是否存在
fs::file_size(path); // 获取文件大小
auto ftime = fs::last_write_time(path); // 获取最后修改时间
fs::last_write_time(path, new_time); // 设置最后修改时间
4.2.2 文件或目录的权限
fs::status(p).permissions() // 获取权限
enum class perms; // 强类型枚举权限值
enum class perm_options; // 强类型枚举权限选项
fs::permissions(p, prms, perm_options::replace) // 设置权限
fs::permissions(p, prms, perm_options::add) // 添加权限
fs::permissions(p, prms, perm_options::remove) // 删除权限
cpp
int main()
{
try
{
fs::path file_path = "test.txt";
// 直接替换权限
// 这将文件的权限直接设置为 (rwxr--r--)
fs::permissions(file_path,
fs::perms::owner_read | fs::perms::owner_write |
fs::perms::group_read |
fs::perms::others_read,
fs::perm_options::replace); // <-- 关键选项:替换
// 添加权限
// 为用户组添加写权限
fs::permissions(file_path,
fs::perms::group_write,
fs::perm_options::add); // <-- 关键选项:添加
// 移除权限
// 移除其他用户的读权限
fs::permissions(file_path,
fs::perms::others_read,
fs::perm_options::remove); // <-- 关键选项:移除
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
}
return 0;
}
4.2.3 文件或目录的操作
创建和删除
fs::create_directory(path); // 创建⽬录
fs::create_directories(path); // 递归创建⽬录
fs::create_symlink(target, link); // 创建符号链接
fs::create_hard_link(target, link); // 创建硬链接
fs::remove(path); // 删除⽂件或空⽬录
fs::remove_all(path); // 递归删除所有内容(谨慎使⽤)
拷贝、重命名、移动
fs::copy(from, to); // 复制⽂件/⽬录
fs::copy_file(from, to); // 复制⽂件内容
fs::rename(old, new); // 重命名
fs::rename(old, new_dir / newname); // 移动
4.2.4 目录的遍历
范围for的方式遍历
1、非递归遍历
for (const auto& entry : fs::directory_iterator(dir_path)) {}
2、递归遍历
for (const auto& entry : fs::recursive_directory_iterator(dir_path)) {}
3、带选项的遍历
auto options = fs::directory_options::skip_permission_denied;
for (const auto& entry : fs::recursive_directory_iterator(dir_path, options)) {} // 跳过无该权限的目录
cpp
int main()
{
try
{
// 范围for实现,非递归遍历,只遍历读取当前目录下的内容
for (const auto& entry : fs::directory_iterator(p1))
{
std::cout << entry.path().filename() << "\n";
}
// 范围for实现,递归遍历
for (const auto& entry : fs::recursive_directory_iterator(fs::current_path() / "Debug"))
{
std::cout << entry.path().filename() << "\n";
}
}
catch (const std::exception& e)
{
std::cout << e.what() << '\n';
}
return 0;
}
4.3 错误处理
cpp
int main()
{
// 异常的方式
try
{
fs::path p = "nonexistent.txt";
auto size = fs::file_size(p); // 可能抛出异常
std::cout << "文件大小: " << size << std::endl;
}
catch (const fs::filesystem_error& ex)
{
std::cout
<< "what(): " << ex.what() << '\n'
<< "path1(): " << ex.path1() << '\n'
<< "path2(): " << ex.path2() << '\n';
}
// 错误码方式
std::error_code ec;
fs::path p = "nonexistent.txt";
auto size = fs::file_size(p, ec);
if (ec)
{
std::cout << "错误代码: " << ec.value() << std::endl;
std::cout << "错误信息: " << ec.message() << std::endl;
std::cout << "错误类别: " << ec.category().name() << std::endl;
}
else
{
std::cout << "文件大小: " << size << std::endl;
}
}
五、并行算法
C++17 引入了并行算法,这是对标准库算法的⼀个重要扩展,允许开发者通过简单的执行策略参数来并行化许多常见的算法操作。并行算法通过在现有标准库算法基础上添加执行策略参数来实现并行化,这些策略告诉算法如何执行。
C++17 定义了三种执行策略:
1、 顺序执行
▪ 标识符: std::execution::seq
▪ 保证执行是顺序的,如同没有指定策略⼀样
2、并行执行
▪ 标识符: std::execution::par
▪ 允许算法以多线程并行执行
3、并行+向量化执行
▪ 标识符: std::execution::par_unseq
▪ 允许并行化和向量化(如SIMD指令)
许多标准库算法都支持并行算法,比如:sort(排序算法)、find(查找算法)。
cpp
std::sort(data1.begin(), data1.end()); // 顺序排序
std::sort(std::execution::par, data2.begin(), data2.end()); // 并⾏排序