【C++】C++11部分

news2025/3/10 19:50:09

目录

一、列表初始化

1.1 {}初始化

1.2 initializer_list

二、变量类型推导

2.1 auto

2.2 decltype

三、STL中一些变化

3.1 新增容器

四、lambda表达式

4.1 C++98中的一个例子

4.2 lambda表达式

4.3 函数对象与lambda表达式

五、包装器

5.1 function包装器

5.2 function 的使用方法

5.3 bind


一、列表初始化

1.1 {}初始化

C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自
定义的类型,使用初始化列表时,可添加等号(=),也可不添加

    int a[2]{ 1,2 };
    int b{ 3 };
    vector<int> c{ 4,5,6,7,8 };

1.2 initializer_list

我们能使用{}初始化,是因为initializer_list的原因,下面我们来了解一下它。

它是一个新的C++的容器,一个类模板,是一个用于初始化的工具。

举一个例子,你如何用下面的数组来初始化vector呢?

int arr[5] = { 1, 2, 3, 4 };

vector<int> v;

你可能会用下面的代码来初始化

for (int i = 0; i < 5; i++)
{
    v.push_back(arr[i]);
}

但是这好像太麻烦了吧。C++11后,STL的容器都增加了新的构造函数,可以通过initializer_list来初始化容器。

vector<int> v({ 1, 2, 3, 4, 5 });

这个写法,是单参数的类型转化,因为{ 1, 2, 3, 4, 5 }整体就是一个initializer_list类型的参数

同样的,我们也可以用它来初始化map

map<string, string> m = { {"apple","苹果"}, {"banana","香蕉"}, {"pear", "梨"} };

最外层的{ }就是一个initializer_list。

它的接口也很简单

(constructor)构造空 initializer_list
size返回列表大小
begin返回迭代器的开头
end返回迭代器的最后

我们也可以看出来initializer_list的本质上是一个通过迭代器访问数组的容器。当其它容器通过initializer_list构造自己时,其实就是通过迭代器遍历那个存储了节点的数组,然后把数组元素一个一个插入。

换而言之,下面这两种写法是一样的。

initializer_list<int> lt = { 1, 2, 3, 4 };

list<int> l1({ 1, 2, 3, 4 });
list<int> l2(lt.begin(), lt.end());


二、变量类型推导

2.1 auto

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局
部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将
其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初
始化值的类型。

auto i = 1;//整型
auto d = 3.14;//浮点型
auto p = &i;//指针
vector<int> v;
auto it = v.begin();//迭代器

2.2 decltype

decltype可以检测一个变量的类型,并且拿这个类型去声明新的类型。比如下面这个

int i = 0;
decltype(i) x = 5;

decltype(i)检测出i的类型为int,于是decltype(i)整体就变成int,从而定义出一个新的变量x。


三、STL中一些变化

3.1 新增容器

用橘色圈起来是C++11中的一些几个新容器。但最有用的是unordered_map、unordered_set。


四、lambda表达式

4.1 C++98中的一个例子

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的
类对象。

在C++98中,如果想要对一个结构体进行排序,就需要用户自己写仿函数。

struct Goods
{
     string _name;  // 名字
     double _price; // 价格
     int _evaluate; // 评价
     Goods(const char* str, double price, int evaluate)
         :_name(str)
         , _price(price)
         , _evaluate(evaluate)
     {}
};

如果我们想要按照价格排序

struct ComparePriceLess
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price < gr._price;
    }
};

struct ComparePriceGreater
{
    bool operator()(const Goods& gl, const Goods& gr)
    {
        return gl._price > gr._price;
    }
};

随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法, 
都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,
这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

4.2 lambda表达式

lambda表达式书写格式:

[capture_list] (parameters) mutable -> return_type {statement}

我们来看看各部分是做什么的

  • [capture_list]:捕捉列表。编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用
  • (parameters):参数列表。普通函数的参数列表一致。如果不需要参数传递,则可以
    连同()一起省略
  • mutable:一个关键字。mutable可以取消其常量性。使用该修饰符时,参数列表不可省略
  • -> return_type:返回值类型。用追踪返回类型形式声明函数的返回值类型,返回值类型明确情况下,可省略,由编译器对返回类型进行推导。没有返回值时此部分也可省略
  • {statement}: 函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量

举一个例子,下面是一个完整的lambda表达式。

auto add = [](int a, int b)mutable -> int { return a + b; };

当然我们也可以将其省略成下面这个式子

auto add = [](int a, int b) { return a + b; };

那lambda表达式有什么作用呢?答案是:lambda会返回一个仿函数对象

比如我们上面写的例子,其实add就是一个仿函数对象了,我们可以直接按照调用函数的方式来调用这个仿函数:add(1, 2);。但是要注意, lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,因此必须使用auto来接受这个仿函数对象。


下面我们再来详细地讲一讲lambda表达式最前面的[]的作用。

[]描述了上下文中那些数据可以被lambda使用,以及使用的方式是传值还是传引用。

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

举几个例子解释一下。

int x = 1;
int y = 2;

/*如果直接通过变量名捕获,此时是传值调用,修改函数体内部的变量,不会影响父作用域的变量*/

auto add = [x, y] {return x + y; };
 

/*通过直接传值捕获的变量,自带const属性,不允许修改。此时代码就会报错*/

/*auto add = [x, y] 
    {
        x += 5;
        y += 5;
    };*/

/*mutable可以让被捕获的参数可以修改。需要注意的是如果使用了mutable,()不可省略*/

auto add = [x, y] () mutable
    {
        x += 5;
        y += 5;
    };


/*以传引用的方式来捕获变量,此时修改函数内部的x和y,就是在修改父作用域的x和y。如果使用了传引用捕获变量,就算没有mutable也可以修改参数*/

auto add = [&x, &y]
    {
        x += 5;
        y += 5;
    };
 

/*[=]是以传值的形式捕获父作用域所有变量,[&]是以传引用的形式捕获父作用域所有变量*/

auto add = [=]
    {
        return x + y;
    };

auto add = [&]
    {
        return x + y;
    };

/*

我们还可以把传值和传引用混合使用,让部分参数传参,部分参数传引用。

[x, &y]:以传值的形式捕获x,以传引用的形式捕获y
[=, &x]:以传值的形式捕获父作用域所有变量,以传引用的形式捕获x
[&, x]:以传值的形式捕获x,以传引用的形式捕获父作用域所有变量

*/

有了lambda表达式之后,我们就可以改变一下最开始的排序了

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; });

4.3 函数对象与lambda表达式

从使用方式上来看,函数对象与lambda表达式完全一样。

函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可
以直接将该变量捕获到。

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。


五、包装器

当我们在寄快递的时候,快递会被包装,这样我们就可以统一的在上面贴上快递信息,随后以统一的形式管理所有快递。包装器也是如此,包装器可以将具有相似属性的东西包装起来成为一个整体。

5.1 function包装器

function包装器,也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

那么我们在什么时候会用到包装器呢?我们一步步来看

我们首先回忆一下我们知道的可调用对象

  1. 函数指针
  2. 仿函数实例化出的对象
  3. lambda表达式

但是它们都有各自的缺点

函数指针:类型复杂,不好用
仿函数实例化出的对象:哪怕参数返回值都相同,仿函数之间的类型也不同
lambda表达式:类型是随机的,必须用auto接收

可以看到,这三者都有类型方面的大问题,我们也没有一种方式可以把所有参数类型和返回值类型相同的函数,统一的管理起来,让它们都变成一个类型?这个时候就用到包装器了。

std::function在头文件<functional>


// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;


模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参

function包装器,只要是返回值和参数列表都相同的可调用对象,经过这一层封装,都会变成相同的类型。

举一个例子

double func(double x)
{
    return x / 2;
}

struct Functor
{
    double operator()(double x)
    {
        return x / 3;
    }
};

int main()
{
    auto lambadaFunc = [](double d) {return d / 4; };

     /*func,Functor,lambadaFunc 。它们的返回值都是double,参数类型也是double,因此可以经过包装器包装为function<double<double>>*/

     /*现在三者的类型就都是function<double(double)>*/

    function<double(double)> func1 = func;
    function<double(double)> func2 = Functor();
    function<double(double)> func3 = lambadaFunc;

    return 0;
}

5.2 function 的使用方法

// 使用方法如下:
#include <functional>
int f(int a, int b)
{
        return a + b;
}
struct Functor
{
public:
         int operator() (int a, int b)
         {
                 return a + b;
         }
};

class Plus
{
public:
         static int plusi(int a, int b)
         {
                return a + b;
         }
         double plusd(double a, double b)
         {
                 return a + b;
         }

};
int main()
{
         // 函数名(函数指针)
         std::function<int(int, int)> func1 = f;
         cout << func1(1, 2) << endl;
         // 函数对象
         std::function<int(int, int)> func2 = Functor();
         cout << func2(1, 2) << endl;
         // lamber表达式
         std::function<int(int, int)> func3 = [](const int a, const int b) 
        {return a + b; };
         cout << func3(1, 2) << endl;
 
         // 类的成员函数
         std::function<int(int, int)> func4 = &Plus::plusi;
         cout << func4(1, 2) << endl;
         std::function<double(Plus, double, double)> func5 = &Plus::plusd;
         cout << func5(Plus(), 1.1, 2.2) << endl;


         return 0;
}

5.3 bind

bind函数

  1. 是一个函数模板
  2. 其接收多个参数,第一个参数为可调用对象,后续参数为该可调用对象的参数
  3. 其主要有两个功能:改变参数顺序,给指定参数绑定固定值

C++11后新增一个命名空间域placeholders,其内部会存储很多变量,这些变量用于函数的传参,变量的名字为_x表示第x个参数。

举个例子

int sub(int a, int b)
{
    return a - b;
}

int main()
{

        /*sub这个参数是一个可调用对象。
        placeholders::_2表示第二个参数,

        placeholders::_1表示第一个参数*/
        auto f1 = bind(sub, placeholders::_2, placeholders::_1);

        /*f1最后拿到了这个bind封装的函数,那么f1(3, 5)执行的并不是3 - 5,而是5 - 3*/

        f1(3, 5);

        return 0;
}

int sub(int a, int b)
{
    return a - b;
}

int main()
{

        /*第一个参数为可调用对象sub,第二个参数是一个固定值3.14,参数a都固定为3.14*/
        auto f2 = bind(sub, 3.14, placeholders::_1);

        f2(10);//3.14 - 10

        return 0;
}

bind 函数还可以用来处理函数的返回值

int add(int x, int y) {
    return x + y;
}

int main() {
       // 绑定 add 函数,并将返回值乘以 2
        auto doubleAdd = bind([](int result) { return result * 2; },         add(placeholders::_1,placeholders::_2));
        int result = doubleAdd(3, 4);
        cout << result << endl; // 输出 14


        return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2312859.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【分布式】聊聊分布式id实现方案和生产经验

对于分布式Id来说&#xff0c;在面试过程中也是高频面试题&#xff0c;所以主要针对分布式id实现方案进行详细分析下。 应用场景 对于无论是单机还是分布式系统来说&#xff0c;对于很多场景需要全局唯一ID&#xff0c; 数据库id唯一性日志traceId 可以方便找到日志链&#…

c#面试题整理6

1.String类能否被继承&#xff0c;为什么 可以看到String类的修饰符是sealed&#xff0c;即是密封类&#xff0c;故不可被继承 2.一个对象的方法是否只能由一个线程访问 不是&#xff0c;但是可通过同步机制&#xff0c;确保同一个时间只有一个线程访问 3.计算2*8&#xff…

简洁实用的3个免费wordpress主题

高端大气动态炫酷的免费企业官网wordpress主题 非常简洁的免费wordpress主题&#xff0c;安装简单、设置简单&#xff0c;几分钟就可以搭建好一个wordpress网站。 经典风格的免费wordpress主题 免费下载 https://www.fuyefa.com/wordpress

Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露

一&#xff1a;背景 1. 讲故事 前面跟大家分享过一篇 C# 调用 C代码引发非托管内存泄露 的文章&#xff0c;这是一个故意引发的正向泄露&#xff0c;这一篇我们从逆向的角度去洞察引发泄露的祸根代码&#xff0c;这东西如果在 windows 上还是很好处理的&#xff0c;很多人知道…

【HDLbits--counter】

HDLbits--counter 在IC设计中&#xff0c;counter是十分普遍和重要的设计内容&#xff1b; 题目&#xff1a;基础计数器 module top_module (input clk,input reset,output [9:0] q);always (posedge clk) beginif(reset) beginq < 0;end else beginif(q999) beginq < 0…

nvm 让 Node.js 版本切换更灵活

有很多小伙伴前端开发进程中&#xff0c;我们常常会遇到不同项目依赖不同版本 Node.js 的情况。我们不可能去卸载重新安装适应的版本去安装依赖或者启动项目。为了避免版本冲突带来的一系列麻烦&#xff0c;在这里给大家推荐一款Node.js 版本管理工具——nvm&#xff08;Node V…

美畅物联丨P2P系列之STUN服务器:助力网络穿透

在当今WebRTC等实时通信应用广泛兴起的复杂网络环境下&#xff0c;如何在NAT&#xff08;网络地址转换&#xff09;环境中实现高效、稳定的点对点&#xff08;P2P&#xff09;连接成为关键。STUN&#xff08;Session Traversal Utilities for NAT&#xff09;服务器作为应对这一…

基于SpringBoot的“积分制零食自选销售平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“积分制零食自选销售平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统总体结构图 局部E-R图 系统首页界面…

DeepSeek-V3 技术报告解读

DeepSeek火了有一段时间了&#xff0c;春节假期因为没时间&#xff0c;所以关于deepseek大模型一系列的技术报告一直没看&#xff0c;新年开工后&#xff0c;抽一点时间把之前的坑补起来&#xff0c;关于DeepSeek-V3技术报告的解读已经有很多了&#xff0c;但我相信不同的人去读…

力扣72题编辑距离

题目 原理 三个操作对应的操作次数分别是: 插入:在原本的次数上 1删除:在原本的次数上1替换:如果两个位置的字符串一样,则等于原本的次数, 如果不等,在原本的次数上1 去三者的最小值,就是最小的编辑次数 示例 代码 答案是2 package org.example;public class _72_编辑距离 {pu…

聊天服务器分布式改造

目前的聊天室是单节点的&#xff0c;无论是http接口还是socket接口都在同一个进程&#xff0c;无法承受太多人同时在线&#xff0c;容灾性也非常差。因此&#xff0c;一个成熟的IM产品一定是做成分布式的&#xff0c;根据功能分模块&#xff0c;每个模块也使用多个节点并行部署…

linux上安装redis[从0到1]

redis安装步骤 1.下载redis2.新建redis文件夹3.解压安装Redis4.编译5.修改相关配置6.错误 redis下载官网: https://download.redis.io/releases/ 找到自己需要的版本 1.下载redis 选着自己需要下载的版本后&#xff0c;右击选择复制链接&#xff0c;然后利用命令进行下载&am…

批量删除 Excel 中的空白行、空白列以及空白表格

我们经常会碰到需要删除 Excel 文档表格中的空白行及空白列的场景&#xff0c;有一些空白行或空白列可能我们人工不好识别&#xff0c;因此删除空白行空白列对我们来讲就非常的繁琐&#xff0c;因为我们需要先识别哪些 Excel 文档中包含空白行或者空白列&#xff0c;我们才能够…

MYSQL之创建数据库和表

创建数据库db_ck &#xff08;下面的创建是最好的创建方法&#xff0c;如果数据库存在也不会报错&#xff0c;并且指定使用utf8mb4&#xff09; show databases命令可以查看所有的数据库名&#xff0c;可以找到刚刚创建的db_ck数据库 使用该数据库时&#xff0c;发现里面没有…

腾讯元宝:AI 时代的快速论文阅读助手

1. 背景与需求 在 AI 研究领域&#xff0c;每天都会涌现大量学术论文。如何高效阅读并提取关键信息成为研究者的一大难题。腾讯元宝是腾讯推出的一款大模型&#xff0c;结合了**大语言模型&#xff08;LLM&#xff09;和自然语言处理&#xff08;NLP&#xff09;**技术&#x…

重构谷粒商城09:人人开源框架的快速入门

谷粒商城09——人人开源框架的快速入门 前言&#xff1a;这个系列将使用最前沿的cursor作为辅助编程工具&#xff0c;来快速开发一些基础的编程项目。目的是为了在真实项目中&#xff0c;帮助初级程序员快速进阶&#xff0c;以最快的速度&#xff0c;效率&#xff0c;快速进阶…

redis数据迁移教程(使用RedisShake实现不停机迁移十分便捷)

1.我的场景 需要把本地的redis数据上传到阿里云服务器上面,服务器上redis并没有开aof持久化,但是将rdb文件上传至服务器后每次重启redis,rdb文件会被覆盖导致无法同同步数据,最终决定使用RedisShake 2.RedisShake介绍 什么是 RedisShake​ RedisShake 是一个用于处理和迁移…

2025年2月平价旗舰手机性能对比

1、荣耀Magic7 点评&#xff1a;缺席潜望式长焦&#xff0c;3X直立长焦体验还行。兼顾性能、游戏、屏幕、影像、续航、快充等诸多方面&#xff0c;且外围配置比较齐全。 2、vivo x200 点评&#xff1a;潜望式长焦相机&#xff0c;拍照效果好&#xff0c;30W无线充电着实鸡肋&a…

Golang学习笔记_44——命令模式

Golang学习笔记_41——观察者模式 Golang学习笔记_42——迭代器模式 Golang学习笔记_43——责任链模式 文章目录 一、核心概念1. 定义2. 解决的问题3. 核心角色4. 类图 二、特点分析三、适用场景1. 事务管理系统2. 多媒体遥控器3. 操作审计系统 四、Go语言实现示例五、高级应用…

【单片机通信技术】STM32 HAL库 SPI主从机通过串口发送数据

一、说明 使用STM32F103C8T6最小系统板&#xff0c;让板载SPI1与SPI2通信&#xff0c;通过串口收发数据。本文章说明了在配置与编写时遇到的一些问题&#xff0c;以及详细说明如何使用cubeMAX进行代码编写。 二、CubeMAX配置 1.时钟配置选择外部高速时钟 2.系统模式与时钟配…