C++中常用的十个特性
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等,在此就不再一一赘述了,通过此文,希望能帮助到您~