欢迎光临
我们一直在努力

C++11核心特性全面解析

C++11 是一个里程碑式的标准,它让 C++ 变得更现代、更安全、更高效。可以说,掌握 C++11 的新特性,是写出优雅、高性能 C++ 代码的关键。下面我会分门别类,把最常用、最核心的特性试着讲清楚。


1. 类型推导:auto 与 decltype

1.1 auto 关键字

以前声明变量必须明确写出类型,现在可以让编译器自动推导:

auto x = 42; // int
auto y = 3.14; // double
auto s = std::string("hello"); // std::string

std::vector<int> vec = {1, 2, 3};
for (auto it = vec.begin(); it != vec.end(); ++it) { /* … */ }

auto 在模板编程和迭代器遍历中尤其方便。注意 auto 会忽略引用和顶层 const,如果需要保持,要写成 auto& 或 const auto&。

1.2 decltype

有时想获得一个表达式的类型(但不计算表达式),用 decltype:

int a = 10;
decltype(a) b = 20; // b 是 int
decltype((a)) c = a; // 注意:双括号推导出 int&

decltype 常用于泛型编程中推导返回类型。


2. 统一的初始化与初始化列表

2.1 列表初始化(统一初始化)

C++11 推广了花括号初始化,基本所有类型都能用 {} 进行初始化,且它不允许窄化转换(更安全)。

int a{5};
double b{3.14};
std::vector<int> v{1, 2, 3, 4};
std::map<std::string, int> m{{"Alice", 25}, {"Bob", 30}};

// 防止窄化
// int x{3.14}; // 编译错误!double 到 int 窄化

2.2 std::initializer_list

让自定义容器也能支持列表初始化。在类中提供一个参数为 std::initializer_list<T> 的构造函数即可。

class MyVec {
public:
MyVec(std::initializer_list<int> list) {
for (auto x : list) std::cout << x << " ";
}
};
MyVec v = {1, 2, 3}; // 打印 1 2 3


3. 范围 for 循环(Range-based for loop)

告别传统下标或迭代器循环,遍历容器更简洁:

std::vector<int> nums = {1, 2, 3, 4};
for (int n : nums) {
std::cout << n << " ";
}
// 如需修改,使用引用
for (int& n : nums) {
n *= 2;
}

它对 C 风格数组、std::initializer_list、所有有 begin()/end() 的类型都有效。


4. 空指针 nullptr

以前用 NULL(本质是整数 0)可能会引起重载歧义。C++11 引入 nullptr,类型为 std::nullptr_t,可以隐式转换为任意指针类型,但不能转换为整数。

void func(int);
void func(char*);
func(nullptr); // 调用 char* 版本,而不会错误调用 int 版本

建议:从现在开始,用 nullptr 完全替代 NULL 或 0。


5. 面向对象增强:override、final、委托构造、默认/删除函数

5.1 override 和 final

在继承中,重写虚函数时加上 override 可让编译器检查签名是否正确。

class Base {
public:
virtual void foo() const;
};
class Derived : public Base {
public:
void foo() const override; // 正确重写
// void foo() override; // 编译错误:签名不匹配(缺少 const)
};

final 可以阻止虚函数被进一步重写,或者阻止类被继承:

class NoMoreDerived final { /* … */ };
class A { virtual void bar() final; };

5.2 委托构造函数

一个构造函数可以调用同类的另一个构造函数,避免重复代码。

class Widget {
public:
Widget() : Widget(0, 0) {} // 委托给下面的构造函数
Widget(int x, int y) : x_(x), y_(y) {}
private:
int x_, y_;
};

5.3 默认和删除函数

= default 显式要求编译器生成默认实现(如默认构造),= delete 则禁掉某个函数。

class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete; // 禁止拷贝构造
NonCopyable& operator=(const NonCopyable&) = delete; // 禁止拷贝赋值
};


6. 函数式编程:Lambda 表达式

Lambda 是一个可以捕获外部变量的匿名函数对象,极大地简化了 STL 算法中的自定义操作。

基本语法:

[捕获列表](参数列表) -> 返回类型 { 函数体 }

例子:

std::vector<int> v = {3, 1, 4, 1, 5};
// 降序排序
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

// 捕获外部变量
int threshold = 3;
auto it = std::find_if(v.begin(), v.end(),
[threshold](int x) { return x > threshold; });

常用捕获方式:

  • [] 不捕获

  • [=] 按值捕获全部

  • [&] 按引用捕获全部

  • [a, &b] 混合捕获

Lambda 是 C++11 里最受欢迎的特性之一,配合算法库特别好用。


7. 智能指针:unique_ptr、shared_ptr、weak_ptr

手动 new/delete 容易导致内存泄漏,智能指针利用 RAII 思想自动管理内存。

7.1 std::unique_ptr

独占所有权,不可拷贝,只能移动(move)。轻量级,开销极小。

std::unique_ptr<int> p1(new int(42));
auto p2 = std::make_unique<int>(100); // C++14 写法,C++11 中可自己封装
// auto p3 = p1; // 编译错误!不能拷贝
auto p3 = std::move(p1); // 转移所有权,p1 变为空

7.2 std::shared_ptr

共享所有权,内部有引用计数,当计数归零时自动释放资源。

std::shared_ptr<int> sp1 = std::make_shared<int>(200);
std::shared_ptr<int> sp2 = sp1; // 引用计数变为 2

7.3 std::weak_ptr

配合 shared_ptr 使用,可观察对象但不增加引用计数,解决循环引用问题。

std::weak_ptr<int> wp = sp1;
if (auto sp = wp.lock()) { // 尝试提升为 shared_ptr
// 使用 sp
}

强烈建议:现代 C++ 中尽量避免直接使用 new/delete,优先使用智能指针。


8. 右值引用与移动语义

8.1 左值与右值

  • 左值:有名字、可取地址的表达式(变量、引用等)。

  • 右值:临时对象、字面量、表达式结果等。

&& 表示右值引用,可以绑定到右值上,从而“偷”它的资源。

8.2 移动构造函数与移动赋值

允许容器或对象在拷贝时“转移”资源而不是深拷贝,极大提升效率。标准库容器(如 vector、string)都支持移动语义。

std::string s1 = "hello";
std::string s2 = std::move(s1); // 移动后 s1 不再使用(处于有效但未定义状态)

8.3 如何为自己的类加上移动语义

定义移动构造函数和移动赋值运算符:

class Buffer {
char* data;
size_t size;
public:
// 移动构造
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
// 移动赋值
Buffer& operator=(Buffer&& other) noexcept {
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}
};

加上 noexcept 至关重要,它能让标准容器在做扩容等操作时优先选择移动,从而提升性能。


9. constexpr 与编译期计算

constexpr 指明一个变量或函数的值或结果可以在编译期确定,从而用于模板参数、数组大小等场景,也可以提升运行时性能。

constexpr int square(int x) { return x * x; }
int arr[square(5)]; // 数组大小为 25,编译期计算

constexpr 函数有较多限制(如函数体必须只有一条 return 语句,C++11),但到了 C++14/17 逐步放宽。


10. 枚举类(强类型枚举)

传统 enum 会将枚举值暴露到外层作用域,且可以隐式转为 int。C++11 引入了 enum class:

enum class Color { Red, Green, Blue };
Color c = Color::Red; // 必须加作用域
// int n = c; // 编译错误,不能隐式转换
int n = static_cast<int>(c); // 显式转换

这样更安全,不会命名污染,也不会无意间算术运算。


11. 静态断言 static_assert

在编译期检查条件,如果不满足则产生编译错误并显示提示信息。比运行时的 assert 更早发现错误。

static_assert(sizeof(void*) == 8, "This code requires 64-bit platform");
template <typename T>
void process(T t) {
static_assert(std::is_integral<T>::value, "T must be integral type");
}


12. 可变参数模板(Variadic Templates)

模板现在可以接受任意数量和类型的参数,使得实现通用函数和类型更加灵活。

void print() {} // 递归终止函数
template <typename T, typename… Args>
void print(T first, Args… rest) {
std::cout << first << " ";
print(rest…);
}
print(1, 2.5, "hello"); // 输出 1 2.5 hello

标准库中 std::tuple、std::make_shared 等都依赖于可变参数模板。


13. 多线程支持

C++11 提供了标准化的线程库,位于 <thread>、<mutex>、<condition_variable>、<atomic>、<future> 中。

#include <thread>
#include <iostream>
void hello() {
std::cout << "Hello from thread\\n";
}
int main() {
std::thread t(hello);
t.join(); // 等待线程结束
return 0;
}

配合 lambda、std::async 等可以实现方便的异步编程。原子操作和互斥锁也不再依赖平台 API。


14. 其他有用的小特性

  • using 别名:比 typedef 更可读,支持模板别名。

    cpp

    template<typename T>
    using Vec = std::vector<T, std::allocator<T>>;

  • noexcept 说明符:标记函数不会抛出异常,帮助编译器优化。

  • = default / = delete:前面已述。

  • long long 类型:至少 64 位的整数。

  • begin()/end() 非成员函数:可以操作 C 数组。


15. 综合实例:结合多种特性

下面这个小程序展示了一些特性的混合使用:

#include <iostream>
#include <vector>
#include <memory>
#include <algorithm>

int main() {
// 列表初始化 + auto
auto nums = {5, 2, 8, 1, 9};

// 智能指针 + vector
std::vector<std::shared_ptr<int>> ptrVec;
for (auto n : nums) {
ptrVec.push_back(std::make_shared<int>(n));
}

// lambda 表达式排序
std::sort(ptrVec.begin(), ptrVec.end(),
[](const auto& a, const auto& b) { return *a < *b; });

// 范围 for 输出
for (const auto& p : ptrVec) {
std::cout << *p << " ";
}
std::cout << std::endl;

// nullptr
std::shared_ptr<int> empty = nullptr;
std::cout << "empty is " << (empty ? "not null" : "null") << std::endl;

return 0;
}


16. 小结

C++11 的新特性远不止这些,但以上列举的是实际编码中使用频率最高、最能改变编程习惯的内容。建议:

  • 循序渐进:先掌握 auto、范围 for、智能指针、lambda,再深入右值引用和模板。

  • 改写旧代码:将以前的裸指针逐步替换为智能指针,将遍历改为范围 for,体验安全性和简洁性的提升。

  • 关注异常安全:移动语义和智能指针帮你写出异常安全的代码。

  • 利用编译期能力:善用 constexpr 和 static_assert。

C++11 是通往现代 C++ 的大门,掌握它就是掌握了更高效、更安全的编程范式。建议把以前写的部分代码用 C++11 新特性重构一下,感受一下那种轻爽的感觉!

赞(0)
未经允许不得转载:171主机测评 » C++11核心特性全面解析
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址