C++11
C++11
统一的列表初始化
在介绍这里的列表初始化之前,首先我认为这是一个比较鸡肋的功能特性,而且使用起来会和之前C++98时有些区别。
// 首先可以定义普通的内置类型的变量int x1 = 1;int x2 = { 1 };int x3{ 1 }; // 这样看起来着实有些怪int arry1[] = { 1,2,3,4,5 };int arry2[]{ 1,2,3,4,5 }; // 数组还可以这样定义
如果是自定义类型,定义可以如下:
struct Point
{int _x;int _y;
};
int main()
{Point p1 = { 1,2 };Point p2{ 2,2 };
}
上面的定义更多看起来兼容了C语言,如果有一个类,成员为私有成员,并且有自己的构造函数,那么这个时候使用花括号其实就是在调用这个类的构造函数:
class Date { private:int _hour;int _minute;int _second; public:Date(int x = 0, int y = 0, int z = 0):_hour(x),_minute(y),_second(z){}void Print(){cout << _hour << " " << _minute << " " << _second;} }; int main() {Date d1{ 10,20,30 };d1.Print();return 0; }
在定义vector的时候可以如下定义:
vector<int> v1 = { 1,2,3,4 };
vector<int> v2{ 1,2,3,4,5,6,7 };
但是我们之前在学习vector的时候并没有看到过类似第二行的初始化方式,所以这里C++11是如何实现这种方式的?
C++11为容器提供了一个初始化的方式:initializer_list,它作为构造函数的参数,C++11为STL中的不少容器添加了initializer_list作为参数的构造函数,初始化对象就方便了很多。
initializer_list的成员函数中,begin返回指向第一个位置的指针,end返回指向最后一个位置的下一个位置的指针(nullptr)。具体来说就是initializer_list封装了一个常量数组,然后支持迭代器返回数组的begin和end以方便对其进行遍历。
decltype
之前我们使用typeid可以拿到元素的类型的字符串,但是这似乎是一个很鸡肋的功能,因为它除了能打印什么都做不了。
auto il = { 1,2,3,4,5 };cout << typeid(il).name() << endl;
但是decltype的作用就不一样了,可以参考下面这个例子:
// decltype的一些使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{decltype(t1 * t2) ret = t1 * t2;cout << typeid(ret).name() << endl;
}
void Func()
{F(1.2, 2);
}
最后打印的结果是double
其次这里提一下,nullptr和范围for都是C++11新增的内容,nullptr解决了之前NULL被定义成字面量0的问题,范围for想必大家都很熟悉了,这里就不多赘述了。
STL中的一些变化
C++11在STL中添加了一些新容器并且对已有容器增加了一些新的接口函数。
除掉我们熟知的unordered_map和unordered_set,有两个容器这里需要介绍一下:array和forward_list
array是一个固定大小的容器,和vector相比,vector是动态的数组。array对比的主要是静态数组,那么与静态数组相比,优势在哪儿呢?
之前我们知道系统对普通数组的越界检查是一种抽查,只能检查到临界位置的一些元素是否有越界问题,array这里就对这个问题做出了很好的解决;但是它任然是一个很鸡肋的容器,并不会对容器中的元素进行初始化。
forward_list容器是一个单链表,容器并没有提供尾插和尾删,因为找尾的时间复杂度是很高的,对比list,唯一的优点就是每一个节点节省一个指针,但是用起来真的差list好远
在vector容器中,增加了cbegin和cend以及shrink_to_fit三个接口,cbegin和cend返回const迭代器,二shrink_to_fit是一个缩容接口,一般情况都是异地缩容,一般不要使用这个接口,因为这个接口的使用会导致拷贝。在容器中还提供了许多右值引用相关的接口,之后再谈。
在我之前的博客中继承和多态部分提到的final和override也是C++11新增的部分,如果一个虚函数不想被重写,可以加final修饰,也可以修饰一个类,这个类叫最终类,override是一个检查子类虚函数是否完成重写
右值引用
在引入右值引用之前,先来谈一下什么是左值什么是右值
左值是一个数据表达式,说人话就是可以获取它的地址,也可以对其进行赋值,左值是可以出现在复制符号的右边的,但是右值不可以出现在赋值符号的左边
左值引用就是给左值起别名,左值引用也可以引用右值,但是必须是const左值引用。
右值也是一个数据表达式,如字面常量,表达式返回值,函数返回值(传值返回)等;右值不能取地址
右值引用不可以引用左值,但是如果**std::move(左值)**就可以被右值引用所引用
int main() {double x = 1.1, y = 2.2;// 常见的右值10;x + y;fmin(x, y);// 对右值的右值引用int&& rr1 = 10;double&& rr2 = x + y;double&& rr3 = fmin(x, y); }
所以右值引用究竟有什么用呢?和const左值引用有什么区别?
引用出现的目的就是为了减少拷贝,主要使用在函数传参,我们之前的学习中使用引用传参是非常舒服的。但是如果我们想要传递一个参数作为返回值,就有些麻烦了,在栈上创建的内存出栈后会被销毁,如果在堆上开辟空间又需要对空间进行回收,在C++11之前解决问题的方法就是输出型参数,在传递进去的参数就是要改变的变量的引用或地址,但是用起来总感觉怪怪的,所以今天的右值引用就很好的解决了这个问题。
template<class T>
T&& func(const T& x)
{T ret;// ...return ret;
}
如果类似上面的这种写法,是会报错的,ret出了函数任然会被销毁,来看下面这个例子:
string to_string(int value)
{bool flag = true;if (value < 0){flag = false;value = 0 - value;}string str;while (value > 0){int x = value % 10;value /= 10;str += ('0' + x);}if (flag == false)str += '-';std::reverse(str.begin(), str.end());return str;
}
int main()
{string str = to_string(1234);return 0;
}
这是一个传值返回的函数,这里会造成两次拷贝构造,编译器经过优化后只有一次拷贝构造.
在传递参数返回值的时候,如果返回值不是很大,会将返回值经过一次拷贝后返回寄存器中;如果返回值比较大,在main函数的函数栈帧边缘上开辟一段空间进行存放。
编译器在类似上面的这种写法,可以直接进行优化,只进行一次构造,不会生成中间变量。
但是如果这么写呢?
int main()
{string str;str = to_string(1234);return 0;
}
这样写VS编译器就不会再做出优化了,产生临时变量,进行两次拷贝构造。
C++11对右值又分为纯右值和将亡值,将亡值顾名思义就是即将要被销毁的值,上面函数中我们所在函数栈帧中创建的变量str,就算有编译器优化,也需要最少一次拷贝构造,那么右值引用的意义就是为了减少拷贝构造所带来的消耗。
在拷贝构造部分提供一个右值引用的版本:
// 移动构造
string(string&& s):_str(nullptr)
{cout << "string(const string& s) -- 移动拷贝" << endl;swap(s);
}
// 拷贝构造
string(const string& s):_str(nullptr),_capacity(0),_size(0)
{cout << "string(const string& s) -- 深拷贝" << endl; string tmp(s._str);swap(tmp);
}
通过代码区分一下移动拷贝和拷贝构造,移动构造将this指向的对象和s进行交换,可以理解为,函数栈帧中创建的且要返回的值是一个将亡值,所以它匹配移动构造函数,进行资源转移。
那么在有了移动构造之后,编译器在没有进行优化时就会进行一次拷贝构造和一次移动构造;在优化之后,会直接移动构造,并且将返回值直接识别为将亡值,这一切都是编译器做的。
赋值运算符重载也是一样的,除了实现拷贝复制,还可以实现移动赋值:
// 拷贝赋值
string& operator=(const string& s1)
{cout << "string& operator = (string s) -- 拷贝赋值" << endl;if (this == &s1){return *this;}delete[] _str;_str = new char[s1._capacity + 1];strcpy(_str, s1._str);_capacity = s1._capacity;_size = _capacity;return *this;
}
// 移动赋值
string& operator=(string&& s)
{cout << "string& operator = (string s) -- 移动赋值" << endl;swap(s);return *this;
}
所以移动构造的使用几乎是我们察觉不到的,在设计类的时候设计对应的函数即可,编译器会帮我们做好这一切相关的工作的。右值引用和左值引用减少拷贝的原理还不太一样,左值引用是取别名直接起作用,右值引用是间接起作用,实现移动构造和移动赋值,在拷贝的场景中,如果是右值(将亡值),转移资源。
另外就是右值的插入,如果要插入一个匿名对象,在没有移动拷贝的时候,进行的就是深拷贝,有移动拷贝的情况下就节省了空间。
右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定的位置,且可以取到该位置地址;比如不能取字面常量10的地址,但是字面常量被rr1引用后,可以对rr1取地址,也可以修改rr1,可以理解为rr1指向的不是10本身,修改rr1并不会影响10这个字面常量,用const修饰rr1就可以防止其被修改
右值引用本身是左值。我们之前模拟实现过的list如果也想支持C++11,就要注意一个问题,当参数作为右值引用传入进来进行资源转移后,再将参数继续传下去(在函数中继续使用该参数调用下一个函数),就无法再保持参数的右值属性了
看下面这个例子:
// 万能引用
template<typename T>
void func1(T&& t)
{cout << "func1(T&& t)" << endl;
}
void func2(int& x)
{cout << "func2(int&)" << endl;
}
void func3(int&& x)
{cout << "func3(int&&)" << endl;
}
int main()
{int x = 10;func2(x);// func3(x);(会编译报错,右值引用无法引用一个左值)func3(10);func1(x);func1(10);return 0;
}
通过上面的代码我们可以发现,在普通函数的调用中,右值引用是不可以传递一个左值进去的;但是在模板函数中,是可以这么做的,这里能实现是发生了引用折叠(&&->&),更离谱的是,这里还可以传递const左值和const右值!但是带const的传值是不可以对参数进行修改的。
右值引用在一些情况下会有一些问题:
void func(int& x)
{ cout << "func(int&)" << endl; }
void func(const int& x)
{ cout << "func(const int&)" << endl; }void func(int&& x)
{ cout << "func(int&&)" << endl; }
void func(const int&& x)
{ cout << "funcconst int&&)" << endl; }template<typename T>
void func1(T&& t)
{func(t);
}
int main()
{func1(10);int a = 0;func1(a);func1(move(a));const int b = 9;func1(b);func1(move(b));return 0;
}
最后运行的结果如下:
可以理解,所有的参数无论是左值还是右值,在传入func1函数后,t实际上有左值属性,所以在掉func函数的时候,就相当于左值调用,所以才有上面的打印。
这里就需要认识一个新的名词:完美转发
template<typename T>
void func1(T&& t)
{// 完美转发 保持属性func(std::forward<T>(t));
}
在更改过代码后,再运行试试:
类的变化
移动构造和移动赋值
在上面我们提到了基于右值的移动构造和移动赋值,C++11中提供了默认生成的移动构造和移动赋值,但是有一些需要注意的点:
如果没有自己实现移动构造函数,且没有实现析构函数,拷贝构造,拷贝赋值重载中的任意一个,那么编译器就会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
默认移动赋值和上面的移动构造完全类似
如果提供了移动构造和移动赋值,编译器不会自动提供拷贝构造和拷贝赋值
类成员变量初始化
C++11允许类在定义时给成员变量初始缺省值,默认构造函数会使用这些缺省值初始化
default和delete关键字
默认生成的函数是有条件的,有些情况下,函数可能会因为某些原因没有默认生成,比如提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成。
class Person
{public:Person(const char* name = "", int age = 0):_name(name),_age(age){}Person(const Person& x):_name(x._name())_age(x._age){}Person(Person&& p) = default;private:mystring::string _name;int _age;
};
delete关键字和default关键字正好相反,delete可以禁止生成默认函数,用法也是相似的。
lambda表达式
首先来说一下lambda表达式的应用场景,C++为了减少使用函数指针的使用(函数指针确实是一个很烦人的东西),就有了仿函数,之后C++11认为仿函数也不是那么好用,于是就有了lambda表达式,lambda本质就是一个可调用的对象,之后我也会多次提到。
lambda书写表达式:[capture-list] (parameters) mutable -> return-type { statement }
这个书写表达式一看一脸懵,第一部分[capture-list]是必须要写的;第二部分是参数 (parameters) 没有参数可以不写;第三部分 mutable 一般不需要;第四部分是返回值 -> return-type ,一般都不写,会自动推导;最后一个部分是函数体,必须要写 { statement }
// 进行int对象比较的lambdaauto compare = [](int x, int y)->bool {return x > y; };
// 也可以写成下面这样:auto compare = [](int x, int y){return x > y};
lambda本质是一个可调用的对象,返回值auto是因为返回值是编译器生成的,我们不知道返回值究竟是什么。
前面我只介绍了lambda表达式的语法组成,但是各个部分究竟怎么用:
auto compare = [](int x, int y)->bool {return x > y; };
compare可以看作是一个对象,这里可以给compare传递两个变量来进行大小的判断,那么已有变量可不可以不进行传递直接使用呢?
那就提到第一个部分:[capture-list]:捕捉列表,编译器根据 [] 这个符号来判断接下来的代码是否为lambda函数,同时捕捉列表可以捕捉上下文中的变量供lambda函数使用。如何使用?
int a = 0;
int b = 1;
auto add2 = [b](int x) {return x + b;};
cout << add2(a);
上面的代码中,在捕捉列表中捕捉变量b,参数传递就可以只传一个。但是这里不能传递add2函数之后所创建变量。
第二条中[=]的父作用域是函数体外的变量,由于编译器由上到下扫描,所以这里父作用域中的变量是不包括lambda函数之后所创建的变量的。
如果这里想要写一个lambda函数来交换两个变量,怎么做?
int a = 0;
int b = 1;
auto swap = [a, b]()
{int tmp = a;a = b;b = tmp;
};
这里是无法编译通过的,捕捉列表中的的变量本质上是拷贝过来的,并且增加了const属性,这也就是第三部分mutable的作用
编译并未报错,但是没有真的交换变量,所以验证了捕捉列表中的变量是拷贝。
将捕捉列表中的值改为引用就可以对变量进行交换了。
之前提到了lambda表达式本质上是实现了一个对象,这里我们对lambda表达式进行底层的分析:
在底层中,编译器会自动帮我们生成一个类;call部分就是定义构造函数,可以发现构造了一个什么类是编译器自己决定的,这也就是为什么返回值中我们要使用auto。
可变参数模板
在C++11中引入了一个新的特性:可变参数模板
在C语言中就有可变参数:printf函数第一个参数为format,第二个参数就是可变参数,也就是说不确定有几个参数。
template <class ...Args>
void ShowList(Args... args)
{}
Args是一个模板参数包,args是一个函数形参参数包;在STL的很多容器中也支持插入参数包:
看一下可变参数模板的使用:
void ShowList()
{cout << endl;
}
// args参数包可以接收0-N个参数
template <class T, class ...Args>
void ShowList(T val, Args... args)
{cout << val << " ";ShowList(args...);
}int main()
{ShowList(1);ShowList(1, 1.1);ShowList(1, 1.1, string("xxxxxx"));return 0;
}
上面的代码中,ShowList函数中还有一个参数val,他的类型是T,也就将参数包中的参数一个一个传递给val,然后再执行函数,最后提供一个没有参数的ShowList函数。
上面是C++容器和线程库中的一些函数对可变参数模板的使用,可变参数模板的底层晦涩难懂,但是用起来还是很好的。
这里举一个list的例子:
list<int> list1;
list1.push_back(1);
list1.emplace_back(2);
list1.emplace_back();
for (auto e : list1)
{cout << e << " ";
}
cout << endl;
上面的代码中,list调用emplace_back和调用普通的push_back没有区别,当调用emplace_back的时候,如果没有传递参数,默认构造一个值为0的对象。
list<pair<int, char>> mylist;
mylist.push_back(make_pair(1, 'a'));
mylist.emplace_back(2, 'b');
如果list中的元素是pair类型的,就可以不写make_pair而直接使用参数包讲参数写入;在第二行中调用make_pair构造一个对象后再通过拷贝构造或移动构造将值插入到list中,而调用emplace_back则是直接通过递归调用直接构造。
可以发现emplace_back对左值没有任何优化空间,在实现了移动构造后。emplace_back减少的只是一次浅拷贝,所以emplace_back并没有想象中那么高效;但没有实现移动构造,差距就很大了。
包装器
如果我们想要实现一个让两个int类型的值相加的函数,那么实现的方法有很多种:
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类型变量的相加(但函数是静态的)和一个double类型变量的相加;不同的函数调用起来有不同的方法,那么包装器的作用就是将这些不同类型的函数进行包装,使其调用统一。
使用包装器需要添加头文件functional:
#include <iostream>
#include <functional>using namespace std;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()
{function<int(int, int)> func1(f);cout << func1(1, 2);function<int(int, int)> func2 = Functor();cout << " " << func2(1, 2);function<int(int, int)> func3 = [](const int a, const int b) { return a + b; };// 静态成员函数的包装function<int(int, int)> func4 = Plus::plusi; // 类成员函数function<int(Plus, int, int)> f6 = &Plus::plusd;cout << f6(Plus(), 1, 2) << endl;Plus plus;function<int(int, int)> f7 = [&plus](int x, int y)->int { return plus.plusd(x, y); };return 0;
}
包装器的本质是一个类模板:
Ret是被调用函数的返回类型,Args…是被调用函数的形参;知道这些,相信大家对上面func1和func2都可以很好地理解,func3说明包装器还可以包装lambda表达式
func4和func5是对类内成员的包装,可以发现包装这些函数的时候和包装普通函数不太一样,在赋值的时候,需要指定类域,如果这个函数是静态函数,类域前可以加取地址符也可以不加,但是如果不是静态函数,必须加取地址符,另外就是在参数部分要在第一个参数之前添加类域这个参数。在调用这些被包装的类内的函数的时候,需要在调用的时候传递类对象,因为调用类内的函数需要this指针
func7则是使用lambad的捕获列表对类对象进行捕获,这就说明,其实包装器本身更在意的是函数参数和返回值是否正确。
bind
在包装器部分还有一个知识点是绑定,绑定可以交换传入的两个函数的参数,其次绑定也可以固定绑定参数。
int Plus(int x, int y)
{return x - y;
}class Sub
{
public:int sub(int a, int b){return a + b;}
};
int main()
{function<int(int, int)> func1 = bind(Plus, placeholders::_1, placeholders::_2);cout << func1(1, 2) << endl;// 可以调整参数位置function<int(int, int)> func2 = bind(Plus, placeholders::_2, placeholders::_1);cout << func2(1, 2) << endl;function<bool(int, int)> func3 = bind(less<int>(), placeholders::_1, placeholders::_2);// 固定绑定参数function<int(Sub, int, int)> func4 = &Sub::sub;cout << func4(Sub(), 2, 4) << endl;function<int(int, int)> func5 = bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);cout << func5(10, 20) << endl;return 0;
}
可以将绑定函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表
在参数部分,placeholders_1就是第一个参数,placeholders_2就是第二个参数并以此类推。
固定绑定参数就例如上面代码中,如果包装一个类中的对象,就需要在调用的时候加一个对象,通过对象去调用,在bind绑定参数后可以直接进行调用。
相关文章:

C++11
C11 统一的列表初始化 在介绍这里的列表初始化之前,首先我认为这是一个比较鸡肋的功能特性,而且使用起来会和之前C98时有些区别。 // 首先可以定义普通的内置类型的变量int x1 1;int x2 { 1 };int x3{ 1 }; // 这样看起来着实有些怪int arry1[] { 1,…...
ubuntu18.04 配置zlmediakit 支持ffmpeg转码记录
1、zlmediakt 默认不支持ffmepg转码,需要在根目录下的CamkeLists.txt里面option(ENABLE_FFMPEG "Enable FFmpeg" OFF) 将OFF改成ON, 删除原有的build目录,sudo mkdir build. cd build,cmake .. 这样在编译生成文件夹release/linux/debug/生…...
H68K配置路由功能
系统环境Armbian ubuntu系统 参考 如何使用Debian/Ubuntu等Linux做软路由(物理机版本,非虚拟机容器版) - 知乎 https://zhuanlan.zhihu.com/p/587068225 按照他操作的结果,就是只有一个网卡正常 最后一顿操作就出现了我这么个配置 更新源…...

*2.5 迭代法的收敛阶与加速收敛方法
学习目标: 了解迭代法的基本概念和原理。学习者需要理解迭代法的基本概念和原理,包括迭代过程、迭代格式、收敛性等基本概念。 熟练掌握迭代法的收敛阶和收敛速度。学习者需要了解迭代法的收敛阶和收敛速度,掌握如何计算迭代法的收敛阶和收敛…...

仪表板展示 | X-lab开放实验室GitHub开源项目洞察大屏
背景介绍 X-lab开放实验室是一个开源软件产业开放式创新的共同体,由来自国内外著名高校、创业公司、部分互联网与IT企业的专家学者与工程师所构成,目前已在包括开源治理标准制定、开源社区行为度量与分析、开源社区流程自动化、开源全域数据治理与洞察等…...

【c语言】五大内存区域 | 堆区详解
创作不易,本篇文章如果帮助到了你,还请点赞支持一下♡>𖥦<)!! 主页专栏有更多知识,如有疑问欢迎大家指正讨论,共同进步! 给大家跳段街舞感谢支持!ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ ኈ ቼ ዽ ጿ…...

【JavaScript】动态表格
🎊专栏【 前端易错合集】 🍔喜欢的诗句:更喜岷山千里雪 三军过后尽开颜。 🎆音乐分享【如愿】 大一同学小吉,欢迎并且感谢大家指出我的问题🥰 🍔介绍 就是在输入框中输入数字后,再按…...

Css如何优雅的实现抽奖转盘
如图,抽奖转盘,可以拆分为几部分: 1.底部大圆; 2.中间小圆; 3.扇形区; 4.扇形内部奖品区; 5.抽奖按钮; 6.点击抽奖按钮时旋转动效及逻辑; 这其中,扇形区&am…...
在Java的小问题
问题1:如何在Java中创建一个对象? 解决方法: 在Java中,要创建一个对象,需要以下步骤: 创建一个类,定义对象的属性和行为。在类中定义一个构造函数,用于初始化对象的属性。在程序中…...

HashMap的扩容机制、初始化容量大小的选择、容量为什么是2的次幂
前置知识 先来看看HashMap中的成员属性 解释: size当前的容器中Entry的数量,也就是当前K-V的数量loadFactory装载因子,用来衡量HashMap满的程度,loadFactory的默认值是0.75threshold临界值,当实际KV数量超过threshol…...

[jenkins自动化2]: linux自动化部署方式之流水线(下篇)
目录 1. 引言: 2. 进阶操作 流水线 -> 2.1 简介: -> 2.2 最终效果图展示: -> 2.3 有没有心动, 真的像流水线一样, 实现了一键部署启动 3. 实现方式 3.1 下载几个插件 3.2 创建流水线任务 3.3 点击配置 3.4 根据流水线语法 写一个简单的helloworld 3.5 执行…...

idea使用 ( 二 ) 创建java项目
3.创建java项目 3.1.创建普通java项目 3.1.1.打开创建向导 接 2.3.1.创建新的项目 也可以 从菜单选择建立项目 会打开下面的选择界面 3.1.2.不使用模板 3.1.3.设置项目名 Project name : 项目名 Project location : 项目存放的位置 确认创建 3.1.4.关闭tips 将 Dont s…...

RabbitMq-接收消息+redis消费者重复接收
在接触RammitMQ时,好多文章都说在配置中设置属性 # rabbitmq 配置 rabbitmq:host: xxx.xxx.xxx.xxxport: xxxxusername: xxxpassword: xxxxxx## 生产端配置# 开启发布确认,就是confirm模式. 消费端ack应答后,才将消息从队列中删除#确认消息已发送到队列(Queue)pub…...

Orangepi Zero2 全志H616简介
为什么学 学习目标依然是Linux 系统 ,平台是 ARM 架构 蜂巢快递柜,配送机器人,这些应用场景用C51,STM32单片机无法实现 第三方介入库的局限性,比如刷脸支付和公交车收费设备需要集成支付宝SDK,提供的libalipay.so 是…...

Golang每日一练(leetDay0047)
目录 138. 复制带随机指针的链表 Copy List with Random-pointer 🌟🌟 139. 单词拆分 Word Break 🌟🌟 140. 单词拆分 II Word Break II 🌟🌟🌟 🌟 每日一练刷题专栏 &…...

HCL Nomad Web 1.0.7发布和新功能验证
大家好,才是真的好。 要问在HCL Notes/Domino系列产品中,谁更新得最快,那么答案一定是HCL Nomad Web。 你看上图右边,从1.0.1更新到1.0.7,都没花多少时间。 从HCL Nomad Web 1.0.5版本开始,可以支持直接…...
春招网申简历填写三技巧
网申第一关很重要,不夸张的说网申决定了你的笔试机会,从如信银行考试中心了解到,银行网申筛选过程中,有机器筛选人工筛选两道程序,掌握填写技巧后对提升简历通过率有较大帮助,一定要把握住,关于…...

计算机网络基础知识总结
经过学习我们可以知道: 关于计算机网络: ip地址端口号协议协议分层TCP五层协议协议封装两台计算机之间的通信 目录 ip地址 端口号 协议 协议分层 五层协议体系结构 (1) 应用层 (2) 运输层 (3) 网络层 (4)数据链路层 (5)物理层 封装&分用 两台主机之间的通信 …...

(下)苹果有开源,但又怎样呢?
一开始,因为 MacOS X ,苹果与 FreeBSD 过往从密,不仅挖来 FreeBSD 创始人 Jordan Hubbard,更是在此基础上开源了 Darwin。但是,苹果并没有给予 Darwin 太多关注,作为苹果的首个开源项目,它算不上…...

row_number 和 cte 使用实例:考场监考安排
row_number 和 cte 使用实例:考场监考安排 考场监考安排使用 cte 模拟两个表的原始数据使用 master..spt_values 进行数据填充优先安排时长较长的考试使用 cte 安排第一个需要安排的科目统计老师已有的监考时长尝试使用 cte 递归,进行下一场考试安排&…...
ES6从入门到精通:前言
ES6简介 ES6(ECMAScript 2015)是JavaScript语言的重大更新,引入了许多新特性,包括语法糖、新数据类型、模块化支持等,显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
大学生职业发展与就业创业指导教学评价
这里是引用 作为软工2203/2204班的学生,我们非常感谢您在《大学生职业发展与就业创业指导》课程中的悉心教导。这门课程对我们即将面临实习和就业的工科学生来说至关重要,而您认真负责的教学态度,让课程的每一部分都充满了实用价值。 尤其让我…...

什么是Ansible Jinja2
理解 Ansible Jinja2 模板 Ansible 是一款功能强大的开源自动化工具,可让您无缝地管理和配置系统。Ansible 的一大亮点是它使用 Jinja2 模板,允许您根据变量数据动态生成文件、配置设置和脚本。本文将向您介绍 Ansible 中的 Jinja2 模板,并通…...

2025季度云服务器排行榜
在全球云服务器市场,各厂商的排名和地位并非一成不变,而是由其独特的优势、战略布局和市场适应性共同决定的。以下是根据2025年市场趋势,对主要云服务器厂商在排行榜中占据重要位置的原因和优势进行深度分析: 一、全球“三巨头”…...
LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》
这段 Python 代码是一个完整的 知识库数据库操作模块,用于对本地知识库系统中的知识库进行增删改查(CRUD)操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 📘 一、整体功能概述 该模块…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...

R 语言科研绘图第 55 期 --- 网络图-聚类
在发表科研论文的过程中,科研绘图是必不可少的,一张好看的图形会是文章很大的加分项。 为了便于使用,本系列文章介绍的所有绘图都已收录到了 sciRplot 项目中,获取方式: R 语言科研绘图模板 --- sciRplothttps://mp.…...

MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...