1. 类中的移动构造与移动赋值
编译器默认生成条件:
- 移动构造函数:若未显式声明移动构造、拷贝构造、拷贝赋值或析构函数,且类成员支持移动语义,编译器会自动生成默认移动构造。
- 移动赋值运算符:生成条件与移动构造类似,且未声明移动赋值运算符。
作用:
- 资源所有权转移:通过指针窃取而非深拷贝,减少内存复制开销(如
ModernString
类中的data_
指针转移)。 - 异常安全保证:移动操作通常标记为
noexcept
,适用于容器扩容等关键场景。 - 优化临时对象处理:如
std::move(obj)
将左值转为右值引用,触发移动语义。
示例:
class ModernString {
public:
ModernString(ModernString&& other) noexcept
: data_(other.data_), length_(other.length_) { // 移动构造
other.data_ = nullptr; // 原对象置空
}
ModernString& operator=(ModernString&& other) noexcept { // 移动赋值
if (this != &other) {
delete[] data_;
data_ = other.data_;
other.data_ = nullptr;
}
return *this;
}
};
注意事项:
- 若自定义了拷贝操作,编译器不会自动生成移动操作,需显式定义或使用
= default
。 - 内置类型成员按值拷贝,自定义类型成员需自行实现移动语义。
2. default
与 delete
关键字
default
:
- 作用:显式要求编译器生成默认实现的函数(如默认构造、析构)。
- 适用场景:当类中存在其他构造函数但仍需保留默认构造时。
class A { public: A() = default; // 显式生成默认构造 A(int x) { /* ... */ } };
delete
:
- 作用:禁止特定函数的生成或调用(如禁用拷贝)。
- 示例:
class NonCopyable { public: NonCopyable(const NonCopyable&) = delete; // 禁用拷贝构造 };
注意事项:
delete
可应用于任何函数(包括普通成员函数),而default
仅限特殊成员函数。- 替代 C++98 中通过私有化函数实现禁用拷贝的方法,更直观。
3. 可变参数模板
核心机制:
- 参数包:通过
typename... Args
声明模板参数包,Args... args
声明函数参数包。 - 展开方式:
- 递归展开:需定义终止条件(如空参数包处理)。
- 折叠表达式(C++17):简化参数包操作,如
(args + ...)
求和。
示例:
// 递归终止函数
template <class T>
void ShowList(const T& t)
{
cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
cout << value <<" ";
ShowList(args...);
}
int main()
{
ShowList(1);
ShowList(1, 'A');
ShowList(1, 'A', std::string("sort"));
return 0;
}
注意事项:
- 参数包不支持直接索引访问,必须通过展开操作。
- 结合
sizeof...(args)
可获取参数包大小。 - 除此之外还可以用逗号表达式展开参数包,这里就不做展示了。
4. STL 的 emplace
系列函数
高效原因:
- 直接构造:在容器内部直接构造对象,避免临时对象的拷贝/移动(如
vector::emplace_back
直接调用元素的构造函数)。 - 完美转发:通过
std::forward
保留参数类型(左值/右值),优化传递效率。
示例:
std::vector<std::pair<int, std::string>> vec;
vec.emplace_back(1, "example"); // 直接构造 pair,无需创建临时对象
对比 push_back
:
push_back
需先构造临时对象,再拷贝/移动到容器。- 对复杂对象(如含大量资源的类),
emplace
性能提升显著。
5. Lambda 表达式
语法与原理:
- 语法:
[捕获列表](参数) mutable -> 返回类型 { 函数体 }
。 -
[capture-list] (parameters) mutable -> return-type { statement }
- 底层实现:编译器生成匿名类,捕获变量作为成员,重载
operator()
。 - lambda表达式各部分说明:
- [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
- (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
连同()一起省略。 - mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
性。使用该修饰符时,参数列表不可省略(即使参数为空)。 - ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
导。 - {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
到的变量。 - 注意:在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
示例:
int x = 10;
auto lambda = [x](int y) mutable {
x++; // 需 mutable 才能修改按值捕获的变量
return x + y;
};
int main()
{
vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price < g2._price; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price > g2._price; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._evaluate < g2._evaluate; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._evaluate > g2._evaluate; });
}
int main()
{
// 最简单的lambda表达式, 该lambda表达式没有任何意义
[]{};
// 省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=]{return a + 3; };
// 省略了返回值类型,无返回值类型
auto fun1 = [&](int c){b = a + c; };
fun1(10)
cout<<a<<" "<<b<<endl;
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int{return b += a+ c; };
cout<<fun2(10)<<endl;
// 复制捕捉x
int x = 10;
auto add_x = [x](int a) mutable { x *= 2; return a + x; };
cout << add_x(10) << endl;
return 0;
}
//lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。
注意事项:
- 捕获方式:捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
-
[var]:表示值传递方式捕捉变量var[=]:表示值传递方式捕获所有父作用域中的变量(包括this)[&var]:表示引用传递捕捉变量var[&]:表示引用传递捕捉所有父作用域中的变量(包括this)[this]:表示值传递方式捕捉当前的this指针
-
- 父作用域指包含lambda函数的语句块
-
语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 。 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
-
捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
-
在块作用域以外的lambda函数捕捉列表必须为空.
-
在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者 非局部变量都会导致编译报错。
-
lambda表达式之间不能相互赋值,即使看起来类型相同