本文将带您了解现代C++(C++11/14/17/20)的核心特性,并通过实例展示如何利用这些特性编写更高效、更安全的代码
一、C++11 新特性
1. 自动类型推导(auto)
C++11 引入的 auto
关键字极大地简化了代码编写,使编译器能够自动推导变量类型。这在模板编程和迭代器使用中尤为实用,能避免冗长的类型声明。
#include <iostream>
#include <vector>
#include <string>
int main() {
auto i = 42; // 编译器自动推导为 int 类型
auto d = 3.14; // 编译器自动推导为 double 类型
auto s = "hello"; // 编译器自动推导为 const char* 类型
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
// 使用 auto 自动推导迭代器类型
for(auto it = names.begin(); it != names.end(); ++it) {
std::cout << *it << std::endl;
}
return 0;
}
2. 范围 for 循环
范围 for 循环简化了容器的遍历语法,使代码更加简洁易读。
#include <iostream>
#include <vector>
#include <string>
int main() {
std::vector<std::string> names = {"Alice", "Bob", "Charlie"};
// 范围 for 循环,const auto& 避免拷贝,提高效率
for(const auto& name : names) {
std::cout << name << std::endl;
}
return 0;
}
3. 智能指针(Smart Pointers)
智能指针是 C++11 引入的重要特性,用于管理动态分配的内存,避免内存泄漏。主要有三种类型:std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
。
#include <iostream>
#include <memory>
int main() {
// 独占指针,同一时间只能有一个指针指向该对象
std::unique_ptr<int> p1(new int(10));
std::cout << *p1 << std::endl;
// 共享指针,多个指针可以共享同一个对象,使用引用计数管理对象生命周期
std::shared_ptr<int> p2 = std::make_shared<int>(20);
std::cout << *p2 << std::endl;
// 弱指针,不拥有对象,只是对 shared_ptr 的一个引用,用于解决循环引用问题
std::weak_ptr<int> p3 = p2;
if (auto shared_p3 = p3.lock()) {
std::cout << *shared_p3 << std::endl;
}
return 0;
}
4. Lambda 表达式
Lambda 表达式是一种匿名函数,提供了一种便捷的方式来定义简单的函数对象。
#include <iostream>
int main() {
// 简单的 Lambda 表达式,计算两个整数的和
auto sum = [](int a, int b) { return a + b; };
std::cout << sum(3, 4) << std::endl; // 输出 7
// 带有捕获列表的 Lambda 表达式,捕获外部变量 x
int x = 10;
auto add_x = [x](int a) { return a + x; };
std::cout << add_x(5) << std::endl; // 输出 15
return 0;
}
5. 移动语义与完美转发
移动语义和完美转发是 C++11 最重要的改进之一,通过移动构造函数和移动赋值运算符,避免了不必要的拷贝,提高了性能。
#include <iostream>
class MyString {
public:
// 默认构造函数
MyString() : data(nullptr), size(0) {}
// 构造函数
MyString(const char* str) {
if (str) {
size = std::strlen(str);
data = new char[size + 1];
std::strcpy(data, str);
} else {
data = nullptr;
size = 0;
}
}
// 移动构造函数
MyString(MyString&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// 移动赋值运算符
MyString& operator=(MyString&& other) noexcept {
if(this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
// 析构函数
~MyString() {
delete[] data;
}
void print() const {
if (data) {
std::cout << data << std::endl;
}
}
private:
char* data;
size_t size;
};
int main() {
MyString str1("Hello");
MyString str2 = std::move(str1); // 调用移动构造函数
str2.print();
return 0;
}
6. constexpr 与编译时计算
constexpr
关键字允许在编译时进行计算,提高程序的性能。
#include <iostream>
// constexpr 函数,在编译时计算阶乘
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
int main() {
constexpr int fact5 = factorial(5); // 编译时计算
std::cout << fact5 << std::endl; // 输出 120
return 0;
}
二、C++17/20 新特性
1. 结构化绑定(C++17)
结构化绑定允许将一个对象的成员或元组的元素直接绑定到变量上,使代码更加简洁。
#include <iostream>
#include <utility>
#include <string>
std::pair<int, std::string> getPerson() {
return {25, "Alice"};
}
int main() {
// 结构化绑定,将 pair 的元素分别绑定到 age 和 name 变量上
auto [age, name] = getPerson();
std::cout << name << " is " << age << " years old." << std::endl;
return 0;
}
2. std::optional(C++17)
std::optional
用于处理可能不存在的值,避免了使用指针和空值检查的繁琐。
#include <iostream>
#include <vector>
#include <optional>
#include <algorithm>
std::optional<int> findValue(const std::vector<int>& vec, int target) {
auto it = std::find(vec.begin(), vec.end(), target);
if(it != vec.end()) {
return *it;
}
return std::nullopt;
}
int main() {
std::vector<int> numbers = {1, 2, 3, 4, 5};
auto result = findValue(numbers, 3);
if (result) {
std::cout << "Found value: " << *result << std::endl;
} else {
std::cout << "Value not found." << std::endl;
}
return 0;
}
3. 概念(Concepts,C++20)
概念(Concepts)是 C++20 引入的新特性,用于约束模板参数,提高模板代码的可读性和错误信息的明确性。
#include <iostream>
#include <concepts>
// 定义一个概念,要求类型 T 支持加法运算,并且结果类型与 T 相同
template<typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as<T>;
};
// 使用概念约束模板参数
template<Addable T>
T add(T a, T b) {
return a + b;
}
int main() {
int x = 3, y = 4;
std::cout << add(x, y) << std::endl; // 输出 7
return 0;
}
三、性能优化技巧
1. 避免不必要的拷贝:使用移动语义和完美转发
移动语义和完美转发可以避免不必要的拷贝,提高程序的性能。在函数参数传递和对象构造时,优先使用移动语义。
2. 利用返回值优化(RVO)
返回值优化(RVO)是编译器的一种优化技术,当函数返回一个临时对象时,编译器会直接在调用者的栈帧上构造对象,避免了额外的拷贝。
#include <iostream>
#include <vector>
// 函数返回一个 std::vector 对象,大多数编译器会应用 RVO
std::vector<int> createVector() {
return std::vector<int>{1, 2, 3};
}
int main() {
std::vector<int> vec = createVector();
for (const auto& num : vec) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
3. 选择合适的容器
根据不同的使用场景,选择合适的容器可以提高程序的性能。
- 随机访问:
std::vector
提供了连续的内存存储,支持快速的随机访问,适合需要频繁随机访问元素的场景。 - 频繁插入删除:
std::list
是双向链表,支持在任意位置快速插入和删除元素;std::unordered_map
是哈希表,支持快速的查找、插入和删除操作,适合需要频繁进行插入和删除操作的场景。
4. 使用内存池:对于频繁的小对象分配
内存池是一种内存管理技术,通过预先分配一大块内存,然后在需要时从内存池中分配小块内存,避免了频繁的系统调用,提高了内存分配和释放的效率。
四、最佳实践
1. 优先使用智能指针管理资源
智能指针可以自动管理动态分配的内存,避免内存泄漏。在使用动态内存时,优先使用 std::unique_ptr
、std::shared_ptr
和 std::weak_ptr
。
2. 使用 nullptr 代替 NULL
nullptr
是 C++11 引入的空指针常量,比 NULL
更安全,避免了一些潜在的类型转换问题。
3. 使用 override 和 final 关键字
override
关键字用于明确表示一个虚函数是重写基类的虚函数,提高代码的可读性和安全性。final
关键字用于禁止一个虚函数被进一步重写,或者禁止一个类被继承。
#include <iostream>
class Base {
public:
virtual void foo() {
std::cout << "Base::foo()" << std::endl;
}
};
class Derived : public Base {
public:
// 使用 override 关键字明确表示重写基类的虚函数
void foo() override {
std::cout << "Derived::foo()" << std::endl;
}
};
class FinalClass final {
public:
void bar() {
std::cout << "FinalClass::bar()" << std::endl;
}
};
// 以下代码会编译错误,因为 FinalClass 被声明为 final,不能被继承
// class DerivedFinal : public FinalClass {};
int main() {
Base* basePtr = new Derived();
basePtr->foo(); // 调用 Derived::foo()
delete basePtr;
FinalClass finalObj;
finalObj.bar();
return 0;
}
4. 使用范围 for 循环简化代码
范围 for 循环可以简化容器的遍历,使代码更加简洁易读。
5. 合理使用 constexpr 进行编译时优化
constexpr
可以在编译时进行计算,提高程序的性能。在需要编译时计算的场景中,使用 constexpr
函数和变量。
6. 使用 static_assert 进行编译时检查
static_assert
是 C++11 引入的编译时断言,用于在编译时检查某个条件是否成立,如果条件不成立,编译器会报错。
#include <iostream>
// 编译时检查数组大小是否为正
template <typename T, size_t N>
class Array {
static_assert(N > 0, "Array size must be positive");
T data[N];
public:
// 其他成员函数
};
int main() {
// 以下代码会编译错误,因为数组大小为 0
// Array<int, 0> arr;
Array<int, 5> arr;
std::cout << "Array created successfully." << std::endl;
return 0;
}
通过以上的代码示例和解释,可以更深入地了解 C++11、C++17 和 C++20 的新特性,以及性能优化技巧和最佳实践。在实际编程中,合理运用这些特性和技巧,可以提高代码的质量和性能。