算法工程师应该熟知的C++高级用法

news2024/12/24 9:01:09

1. lambda表达式(C11)

1.1 lambda表达式的组成

[=]/*1*/ ()/*2*/ mutable/*3*/ throw()/*4*/ -> int/*5*/ {}/*6*/
  • capture子句
  • 参数列表(optional)
  • 可变规范(optional)
  • 异常定义(optional)
  • 返回类型(optional)
  • 函数体

1.2 即看即用

语法:

[capture](parameters)->return-type {body}

[]叫做捕获说明符

parameters参数列表

->return-type表示返回类型,如果没有返回类型,则可以省略这部分。

我们可以这样输出"hello,world"

auto func = [] () { cout << "hello,world"; };  
func(); // now call the function

变量捕获与lambda闭包实现

string name;  
cin >> name;  
[&](){cout << name;}();

lambda函数能够捕获lambda函数外的具有自动存储时期的变量。函数体与这些变量的集合合起来叫闭包。

[ ] 不截取任何变量
[&} 截取外部作用域中所有变量,并作为引用在函数体中使用
[=] 截取外部作用域中所有变量,并拷贝一份在函数体中使用

[=, &foo] 截取外部作用域中所有变量,并拷贝一份在函数体中使用,但是对foo变量使用引用

[bar] 截取bar变量并且拷贝一份在函数体重使用,同时不截取其他变量

[x, &y] x按值传递,y按引用传递

[this] 截取当前类中的this指针。如果已经使用了&或者=就默认添加此选项。

1.3 基本概念和用法

c++中,普通的函数是长这样子的:

ret_value function_name(parameter) option { function_body; }

比如:

int get_value(int a) const {return a++;}

lambda表达式定义了一个匿名函数,并且可以捕获所定义的匿名函数外的变量。它的语法形式是:

[ capture ] ( parameter ) option -> return_type { body; };

其中:

  • capture 捕获列表
  • parameter 参数列表
  • option 函数选项
  • return_type 函数返回值类型
  • body 函数体

比如:

// defination
auto lamb = [](int a) -> int { return a++; };

// usage
std::cout << lamb(1) << std::endl;  // output: 2

组成lambda的各部分并不是都必须存在的:

  1. 当编译器可以推导出返回值类型的时候,可以省略返回值类型的部分
auto lamb = [](int i){return i;};   // OK, return type is int
auto lamb2 = [] () {return {1, 2};};    // Error
  1. lambda表达式没有参数时,参数列表可以省略
auto lamb = []{return 1;};      // OK

所以一个最简单的lambda表达式可以是下面这样,这段代码是可以通过编译的:

int main()
{
  []{};     // lambda expression
  return 0;
}

捕获列表

捕获列表是lambda表达式和普通函数区别最大的地方。[]内就是lambda表达式的捕获列表。一般来说,lambda表达式都是定义在其他函数内部,捕获列表的作用,就是使lambda表达式内部能够重用所有的外部变量。捕获列表可以有如下的形式:

  • [] 不捕获任何变量
  • [&]引用方式捕获外部作用域的所有变量
  • [=]赋值方式捕获外部作用域的所有变量
  • [=, &foo] 以赋值方式捕获外部作用域所有变量,以引用方式捕获foo变量
  • [bar] 以赋值方式捕获bar变量,不捕获其它变量
  • [this] 捕获当前类的this指针,让lambda表达式拥有和当前类成员同样的访问权限,可以修改类的成员变量,使用类的成员函数。如果已经使用了&或者=,就默认添加此选项。

捕获列表示例:

#include <iostream>

class TLambda
{
public:
    TLambda(): i_(0) { }
    int i_;

    void func(int x, int y) {
        int a;
        int b;

        // 无法访问i_, 必须捕获this,正确写法见l4
        auto l1 = [] () {
            return i_;
        };

        // 以赋值方式捕获所有外部变量,这里捕获了this, x, y
        auto l2 = [=] () {
            return i_ + x + y;
        };

        // 以引用方式捕获所有外部变量
        auto l3 = [&] () {
            return i_ + x + y;
        };

        auto l4 = [this] () {
            return i_;
        };

        // 以为没有捕获,所以无法访问x, y, 正确写法是l6
        auto l5 = [this] () {
            return i_ + x + y;
        };
        
        auto l6 = [this, x, y] () {
            return i_ + x + y;
        };

        auto l7 = [this] () {
            return ++i_;
        };

        // 错误,没有捕获a变量
        auto l8 = [] () {
            return a;
        };

        // a以赋值方式捕获,b以引用方式捕
        auto l9 = [a, &b] () {
            return a + (++b);
        };

        // 捕获所有外部变量,变量b以引用方式捕获,其他变量以赋值方式捕获
        auto l10 = [=, &b] () {
            return a + (++b);
        }

    }
};


int main()
{
    TLambda a;
    a.func(3, 4);

    return 0;
}

引用和赋值,就相当于函数参数中的按值传递和引用传递。如果lambda捕获的变量在外部作用域改变了,以赋值方式捕获的变量则不会改变。按值捕获的变量在lambda表达式内部也不能被修改,如果要修改,需使用引用捕获,或者显示的指定lambda表达式为 mutable

// [=]
int func(int a);

// [&]
int func(int& a);

// ------------------------------------------

int a = 0;
auto f = [=] {return a;};   // 按值捕获
a += 1;                    // a被修改
cout << f() << endl;        // output: 0

// ------------------------------------------

int a = 0;
auto f = [] {return a++;};      // Error
auto f = [] () mutable {return a++;}; // OK

1.4 lambda表达式的类型

上面一直使用auto关键字来自动推导lambda表达式的类型,那作为强类型语言的c++,这个lambda表达式到底是什么类型呢?lambda的表达式类型在c++11中被称为『闭包类型(Closure Tyep)』,是一个特殊的、匿名的、非联合(union)、非聚合(aggregate)的类类型。可以认为它是一个带有operator()的类,即防函数(functor)。因此可以使用std::functionstd::bind来存储和操作lambda表达式。

std::function<int (int)> f = [](int a) {return a;};
std::function<int (int)> f = std::bind([](int a){return a;}, std::placeholders::_1);
std::cout << f(22) << std::endl;

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

using func_t = int (*)(int); 
func_t f = [](int a){return a;};
std::cout << f(22) << std::endl;

2. if的高级写法

if (a!=b,b!=c,a!=c)

C++的if语句使用逗号表达式,逗号表达式与加减乘除本质上是一样的, 它的求值是从左向右依次对表达式求值,
整个表达式的结果取逗号表达式中最后一个表达的的结果, 如果非零, 就会使 if 成立!
上面相当于:

a!=b;
b!=c;
if (a!=c)

3. 左值与右值(C11)

本质上说,左值是可以被寻址的值,即左值一定对应内存中的一块区域,而右值既可以是内存中的值,也可以是寄存器中的一个临时变量。一个表达式被用作左值时,使用的是它的地址,而被用作右值时,使用的是它的内容。

在cpp11中,右值又可以进一步分为纯右值(pure rvalue)和将亡值(expiring value);其中纯右值指的是临时变量或者不和变量关联的字面量;其中临时变量包括非引用类型的函数返回值,比如 int f()的返回值,和表达式的结果,比如(a+b)结果就是一个临时变量;字面量比如10,“abc”这些。将亡值是cpp11新增的,适合右值引用相关的概念,包括返回右值引用T&&的函数的返回值,std::move的返回值

如何准确的判断一个表达式是不是左值呢?

  • 如果这个表达式可以出现在等号的左边,一定是左值;
  • 如果可以对一个表达式使用&符号去地址,它一定是左值;
  • 如果它“有名字”,则一定是左值;

形如 const T&的常量左值引用是个万金油的引用类型,其可以引用非常量左值,常量左值和右值。而形如T&的非常量左值引用只能接受非常量左值对其进行初始化。

int &a = 2;       # 非常量左值引用绑定到右值,编译失败
int b = 2;        # 非常量左值变量
const int &c = b; # 常量左值引用绑定到非常量左值,编译通过
const int d = 2;  # 常量左值
const int &e = c; # 常量左值引用绑定到常量左值,编译通过
const int &b =2;  # 常量左值引用绑定到右值,编程通过

右值引用通常不能绑定任何左值,如果要绑定左值,需要使用std::move()将左值转换为右值;

int a;
int&& r1 = a; //非法,a是左值;
int&& r2 = std::move(a); //合法,通过std::move()将左值转换为右值。移动语义是C++11新增的重要功能,其重点是对右值的操作。右值可以看作程序运行中的临时结果,右值引用可以避免复制提高效率

下表列出了在C++11中各种引用类型可以引用的值的类型。值得注意的是,只要能够绑定右值的引用类型,都能够延长右值的生命期。
在这里插入图片描述

4. 重载匹配

在这里插入图片描述

5. costexpr

5.1 costexpr变量

一般来说,在日益复杂的系统中确定变量的初始值到底是不是常量表达式并不是一件容易的事情。为了解决这个问题C++11允许将变量声明为costexpr类型以便由编译器验证变量的值是否是一个常量表达式

变量声明为constexpr类型,就意味着一方面变量本身是常量,也意味着它必须用常量表达式来初始化:

constexpr int mf = 20;
constexpr int limit = mf + 1;
constexpr float y{108};

constexpr int i; // Error! Not initialized
int j = 0;
 constexpr int k = j + 1; //Error! j not a constant expression

如果初始值不是常量表达式,就会发生编译错误

5.2 costexpr函数

除了能用常量表达式初始化constexpr变量声明为,还可以使用consstexpr函数。它是指能用于常量表达式的函数,也就是它的计算结果可以在编译时确定。

例如下面的计算阶乘的constexpr函数。

constexpr long long factorial(int n){

   return n <= 1? 1 : (n * factorial(n - 1));

}

constexpr long long f18 = factorial(20);

可以用它来初始化constexp变量。所有计算都在编译时完成,比较有趣的是像溢出这样的错误也会在编译期检出

定义的方法就是在返回值类型前加constexpr关键字。但是为了保证计算结果可以在编译期就确定,函数体中只能有一条语句,而且该语句必须是return语句,因此,下面:

constexpr int data(){
    const i = 1;
    return i;
}

无法通过编译。不过一些不会产生实际代码的语句在常量表达式函数的使用下,可以通过编译,比如说static_assert,using、typedef之类的。因此,下面:

constexpr int f(int x){
	static_assert(0 == 0, "assert fail.");
	return x;
}

可以通过编译。

  • 在使用前必须已经有定义。

5.3 if constexpr

我们知道,constexpr将表达式或函数编译为常量结果。一个很自然的想法是,如果我们把这一特性引入到条件判断中去,让代码在编译时就完成分支判断,岂不是能让程序效率更高。C++17支持if constexpr(表达式),允许在代码中声明常量表达式的判断条件,如下:

#include <iostream>

template<typename  T>
auto print_type_info(const T & t){
    if constexpr (std::is_integral<T>::value){
        return  t + 1;
    }else{
        return t + 0.01;
    }
}

int main() {
    std::cout << print_type_info(5) << "\n";
    std::cout << print_type_info(5.5) << "\n";
}

在这里插入图片描述

6. Cpp函数指针

在Cpp中,函数名不可以作为形参或者返回参数,但是函数指针可以

#include <iostream>
#include<initializer_list>

using namespace std;

void show(string s, int i) {
    while (i--) cout << s << endl;
}

// 返回函数指针
// decltype返回的是函数类型,而函数只能返回函数指针
decltype(show) *getShow() {
    return show;
}

auto getShow2() -> void (*)(string, int) {
    return show;
}

// F是函数名
typedef decltype(show) F;
// PF是函数指针
typedef decltype(show) *PF;

using FF = decltype(show);
using PFF = decltype(show) *;

using FFF = void(string, int);
using PFFF = void (*)(string, int);

void recvFunc(void(*f)(string, int), string s, int i) {
    f(s, i);
}

void recvFunc2(PFF f, string s, int i) {
    f(s, i);
}

void recvFunc3(F *f, string s, int i) {
    f(s, i);
}


int main() {
    void (*f1)(string, int);
    void (*f2)(string, int);
    // 对于函数指针,show 和 &show是等价的
    f1 = show;
    f2 = &show;
    f1("Hello", 1);
    f2("World !", 2);


    F *f3 = show;
    PF f4 = show;
    f3("Hello", 1);
    f4("World !", 2);


    FF *f5 = show;
    PFF f6 = show;
    f5("Hello", 1);
    f6("World !", 2);

    recvFunc(show, "Do!", 3);
    recvFunc2(show, "HA!", 3);
    recvFunc3(show, "LA!", 3);

    FFF *f7 = show;
    PFFF f8 = show;
}

7. std::string_view(C17)

std::string_view是C++ 17标准中新加入的类,正如其名,它提供一个字符串的视图,即可以通过这个类以各种方法“观测”字符串,但不允许修改字符串。由于它只读的特性,它并不真正持有这个字符串的拷贝,而是与相对应的字符串共享这一空间。即——构造时不发生字符串的复制。同时,你也可以自由的移动这个视图,移动视图并不会移动原定的字符串。

const char *cstr_pointer = "pointer";
char cstr_array[] = "array";
std::string stdstr = "std::string";
 
std::string_view
    sv1(cstr_pointer, 5),  // 使用C风格字符串-指针构造,并指定大小为5
    sv2(cstr_array),    // 使用C风格字符串-数组构造
    sv3("123456", 4),   // 使用字符串字面量构造,并指定大小为4
    sv4(stdstr),        // 使用std::string构造
    sv5("copy"sv);      // 使用拷贝构造函数构造(sv是std::string_view字面量的后缀)
 
std::cout
    << sv1 << endl    // point
    << cstr_pointer << endl // pointer
    << sv2 << endl    // array
    << sv3 << endl    // 1234
    << sv4 << endl    // std::string
    << sv5 << endl;   // copy

7.1 span(C20)

std::span是指向一组连续的对象的对象, 是一个视图view, 不是一个拥有者owner

一组连续的对象可以是 C 数组, 带着大小的指针, std::array, 或者std::string

 #include <ranges>
 #include <vector>
 #include <iostream>
 #include <span>
 #include <format>void printSpan(std::span<int> container)
 {
     std::cout << std::format("container size: {} \n", container.size());
     for (auto ele : container)
     {
         std::cout << ele << " ";
     }
     std::cout << std::endl;
     std::cout << std::endl;
 }
 ​​
 int main()
 {
     std::vector v1{1, 2, 3, 4, 5, 8};
     std::vector v2{9, 2, 4, 2, 6, 78};
 ​
     std::span<int> dynamicSpan(v1);
     std::span<int, 6> staticSpan(v2);printSpan(dynamicSpan);
     printSpan(staticSpan);
 }

8. stack、queue和priority_queue

8.1 stack(栈)

首先,你得写个头文件:

#include <stack>

那么如何定义一个栈呢?

stack <类型> 变量名

接下来是一些关于栈的基本操作~

stack <int> s;(以这个为例子)

1.把元素a加入入栈:s.push(a);
2.删除栈顶的元素:s.pop();
3.返回栈顶的元素:s.top();
4.判断栈是否为空:s.empty();(为空返回TRUE)
5.返回栈中元素个数:s.size();
6.把一个栈清空:(很抱歉没有这个函数,你得写这些:)

while (!s.empty())
	s.pop();

…详情请参照古月居

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

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

相关文章

我现在是如何听歌的?

个人听歌需求 首先讲下个人听歌的情况&#xff0c;再讲讲我是怎么解决自己的听歌需求的&#xff0c;不一定适合所有人&#xff0c;但多少有点启发。我个人听歌的特殊需求其实还蛮多的。 比如&#xff1a; 突然会想单曲循环一首歌&#xff0c;直到耳朵说够了。 喜欢组装各种歌…

小啊呜产品读书笔记001:《邱岳的产品手记-06》第13讲 无用却必要:产品规划【上】 第14讲 留白与节奏:产品规划【下】

小啊呜产品读书笔记001&#xff1a;《邱岳的产品手记-06》第13讲 无用却必要&#xff1a;产品规划【上】 & 第14讲 留白与节奏&#xff1a;产品规划【下】一、今日阅读计划二、泛读&知识摘录1、第13讲 无用却必要&#xff1a;产品规划【上】2、第14讲 留白与节奏&#…

数据中心网络是什么?如何管理数据中心网络

什么是数据中心网络 数据中心网络是提供网络、存储和计算资源&#xff0c;为企业/企业数据中心租户执行广泛的工作负载。这些工作负载需要不同的硬件或高端网络组件来计算、存储、检索和传输来自本地和广域网的工作负载中的数据。 数据中心网络的核心支柱 在数据中心网络架构…

【第四部分 | JavaScript 基础】2:运算、控制流程、数组

目录 | 运算符 概述 浮点数有精度误差 自增和自减 比较运算符 逻辑运算符 赋值运算符 运算优先级 | 流程控制 条件判断 if、if...else 三元表达式 分支语句 switch | 循环 | 断点调试 | 数组 创建数组 访问与遍历 获取数组长度 JavaScript的数组 和 Java 的区…

LabVIEW项目中实时目标出现黄色感叹号

LabVIEW项目中实时目标出现黄色感叹号 当打开一个包含实时目标的LabVIEW项目&#xff08;如CompactRIO&#xff08;cRIO&#xff09;、CompactDAQ&#xff08;cDAQ&#xff09;或实时PXI&#xff09;时&#xff0c;在目标旁边看到一个黄色感叹号。单击目标时&#xff0c;收到以…

目标检测论文解读复现之十四:一种基于残差网络优化的航拍小目标检测算法

前言 此前出了目标改进算法专栏&#xff0c;但是对于应用于什么场景&#xff0c;需要什么改进方法对应与自己的应用场景有效果&#xff0c;并且多少改进点能发什么水平的文章&#xff0c;为解决大家的困惑&#xff0c;此系列文章旨在给大家解读最新目标检测算法论文&#xff0…

代码随想录算法训练营第六天|字符串 栈 队列

字符串 字符串是我的弱点&#xff0c;主要是函数用的不是很熟练 注意因为字符串是左闭右开区间&#xff0c;而且字符串末尾还有一个"/0"&#xff0c;所以我们在reverse的时候是 [s.begin(),s.begin() len] 其中len是string的长度&#xff0c;假如string的长度是6…

[Nacos][Rancher][微服务] 容器化Docker部署的Nacos拒接连接

问题 2022-11-21 16:16:37.836 |-ERROR [main] com.alibaba.nacos.client.naming [552] -| request: /nacos/v1/ns/instance failed, servers: [localhost:8848], code: 500, msg: Connection refused (Connection refused) 2022-11-21 16:16:37.837 |-ERROR [main] com.ali…

Move 学习记录

Move在线IDE Move Playground 基本数据类型 只包括 无符号整型、布尔型、地址 3种类型&#xff0c;没有像其它语言中有字符串类型。 数据类型数据类型表示符号无符号整型u8, u64, u128布尔类型bool地址类型address&#xff0c; ex: Std, 0x1, Sender 变量定义&#xff08;4…

应急响应-文件痕迹排查

文件痕迹排查文件痕迹排查Windows文件痕迹排查敏感目录时间点文件排查Linux文件痕迹排查敏感目录时间点排查特殊文件文件痕迹排查 在应急响应排查的过程中&#xff0c;由于大部分的恶意软件、木马、后门等都会在文件维度上留下痕迹&#xff0c;因此对文件痕迹的排查必不可少。…

【数据挖掘】关联规则挖掘

关联规则挖掘 一、基本概念 概念 关联规则挖掘(Association rules mining) 在数据集中找到各项数据之间的关联关系。 项目(Item) IDItems bought10A,B,D20A,C,D30A,D,E 支持度(support) 项集{x,y}\{x,y\}{x,y}在总项集中出现的概率(x,yx,yx,y同时出现) Support(x→y)P(x,…

使用ESP32连接腾讯云实现远程控制方法

​大家好&#xff0c;上次给大家分享了如何使用ESP32实现蓝牙通信&#xff0c;今天跟大家聊聊如何使用ESP32连接腾讯云实现远程控制。本次实验用到MQTT协议&#xff0c;同样&#xff0c;我用miropython编写程序实现&#xff0c;最终可以通过腾讯连连微信小程序添加设备来发布主…

【GamePlay】两个ScrollView插件,Super ScrollView UIExtensions

前言 记录一下最近了解使用的两个滚动列表插件&#xff0c;UIExtensions 和Super ScrollView 。 原生的ScrollView 只能滚动 Viewport中Content范围内的物体&#xff0c;如果要使用必须得做一些功能扩展&#xff0c;很不方便&#xff0c;于是找了这两个插件。 前者不只是滚动…

一文了解JVM整体设计

一、JDK / JRE / JVM二、.Java 文件运行过程三、JVM运行时数据区3.1 Method Area3.2 Heap3.3 Java Virtual Machine Stacks3.4 Native Method Stacks3.5 The PC Register四、JVM内存模型4.1 JVM对象创建和回收过程五、垃圾收集5.1 确定垃圾收集对象5.1.1 引用计数法5.1.2 可达性…

2023年英语二大作文押题猜想(达立易考)

又到了考前大开脑洞的时间了&#xff01;每年一到这个时间点&#xff0c;关于押题猜题的话题就会铺天盖地而来&#xff0c;众多名师大咖更是会集毕生所学&#xff0c;期待可以在这个环节押中部分题目彰显实力&#xff0c;其中主观题就是大家集中关注的重要热点模块。押题听起来…

声纹识别开源工具 ASV-Subtools

今天非常荣幸有机会在Speechhome语音技术研讨会上分享我们团队在开源项目上的一些工作。今天我分享的主题是声纹识别开源工具ASV-Subtools。 今天我分享的主要有5个部分的内容&#xff0c;分别是背景介绍、工具介绍、实验结果、Subtools工程化、总结与展望。其中Subtools工程化…

【ROS】机械人开发五--ROS基本概念的程序实现

机械人开发五--ROS基本概念的程序实现一、开发工具二、RoboWare Studio的基本使用2.1 软件启动2.2 修改界面语言2.3 使用2.4 编译文件2.5 卸载三、话题通信四、话题的代码编写4.1 发布端4.2 接收端4.3 测试五、自定义消息5.1 自定义消息类型5.2 自定义消息发布端5.3 自定义消息…

el-menu动态加载路由,菜单的解决方案

先看需要实现的效果 这里有一级也有二级菜单&#xff0c;注意二级菜单的父目录&#xff08;”选项设置“点击不会跳转&#xff0c;只是展开目录&#xff09;&#xff0c;然后点击去详情页&#xff0c;需要跳到一个隐藏的路由&#xff0c;不在菜单展示的路由 还有一点要注意&…

LaTex常用技巧5:公式太长换行并加大括号

使用LaTex做笔记的时候发现公式太长&#xff0c;一行会超出页面&#xff0c;于是想到换行。 原来的代码&#xff0c;这里使用了包bm&#xff0c;测试的时候前面请使用\usepackage{bm}。 \begin{equation}_{i}^{G} {\bm{a}}\begin{cases} _{i}^{i-1}\ddot{\bm{p}}, &i1\\_…

web课程设计网页规划与设计 html+css+javascript+jquery+bootstarp响应式游戏网站Bootstrap模板(24页)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…