【C++】C++11新内容
1. 传统方式使用NULL改为使用nullptr
2. 类型推导:auto、decltype、decltype(auto)
3. 智能指针:shared_ptr、weak_ptr、unique_ptr、auto_ptr
4. 匿名函数:Lambda表达式
基本语法:
[capture](parameters) mutable -> return-type {statement};
capture表示捕获当前表达式所在作用域内的其他变量:
- 值传递捕获:直接写变量名如
[x]
; - 值传递捕获全部变量:使用
[=]
表示通过值传递的方式捕获全部变量; - 引用传递捕获:通过在变量名前添加
&
表示引用传递捕获,如[&x]
; - 引用传递捕获全部变量:直接使用
[&]
表示通过引用传递的方式捕获全部变量。 - 同时使用两种捕获方式:通过
[=, &x]
或[&, x]
表示除了x
使用引用传递(值传递)之外,其他变量通过值传递(引用传递)捕获。 - (C++14)扩展捕获:允许变量在捕获时进行初始化,如
[value=14]{ return value; }
。
parameters表示匿名函数的参数,可以与()
一起省略,表示无函数参数。
- (C++14)泛型Lambda:支持auto进行类型推导,如
[](const auto& arg){ std::cout<<arg; }
- (C++14)捕获表达式:允许捕获时执行表达式,如
[p=std::move(std::make_unique<int>(10))]{ return *p; }
- (C++17)constexpr Lambda:允许该lambda在编译期求值。
- (C++20)模版参数支持:允许lambda中使用模板参数,如
[]<typename T>(T t) {};
mutable表示通过值传递捕获的变量可以进行修改(默认情况下,值传递捕获的变量会添加const
属性从而无法修改),同时不会改变外部的原变量的值(因为是值传递)。
return-type表示返回值类型,同样可以和->
一起省略,默认会自动推导返回值类型。
statement即函数体内容。
注意事项:
- 捕获的变量要保证其有效性,否则将导致悬垂引用。
- 大对象的值传递捕获可能产生较大的拷贝开销。
- 复杂返回类型可能需要显示声明以避免歧义和造成可读性下降。
- lambda可以隐式转换为对应的
std::function
类型,但可能引入额外开销。
5. 常量表达式:constexpr
6. 范围for循环
原始的for循环需要编写初始化语句、判断语句和后操作语句,然后通过索引变量或迭代器来简介实现容器中元素的访问:
vector<int> vec{1,2,3,4,5};
for(auto i=vec.begin(); i!=vec.end(); ++i){cout << *i << endl;
}
使用范围for循环可以简化以上步骤,并直接访问容器内的元素:
vector<int> vec{1,2,3,4,5};
for(auto i: vec)
{cout << i << endl;
}
以上代码中i主要是通过值传递的方式实现的,如果希望修改容器中的元素,则需要使用&
来实现容器元素的引用传递:
vector<int> vec{1,2,3,4,5};
for(auto &i: vec)
{cout << i << endl;
}
7. 初始化列表:initializer_list
8. 右值引用与移动语义
9. 可变参数模板与默认模板参数
可变参数模板有4种实现方式:
-
C风格可变参数
通过
...
使用,但类型不安全,容易导致未定义行为,需要配合固定格式使用:#include <cstdarg>void func(int count, ...) {va_list args;va_start(args, count); // 初始化for(int i = 0; i < count; ++i) {int value = va_arg(args, int); // 按类型提取参数// 处理value}va_end(args); // 清理 } // 调用: func(3, 10, 20, 30);
缺点:
- 无法自动推导参数类型
- 参数类型错误会导致未定义行为
- 必须手动维护参数数量和类型
-
(C++11)变参模板
定义模板参数时,按照
template<typename... Args>
的方式使用,类型安全,支持任意数量和类型的参数。可以通过递归展开或折叠表达式使用:
-
递归展开
// 递归终止条件 void process() {} template<typename T, typename... Args> void process(T first, Args... rest) {std::cout << first << " ";process(rest...); // 递归处理剩余参数 }
-
折叠表达式(C++17)
template<typename... Args> auto sum(Args... args) {return (args + ...); // 展开为 args1 + args2 + ... }
优点:
- 类型安全
- 支持任意类型混合
- 可与
std::tuple
、std::apply
等配合使用
-
-
使用
std::initializer_list
(只支持同类型参数)#include <initializer_list>void process_numbers(std::initializer_list<int> nums) {for(int n : nums) {std::cout << n << " ";} } // 调用 process_numbers({1, 2, 3, 4})
限制:
- 所有参数必须为同一类型
- 参数列表必须用
{}
包裹
默认模板参数:对特定类型的模板参数设置默认值,如template<typename T1, typename T2=T1>
,表示没有传入第二个类型时,T2和T1是同一个类型。
10. 原始字符串字面量与用户定义的字面量
11. 类内控制成员函数的默认实现:default和delete
12. 委托构造函数
委托构造函数是一个特性,而不是特定的一个构造函数。它的功能是通过在初始化列表将构造过程委托给其他构造函数来实现的构造函数,主要用于避免代码重复,提高代码复用性和可维护性。特殊的地方在于,在构造函数中调用了另一个构造函数。
class A{
private:int _a,_b,_c;
public:A(int a): A(a,0,0){} // 委托构造函数A(int a, int b, int c): _a(a), _b(b), _c(c){}// 错误示范A(int a, int b): A(a), _b(b), _c(0){} // 同时混用了 成员初始化 和 委托构造函数A(): A(){} // 循环委托
}
注意事项:
-
委托构造函数每次只能选择一个构造函数进行委托,或者直接初始化成员,不可以在使用委托构造函数的同时对其他成员进行初始化。
-
委托构造函数可以存在委托链,但是最终必须调用一个非委托构造函数,否则编译错误。
-
委托构造函数可以与当前类的基类构造函数配合使用:
class Base { public:Base(int a) { /* ... */ } };class Derived : public Base { public:// 委托给另一个构造函数,并指定基类构造Derived() : Derived(0) {}Derived(int a) : Base(a), data(a) {}private:int data; };