C++快餐——C++11(2)

在这里插入图片描述

如期待奇迹发生,那唯有不停伸手去抓紧,去把握才行。

文章目录

  • 类成员变量缺省值
  • default关键字
  • delete关键字
  • final关键字
  • 可变参数模板
  • STL容器中empalce相关接口函数
    • 优点
  • lambda表达式
    • 捕获列表
    • 注意!!!
    • 底层实现
  • 总结


类成员变量缺省值

C++11引入了成员变量的初始缺省值的特性。在类的定义中,可以为成员变量提供初始值。当对象被创建时,如果没有显式提供初始值,编译器会使用这些初始缺省值进行初始化。如下示例:

class MyClass 
{
public:int myInt = 0; // 成员变量的初始缺省值为0float myFloat = 3.14f; // 成员变量的初始缺省值为3.14// 构造函数MyClass() {// 可以在构造函数中访问初始缺省值cout << "myInt: " << myInt << endl;cout << "myFloat: " << myFloat << endl;}
};int main()
{MyClass obj;return 0;
}

上述示例中,myInt和myFloat是具有初始缺省值的成员变量。如果没有提供显式初始值,它们将被分别初始化为0和3.14。构造函数中的输出语句将显示这些初始缺省值。

在这里插入图片描述

default关键字

在C++11及以后的版本中,可以使用关键字default来显式强制生成默认函数。默认函数包括默认构造函数、拷贝构造函数、拷贝赋值运算符和析构函数。使用default关键字可以告诉编译器使用默认实现生成这些函数,而不需要显式定义函数体。这在某些情况下很有用,特别是当需要手动定义某些函数后,仍希望保留其他默认函数的行为。例如:

class MyClass 
{
public:// 默认构造函数MyClass() {}// 拷贝构造函数MyClass(const MyClass& other) = default;// 拷贝赋值运算符MyClass& operator=(const MyClass& other) = default;// 析构函数~MyClass() = default;
};

示例中,通过将函数定义为default,编译器将自动生成默认函数的实现。但是需要注意的是,使用default关键字生成默认函数的前提是该函数在编译器默认情况下是可生成的。如果类中存在不可复制的成员或基类,或者有其他原因导致默认函数无法自动生成,那么不能使用default关键字。

delete关键字

与default相对应的有delete关键字,C++11引入了关键字delete,可以用于显式删除函数,从而禁止生成默认函数。通过将函数声明为delete,编译器将不再生成该函数的默认实现。例如:

class NonCopyable 
{
public:NonCopyable() = default;NonCopyable(const NonCopyable&) = delete; // 删除拷贝构造函数NonCopyable& operator=(const NonCopyable&) = delete; // 删除拷贝赋值运算符
};

示例中,通过将拷贝构造函数和拷贝赋值运算符声明为delete,编译器将禁止生成默认的拷贝构造函数和拷贝赋值运算符。禁止生成默认函数可以用于防止类的对象进行拷贝操作,从而实现不可复制的类。这对于某些情况下的资源管理或设计意图是有用的。但是需要注意的是,当使用delete关键字删除某个函数时,需要确保在使用该函数时会导致编译错误,以避免意外调用被删除的函数。

final关键字

在C++中,final关键字用于继承和多态的语境中,用于限制派生类的进一步继承或虚函数的重写。例如:

class Base final 
{// 类定义
};class Derived : public Base 
{  // 错误!无法继承final类// 类定义
};

在上述示例中,Base类被标记为final,这意味着它不能被其他类继承。因此,尝试从Base派生一个名为Derived的类将导致编译错误。

class Base 
{
public:virtual void foo() const final {// 函数定义}
};class Derived : public Base 
{
public:void foo() const override {  // 错误!无法重写final虚函数// 函数定义}
};

在上述示例中,Base类中的虚函数foo()被标记为final,这意味着它不能在派生类中被重写。因此,尝试在Derived类中重写foo()函数将导致编译错误。

使用final关键字可以提供编译时的限制性,以确保某些类或虚函数不会被继承或重写。这对于需要确保特定类或函数的行为不被改变的情况下很有用。

可变参数模板

可变参数模板是C++11引入的一种特性,它允许函数或类模板接受可变数量的参数。使用可变参数模板可以编写接受任意数量参数的函数或类模板,并对每个参数进行处理,这适合于需要处理未知数量参数的情况。由于无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数。展开参数包通常使用以下两种方式:

  1. 递归函数方式展开参数包
// 可变参数模板的递归终止条件
void print()
{cout << "end" << endl;
}
// 可变参数模板的递归调用
template <class T, class... Args>
void print(const T& x, Args... args)
{cout << "size : " << sizeof...(args) << " x = " << x << endl;print(args...);
}int main()
{print(1, 'y', 5.5, "abcd");
}

示例中,print()函数是一个可变参数模板函数。它的终止条件是当参数列表为空时,在这种情况下它只打印换行符。递归调用中,它首先打印第一个参数,然后通过递归调用print(args…)打印剩余的参数。在main()函数中,我们调用print()函数传递了不同类型和数量的参数。编译器会根据传递的参数自动生成对应的函数实例,并输出这些参数。

在这里插入图片描述

  1. 逗号表达式展开参数包
template <class T>
void print(const T& t)
{cout << t << endl;
}template <class... Args>
void print(Args... args)
{int arr[] = { (print(args), 0)... };
}int main()
{print(1, 'y', 5.5, "abcd");
}

在这个示例中,我们定义了两个重载的print()函数。第一个函数用于打印单个参数,只接受一个参数并输出到标准输出。第二个函数使用可变参数模板template <class… Args>来接受任意数量的参数。

在第二个函数中,我们使用逗号表达式和参数包展开来依次调用print()函数并打印每个参数。逗号表达式(print(args), 0)首先调用print(args)打印参数,然后返回0。这样,我们实际上只是在每个参数之后插入了一个0,并将它们放入一个整数数组中。这个数组的目的只是为了正确展开参数包,并不会被使用。

在main()函数中,我们调用了print()函数,并传递了四个不同类型的参数:整数1,字符’y’,浮点数5.5和字符串"abcd"。这些参数会被正确展开并传递给相应的print()函数进行打印。

在这里插入图片描述

总的来说,可变参数模板在实现各种通用函数和类模板时非常有用,例如日志记录、格式化输出、数据结构等,它提供了一种灵活而强大的方式来处理可变数量的参数。

STL容器中empalce相关接口函数

STL提供了一些emplace相关的接口函数,用于在容器中就地构造元素,避免了额外的拷贝或移动操作。下面是一些常见的emplace相关接口函数:

  1. emplace_back():在容器的末尾就地构造一个元素,并将其插入容器中。它接受构造元素所需的参数,并直接在容器内构造元素,而不是先构造一个临时对象再进行拷贝或移动。
    在这里插入图片描述
vector<int> arr;
arr.emplace_back(1);
  1. emplace():在容器中指定位置就地构造一个元素,并将其插入容器中。它接受一个迭代器参数和构造元素所需的参数,直接在容器内指定位置构造元素。
    在这里插入图片描述
vector<int> arr;
vector<int>::iterator it = arr.emplace(arr.begin(), 2);

优点

可以看到的emplace系列的接口都支持模板的可变参数,还是万能引用,相对于insert系列接口它具有如下优势:

优势说明
避免了额外的拷贝或移动操作emplace系列接口允许在容器中就地构造元素,而不是通过拷贝或移动构造函数构造一个临时对象再进行插入。这样可以避免不必要的拷贝或移动操作,提高了性能和效率。
减少对象构造的开销使用insert系列接口时,需要先构造一个临时对象,然后将该对象拷贝或移动到容器中。而emplace系列接口直接在容器内部构造元素,减少了额外的构造开销。特别是对于大型或复杂的对象,避免了多次构造和析构的开销。
支持可变参数和万能引用emplace系列接口可以接受可变数量的参数,并支持引用折叠,即万能引用。这使得我们可以直接将构造元素所需的参数传递给emplace函数,而不需要显式地构造一个临时对象或进行类型转换。这样可以提高代码的简洁性和灵活性。
提高代码的可读性和维护性使用emplace系列接口可以更直观地表达我们的意图,即在容器中就地构造元素。这样可以使代码更易于理解和维护,减少了不必要的中间步骤和临时对象的引入。

总的来说,emplace系列接口相对于insert系列接口在性能、效率和代码简洁性方面具有优势。它们通过就地构造元素,减少了不必要的拷贝或移动操作,并支持可变参数和万能引用,提供了更高效和灵活的方式来插入元素到容器中。

lambda表达式

Lambda表达式是C++11引入的一种匿名函数的语法特性,它允许我们在需要函数对象的地方编写简洁的、临时的函数定义。Lambda表达式的语法形式如下:

[capture list] (parameters)mutable -> return_type 
{// 函数体
}

其中capture list是捕获列表,用于在Lambda表达式中捕获外部变量。可以为空,也可以包含一个或多个变量,以逗号分隔。捕获列表指定了Lambda表达式所使用的外部变量的方式。

parameters是参数列表,用于指定Lambda表达式的参数。和普通函数一样,可以为空或包含一个或多个参数,以逗号分隔。

mutable是Lambda表达式的一个关键字,用于指示Lambda函数体中的捕获变量可以被修改。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可省略(即使参数为空)。

return_type为返回类型,用于指定Lambda表达式的返回类型。可以省略,编译器可以自动推导返回类型。最后是Lambda表达式的函数体,用于实现具体的功能。

除了捕获列表和函数体外,其他都可以省略,因此可以写出最简单的Lambda函数,[]{},但是该表达式并没有什么作用,没有意义。可以写一个有意义的Lambda,通过Lambda表达式实现求和功能,如下:

int sum = [](int x, int y){return x + y; }(5, 6);

在这里插入图片描述

捕获列表

捕获列表位于Lambda表达式的方括号[]中,用逗号分隔捕获的变量。捕获列表有以下几种形式:

  1. 值捕获:通过值方式捕获外部变量,可以在Lambda函数体内部以只读方式使用这些变量。变量的值在Lambda创建时被复制,后续对外部变量的修改不会影响Lambda内部的值。
int main()
{int x = 10, y = 20;auto sum = [x, y]()->int{return x + y; };cout << sum() << endl;return 0;
}

在这里插入图片描述

  1. 引用捕获:通过引用方式捕获外部变量,可以在Lambda函数体内部以读写方式使用这些变量。变量的引用在Lambda创建时被捕获,后续对外部变量的修改会影响Lambda内部的值。
int main()
{int x = 10, y = 20;auto sum = [&x, &y]()->int{x = 1;y = 2;return x + y;};int ret = sum();cout << "x = " << x << " y = " << y << " sum = " << ret << endl;return 0;
}

在这里插入图片描述

  1. 隐式捕获:通过自动推导的方式捕获外部变量。Lambda表达式可以根据函数体内部是否使用外部变量来自动推导需要捕获的变量。使用隐式捕获时,可以通过=表示以值方式捕获所有外部变量,或通过&表示以引用方式捕获所有外部变量。
int main()
{int x = 10, y = 20;auto sum = [=]()->int{return x + y;};int ret1 = sum();cout << "x = " << x << " y = " << y << " sum = " << ret1 << endl;auto dif = [&]()->int{x *= 10;y *= 5;return x - y;};int ret2 = dif();cout << "x = " << x << " y = " << y << " dif = " << ret2 << endl;return 0;
}

在这里插入图片描述

  1. 显式捕获:通过显式指定捕获的变量来捕获外部变量。可以使用逗号分隔来指定多个捕获的变量。
int main()
{int x = 10, y = 20;auto sum = [x, &y]()->int{y += 5;return x + y;};int ret = sum();cout << "x = " << x << " y = " << y << " sum = " << ret << endl;return 0;
}

在这里插入图片描述

需要注意的是,在Lambda表达式中可以同时使用不同的捕获方式,根据需要选择合适的捕获方式。捕获列表的选择取决于Lambda函数体内部对外部变量的访问需求和修改需求。总的来说捕获列表是Lambda表达式中非常重要的一部分,它决定了Lambda函数体内部可以访问和使用的外部变量。合理使用捕获列表可以使Lambda表达式更加灵活和功能强大。

注意!!!

lambda表达式之间不能相互赋值。在C++中,Lambda表达式是一种匿名函数对象,每个Lambda表达式都有其自己的唯一类型。即使两个Lambda表达式在语法上看起来具有相同的参数列表和返回类型,但是它们实际上是不同的类型,不能相互赋值。这是因为C++编译器会为每个Lambda表达式生成一个独特的闭包类型,该闭包类型包含Lambda表达式的函数体和捕获的变量。即使两个Lambda表达式的函数体和捕获变量完全相同,它们也不会共享相同的闭包类型。例如:

#include <iostream>int main() {auto lambda1 = [](int x) {return x * 2;};auto lambda2 = [](int x) {return x * 2;};lambda1 = lambda2;  // 错误:Lambda表达式之间不能相互赋值return 0;
}

在上述示例中,lambda1和lambda2是两个具有相同函数体的Lambda表达式。尽管它们函数体相同,但由于它们是不同的闭包类型,所以不能相互赋值。编译器将会报告错误。但如果需要在不同的Lambda表达式之间共享函数体,可以考虑将函数体提取为一个可调用对象(例如函数指针、std::function等),然后将该可调用对象分配给不同的Lambda表达式,这样可以实现Lambda表达式之间的函数体共享。如下:

#include <iostream>
#include <functional>int main() 
{auto function = [](int x) {return x * 2;};auto lambda1 = function;auto lambda2 = function;std::cout << lambda1(2) << std::endl;  std::cout << lambda2(3) << std::endl;  return 0;
}

将Lambda表达式的函数体提取为一个可调用对象function,然后将其分配给lambda1和lambda2。现在lambda1和lambda2共享相同的函数体,就可以独立的调用它们。

在这里插入图片描述

底层实现

在底层编译器实现中,Lambda表达式通常被转换为函数对象的方式来处理。编译器将Lambda表达式转换为一个匿名的、自动生成的类,并为该类生成一个重载了函数调用运算符的成员函数。该成员函数包含了Lambda表达式的函数体,并对捕获的变量进行处理。

这个自动生成的类可以被视为函数对象,它具有与Lambda表达式相同的行为和语义。编译器会为每个Lambda表达式生成一个唯一的类类型,并为每个捕获的变量生成相应的成员变量。编译器还会根据Lambda表达式的捕获列表,将需要捕获的变量作为该类的成员变量,并在构造函数中进行初始化。对于值捕获的变量,编译器会在类的构造函数中将其复制或移动到成员变量中。对于引用捕获的变量,编译器会将其作为成员变量的引用。

class Sub
{
public:int operator()(int x, int y){return x / y;}
};int main()
{auto sum = [](int x, int y) {return x + y;};Sub sub;cout << sub(30, 7) << endl;cout << sum(18, 7) << endl;  return 0;
}

在这里插入图片描述

需要注意的是,编译器的具体实现可能会有所不同,不同的编译器可能采用不同的优化策略和实现方式。但是,从语义上讲,Lambda表达式可以被看作是被转换为函数对象的形式来处理。

总结

文章介绍了C++11中的default、delete和final关键字,对可变参数模板也进行了详细的介绍。在C++11中STL容器新增加了emplace相关接口,就接口的用法以及特点也进行了详细的介绍,并与insert接口进行比对,阐述emplace接口的优点。最后对lambda表达式的用法、捕获列表以及底层实现都进行了详细的介绍。

如果文章对你有帮助的话就来一个三连呗,谢谢!🌹🌹🌹🌹

在这里插入图片描述

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

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

相关文章

leetcode-栈与队列

C中stack 是容器么&#xff1f; 栈&#xff0c;队列往往不被归类为容器&#xff0c;而被归类为container adapter(容器适配器)。因为由底层的容器实现&#xff0c;同时使用适配器模式的设计模式&#xff0c;封装了一层。 我们使用的stack是属于哪个版本的STL&#xff1f;SGI ST…

电路的电线的拼接

不积跬步无以至千里&#xff0c;今天小编也是复习今天学习的内容&#xff0c;废话不多说&#xff0c;看博客吧&#xff01;&#xff01;&#xff01; 目录 准备条件 操作 成品 准备条件 操作 将定制的套管插入导线当中&#xff0c;24V或者0V是尖端的端子&#xff0c;后面根…

基于SSM的养老院管理系统

基于SSM的养老院管理系统的设计与实现~ 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;SpringSpringMVCMyBatisVUE工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 摘要 养老院管理系统是一个基于SSM&#xff08;Spring、Spring MVC、MyBatis&…

微信定时发圈,快人一步不落索

现在的社交媒体运营已经成为了私域流量获取的重要手段&#xff0c;而微信作为最大的社交平台之一&#xff0c;更是吸引了众多使用者。但是&#xff0c;你是否曾经感叹过每天手动发朋友圈的繁琐&#xff1f;是否希望能够事先设置好定时发送的功能&#xff0c;让你的朋友圈自动更…

Spring Boot 3系列之一(初始化项目)

近期&#xff0c;JDK 21正式发布&#xff0c;而Spring Boot 3也推出已有一段时间。作为这两大技术领域的新一代标杆&#xff0c;它们带来了许多令人振奋的新功能和改进。尽管已有不少博客和文章对此进行了介绍&#xff0c;但对于我们这些身处一线的开发人员来说&#xff0c;有些…

【JavaEE】HTTP协议

HTTP协议 HTTP是什么?HTTP 协议格式HTTP 请求格式HTTP响应格式协议格式总结 HTTP 请求 (Request)认识 URLURL 基本格式 关于 URL encode认识 "方法" (method)1. GET 方法2. POST 方法 认识请求 "报头" (header) HTTP 响应详解认识 "状态码" (st…

【嵌入式开发 Linux 常用命令系列 9 -- linux系统终端命令提示符设置(PS1)】

文章目录 Linux PS1 介绍PS1 纯文本和特殊的转义序列PS1 颜色设置 Linux PS1 介绍 在Linux中&#xff0c;PS1&#xff08;Prompt String 1&#xff09;是一个环境变量&#xff0c;用来定义shell命令提示符的显示内容和格式。当你在终端中输入命令时&#xff0c;PS1定义的就是那…

Jupyter Notebook的使用

文章目录 Jupyter Notebook一、Jupyter Notebook是什么&#xff1f;二、使用步骤1.安装Miniconda2.安装启动**Jupyter Notebook**3.一些问题 三、Jupyter Notebook的操作1.更换解释器2.在指定的文件夹中打开 四.报错解决1.画图的时候出现报错2.画图的时候空白3.pandas相关的错误…

flink 反压原理

背景 在flink中由于数据倾斜或者数据处理速率的不匹配&#xff0c;很容易引起反压&#xff0c;本文就看一下flink反压的原理 flink反压原理 flink全流程pineline的反压实现其实依赖于TaskManager之间的反压和TaskManager内部的反压来实现 1.TaskManager之间的反压 2.Task…

RT-Thread内核——内核基础(上)

1、内核简介 内核是操作系统的核心&#xff0c;是操作系统最基础也是最重要的部分&#xff0c;主要负责系统的线程、线程间通信、系统时钟、中断以及内存等。其架构图如下&#xff1a; 2、线程调度 线程是RT-Thread操作系统中最小的调度单位&#xff0c;线程调度算法的基于…

CentOS 7 安装和配置java环境

1 安装包准备 安装包可以通过下面地址进行版本选择安装&#xff1a; https://www.oracle.com/java/technologies/downloads/#java8 2 正式开始安装 本次分享的安装方法直接通过编辑/etc/profile文件实现java的安装 2.1 新建安装包存放目录 mkdir /java cd /java/ 2.2 解压安…

Redis(10)| I/O多路复用(mutiplexing)

上文select/epoll 在上文《Redis&#xff08;09&#xff09;| Reactor模式》 思考问题可以使用I/O多路复用技术解决多多客户端TCP连接问题&#xff0c;同时也提到为了解决最早期的UNIX系统select调用存在的四个问题。 select(int nfds, fd_set *r, fd_set *w, fd_set *e, stru…

机器学习第一周

一、概述 机器学习大致会被划分为两类&#xff1a;监督学习&#xff0c;无监督学习 1.1 监督学习 监督学习其实就是&#xff0c;给计算机一些输入x和正确的输出y&#xff08;训练数据集&#xff09;&#xff0c;让他总结x->y的映射关系&#xff0c;从而给他其他的输入x&a…

Docker安全

目录 一. cgroup 2. cpu优先级 3. 内存资源限制 4. 磁盘io限制 二. lxcfs隔离 三. 容器特权 一. cgroup 1. cpu资源限制 docker run -it --rm --cpu-period 100000 --cpu-quota 20000 ubuntu root433a1612a171:/# dd if/dev/zero of/dev/null & 2. cpu优先级 docker run -i…

探营云栖大会:蚂蚁集团展出数字人全栈技术,三大AI“机器人”引关注

一年一度的科技盛会云栖大会将于10月31日正式开幕。30日&#xff0c;记者来到云栖大会展区探营&#xff0c;提前打卡今年上新的“黑科技”。 记者在蚂蚁集团展馆看到&#xff0c;超1亿人参与的亚运“数字火炬手”全栈技术首次公开展示&#xff0c;还可体验基于数字人技术的“数…

什么是全排列?(算法实现)

全排列是什么&#xff1f; 全排列是指将一组元素按照一定顺序进行排列的所有可能结果。以一组数字为例&#xff0c;比如[1, 2, 3]的全排列结果为&#xff1a;[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]。 全排列有许多不同的计算方法&#xff0c;其中…

云服务器搭建Zookeeper集群

文章目录 1.集群配置2.zookeeper的群起脚本3. Zookeeper节点的创建和删除相关4. Zookeeper的选举机制 1.集群配置 Zookeeper的集群个数最好保证是奇数个数&#xff0c;因为Zookeeper的选举过程有一个“半数机制”。 5台服务器&#xff0c;可以设置Zookeeper的集群为3或者5&…

大数据学习(18)-任务并行度优化

&&大数据学习&& &#x1f525;系列专栏&#xff1a; &#x1f451;哲学语录: 承认自己的无知&#xff0c;乃是开启智慧的大门 &#x1f496;如果觉得博主的文章还不错的话&#xff0c;请点赞&#x1f44d;收藏⭐️留言&#x1f4dd;支持一下博主哦&#x1f91…

通信原理板块——卷积码(原理、代数和几何表示、编码和解码)

微信公众号上线&#xff0c;搜索公众号小灰灰的FPGA,关注可获取相关源码&#xff0c;定期更新有关FPGA的项目以及开源项目源码&#xff0c;包括但不限于各类检测芯片驱动、低速接口驱动、高速接口驱动、数据信号处理、图像处理以及AXI总线等 1、卷积码定义 卷积码(convolution…

网络工程综合试题(二)

1. SR技术有哪些缺点&#xff1f; SR&#xff08;Segment Routing&#xff09;技术是一种新兴的网络编程技术&#xff0c;它具有很多优点&#xff0c;但也存在一些缺点&#xff0c;包括&#xff1a; 部署复杂性&#xff1a;SR技术需要对网络进行改造和升级&#xff0c;包括更新…