C++学习 day8

发布于 21 天前  29 次阅读


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 练资源管理,单例控制对象数量。

"When faced with uncertainty, ask the spring breeze."