Day 8:lambda 表达式、移动语义、自定义字符串和单例模式
今天重点:掌握 lambda 的基本用法,理解移动语义和 std::move,复习资源管理类的写法,认识单例模式。
对应代码:
2-代码/day8-lambda表达式/44-lambda表达式示例.cpp
2-代码/day8-lambda表达式/45-lambda表达式使用示例.cpp
2-代码/day8-lambda表达式/46-移动操作示例.cpp
2-代码/day8-lambda表达式/47-自定义字符串类型.cpp
2-代码/day8-lambda表达式/48-单例模式示例1.cpp
2-代码/day8-lambda表达式/49-单例模式示例2.cpp
2-代码/day8-lambda表达式/50-单例模式示例3.cpp
1、lambda 表达式
lambda 表达式就是匿名函数,适合临时写一段简单逻辑。
基本格式:
[捕获列表](参数列表) -> 返回值类型
{
函数体;
};
常见写法:
auto print = []{
cout << "hello lambda!" << endl;
};
print();
捕获方式:
[=] // 按值捕获,拿到外部变量的副本
[&] // 按引用捕获,可以修改外部变量
示例:
int x = 1;
int y = 2;
[=]() {
cout << x << endl;
cout << y << endl;
}();
[&](int z) {
x += z;
y += z;
}(100);
注意:
无捕获 lambda 可以转换成普通函数指针。
有捕获 lambda 不能转换成普通函数指针。
示例:
int index = find_array(a, 5, [](int x){
return x % 2 == 0;
});
2、函数对象和 lambda
函数对象是重载了 operator() 的对象。
class EvenNumber
{
public:
bool operator()(int x)
{
return x % 2 == 0;
}
};
使用:
EvenNumber func;
func(10);
lambda 本质上也类似一个函数对象。
[](int x){ return x % 2 == 0; }
简单理解:
函数对象:适合复用、保存状态。
lambda:适合临时传入一小段逻辑。
3、移动语义
移动语义用于减少不必要的深拷贝。
如果类中管理堆内存:
class Demo
{
private:
char* p;
};
普通拷贝需要重新申请内存并复制内容。移动语义则是直接转移资源所有权。
核心思想:
能移动就不拷贝。
把右边对象的资源拿过来,再把右边对象置空。
4、移动构造和移动赋值
移动构造函数:
Demo(Demo&& rhs)
{
cout << "移动构造" << endl;
p = rhs.p;
rhs.p = nullptr;
}
移动赋值运算符:
Demo& operator=(Demo&& rhs)
{
cout << "移动赋值" << endl;
if (this == &rhs)
return *this;
delete[] p;
p = rhs.p;
rhs.p = nullptr;
return *this;
}
关键点:
1. 接管右侧对象的资源。
2. 把右侧对象的指针置空。
3. 防止两个对象释放同一块内存。
5、std::move
std::move 本身不移动数据,它只是把对象转换成右值引用。
Demo tmp = std::move(s1);
含义:
告诉编译器:s1 可以被当作临时对象使用。
如果类中有移动构造,就优先调用移动构造。
交换函数示例:
void swap(Demo& s1, Demo& s2)
{
Demo tmp = std::move(s1);
s1 = std::move(s2);
s2 = std::move(tmp);
}
注意:
被 std::move 后的对象仍然可以析构,也可以重新赋值,
但不要再依赖它原来的值。
6、自定义字符串类型
47-自定义字符串类型.cpp 中实现了一个简单的 china::string。
核心成员:
char* _data;
因为它管理堆内存,所以要重点写好:
构造函数
析构函数
拷贝构造函数
拷贝赋值运算符
构造函数:
string(const char *s = NULL)
{
if (s == NULL)
{
_data = new char[1];
_data[0] = '';
}
else
{
_data = new char[strlen(s) + 1];
strcpy(_data, s);
}
}
析构函数:
~string()
{
delete[] _data;
}
拷贝构造:
string(const string &rhs)
{
_data = new char[strlen(rhs._data) + 1];
strcpy(_data, rhs._data);
}
拷贝赋值:
string &operator=(const string &rhs)
{
if (this != &rhs)
{
delete[] _data;
_data = new char[strlen(rhs._data) + 1];
strcpy(_data, rhs._data);
}
return *this;
}
重点:
默认拷贝只复制指针地址,会造成浅拷贝问题。
自定义 string 必须深拷贝,避免重复释放同一块内存。
常用接口:
int size() const;
bool empty() const;
const char* c_str() const;
char& operator[](int index);
string operator+(const string& rhs) const;
bool operator<(const string& rhs) const;
7、单例模式
单例模式保证一个类在程序中只有一个对象。
核心写法:
1. 构造函数私有化。
2. 提供 static instance() 函数获取唯一对象。
3. 禁止拷贝构造和赋值。
推荐写法:
class Singleton
{
public:
static Singleton& instance()
{
static Singleton obj;
return obj;
}
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
使用:
Singleton& s1 = Singleton::instance();
Singleton& s2 = Singleton::instance();
说明:
s1 和 s2 引用的是同一个对象。
C++11 以后,函数内 static 局部对象初始化是线程安全的。
饿汉式:
程序开始时就创建对象。
懒汉式:
第一次使用时才创建对象。
当前阶段推荐记住:
函数内 static 局部对象是实现单例的常用写法。
8、Day8 总结
1. lambda 是匿名函数,适合临时写一段逻辑。
2. [=] 是值捕获,[&] 是引用捕获。
3. 无捕获 lambda 可以转换成函数指针。
4. 函数对象是重载了 operator() 的对象。
5. 移动语义用于转移资源,减少深拷贝。
6. std::move 只是把对象转换成右值引用。
7. 移动构造和移动赋值都要把原对象指针置空。
8. 管理堆内存的类要注意深拷贝和析构。
9. 自定义 string 的重点是资源管理。
10. 单例模式保证一个类只有一个对象。
11. 单例要私有化构造函数,并禁止拷贝和赋值。
12. 函数内 static 局部对象是推荐的单例写法。
一句话记忆:
Day8 重点:lambda 写临时逻辑,move 转移资源,string 练资源管理,单例控制对象数量。






Comments | NOTHING