C++中常用的十个特性

程序员咋不秃头 2025-03-27 02:14:05

C++ 语言自1983年发布以来,经历了多个版本的迭代。随着C++11、C++14、C++17、C++20等新版本的发布,在原有的基础上引入了大量现代化的特性,使得C++的编程体验更加强大和简洁。今天,C++不仅仅是一个强大的系统级编程语言,也逐渐成为了一门高效、灵活的开发工具,广泛应用于各类应用开发中。

大致是在10年前开始将Modern 用到项目中,那时编译器版本是gcc4.9.2,支持了C++11,也就是从那个时候开始接触Modern C++,开始发现了新世界的大门,后面为了使用新特性,着手升级了两次,经历了gcc11.2以及现在的gcc14.2版本。

本文主要聊聊项目中常用常用到的十个特性,这些特性能够帮助开发者提升代码的可读性、可维护性和执行效率。

PS:本文旨在简单介绍,而不是深入各个特性的用法或者实现细节~

希望能够借助本文,能够帮您更好的了解Modern C++。

基于范围的带初始化器的for循环

C++20在基于范围的for循环基础上,引入了新的功能初始化器。此功能允许我们在for循环头内声明变量,从而简化代码并缩小范围。

std::vector<int> vec = {1, 2, 3, 4, 5};for (auto size = numbers.size(); auto&& num : vec) { std::cout << num << " " << size;}

这个特性特别适用于在循环中需要使用一个变量,但又希望将其作用域限制在循环内部,以防止在其他地方不小心使用该变量。

结构化绑定

C++17 引入的结构化绑定 (Structured bindings) 允许将元组、对 (pair) 和其他多元素对象解包为具名变量。这一特性极大地提升了代码的可读性。

#include <map>#include <string>#include <iostream>int main() { std::map<std::string, int> ages = {{"A", 30}, {"B", 25}}; for (auto&& [name, age] : ages) { std::cout << name << " is " << age << " years old." << std::endl; } return 0;}

结构化绑定在处理返回多个值的函数或遍历键值容器时特别有用。它使得解构和使用多个返回值变得更加简洁和直观。

inline变量

C++17 引入了inline变量,其语义与inline函数类似,只是它适用于变量。

最常见的是用于类内static变量的初始化:

class Test { private: inline static int value_ = 0; // 等同于类外执行 int Test::value = 0;};

与inline function一样,inline variable也允许在多个编译单元对同一个变量进行定义,并且在链接时只保留其中的一份作为该变量的定义。当然,同时在多个源文件中定义同一个inline变量必须保证它们的定义都相同,否则和inline函数一样,你没办法保证链接器最终采用的是哪个定义。

inline variable除了支持类内静态成员初始化外,也支持头文件中定义全局变量,这样不会违反ODR规则。

std::optional

C++17 引入的 std::optional 提供了一种更安全、更具表现力的方式来处理可选值。它是使用哨兵值或指针来表示可选数据的一个很好的替代方案。

struct Result {char c;uint32_t pos;};std::optional<Result> GetFirstUpper(const std::string &str) { std::optional<Result> res;for (int i = 0; i < str.size(); ++i) { if (std::isupper(str[i])) { res.c = elem; res.pos = i; return res; } }return std::nullopt;}

std::optional 对于那些可能返回值也可能不返回值的函数特别有用,它在很多情况下消除了使用错误代码或异常的需求。

std::variant

std::variant,在 C++17 中引入,是一种类型安全的联合体,可以存储多个不同类型的值,但在任何时刻只能存储其中一种类型的值。与传统的 C 语言联合体不同,std::variant 提供了类型安全检查,避免了不小心访问错误类型的风险。

#include <variant>#include <string>#include <iostream>int main() { std::variant<int, float, std::string> data; data = 10; std::cout << std::get<int>(data) << std::endl; data = "Hello"; std::cout << std::get<std::string>(data) << std::endl; return 0;}

std::variant 在需要以类型安全的方式处理多种类型的场景中特别有用,例如解析不同的数据格式或实现状态机。它可以帮助你在处理多个不同类型的数据时,避免类型错误,并且使得代码更加清晰和易于维护。

std::visit

std::visit 是 C++17 引入的一个函数模板,用于访问 std::variant 中存储的值。它提供了一种类型安全的方式来处理 std::variant 中的不同类型,并通过访问器函数或 lambda 表达式来操作存储的值。

#include <variant>#include <string>#include <iostream>structVisitor { void operator()(int i) { std::cout << "Integer: " << i << std::endl; } void operator()(float f) { std::cout << "Float: " << f << std::endl; } void operator()(const std::string& s) { std::cout << "String: " << s << std::endl; }};int main() { std::variant<int, float, std::string> data = 3.14f; std::visit(Visitor{}, data); return0;}

std::visit 在你需要根据 std::variant 中存储的类型执行不同操作时特别有用,它避免了类型检查和类型转换的繁琐过程。通过 std::visit,你可以直接根据 variant 中的实际类型来调用相应的操作,而无需手动进行类型识别或强制类型转换,从而保持代码的简洁和类型安全。

constexpr if

constexpr if 是 C++17 中引入的一个特性,它允许你在编译时根据条件选择性地包含或排除代码块。这使得模板编程更加灵活和高效。与常规的 if 语句不同,constexpr if 只有在条件为 true 时,相关代码才会被编译;如果条件为 false,编译器将会完全忽略对应的代码段,这样就避免了不必要的代码生成和运行时开销。

#include <type_traits>#include <iostream>template<typename T>void PrintType(const T& value) { if constexpr (std::is_integral_v<T>) { std::cout << "Integral type: " << value << std::endl; } elseifconstexpr (std::is_floating_point_v<T>) { std::cout << "Floating-point type: " << value << std::endl; } else { std::cout << "Other type" << std::endl; }}int main() { PrintType(42); PrintType(3.14); PrintType("Hello"); return0;}

constexpr if 使你能够编写更灵活的模板,并在许多情况下避免使用 SFINAE,从而使代码更加简洁和可维护。通过在编译时做出条件判断,constexpr if 可以根据类型或其他条件选择性地编译不同的代码,而不需要依赖复杂的类型特化或 SFINAE 技巧。这不仅提升了代码的可读性,还减少了模板编程中的冗余,简化了错误处理和调试过程。

default & delete

在 C++11 中,**默认函数(Defaulted Functions)和删除函数(Deleted Functions)**是两个重要的特性,用来控制类的构造、拷贝、赋值等操作的行为。它们使得代码更加安全和可维护,避免了不必要的默认行为或不合法的操作。

#include <iostream>classObj {public: Obj() = default; // 默认构造函数 Obj(const Obj&) = default; // 默认拷贝构造函数 Obj& operator=(const Obj&) = default; // 默认赋值运算符 ~Obj() = default; // 默认析构函数};int main() { Obj obj1; // 使用默认构造函数 Obj obj2 = obj1; // 使用默认拷贝构造函数 obj1 = obj2; // 使用默认赋值运算符 return0;}nodiscard属性

[[nodiscard]] 属性是在 C++17 中引入的,它有助于防止开发者无意中忽略重要的返回值。当你将这个属性应用到一个函数或类型时,编译器会发出警告,提醒调用者不要忽视该函数的返回值。这个特性在返回错误码、状态信息或其他需要处理的结果时尤其有用。

#include <iostream>[[nodiscard]] int GetArea(int width, int height) { return width * height;}int main() { GetArea(5, 10); // 这里会触发编译器警告,因为返回值被忽略了 int area = GetArea(5, 10); // 正确用法 std::cout << "Area: " << area << std::endl; return 0;}std::string_view

std::string_view 是在 C++17 中引入的一个新特性,它提供了一个对字符串的非拥有性视图。与 const std::string& 不同,std::string_view 不会复制字符串数据,而是直接引用原始数据。它是一个高效的替代方案,尤其在传递字符串时,可以避免不必要的内存分配和拷贝。

#include <iostream>#include <string_view>void Print(std::string_view str) { std::cout << "String: " << str << std::endl;}int main() { std::string_view view = "Hello, World!"; Print(view); return 0;}结语

这十个特性,在项目中用的算是很多了,当然了,还有lambda、auto等,在此就不再一一赘述了,通过此文,希望能帮助到您~

0 阅读:8