C++11 核心新特性完整梳理

本篇博客结合学习笔记,梳理 C++11 最重要的特性:string底层模拟、右值引用 & 万能引用、引用折叠、完美转发、可变参数模板、移动语义、emplace 系列、类新特性、lambda、std::function、std::bind。

一、std::string 底层简单模拟

std::string底层本质是动态字符数组,维护三个核心成员变量:

  • _strchar*,指向堆上字符数组

  • _size:有效字符个数,不包含 \0

  • _capacity:数组总容量

1. 迭代器实现

string迭代器本质就是原生char*指针。

复制代码
typedef char* iterator;
iterator begin()
{
    return _str;
}
iterator end()
{
    return _str + _size;
}

begin()返回首元素地址,end()返回最后有效字符的下一个位置 。范围 for 底层就是调用begin()end()

迭代器遍历示例:

复制代码
string::iterator it4 = s4.begin();
while (it4 != s4.end())
{
    cout << *it4 << " ";
    ++it4;
}
cout << endl;

2.operator\[\]

复制代码
char& operator[](size_t pos)
{
    assert(pos < _size);
    return _str[pos];
}

返回引用,支持读写字符串内部字符,例如s2[0] = 'x';

3.push_back 尾插

复制代码
void string::push_back(char ch)
{
    if (_size == _capacity)
    {
        reserve(_capacity * 2);
    }
    _str[_size] = ch;
    _size++;
    _str[_size] = '\0';
}

容量满就扩容,把字符写到_size下标,_size++,手动维护末尾\0

4.insert 指定位置插入字符

插入需要把 pos 位置之后所有字符向后挪动一位。

复制代码
void string::insert(size_t pos, char ch)
{
    assert(pos <= _size);
    if (_size == _capacity)
    {
        reserve(_capacity == 0 ? 4 : _capacity * 2);
    }
    size_t end = _size;
    while (end >= pos)
    {
        _str[end + 1] = _str[end];
        --end;
    }
    _str[pos] = ch;
    _size++;
}

5.find、substr 处理 url 示例

复制代码
string url = "https://legacy.cplusplus.com/reference/string/string/rfind/";
size_t i1 = url.find(':');
if (i1 != string::npos)
{
    string protocol = url.substr(0, i1);
    cout << protocol << endl;

    size_t i2 = url.find('/', i1+3);
    if (i2 != string::npos)
    {
        string domain = url.substr(i1+3, i2-(i1+3));
        cout << domain << endl;
        string uri = url.substr(i2 + 1);
        cout << uri << endl;
    }
}

find找不到返回string::npossubstr(pos, len),第二个参数省略直接截取到末尾。

二、右值引用 && 引用折叠 && 完美转发

1. 左值、右值

  • 左值:可以取地址,可以修改,有持久生命周期。普通变量、对象都是左值。

  • 右值 :临时对象,字面量,即将销毁,不能取地址。1+2string("hello")都是右值。

复制代码
template<class T>
void f1(T& x)   // 只能接收左值
{}

template<class T>
void f2(T&& x)  // 万能引用,不是单纯右值引用
{}

2. 引用折叠规则(重点)

模板推导发生引用折叠:

  1. 右值引用 + 右值引用 → 右值引用 T&& && → T&&

  2. 其余所有组合折叠为左值引用

    1. T& && → T&

    2. T& & → T&

    3. T&& & → T&

模板参数T&&不是单纯右值引用,叫万能引用。实参传左值,推导后变成左值引用;实参传右值,推导后变成右值引用。

3.std::forward 完美转发

forward保留参数原有的左 / 右值属性。

复制代码
template<class T>
void push_back(T&& x)
{
    insert(end(), forward<T>(x));
}
  • 如果 x 原来是左值,forward<T>(x)产出左值;

  • 如果 x 原来是右值,forward<T>(x)产出右值。

没有 forward,传入的右值引用形参在函数内部会变成左值

三、可变参数模板

1. 模板参数包、函数参数包

复制代码
template<class ...Args>
void Print(Args... args)
{
    cout << sizeof...(args) << endl;
}
  • class ...Args:模板参数包,0 个或多个类型

  • Args... args:函数参数包,0 个或多个函数参数

  • sizeof...(args) 获取参数包里参数个数。

调用示例:

复制代码
int main()
{
    double x = 2.2;
    Print();
    Print(1);
    Print(1, string("xxxxxx"));
    Print(1.1, string("xxxxxx"), x);
}

2. 递归拆解参数包

复制代码
void ShowList()
{
    cout << endl;
}

template<class T, class ...Args>
void ShowList(T x, Args... args)
{
    cout << x << " ";
    ShowList(args...);
}

编译器递归推导,不断拆出第一个参数,剩余参数继续递归,直到走到无参终止函数。

3.push_back vs emplace_back

  • push_back:接收对象,要么拷贝,要么移动。

  • emplace_back:接收构造函数参数包,直接在容器节点原地构造对象,减少拷贝 / 移动

复制代码
list<string> lt;
lt.emplace_back(10, 'x'); // 直接调用string(10,'x')在节点构造,不需要临时对象

优先推荐emplace_back,综合场景下效率更高。直接把构造参数包往下传,去插入,实现直接构造。

四、C++11 新增默认成员函数

C++98 有 6 个默认成员函数:构造、析构、拷贝构造、拷贝赋值、取地址重载、const 取地址重载。 C++11 新增两个:移动构造函数、移动赋值运算符重载

  1. 移动构造:资源窃取,把传入对象的资源偷过来,不拷贝堆内存。

  2. 移动赋值:把 this 旧资源释放,窃取传入对象资源。

编译器自动生成默认移动构造 / 移动赋值条件:

用户没有手写:拷贝构造、拷贝赋值、移动构造、移动赋值、析构函数,编译器才会自动生成默认移动版本。 如果类里面有自定义成员,会调用成员的移动构造,成员不支持移动就回退拷贝。

=default强制生成默认函数;=delete删除该函数。

委托构造函数

同一个类中,一个构造函数调用本类其他构造函数,减少代码重复。

复制代码
class A
{
public:
    A(int a)
    {}
    A() : A(10) //委托构造
    {}
};

委托构造不能循环调用,不能在函数体内调用,只能写在初始化列表。

继承构造函数

派生类使用using 基类::构造函数;直接继承基类全部构造函数。

复制代码
class Base
{
public:
    Base(int x) {}
};

class Derive : public Base
{
public:
    using Base::Base;
};

五、lambda 表达式

lambda 本质:编译器生成匿名仿函数类,创建该类对象

语法格式

复制代码
[capture-list](params) -> return_type { body };
  1. [捕获列表]:捕获外部变量;

  2. (参数):形参列表;

  3. ->返回类型:返回值类型,可省略,编译器自动推导;

  4. {函数体}:函数逻辑。

捕获列表

|----------|----------------------|
| 写法 | 说明 |
| [x] | 值捕获,拷贝一份,默认 const |
| [&x] | 引用捕获,拿到变量引用,可以修改外部变量 |
| [=] | 隐式值捕获,所有外部变量值拷贝 |
| [&] | 隐式引用捕获,所有外部变量引用 |
| [=,&x] | 混合捕获:其余值捕获,x 引用捕获 |
| [] | 不捕获任何变量 |

mutable:去掉值捕获变量的 const 属性,允许修改拷贝副本,不会修改外部真实变量

复制代码
auto f = [x]() mutable { x++; };

lambda 底层原理

写一个 lambda,编译器生成一个仿函数类:

复制代码
// 你写的lambda
auto lam = [](int a){return a*2;};

//编译器生成类似:
class __lambda_xxx
{
public:
    int operator()(int a) const
    {
        return a*2;
    }
};

捕获的变量变成仿函数类的成员变量。

std::function 包装器

头文件<functional>,包装可调用对象:普通函数、函数指针、lambda、仿函数。

复制代码
#include <functional>

int add(int a,int b){return a+b;}

int main()
{
    function<int(int,int)> f = add;
    cout << f(1,2);
    f = [](int a,int b){return a*b;};
    cout << f(3,4);
}

类型统一,把不同类型可调用对象包装成同一个 function 类型。

std::bind 绑定器

bind 是函数适配器,修改可调用对象参数顺序、绑定固定参数。

复制代码
int sub(int a,int b)
{
    return a-b;
}

int main()
{
    auto f1 = bind(sub, 10, placeholders::_1);
    cout << f1(5); // 10-5
}

placeholders::_1,_2代表调用时传入的第 1、2 个参数。

六、小结

  1. string迭代器本质char*push_back/insert底层管理堆数组,扩容 + 挪动数据完成增容插入。

  2. 万能引用 T&& + forward 完美转发,是移动语义基础;引用折叠是模板推导底层规则。

  3. 可变参数模板,递归拆解参数包;emplace_back直接原地构造对象,性能优于push_back

  4. C++11 新增移动构造、移动赋值;委托构造、继承构造简化类代码。

  5. lambda = 编译器生成匿名仿函数;捕获列表捕获外部变量;std::function统一封装可调用对象,bind调整参数。

相关推荐
艾莉丝努力练剑1 小时前
【AI大模型接入SDK】Ollama本地大语言模型部署
c++·人工智能·语言模型·自然语言处理·面试
晴天的雨.9921 小时前
[C++]算法双指针 复写0
数据结构·c++·算法
橘子汽水1681 小时前
Leetcode 128,49最长连续序列,字母异位词分组
java·算法·leetcode
mmmmath_31 小时前
LeetCode.438.找到字符串中所有字母异位词
数据结构·算法·leetcode
Nil2081 小时前
leetcode 22括号生成
算法·leetcode·深度优先
Navigator_Z2 小时前
LeetCode //C - 1237. Find Positive Integer Solution for a Given Equation
c语言·算法·leetcode
码少女2 小时前
数据结构——堆排序
数据结构
啦啦啦啦啦zzzz2 小时前
Appender抽象类的实现c++20
linux·服务器·c++·c++20