【C++ 面试 - 新特性】每日 3 题(六)

news2024/12/23 3:02:57

✍个人博客:Pandaconda-CSDN博客
📣专栏地址:http://t.csdnimg.cn/fYaBd
📚专栏简介:在这个专栏中,我将会分享 C++ 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞👍收藏📁,您的支持就是我创作的最大动力💪

16. Lambda 表达式

基本用法

lambda 表达式是 C++11 最重要也是最常用的特性之一,这是现代编程语言的一个特点,lambda 表达式有如下的一些优点:

  • 声明式的编程风格:就地匿名定义目标函数或函数对象,不需要额外写一个命名函数或函数对象。
  • 简洁:避免了代码膨胀和功能分散,让开发更加高效。
  • 在需要的时间和地点实现功能闭包,使程序更加灵活。
    lambda 表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda 表达式的语法形式简单归纳如下:
[capture](params) opt -> ret {body;};

其中 capture 是捕获列表,params 是参数列表,opt 是函数选项,ret 是返回值类型,body 是函数体。

  1. 捕获列表 []:捕获一定范围内的变量。
  2. 参数列表 () : 和普通函数的参数列表一样,如果没有参数参数列表可以省略不写。
    auto f = {return 1;} // 没有参数, 参数列表为空
    auto f = []{return 1;} // 没有参数, 参数列表省略不写
  3. opt 选项:不需要可以省略。
  • mutable :可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)。
  • exception :指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw()。
  1. 返回值类型:在 C++11 中,lambda 表达式的返回值是通过返回值后置语法来定义的。
  2. 函数体:函数的实现,这部分不能省略,但函数体可以为空。
    捕获列表
    lambda 表达式的捕获列表可以捕获一定范围内的变量,具体使用方式如下:
  • [] - 不捕捉任何变量
  • [&] - 捕获外部作用域中所有变量,并作为引用在函数体内使用 (按引用捕获)
  • [=] - 捕获外部作用域中所有变量,并作为副本在函数体内使用 (按值捕获)
    • 拷贝的副本在匿名函数体内部是只读的
  • [=, &foo] - 按值捕获外部作用域中所有变量,并按照引用捕获外部变量 foo
  • [bar] - 按值捕获 bar 变量,同时不捕获其他变量
  • [&bar] - 按引用捕获 bar 变量,同时不捕获其他变量
  • [this] - 捕获当前类中的 this 指针
    • 让 lambda 表达式拥有和当前类成员函数同样的访问权限
    • 如果已经使用了 & 或者 = , 默认添加此选项
      下面通过一个例子,看一下初始化列表的具体用法:
#include <iostream>
#include <functional>
using namespace std;

class Test
{
public:
    void output(int x, int y)
    {
        auto x1 = [] {return m_number; };                      // error
        auto x2 = [=] {return m_number + x + y; };             // ok
        auto x3 = [&] {return m_number + x + y; };             // ok
        auto x4 = [this] {return m_number; };                  // ok
        auto x5 = [this] {return m_number + x + y; };          // error
        auto x6 = [this, x, y] {return m_number + x + y; };    // ok
        auto x7 = [this] {return m_number++; };                // ok
    }
    int m_number = 100;
};

x1 :错误,没有捕获外部变量,不能使用类成员 m_number
x2 :正确,以值拷贝的方式捕获所有外部变量。
x3 :正确,以引用的方式捕获所有外部变量。
x4 :正确,捕获 this 指针,可访问对象内部成员。
x5 :错误,捕获 this 指针,可访问类内部成员,没有捕获到变量 x,y,因此不能访问。
x6 :正确,捕获 this 指针,x,y 。
x7 :正确,捕获 this 指针,并且可以修改对象内部变量的值。

int main(void)
{
    int a = 10, b = 20;
    auto f1 = [] {return a; };                        // error
    auto f2 = [&] {return a++; };                     // ok
    auto f3 = [=] {return a; };                       // ok
    auto f4 = [=] {return a++; };                     // error
    auto f5 = [a] {return a + b; };                   // error
    auto f6 = [a, &b] {return a + (b++); };           // ok
    auto f7 = [=, &b] {return a + (b++); };           // ok

    return 0;
}

f1 :错误,没有捕获外部变量,因此无法访问变量 a 。
f2 :正确,使用引用的方式捕获外部变量,可读写。
f3 :正确,使用值拷贝的方式捕获外部变量,可读。
f4 :错误,使用值拷贝的方式捕获外部变量,可读不能写。
f5 :错误,使用拷贝的方式捕获了外部变量 a ,没有捕获外部变量 b ,因此无法访问变量 b 。
f6 :正确,使用拷贝的方式捕获了外部变量 a ,只读,使用引用的方式捕获外部变量 b ,可读写。
f7 :正确,使用值拷贝的方式捕获所有外部变量以及 b 的引用,b 可读写,其他只读。

在匿名函数内部,需要通过 lambda 表达式的捕获列表控制如何捕获外部变量,以及访问哪些变量。默认状态下 lambda 表达式无法修改通过复制方式捕获外部变量,如果希望修改这些外部变量,需要通过引用的方式进行捕获。
返回值

很多时候,lambda 表达式的返回值是非常明显的,因此在 C++11 中允许省略 lambda 表达式的返回值。

// 完整的lambda表达式定义
auto f = [](int a) -> int
{
    return a+10;  
};

// 忽略返回值的lambda表达式定义
auto f = [](int a)
{
    return a+10;  
};

一般情况下,不指定 lambda 表达式的返回值,编译器会根据 return 语句自动推导返回值的类型,但需要注意的是 labmda 表达式不能通过列表初始化自动推导出返回值类型。

// ok,可以自动推导出返回值类型
auto f = [](int i)
{
    return i;
}

// error,不能推导出返回值类型
auto f1 = []()
{
    return {1, 2};  // 基于列表初始化推导返回值,错误
}

函数本质

使用 lambda 表达式捕获列表捕获外部变量,如果希望去修改按值捕获的外部变量,那么应该如何处理呢?这就需要使用 mutable 选项,被 mutable 修改是 lambda 表达式就算没有参数也要写明参数列表,并且可以去掉按值捕获的外部变量的只读(const)属性。

int a = 0;
auto f1 = [=] {return a++; };              // error, 按值捕获外部变量, a是只读的
auto f2 = [=]()mutable {return a++; };     // ok

再举个例子:

int a = 10, b = 20;
auto f3 = [=]() {  //error 常方法里试图改变变量的值
    int tmp = a;
    a = b;
    b = tmp;
};

修改后:

int a = 10, b = 20;
printf("%p %p\n", &a, &b);
auto f3 = [a, b]() mutable
{
    int tmp = a;
    a = b;
    b = tmp;
    printf("%p %p\n", &a, &b);
};
f3();
printf("%d %d\n", a, b);
/*
00CFFCE4 00CFFCD8
00CFFCC8 00CFFCCC
10 20
*/

最后再剖析一下为什么通过值拷贝的方式捕获的外部变量是只读的:

  1. lambda 表达式的类型在 C++11 中会被看做是一个带 operator() 的类,即仿函数。
  2. 按照 C++ 标准,lambda 表达式的 operator() 默认是 const 的,一个 const 成员函数是无法修改成员变量值的。
    mutable 选项的作用就在于取消 operator() 的 const 属性。
    因为 lambda 表达式在 C++ 中会被看做是一个仿函数,因此可以使用 std::function 和 std::bind 来存储和操作。

lambda 表达式:

#include <iostream>
#include <functional>
using namespace std;

int main(void)
{
    // 包装可调用函数
    std::function<int(int)> f1 = [](int a) {return a; };
    // 绑定可调用函数
    std::function<int(int)> f2 = bind([](int a) {return a; }, placeholders::_1);

    // 函数调用
    cout << f1(100) << endl;
    cout << f2(200) << endl;
    return 0;
}

对于没有捕获任何变量的 lambda 表达式,还可以转换成一个普通的函数指针:

using func_ptr = int(*)(int);
// 没有捕获任何外部变量的匿名函数
func_ptr f = [](int a)
{
    return a;  
};
// 函数调用
f(1314);

自定义 sort

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
 
class A
{
public:
    A(int _a,int _b)
    {
        a=_a;
        b=_b;
    };
    int a;
    int b;
};
int main()
{
    vector<A> test;
    for(int i=0;i<10;i++)
    {
        A a(i,10-i);
        test.emplace_back(a);
    }
    sort(test.begin(),test.end(),[](A x,A y){return x.a>y.a;});
    for(int i=0;i<10;i++)
    {
        cout<<test[i].a<<endl;
    }
    return 1;
}

多元组的优先队列

class Data
{
public:
    Data(int val1 = 10, int val2 = 10) :ma(val1), mb(val2) {}
    //bool operator>(const Data& data) const { return ma > data.ma; }  //方法一
    //bool operator<(const Data& data) const { return ma < data.ma; }
    int ma;
    int mb;
private:
};

//main
//优先级队列
using FUNC = function<bool(Data&, Data&)>; //方法二
priority_queue<Data, vector<Data>, FUNC>
    queue([](Data& d1, Data& d2)->bool
        {
            return d1.ma > d2.ma;
        });
queue.push({ 10, 20 });
queue.push({ 15, 25 });

17. 说一下 C++ 左值引用和右值引用

C++11 正是通过引入右值引用来优化性能,具体来说是通过移动语义来避免无谓拷贝的问题,通过 move 语义来将临时生成的左值中的资源无代价的转移到另外一个对象中去,通过完美转发来解决不能按照参数实际类型来转发的问题(同时,完美转发获得的一个好处是可以实现移动语义)。

  1. 在 C++11 中所有的值必属于左值、右值两者之一,右值又可以细分为纯右值、将亡值。在 C++11 中可以取地址的、有名字的就是左值,反之,不能取地址的、没有名字的就是右值(将亡值或纯右值)。举个例子,int a = b+c,a 就是左值,其有变量名为 a,通过 &a 可以获取该变量的地址;表达式 b+c、函数 int func() 的返回值是右值,在其被赋值给某一变量前,我们不能通过变量名找到它,&(b+c) 这样的操作则不会通过编译。
  2. C++11 对 C++98 中的右值进行了扩充。在 C++11 中右值又分为纯右值(prvalue,Pure Rvalue)和将亡值(xvalue,eXpiring Value)。其中纯右值的概念等同于我们在 C++98 标准中右值的概念,指的是临时变量和不跟对象关联的字面量值;将亡值则是 C++11 新增的跟右值引用相关的表达式,这样表达式通常是将要被移动的对象(移为他用),比如返回右值引用 T&& 的函数返回值、std::move 的返回值,或者转换为 T&& 的类型转换函数的返回值。将亡值可以理解为通过 “盗取” 其他变量内存空间的方式获取到的值。在确保其他变量不再被使用、或即将被销毁时,通过 “盗取” 的方式可以避免内存空间的释放和分配,能够延长变量值的生命期。
  3. 左值引用就是对一个左值进行引用的类型。右值引用就是对一个右值进行引用的类型,事实上,由于右值通常不具有名字,我们也只能通过引用的方式找到它的存在。右值引用和左值引用都是属于引用类型。无论是声明一个左值引用还是右值引用,都必须立即进行初始化。而其原因可以理解为是引用类型本身自己并不拥有所绑定对象的内存,只是该对象的一个别名。左值引用是具名变量值的别名,而右值引用则是不具名(匿名)变量的别名。左值引用通常也不能绑定到右值,但常量左值引用是个 “万能” 的引用类型。它可以接受非常量左值、常量左值、右值对其进行初始化。不过常量左值所引用的右值在它的 “余生” 中只能是只读的。相对地,非常量左值只能接受非常量左值对其进行初始化。
  4. 右值值引用通常不能绑定到任何的左值,要想绑定一个左值到右值引用,通常需要 std::move() 将左值强制转换为右值。
    左值和右值
  • 左值:表示的是可以获取地址的表达式,它能出现在赋值语句的左边,对该表达式进行赋值。但是修饰符 const 的出现使得可以声明如下的标识符,它可以取得地址,但是没办法对其进行赋值:
const int& a = 10;
  • 右值:表示无法获取地址的对象,有常量值、函数返回值、lambda 表达式等。无法获取地址,但不表示其不可改变,当定义了右值的右值引用时就可以更改右值。

左值引用和右值引用

  • 左值引用:传统的 C++ 中引用被称为左值引用。
  • 右值引用:C++11 中增加了右值引用,右值引用关联到右值时,右值被存储到特定位置,右值引用指向该特定位置,也就是说,右值虽然无法获取地址,但是右值引用是可以获取地址的,该地址表示临时对象的存储位置。

这里主要说一下右值引用的特点:

  • 特点 1:通过右值引用的声明,右值又 “重获新生”,其生命周期与右值引用类型变量的生命周期一样长,只要该变量还活着,该右值临时量将会一直存活下去。
  • 特点 2:右值引用独立于左值和右值。意思是右值引用类型的变量可能是左值也可能是右值。
  • 特点 3:T&& t 在发生自动类型推断的时候,它是左值还是右值取决于它的初始化。
    举个例子:
    在这里插入图片描述
#include <bits/stdc++.h>
using namespace std;

template<typename T>
void fun(T&& t)
{
    cout << t << endl;
}

int getInt()
{
    return 5;
}

int main() {
    int a = 10;
    int& b = a;  //b是左值引用
    int& c = 10;  //错误,c是左值不能使用右值初始化
    int&& d = 10;  //正确,右值引用用右值初始化
    int&& e = a;  //错误,e是右值引用不能使用左值初始化
    const int& f = a; //正确,左值常引用相当于是万能型,可以用左值或者右值初始化
    const int& g = 10;//正确,左值常引用相当于是万能型,可以用左值或者右值初始化
    const int&& h = 10; //正确,右值常引用
    const int& aa = h;//正确
    int& i = getInt();  //错误,i是左值引用不能使用临时变量(右值)初始化
    int&& j = getInt();  //正确,函数返回值是右值
    fun(10); //此时fun函数的参数t是右值
    fun(a); //此时fun函数的参数t是左值,fun必须用模板才不会报错
    return 0;
}

18. 在使用 move 后,之前的变量会失效吗?

在使用 C++ 的移动语义(move semantics)之后,之前的变量不会直接失效,但它的状态会被修改,并且不再保证是有效或可用的。

移动语义是通过将资源的所有权(如内存或文件句柄)从一个对象转移到另一个对象来实现高效的资源管理。使用 std::move() 函数可以将对象转换为右值引用,并且在移动语义的背景下,对右值引用的对象进行移动操作。

移动操作的结果是源对象的状态被修改,通常会使其处于某种无效或未定义的状态。与之对应的是目标对象获得了源对象的资源所有权,并且处于一个有效的状
态。

下面是一个使用移动语义的示例:

#include <iostream>
#include <string>

int main() {
    std::string source = "Hello";

    std::string destination = std::move(source);

    std::cout << "Source: " << source << std::endl; // 输出为空,原始源对象状态被修改
    std::cout << "Destination: " << destination << std::endl; // 输出为 "Hello"

    return 0;
}

在上面的示例中,source 对象在使用 std::move() 后即可视为失效,因为它的状态被修改,不再保证是有效的字符串。而 destination 对象获得了 source 对象的资源所有权,并且仍然保持有效的字符串。

需要注意的是,虽然源对象被移动后不再保证是有效的,但并没有强制要求在移动之后立即停止对源对象的使用。在某些情况下,仍然可以安全地使用源对象,例如重新赋值或销毁。但是,需要小心确保源对象没有被再次移动或使用。

综上所述,使用移动语义后,之前的变量不会直接失效,但它们的状态会被修改,并且不再保证是有效或可用的。因此,在使用移动语义后,要注意适当地处理和管理移动操作的源对象和目标对象。

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

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

相关文章

八、3 DMA数据转运(代码)

&#xff08;1&#xff09;DMA函数介绍 &#xff08;2&#xff09;DMA是AHB总线的设备&#xff0c;要用AHB开启时钟 若将DataA放在外设站点&#xff0c;DataB放在存储器站点&#xff0c;传输方向就是外设站点—>存储器站点 DMA转运的三个条件&#xff1a; 1&#xff09;传输…

【C语言】字符串函数详细讲解

文章目录 前言求字符串长度&#xff08;strlen&#xff09;strlen的声明和使用strlen模拟实现 字符串拷贝&#xff08;strcpy&#xff09;strcpy的声明和使用strcpy模拟实现 字符串追加函数&#xff08;strcat&#xff09;strcat的声明和使用strcat模拟实现 字符串比较函数&…

C语言深入理解指针5

1.sizeof和strlen 对比 1.1sizeof sizeof用来计算变量所占内存空间大小&#xff0c;单位是字节&#xff0c;操作数是类型的话&#xff0c;计算的是使用类型创建的变量所占空间的大小 sizeof只关注占用内存空间大小&#xff0c;不在乎内存中存放什么数据 int main() {int a …

验证码识别之点选验证码识别——绪论

基于深度学习与传统算法的点选验证码识别 绪论 随着互联网的飞速发展&#xff0c;网络安全问题日益凸显&#xff0c;验证码作为一种有效的安全防护手段&#xff0c;广泛应用于登录验证、注册验证、防止自动化攻击等多个场景。传统的验证码形式如文本验证码、图形验证码等&…

使用 Parallel 类进行多线程编码(上)

用 C# 进行多线程编程有很多方式&#xff0c;比如使用 Thread 对象开启一个新线程&#xff0c;但这已经是一种落后的写法了&#xff0c;现在推荐的写法是使用 Parallel 类&#xff0c;它可以让我们像写传统代码一样编写多线程的程序&#xff0c;Parallel 类有三个常用的方法如下…

erlang学习: Mnesia Erlang数据库

创建Mnesia数据库 mnesia:create_schema([node()]).在shell里输入该行代码即可创建一个mnesia数据库于当前文件夹下 编译器文件路径下同样也有 数据库表定义创建 之后是数据库表定义&#xff0c;打开数据库创建完成后&#xff0c;启动数据库&#xff0c;添加一些表定义&…

ccpc网络热身赛: Iris’Food

题目 做法 第一位选除0外最小的数&#xff0c;其他位按从小到大选。 #include<bits/stdc.h> #define int unsigned long long using namespace std; int t,a[20],m; const int mod1e97; int ksm(int a,int b){int ans1;while(b){if(b%2) ansans*a%mod;b/2;aa*a%mod;}r…

哪里打印便宜一点?什么地方打印便宜?

在这个快节奏的时代&#xff0c;无论是学生、上班族还是创业者&#xff0c;都有可能面临需要紧急打印文件的情况。然而&#xff0c;面对市面上琳琅满目的打印服务提供商&#xff0c;如何选择性价比高的打印服务成了许多人关心的问题。今天&#xff0c;我们就来探讨一下“哪里打…

SQL注入基础入门完整教学

SQL注入-概述 什么是sql注入漏洞&#xff1f; 攻击者利用Web应用程序对用户输入验证上的疏忽&#xff0c;在输入的数据中包含对某些数据 库系统有特殊意义的符号或命令&#xff0c;让攻击者有机会直接对后台数据库系统下达指令&#xff0c;进而 实现对后台数据库乃至整个应用…

如何进行不同数据库的集群操作?--从部署谈起,今天来看MySQL和NoSql数据库Redis的集群

篇幅较长&#xff0c;主要分为mysql和Redis两部分。找想要的部分可见目录食用。。 目录 什么是集群&#xff1f;为什么要集群&#xff1f; 1.1 数据库主要分为两大类&#xff1a;关系型数据库与 NoSQL 数据库 1.2 为什么还要用 NoSQL 数据库呢&#xff1f; ----------------…

python怎么输入中文

解决中文输入的两种应用&#xff1a; 在脚本中加语言编码声明 “-*- coding: uft-8 -*-” 应用一&#xff1a;print中出现中文 方法一&#xff1a;用unicode( , encoding utf-8 ) 或者 unicode(" ", encoding "utf-8" )。 方法二&#xff1a;用u 或者…

springboot 的共享session方案?

问&#xff1a;springboot 的共享session方案&#xff1f; 参考&#xff1a; https://juejin.cn/post/7195227930077691963分布式之session共享问题 4种解决方案及spring session的使用_分布式session共享方案-CSDN博客 什么是 Session &#xff1f; 答&#xff1a;因为Http协…

新能源动力组中预充电路及电阻选型分析

新能源动力组中预充电路及电阻选型分析 1.概述2.预充电路与预充电阻3.预充电阻参数选择4.实例分析 1.概述 最近几年&#xff0c;新能源行业在中国得到迅猛发展。由于其高效、节能、低噪声、无污染等特点&#xff0c;它已成为国内工业发展的新趋势包括汽车和飞机。虽然应用在新…

微波无源器件2 用于双极化波束形成网络的增强型双极化定向耦合器

摘要&#xff1a; 定向耦合器和混合相移器是用于实现波束形成网络的关键器件。通常一个波束形成网络用线极化和正交极化两个极化给天线馈电。双极化器件被用于降低波束形成网络的复杂性和尺寸。双极化定向耦合器由相同的作者提出。一种增强型的双极化耦合器在本文中提出。此器件…

JumpServer关闭admin mfa验证

背景 因为上一次启动了mfa验证&#xff0c;但是没有验证就关机重启&#xff0c;导致再开机输入密码后需要mfa绑定&#xff0c;但是怎么也无法绑定成功&#xff0c;导致无法登录。 故希望通过后台取消mfa的验证 from users.models import Useru User.objects.get(usernameadmin…

ThreadLocal 释放的方式有哪些

ThreadLocal基础概念&#xff1a;IT-BLOG-CN ThreadLocal是Java中用于在同一个线程中存储和隔离变量的一种机制。通常情况下&#xff0c;我们使用ThreadLocal来存储线程独有的变量&#xff0c;并在任务完成后通过remove方法清理这些变量&#xff0c;以防止内存泄漏。然而&…

前端开发的单例设计模式

一、什么是单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是一种常见的设计模式&#xff0c;它确保在整个应用程序的生命周期中&#xff0c;一个类只能创建一个实例。无论你在代码的任何地方尝试创建该类的新实例&#xff0c;它都会返回已经存在的唯一实例。这在…

鸿蒙开发(API 12 Beta6版)【NFC标签读写】 网络篇

简介 近场通信(Near Field Communication&#xff0c;NFC)是一种短距高频的无线电技术&#xff0c;在13.56MHz频率运行&#xff0c;通信距离一般在10厘米距离内。电子设备可以通过NFC通信技术和NFC标签通信&#xff0c;从标签中读取数据&#xff0c;或写入数据到标签。 NFC标…

XInput手柄输入封装

功能全面地封装了XInput的输入, 1. 普通按钮按下, 按住, 弹起状态检查, 2. 摇杆4个方向的按下, 按住, 弹起检查 3. 按键状态变化检测并且记录按下触发时间, 按住保持时间, 方便用来完全自定义的输入功能 4. 多手柄输入合并 CXinputHelper.h #pragma once #include <win…

微信支付开发避坑指南

1 微信支付的坑 1.1 不能用前端传递过来的金额 订单的商品金额要从数据库获取&#xff0c;前端只传商品 id。 1.2 交易类型trade type字段不要传错 v2版API&#xff0c;不同交易类型&#xff0c;要调用的支付方式也不同。 1.3 二次签名 下单时&#xff0c;在拿到预支付交…