【C++学习】C++11新特性(第三节)——可变参数模板, lambda表达式与function包装器

文章目录

  • 文章前言
  • 一.可变参数模板
      • 1.什么是可变参数模板
      • 2.获取可变参数模板里参数包的方法
      • 3.可变参数模板在容器中的引用
  • 二. lambda表达式
      • 1. lambda表达式的由来
      • 2. lambda表达式
        • 1.lambda表达式语法
        • 2. 捕获列表说明
      • 3.函数对象与lambda表达式
  • 三.包装器
      • 1.***function包装器***
        • 2.普通函数,静态成员函数与非静态成员函数的包装
      • 2.bind函数

文章前言

本篇文章是C++11新特性的最后一节,主要会讲解到模板的可变参数lambda表达式function包装器的相关知识及其使用场景。

一.可变参数模板

C++11的新特性可变参数模板能够创建可以接受可变参数的函数模板和类模板,相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,而可变参数模板可以含有不固定数量的模板参数。在有些场景下较方便。

1.什么是可变参数模板

下面就是一个基本可变参数的函数模板

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>      //名字Args是可以改变的,自己取
void ShowList(Args... args)
{}

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了 0到N(N>=0)个模版参数。

template <class ...Args>     
void ShowList(Args... args)
{}
int main()
{ShowList();   //可以没有参数ShowList(1);   //可以是1个参数ShowList(1.1,std::string("hello"));     //可以是2个参数....                   //可以是N个参数//编译器实例化后void ShowList(){}void ShowList(int ){}void ShowList(double ,string ){}}

在这里插入图片描述

我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,下面我们讲解一些方法来获取参数包的值。

那为什么不能args[i]这样方式获取可变参数呢?
答:C语言的可变参数是运行时解析,可以在运行时用运行逻辑解析,但是这里是模板,是在编译时解析。

2.获取可变参数模板里参数包的方法

  1. 利用sizeof()可以计算出参数包里面的参数个数
template <class ...Args>      
void ShowList(Args... args)
{cout << sizeof...(args) << endl;   //可以计算出参数包里面的参数个数
}
int main()
{ShowList();   ShowList(1);   ShowList(1.1, std::string("hello"));     //运行结果:// 0// 1// 2
}
  1. 可以用递归函数方式展开参数包(编译时递归解析)
//终止函数
template<class T>
void _ShowList(T val)
{cout << val << endl;
}
template <class T,class ...Args>
void _ShowList(const T& val,Args... args)
{cout << val << " ";_ShowList(args...);
}
template <class ...Args>
void ShowList(Args... args)
{_ShowList(args...);
}
int main()
{ShowList(1,1,2,3,4,5);//运行结果://1 1 2 3 4 5return 0;
}

解析:
在这里插入图片描述
在这里插入图片描述

3.可变参数模板在容器中的引用

例如:在list中的尾插函数,emplace系列就是利用了可变参数模板。
如下图:
在这里插入图片描述
他们之间的区别:
push_back()与emplace_back都是尾插,不同的是,push_back()只能接受一个参数,而emplate可以接受多个参数(这里不是插入多个值)。
他们在这种场景下没有区别:

我们自己实现简单的类类似测试:
简易版string类代码:

namespace Test
{class string{public:string(const char* str = ""):_size(strlen(str)), _capacity(_size){cout << "构造" << endl;_str = new char[_capacity + 1];strcpy(_str, str);}void swap(string& s){::swap(_str, s._str);::swap(_size, s._size);::swap(_capacity, s._capacity);}// 拷贝构造string(const string& s):_str(nullptr){cout << "拷贝构造" << endl;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}// 移动构造string(string&& s){cout << "移动拷贝" << endl;swap(s);}// 拷贝赋值// s2 = tmpstring& operator=(const string& s){cout << "赋值拷贝" << endl;string tmp(s);swap(tmp);return *this;}// 移动赋值string& operator=(string&& s){cout << "移动赋值" << endl;swap(s);return *this;}~string(){delete[] _str;_str = nullptr;}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size >= _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}_str[_size] = ch;++_size;_str[_size] = '\0';}private:char* _str = nullptr;size_t _size = 0;size_t _capacity = 0; };
}

插入的类型的参数的是单个值时:

int main()
{std::list<Test::string> lt1;Test::string s1("xxxx");lt1.push_back(s1);lt1.push_back(move(s1));cout << "---------------------------------------------" << endl;Test::string s2("xxxx");lt1.emplace_back(s2);lt1.emplace_back(move(s2));cout << "---------------------------------------------" << endl;lt1.push_back("xxxx");lt1.emplace_back("xxxx");cout <<"---------------------------------------------"<< endl;return 0;
}
//运行结果:
//构造
//拷贝构造
//移动拷贝
//--------------------------------------------
//构造
//拷贝构造
//移动拷贝
//--------------------------------------------
//构造
//移动拷贝
//构造
//--------------------------------------------

总结
根据上面的测试可以发现:(对于插入类型的参数是单个的来说)

  • 当插入的是有名对象和匿名对象来讲,他们之间没有区别;
  • 当直接插入对象的参数时,区别不是很大,对于push_back只是多了一个构造;

当插入的类型的参数是多个值时,比如piar类型
如:

int main()
{std::list<pair<Test::string, Test::string>> lt2;pair<Test::string, Test::string> kv1("xxxx", "yyyy");lt2.push_back(kv1);lt2.push_back(move(kv1));cout <<"---------------------------------------------" << endl;pair<Test::string, Test::string> kv2("xxxx", "yyyy");lt2.emplace_back(kv2);lt2.emplace_back(move(kv2));cout << "---------------------------------------------" << endl;lt2.emplace_back("xxxx", "yyyy");cout << "---------------------------------------------" << endl;return 0;
}
//运行结果:
//构造
//构造
//拷贝构造
//拷贝构造
//移动拷贝
//移动拷贝
//---------------------------------------------
//构造
//构造
//拷贝构造
//拷贝构造
//移动拷贝
//移动拷贝
//---------------------------------------------
//构造
//构造
//---------------------------------------------

总结:
根据上面的测试可以发现:(对于插入类型的参数是多个的来说)

  • 当插入的是有名对象和匿名对象来讲,他们之间没有区别;
  • push_back不能直接传入类型的参数(因为类插入对象的类型的参数为多个);
  • 但是emplace_back可以直接传插入对象的参数(因为emplace_back利用的是可变参数模板),并且直接构造。在这方面上,emplace_back有优势;

emplace系列在插入操作(如上面研究的),直接传参数的时候效率更高,可以直接构造,当传有名对象与匿名对象时,差别不大;

二. lambda表达式

1. lambda表达式的由来

在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法。如果待排序元素为自定义类型,需要用户定义排序时的比较规则:

例如:

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;}
};
struct Goods
{string _name;    // 名字double _price;   // 价格int _evaluate;   // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};
int main()
{vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2, 3 }, { "菠萝", 1.5, 4 } };sort(v.begin(), v.end(), ComparePriceLess());sort(v.begin(), v.end(), ComparePriceGreater());}
  • 随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

2. lambda表达式

1.lambda表达式语法

lambda表达式书写格式:
[capture-list] (parameters) mutable -> return-type { statement }
[捕捉列表] (参数列表) mutable -> 返回值类型 { 函数体}

  1. lambda表达式各部分说明
  1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[ ]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。不可以省略
  2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
  3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性。可以省略。使用该修饰符时,参数列表不可省略(即使参数为空)。
  4. ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。可省略。
  5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。不可省略。
    注意:
    在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

假设我们要写一个ADD(实现两个int相加)的 lamdba 就应该这样写

[](int x, int y)->int {return x + y; };
[](int x, int y)->{return x + y; };  //返回值可以省略,编译器自动推

lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。
例如想要调用上面的ADD函数:

int main()
{auto func = [](int x, int y)->int {return x + y; };cout << func(3, 4);return 0;
}
//运行结果:
//  7

有了lambda表达式,上面的比较问题就可以这样解决:

int main()
{//[capture-list] (parameters) mutable -> return-type { statement }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)->bool {return G1._price > G2._price; });//按照水果的销量排序sort(v.begin(), v.end(), [](const Goods& G1, const Goods& G2)->bool {return G1._evaluate > G2._evaluate; });
}

上述代码就是使用C++11中的lambda表达式来解决,可以看出lambda表达式实际是一个局部的匿名函数对象。

2. 捕获列表说明

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

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

代码演示:

  1. 传值捕捉,传值+mutable,传引用捕捉
int main()
{int x = 10,y = 20;//传值捕捉   捕捉到的是的变量的拷贝 并且不能被修改 auto ADD = [x, y]{ //x++;                //不能被修改,会报错   return x + y; };  //如果想要修改,就必须加mutable  auto ADD = [x, y]()mutable{x+=10;   //函数体里面的x是被捕捉x的拷贝,这里对x的改变不影响外面的xreturn x + y; };   //返回的是30//如果想要变量本身被修改,就要传引用捕捉auto ADD = [&x, &y]{x += 10;           //x变为20return x + y; };   //返回的是40return 0;
}
  1. 传值捕捉当前域所有对象+传引用捕捉当前域所有对象+混着使用
int main()
{//传值捕捉当前域的所有对象,也不能被修改int x = 10, y = 20, m = 1, n = 2;auto ADD = [=] {//x++;   会报错,不能修改return x + y + m + n; };    //传引用捕捉当前域的所有对象auto ADD = [&] {x += 10;return x + y + m + n; };   //可以混着使用//传值捕捉所有,传引用捕捉xauto ADD = [ = , &x ] {x++;   return x + y + m + n; };    return 0;
}

注意:

  • 父作用域指包含lambda函数的语句块
  • 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 。
    [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他所有变量。
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误。
    比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • 在块作用域以外的lambda函数捕捉列表必须为空。
  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都 会导致编译报错。
  • lambda表达式之间不能相互赋值,即使看起来类型相同
  • lambda对象紧禁了默认构造,但是可以拷贝构造

3.函数对象与lambda表达式

  1. lambda表达式的底层是怎么是实现的呢?

我们通过汇编简单的看一看:
测试代码:

int main()
{auto func1 = [] {cout << "hello world"; };func1();return 0;
}

在这里插入图片描述
我们根据上面的汇编可以看出,lambda表达式底层是通过调用operator()实现的,和仿函数一样。
实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator(),operator()的参数可以理解为就是捕捉列表的参数,捕捉就像是它的成员变量,访问捕捉就像是访问自己的成员变量。

扩展:思考:这两个lambda表达式类型相同吗?

auto f1 = [] {cout << "hello world"<<endl; };
f1();
auto f2 = [] {cout << "hello world"<<endl; };
f2();

我们通过看汇编(如下图):
在这里插入图片描述

从上图可以清楚的看到他们虽然实现的内容这些完全一样,但是他们底层是两个类型,这里也说明了为什么 lambda表达式之间不能相互赋值。只能通过auto去推演。

  • decltype的一个使用场景:
    假设要创建一个优先级队列,用自己的比较方式去实现大小堆的控制,这时候就需要在创建优先级队列时自己传一个函数的类型(仿函数),但是如果我们使用lambda表达式的话,就不知道它的类型,这时候只能使用decltype来推演类型了。
    举个例子:
auto func1 = [](const Date* p1, const Date* p2){//假设实现了日期类的大于比较};
int main()
{auto func1 = [](const Date* p1, const Date* p2) {//假设实现了日期类的大于比较};//priority_queue<Date* ,vector<Date*>, decltype(func1)> p1;  编不过//编译不过的原因为:lambda对象紧禁了默认构造,将它类型传给p1,会构造priority_queue<Date*, vector<Date*>, decltype(func1)> p1(func1);  //传func1给p1,这样就不会构造了,支持拷贝构造return 0;
}

三.包装器

1.function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。
那么我们来看看,我们为什么需要function呢?

template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
double f(double i)
{return i / 2;
}
struct Functor
{double operator()(double d){return d / 3;}
};
int main()
{// 函数指针cout << useF(f, 11.11) << endl;// 函数对象cout << useF(Functor(), 11.11) << endl;// lamber表达式cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;return 0;
}
//运行结果:
//count:1
//count:00007FF60E9505E0
//5.555
//count:1
//count:00007FF60E9505F0
//3.70333
//count:1
//count:00007FF60E9505F4
//2.7775

根据上面的代码运行结果进行分析,如果编译器只实例化了一份函数的话,静态变量count应该只有一份,并且地址一样,但是根据运行结果来看,编译器会将useF函数模板实例化了三份。
那怎么才能让编译器不实例化三分,而只是实例化一份呢?

有了包装器就可以很好的解决上面的问题 :

//std::function在头文件<functional> // 类模板原型如下 
template<class T> function;     
undefined template <class Ret, class...Args> 
class function<Ret(Args...)>; 
//模板参数说明: 
//Ret: 被调用函数的返回类型
//Args…:被调用函数的形参

解决方法:

int main()
{// 函数指针function<double(double)> func1 = f;cout << useF(func1, 11.11) << endl;// 函数对象function<double(double)> func2 = Functor();cout << useF(func2, 11.11) << endl;// lamber表达式function<double(double)> func3 = [](double d)->double { return d / 4; };cout << useF(func3, 11.11) << endl;return 0;
}
//运行结果:
//count:1
//count:00007FF6404505E0
//5.555
//count:2
//count:00007FF6404505E0
//3.70333
//count:3
//count:00007FF6404505E0
//2.7775

图解:
在这里插入图片描述

2.普通函数,静态成员函数与非静态成员函数的包装
  1. 对于普通的函数包装,直接包装即可;
  2. 对于静态成员函数,只需要在普通函数的包装中,加一个指定类域;
  3. 对于非静态成员函数,必须在函数前面加&(静态成员函数可加可不加),并且在包装的参数里面多传一个类类型,因为成员函数的参数列表中隐藏了一个this指针,成员函数也需要用类对象的指针或则对象去调用。

代码演示:

template<class F, class T>
T useF(F f, T x)
{static int count = 0;cout << "count:" << ++count << endl;cout << "count:" << &count << endl;return f(x);
}
int f(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()
{//普通函数function<int(int, int)> func1 = f;  //函数cout << func1(1, 1) << endl;//静态成员函数function<int(int, int)> func2 = Plus::plusi; //&可加可不加cout << func2(2, 2) << endl;//非静态成员函数//非静态成员函数需要对象的指针或则对象进行调用,所以非静态成员函数需要多一个参数//第一种方式Plus plus;function<int(Plus*, double, double)> func3 = &Plus::plusd; //&必须加cout << func3(&plus, 1.1 ,11.11) << endl;//第二种方式function<int(Plus, double, double)> func4 = &Plus::plusd;  //&必须加cout << func4(Plus(),11.11, 11.11) << endl;
}

2.bind函数

std::bind函数 定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可调用对象(callable object),生成一个新的可调用对象(返回值)来“适应”原对象的参数列表。

// 原型如下:
template <class Fn, class... Args>bind (Fn&& fn, Args&&... args);template <class Ret, class Fn, class... Args> // with return type (2) bind (Fn&& fn, Args&&... args);   //fn是可调用对象    //Ret返回值(是一个可调用对象)
  • 作用一:调整可调用对象的参数的顺序(价值不大)
    代码演示:

int Sub(int x, int y)
{return x - y;
}
int main()
{cout << Sub(2, 1) << endl;   //1auto func1 = bind(Sub, placeholders::_2, placeholders::_1);cout << func1(2, 1)<<endl;   //-1return 0;
}
  • 作用二:调整可调用对象的参数的个数
#define N 100
int Mul(int n,int x, int y)
{return n*(x + y);
}
int main()
{cout << Mul(N, 2, 1);cout << Mul(N, 4, 5);//假设要计算N*(x+y)的值 因为N是确定不变的值,每次传参都要传,麻烦,就可以用bindauto func1 = bind(Mul, N,placeholders::_1, placeholders::_2);cout << func1(2, 1) << endl;   //只需要传两个参数就行了return 0;
}

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

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

相关文章

智慧安防系统EasyCVR视频汇聚平台接入大华设备无法语音对讲的原因排查与解决

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台支持7*24小时实时高清视频监控&#xff0c;能同时播放多路监控视频流&#xff0c;视频画面1、4、9、16个可选&#xff0c;支持自定义视频轮播。EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标…

Python爬虫怎么挣钱?6个Python爬虫赚钱方式,搞搞副业不是问题

1.最典型的就是找爬虫外包活儿 网络爬虫最通常的的挣钱方式通过外包网站&#xff0c;做中小规模的爬虫项目&#xff0c;向甲方提供数据抓取&#xff0c;数据结构化&#xff0c;数据清洗等服务。新入行的程序员大多都会先尝试这个方向&#xff0c;直接靠技术手段挣钱&#xff0…

【数据库】GROUP BY 详解、示例、注意事项

一、基本介绍 GROUP BY 语句在 SQL 中用于将来自数据库表的记录分组&#xff0c;以便可以对每个组执行聚合函数&#xff08;如 COUNT(), MAX(), MIN(), SUM(), AVG() 等&#xff09;。使用 GROUP BY 时&#xff0c;数据库会根据一个或多个列的值将结果集分为多个分组&#xff…

【Linux学习】初识Linux指令(一)

文章目录 1.指令操作与图形化界面操作1.什么是指令操作&#xff0c;什么是图形化界面操作&#xff1f; 2.Linux下基本指令1.Linux下的复制粘贴2.Linux两个who命令3.补充知识4.pwd指令5. ls 指令6.cd 指令1.目录树2.相对路径与绝对路劲3.常用cd指令 7.tree指令8. touch指令9.sta…

产品经理常用UML图之「用例图」,附7张优质实例图!

用例图是产品经理应该会画的图之一&#xff0c;它是需求分析的产物&#xff0c;借助用例图&#xff0c;参与者以可视化的方式对问题进行探讨&#xff0c;能够减少大量沟通上的障碍。接下来&#xff0c;我们一起探讨和学习一下产品经理常用的UML用例图。 一、用例图简介 用例图…

数据可视化高级技术Echarts(折线图)

目录 一、什么是折线图 二、如何实现 1.基本折线图 2.如何变得平滑只需要定义&#xff1a; smooth 3.如何定义线条的样式 color&#xff1a;设置线的颜色 width&#xff1a;设置线宽 type&#xff1a;设置线的类型 4.如何定义节点样式 symbol symbolSize&#xff1a…

2024年【T电梯修理】考试总结及T电梯修理考试技巧

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 T电梯修理考试总结考前必练&#xff01;安全生产模拟考试一点通每个月更新T电梯修理考试技巧题目及答案&#xff01;多做几遍&#xff0c;其实通过T电梯修理试题及解析很简单。 1、【多选题】修理工陶、陈&#xff0c…

在vue和 js 、ts 数据中使用 vue-i18n,切换语言环境时,标签文本实时变化

我的项目需要显示两种语言(中文和英文)&#xff0c;并且我想要切换语言时&#xff0c;页面语言环境会随之改变&#xff0c;目前发现&#xff0c;只能在vue中使用$t(‘’)的方式使用&#xff0c;但是这种方式只能在vue中使用&#xff0c;而我的菜单文件是定义在js中&#xff0c;…

neo4j使用详解(十六、集成Kerberos认证(Java/c#)——最全参考)

Neo4j系列导航&#xff1a; neo4j安装及简单实践 cypher语法基础 cypher插入语法 cypher插入语法 cypher查询语法 cypher通用语法 cypher函数语法 neo4j索引及调优 1.简介 Kerberos是一种网络身份验证协议&#xff0c;它允许网络节点在网络上证明其身份。它通过使用密钥分发中…

企业如何使用SNP Glue将SAP与Snowflake集成?

SNP Glue是SNP的集成技术&#xff0c;适用于任何云平台。它最初是围绕SAP和Hadoop构建的&#xff0c;现在已经发展为一个集成平台&#xff0c;虽然它仍然非常专注SAP&#xff0c;但可以将几乎任何数据源与任何数据目标集成。 我们客户非常感兴趣的数据目标之一是Snowflake。Sno…

uniapp 小程序获取WiFi列表

<template><view ><button click"getWifiList">获取WiFi列表</button><scroll-view:scroll-top"scrollTop"scroll-yclass"content-pop"><viewclass"itemInfo"v-for"(item, index) in wifiList&…

21. 【Android教程】评分条 RatingBar

本节将继续学习一个和进度有关的控件&#xff1a;RatingBar &#xff0c;在 Android 中 RatingBar 是一个可以支持用户打分的 UI 控件&#xff0c;相比 ProgressBar 而言&#xff0c;RatingBar 不仅仅可以用来展示同时还可以接收用户的输入操作&#xff1b;而相比 SeekBar&…

【Java面试题】MySQL上篇(索引)

文章目录 索引1.索引的分类&#xff1f;2.B树和B树的区别&#xff1f;2.1B树2.2B树 3.为什么使用索引会加快查询&#xff1f;4.创建索引的注意点&#xff1f;5.索引在哪些情况下会失效&#xff1f;6.聚簇索引和非聚簇索引的区别&#xff1f;7.回表查询是什么&#xff1f;8.什么…

flutter组件_AlertDialog

官方说明&#xff1a;A Material Design alert dialog. 翻译&#xff1a;一个材料设计警告对话框。 作者释义&#xff1a;显示弹窗&#xff0c;类似于element ui中的Dialog组件。 AlertDialog的定义 const AlertDialog({super.key,this.icon,this.iconPadding,this.iconColor,t…

IO_DAY7

1:实现2个终端之间的互相聊天 要求:千万不要做出来2个终端之间的消息发送是读一写的&#xff0c;一定要能够做到&#xff0c;一个终端发送n条消息&#xff0c;另一个终端一条消息都不回复都是没有问题的 终端A&#xff1a; #include<myhead.h> int main(int argc, char…

【LeetCode刷题笔记】LeetCode 1365.有多少小于当前数字的数字

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; 更多算法知识专栏&#xff1a;算法分析&#x1f525; 给大家跳段街舞感谢…

设计模式之解释器模式(上)

解释器模式 1&#xff09;概述 1.定义 定义一个语言的文法&#xff0c;并且建立一个解释器来解释该语言中的句子&#xff0c;这里的“语言”是指使用规定格式和语法的代码。 2.结构图 3.角色 AbstractExpression&#xff08;抽象表达式&#xff09;&#xff1a;在抽象表达…

实况窗助力美团打造鸿蒙原生外卖新体验,用户可实时掌握外卖进展

自2023年华为宣布全新HarmonyOS NEXT蓄势待发&#xff0c;鸿蒙原生应用全面启动以来&#xff0c;已有金融、旅行、社交等多个领域的企业和开发者陆续宣布加入鸿蒙生态。其中&#xff0c;美团作为国内头部的科技零售企业&#xff0c;是首批加入鸿蒙生态的伙伴&#xff0c;其下的…

nginx+uwsgi 和nginx+gunicorn区别、如何部署

nginx + uwsgi: 高并发更稳定一些 nginx + gunicorn: 更方便 ,性能可以从其它方面入手 在架构上是这样的,nginx负责动态的转发和静态文件的直接访问,gunicorn/uwsgi作为网关服务用来解析http请求,后面的flask只是个application而已,没有server的服务特征。 首先浏览器发…

备考ICA----Istio实验17---TCP流量授权

备考ICA----Istio实验17—TCP流量授权 1. 环境准备 1.1 环境部署 kubectl apply -f <(istioctl kube-inject -f istio/samples/tcp-echo/tcp-echo.yaml) -n kim kubectl apply -f <(istioctl kube-inject -f istio/samples/sleep/sleep.yaml) -n kim1.2 测试环境 检测…