c++11 常用新特性大总结

news2024/11/17 0:45:10

1.统一的初始化列表

在c++98中我们经常会用{}初始化数组,而为了更近一步初始化我们在c++11中采用{}的形式,代替等于号来帮助我们初始化的进行

举个例子:

struct Point
{
	int _x;
	int _y;
};
int main()
{
	int array1[] = { 1, 2, 3, 4, 5 };
	int array2[5] = { 0 };
	Point p = { 1, 2 };
	Point p{ 1,2 };
	return 0;
}

int main()
{
 vector<int> v = { 1,2,3,4 };
 list<int> lt = { 1,2 };
 // 这里{"sort", "排序"}会先初始化构造一个pair对象
 map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
// 使用大括号对容器赋值
v = {10, 20, 30};
 return 0;
}

2.三个常用关键字讲解

2.1auto

在C++98中auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局 部的变量默认就是自动存储类型,所以auto就没什么价值了。C++11中废弃auto原来的用法,将 其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。

int main()
{
int i = 10;
auto p = &i;
auto pf = strcpy;
cout << typeid(p).name() << endl;
cout << typeid(pf).name() << endl;
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
//map<string, string>::iterator it = dict.begin();
auto it = dict.begin();
return 0;
}
2.2decltype
// decltype的一些使用使用场景
template<class T1, class T2>
void F(T1 t1, T2 t2)
{
decltype(t1 * t2) ret;
cout << typeid(ret).name() << endl;
}
int main()
{
const int x = 1;
double y = 2.2;
decltype(x * y) ret; // ret的类型是double
decltype(&x) p;      // p的类型是int*
cout << typeid(ret).name() << endl;
cout << typeid(p).name() << endl;
F(1, 'a');
return 0;
}
2.3nullptr

由于C++中NULL被定义成字面量0,这样就可能回带来一些问题,因为0既能指针常量,又能表示 整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针。

#ifndef NULL
#ifdef __cplusplus
#define NULL   0
#else
#define NULL   ((void *)0)
#endif
#endif

3.范围for循坏

本质其实就是迭代器,这个在之前的博客里面讲的非常详细了,这里就不在一一赘述

4.智能指针

因为内容较多且很重要我们后续会开一张全新的章节一一讲解

5.一些新增的容器

array

forward_list

unordered_map

unordered_set

除了这个array,其余容器我们之前的博客均有讲过,这里就不在一一叙述了,至于这个array本质就是一个数组,所以相当于他们增加的一个可有可无的容器吧。

6.右值引用和移动语义(这里是我们这篇文章的重点)

6.1基础概念

在介绍右值之前我们先来回顾一下什么是左值:左值是一个表示数据的表达式(如变量名或解引用的指针),**我们可以获取其地址,并且给它赋值左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。**左值引用就是给左值的引用,给左值取别名。

那么什么又是右值,什么又是右值引用?

右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引 用返回)等等,**右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能 取地址。**右值引用就是对右值的引用,给右值取别名。

举个例子:

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);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;
return 0;
}

tips:需要注意的是右值是不能取地址的,但是给右值取别名后,会导致右值被存储到特定位置,且可以取到该位置的地址.

6.2小总结
int main()
{
    // 左值引用只能引用左值,不能引用右值。
    int a = 10;
    int& ra1 = a;   // ra为a的别名
    //int& ra2 = 10;   // 编译失败,因为10是右值
    // const左值引用既可引用左值,也可引用右值。
    const int& ra3 = 10;
    const int& ra4 = a;
    return 0;
}
int main()
{
 // 右值引用只能右值,不能引用左值。
 int&& r1 = 10;
 
 // error C2440: “初始化”: 无法从“int”转换为“int &&”
 // message : 无法将左值绑定到右值引用
 int a = 10;
 int&& r2 = a;
 // 右值引用可以引用move以后的左值
 int&& r3 = std::move(a);
 return 0;
}
6.3右值引用的使用场景

首先在谈右值应用的使用场景前我们不妨来回忆一下左值应用。

左值引用一般用在接收函数传递的参数上面(或者拷贝构造),若是接收的参数是一个类,如果用等于号接收那么会引发拷贝构造,使程序效率大大降低,因此我们可以使用引用来接收。

而用引用作为参数返回的时候,如果返回的是一个局部变量那么它的空间就会不存在,所以不能使用应用作为参数返回,正常返回情况如下图所示:

在这里插入图片描述

而我们的右值引用和移动语义就是为了解决上述问题:

在bit::string中增加移动构造,移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不 用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。`

// 移动构造
string(string&& s)
 :_str(nullptr)
 ,_size(0)
 ,_capacity(0)
{
 cout << "string(string&& s) -- 移动语义" << endl;
 swap(s);
}
int main()
{
 bit::string ret2 = bit::to_string(-1234);
 return 0;
}

再运行上面bit::to_string的两个调用,我们会发现,这里没有调用深拷贝的拷贝构造,而是调用 了移动构造,移动构造中没有新开空间,拷贝数据,所以效率提高了。

6.4移动赋值

在bit::string类中增加移动赋值函数,再去调用bit::to_string(1234),不过这次是将 bit::to_string(1234)返回的右值对象赋值给ret1对象,这时调用的是移动赋值。

// 移动赋值
string& operator=(string&& s)
{
cout << "string& operator=(string&& s) -- 移动语义" << endl;
swap(s);
return *this;
}
int main()
{
 bit::string ret1;
 ret1 = bit::to_string(1234);
 return 0;
}
// 运行结果:
// string(string&& s) -- 移动语义
// string& operator=(string&& s) -- 移动语义

此处我们可以看到其调用了一次移动构造和移动赋值,这是为什么呢?

在编译器开创临时变量存储*this时,这里编译器将它认定为一个右值,且并没有优化,所以变调用了移动构造,接着在移动赋值给ret1。

6.5move介绍
template<class _Ty>
inline typename remove_reference<_Ty>::type&& move(_Ty&& _Arg) _NOEXCEPT
{
// forward _Arg as movable
 return ((typename remove_reference<_Ty>::type&&)_Arg);
}
int main()
{
 bit::string s1("hello world");
 // 这里s1是左值,调用的是拷贝构造
 bit::string s2(s1);
 // 这里我们把s1 move处理以后, 会被当成右值,调用移动构造
 // 但是这里要注意,一般是不要这样用的,因为我们会发现s1的
 // 资源被转移给了s3,s1被置空了。
 bit::string s3(std::move(s1));
 return 0;
}
6.6完美转发

模板中的外能引用

template<typename T>
void PerfectForward(T&& t)
{
 Fun(t);
}
int main()
{
 PerfectForward(10);           // 右值
 int a;
 PerfectForward(a);            // 左值
 PerfectForward(std::move(a)); // 右值
 const int b = 8;
 PerfectForward(b);      // const 左值
 PerfectForward(std::move(b)); // const 右值
 return 0;
}
// 模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
// 模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,
// 但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,
// 我们希望能够在传递过程中保持它的左值或者右值的属性, 就需要用我们下面学习的完美转发

std::forward 完美转发在传参的过程中保留对象原生类型属性

void Fun(int &x){ cout << "左值引用" << endl; }
void Fun(const int &x){ cout << "const 左值引用" << endl; }
void Fun(int &&x){ cout << "右值引用" << endl; }
void Fun(const int &&x){ cout << "const 右值引用" << endl; }
// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{
 Fun(std::forward<T>(t));
}
int main()
{
 PerfectForward(10);           // 右值
 int a;
 PerfectForward(a);            // 左值
 PerfectForward(std::move(a)); // 右值
 const int b = 8;
 PerfectForward(b);      // const 左值
 PerfectForward(std::move(b)); // const 右值
 return 0;
}

7.新的类功能

默认成员函数

原来C++类中,有6个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 拷贝赋值重载
  5. 取地址重载
  6. const 取地址重载

C++11 新增了两个:移动构造函数和移动赋值运算符重载。

规则如下:

如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任 意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类 型成员会执行逐成员按字节拷贝,自定义类型成员则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造

如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中 的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内 置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋 值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。

(默认移动赋值跟上面移动构造 完全类似) 如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

强制生成默认函数的关键字default:

C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原 因这个函数没有默认生成。

class Person
{
public:
 Person(const char* name = "", int age = 0)
 :_name(name)
 , _age(age)
 {}
 Person(const Person& p)
     :_name(p._name)
 ,_age(p._age)
     {}
Person(Person&& p) = default;
private:
 bit::string _name;
 int _age;
};
int main()
{
 Person s1;
 Person s2 = s1;
 Person s3 = std::move(s1);
 return 0;
}

禁止生成默认函数的关键字delete:

如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁 已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即 可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

class Person
{
public:
 Person(const char* name = "", int age = 0)
 :_name(name)
 , _age(age)
 {}
 Person(const Person& p) = delete;
private:
 bit::string _name;
 int _age;
};
int main()
{
 Person s1;
 Person s2 = s1;
 Person s3 = std::move(s1);
 return 0;
}

8.可变参数模板

直接上例子:

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

上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数 包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的, 只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特 点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变 参数,所以我们的用一些奇招来一一获取参数包的值。

// 递归终止函数
template <class T>
void ShowList(const T& t)
{
 cout << t << endl;
}
// 展开函数
template <class T, class ...Args>
void ShowList(T value, Args... args)
{
 cout << value <<" ";
 ShowList(args...);
}
int main()
{
 ShowList(1);
 ShowList(1, 'A');
 ShowList(1, 'A', std::string("sort"));
    
 return 0;
}

逗号表达式展开参数包

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行 printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列 表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)…}将会展开成((printarg(arg1),0), (printarg(arg2),0), (printarg(arg3),0), etc… ),最终会创建一个元素值都为0的数组int arr[sizeof… // Args是一个模板参数包,args是一个函数形参参数包 // 声明一个参数包Args…args,这个参数包中可以包含0到任意个模板参数。 template void ShowList(Args… args) {} // 递归终止函数 template void ShowList(const T& t) { cout << t << endl; } // 展开函数 template void ShowList(T value, Args… args) { cout << value <<" "; ShowList(args…); } int main() { ShowList(1); ShowList(1, ‘A’); ShowList(1, ‘A’, std::string(“sort”)); return 0; } 比特就业课 (Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args) 打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包

template <class T>
void PrintArg(T t)
{
 cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{
 int arr[] = { (PrintArg(args), 0)... };
 cout << endl;
}
int main()
{
 ShowList(1);
 ShowList(1, 'A');
 ShowList(1, 'A', std::string("sort"));
 return 0;
}

9.emplace接口函数

template <class... Args>
void emplace_back (Args&&... args);

首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insert和 emplace系列接口的优势到底在哪里呢?

int main()
{
 std::list< std::pair<int, char> > mylist;
 // emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象
 // 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别
 mylist.emplace_back(10, 'a');
 mylist.emplace_back(20, 'b');
 mylist.emplace_back(make_pair(30, 'c'));
 mylist.push_back(make_pair(40, 'd'));
 mylist.push_back({ 50, 'e' });
 for (auto e : mylist)
 cout << e.first << ":" << e.second << endl;
  return 0;
}

int main()
{
// 下面我们试一下带有拷贝构造和移动构造的string,再试试呢
// 我们会发现其实差别也不到,emplace_back是直接构造了,push_back
// 是先构造,再移动构造,其实也还好。
 std::list< std::pair<int, bit::string> > mylist;
 mylist.emplace_back(10, "sort");
 mylist.emplace_back(make_pair(20, "sort"));
 mylist.push_back(make_pair(30, "sort"));
 mylist.push_back({ 40, "sort"});
 return 0;
}

总结区别不大

10.lambda表达式

10.1基本介绍

实际上此表达式的出现本质还是仿函数的另一种呈现方式,人们觉得写仿函数太麻烦了,就采用lambda表达式来代替,下面是此种表达式的一个常用场景(不同类型的比较方式)。

int main()
{
 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){
 return g1._price < g2._price; });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._price > g2._price; });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._evaluate < g2._evaluate; });
 sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
 return g1._evaluate > g2._evaluate; });
}
10.2lambda表达式的基本语法

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }

lambda表达式各部分说明 :

  1. [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来 判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda 函数使用。

  2. (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以 连同()一起省略

  3. mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量 性。使用该修饰符时,参数列表不可省略(即使参数为空)。

  4. return-type:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回 值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推 导。

  5. {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获 到的变量。

    注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为 空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。

通过小述例子可以看出,lambda表达式实际上可以理解为**无名函数,该函数无法直接调 用,**如果想要直接调用,可借助auto将其赋值给一个变量。

int main()
{
    // 最简单的lambda表达式, 该lambda表达式没有任何意义
   []{}; 
    
    // 省略参数列表和返回值类型,返回值类型由编译器推导为int
    int a = 3, b = 4;
   [=]{return a + 3; }; 
    
    // 省略了返回值类型,无返回值类型
    auto fun1 = [&](int c){b = a + c; }; 
    fun1(10)
    cout<<a<<" "<<b<<endl;
    
    // 各部分都很完善的lambda函数
    auto fun2 = [=, &b](int c)->int{return b += a+ c; }; 
    cout<<fun2(10)<<endl;
    
    // 复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable { x *= 2; return a + x; }; 
    cout << add_x(10) << endl; 
    return 0;
}
10.3仿函数以及lambda表达式

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的 类对象。

class Rate
{
public:
 Rate(double rate): _rate(rate)
 {}
 double operator()(double money, int year)
 { return money * _rate * year;}
private:
 double _rate;
};
int main()
{
// 函数对象
 double rate = 0.49;
 Rate r1(rate);
 r1(10000, 2);
// lamber
 auto r2 = [=](double monty, int year)->double{return monty*rate*year; 
};
 r2(10000, 2);
 return 0;
}

在这里插入图片描述

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如 果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。

11.包装器

function包装器

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

ret = func(x);
// 上面func可能是什么呢?那么func可能是函数名?函数指针?函数对象(仿函数对象)?也有可能
是lamber表达式对象?所以这些都是可调用的类型!如此丰富的类型,可能会导致模板的效率低下!
为什么呢?我们继续往下看
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()
{
比特就业课
通过上面的程序验证,我们会发现useF函数模板实例化了三份。
包装器可以很好的解决上面的问题
 // 函数名
 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;
}
std::function在头文件<functional>
// 类模板原型如下
template <class T> function;     // undefined
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
// 使用方法如下:
#include <functional>
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()
{
 // 函数名(函数指针)
 std::function<int(int, int)> func1 = f;
 cout << func1(1, 2) << endl;
 // 函数对象
 std::function<int(int, int)> func2 = Functor();
 cout << func2(1, 2) << endl;
 // lamber表达式
 std::function<int(int, int)> func3 = [](const int a, const int b) 
{return a + b; };
 cout << func3(1, 2) << endl;
 
 // 类的成员函数
 std::function<int(int, int)> func4 = &Plus::plusi;
 cout << func4(1, 2) << endl;
 std::function<double(Plus, double, double)> func5 = &Plus::plusd;
 cout << func5(Plus(), 1.1, 2.2) << endl;
 return 0;
}

有了包装器,如何解决模板的效率低下,实例化多份的问题呢?

#include <functional>
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()
{
  // 函数名
 std::function<double(double)> func1 = f;
 cout << useF(func1, 11.11) << endl;
 // 函数对象
 std::function<double(double)> func2 = Functor();
 cout << useF(func2, 11.11) << endl;
 // lamber表达式
 std::function<double(double)> func3 = [](double d)->double{ return d /
4; };
 cout << useF(func3, 11.11) << endl;
 return 0;
}

bind

std::bind函数定义在头文件中,是一个函数模板,它就像一个函数包装器(适配器),接受一个可 调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而 言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些参数,返回一个接收M个(M 可以大于N,但这么做没什么意义)参数的新函数。同时,使用std::bind函数还可以实现参数顺 序调整等操作。

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对 象来“适应”原对象的参数列表。 调用bind的一般形式:auto newCallable = bind(callable,arg_list); 其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应给定的 callable的参数。当我们调用newCallable时,newCallable会调用callable,并传给它arg_list中 的参数。 arg_list中的参数可能包含形如_n的名字,其中n是一个整数,这些参数是“占位符”,表示 newCallable的参数,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对 象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推。

// 使用举例
#include <functional>
int Plus(int a, int b)
{
 return a + b;
}
class Sub
{
public:
 int sub(int a, int b)
 {
 return a - b;
 }
};
int main()
{
 //表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
 std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1, 
placeholders::_2);
 //auto func1 = std::bind(Plus, placeholders::_1, placeholders::_2);
 //func2的类型为 function<void(int, int, int)> 与func1类型一样
 //表示绑定函数 plus 的第一,二为: 1, 2
 auto  func2 = std::bind(Plus, 1, 2);   
 cout << func1(1, 2) << endl;
 cout << func2() << endl;
 Sub s;
 // 绑定成员函数
 std::function<int(int, int)> func3 = std::bind(&Sub::sub, s, 
placeholders::_1, placeholders::_2);
 // 参数调换顺序
 std::function<int(int, int)> func4 = std::bind(&Sub::sub, s, 
placeholders::_2, placeholders::_1);
 cout << func3(1, 2) << endl; 
 cout << func4(1, 2) << endl;
 return 0;
}

12.线程库

这个会在linux一些章节写完后补全(敬请期待)

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

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

相关文章

2023年七大最佳勒索软件解密工具

勒索软件是目前最恶毒且增长最快的网络威胁之一。作为一种危险的恶意软件&#xff0c;它会对文件进行加密&#xff0c;并用其进行勒索来换取报酬。 幸运的是&#xff0c;我们可以使用大量的勒索软件解密工具来解锁文件&#xff0c;而无需支付赎金。如果您的网络不幸感染了勒索软…

【MySQL】实验七 视图

文章目录 1. 建立city值为上海、北京的顾客视图2. 建立城市为上海的客户2016年的订单信息视图3. SQL视图:建立视图AVG_CJ4. SQL视图:建立视图IS_STUDENT5. SQL视图:建立视图CJ_STUDENT6. SQL视图:根据视图CJ_STUDENT创建视图CJ_TJ1. 建立city值为上海、北京的顾客视图 建立…

webgl-画一个彩色矩形

html <!DOCTYPE html> <head> <style> *{ margin: 0px; padding: 0px; } </style> </head> <body> <canvas id webgl> 您的浏览器不支持HTML5,请更换浏览器 </canvas> <script src"./main.js"></script&g…

基于七牛云和PicGo的图床搭建

第一次搭建的博客因为误操作且没有备份导致无法访问和恢复。第二次搭建吸取了第一次的错误&#xff0c;并且准备用图床进行图片的访问。 需要用到的&#xff1a; 七牛云&#xff0c; PicGo&#xff0c; Typora&#xff0c; 已备案域名。 七牛云和域名 进入七牛云的页面&…

Nginx 服务器 SSL 证书安装部署

工具&#xff1a;WinSCP、putty 1.下载证书 cloud.tencent.com_bundle.crt 证书文件cloud.tencent.com_bundle.pem 证书文件&#xff08;可忽略该文件&#xff09;cloud.tencent.com.key 私钥文件cloud.tencent.com.csr CSR 文件 2.上传证书到服务器 通过WinSCP将已获取到的…

Linux宝塔面板搭建Discuz论坛, 并公网远程访问【内网穿透】

文章目录前言1.安装基础环境2.一键部署Discuz3.安装cpolar工具4.配置域名访问Discuz5.固定域名公网地址6.配置Discuz论坛前言 Crossday Discuz! Board&#xff08;以下简称 Discuz!&#xff09;是一套通用的社区论坛软件系统&#xff0c;用户可以在不需要任何编程的基础上&…

paddle实现手写数字识别终章

要点&#xff1a; 资源配置训练调试恢复训练模型部署参考文档&#xff1a; paddle官方文档 一 资源配置 1 概述 从前几节的训练看&#xff0c;无论是房价预测任务还是MNIST手写字数字识别任务&#xff0c;训练好一个模型不会超过10分钟&#xff0c;主要原因是我们所使用的神…

T 级数据量迁移!知名云巨头如何从 Jira 切换至 ONES?

2021 年&#xff0c;Atlassian 旗下 Jira&Confluence 等系列产品 Server 版&#xff08;本地私有化部署版&#xff09;全面停售&#xff0c;并将在 2024 年停止维护&#xff0c;Server 版客户必须迁移至 Cloud&#xff08;云&#xff09;或 Data Center&#xff08;数据中心…

最新阿里、腾讯、华为、字节等大厂的薪资和职级对比,看看你差了多少...

互联网大厂新入职员工各职级薪资对应表(技术线)~ 最新阿里、腾讯、华为、字节跳动等大厂的薪资和职级对比 上面的表格不排除有很极端的收入情况&#xff0c;但至少能囊括一部分同职级的收入。这个表是“技术线”新入职员工的职级和薪资情况&#xff0c;非技术线(如产品、运营、…

Unicode 编码表

正则查找: 中文文字中文符号表情符号... [^\x00-\xff] 其中 \x00-\xff 匹配 ASCII 代码中十六进制代码为 00-ff 的字符&#xff0c; 加个取反 ^ &#xff0c;则就表示表示匹配非单字节的字符&#xff0c;例如汉字&#xff0c;汉字符号等字符集。 中文文字&#xff08;简体繁…

python形态学滤波:腐蚀、膨胀、开、闭运算

文章目录二值形态学灰度形态学最基础的形态学操作有四个&#xff0c;分别是腐蚀、膨胀、开计算和闭计算&#xff0c;scipy.ndimage分别实现了二值数组和灰度数组的这四种运算 二值灰度binary_erosiongrey_erosion腐蚀binary_dilationgrey_dilation膨胀binary_closinggrey_clos…

快速部署专业领域AI处理网站

当前AI大型语言模型展示了很强的信息整合与处理能力&#xff0c;但一些专业知识方面的问答知识量还不够完善&#xff0c;我们在AI的基础上增加了内容训练数据库&#xff0c;相关内容自动添加到AI内容数据库索引。在使用时&#xff0c;可以实时根据对话内容进行检索调取相关上下…

谷歌发布Self-Debug方法,让大模型学会自己修bug,一次性生成正确代码

文 | 智商掉了一地你有没有想过&#xff0c;让一台计算机诊断和修复自己生成的错误代码&#xff1f;一篇最新的研究论文介绍了一种名为 Self-Debugging 的技术&#xff0c;通过在生成的代码中添加自解释的信息&#xff0c;让计算机像一个可以自己修复代码的程序员一样调试自己的…

CAN CRC校验15_CAN FD与CAN协议区别

1 概述 CAN-FD&#xff1a;可以理解成CAN协议的升级版&#xff0c;只升级了协议&#xff0c;物理层未改变。 CAN与CAN-FD主要区别&#xff1a;传输速率不同、数据长度不同、帧格式不同、ID长度不同。 2 传输速率不同 CAN&#xff1a;最大传输速率1Mbps。 CAN-FD&#xff1…

MIPI D-PHYv2.5笔记(16) -- Preamble Sequence、HS-Idle State、Sync Patterns

声明&#xff1a;作者是做嵌入式软件开发的&#xff0c;并非专业的硬件设计人员&#xff0c;笔记内容根据自己的经验和对协议的理解输出&#xff0c;肯定存在有些理解和翻译不到位的地方&#xff0c;有疑问请参考原始规范看 Preamble Sequence 前导码序列&#xff08;Preamble …

chatGPT爆火,什么时候中国能有自己的“ChatGPT“

目录 引言 一、ChatGPT爆火 二、中国何时能有自己的"ChatGPT" 三、为什么openai可以做出chatGPT? 四、结论 引言 随着人工智能技术的不断发展&#xff0c;自然语言处理技术也逐渐成为了研究的热点之一。其中&#xff0c;ChatGPT作为一项领先的自然语言处理技术…

【ChatGPT】ChatGPT还能保持多久的神话?

文章目录引言正文ChatGPT的由来ChatGPT的影响乱象频出ChatGPT未来的走势引言 最近ChatGPT的热度持续拔高&#xff0c;一个功能强大的AI迅速引爆了全世界&#xff0c;上到技术大佬&#xff0c;下到平民百姓&#xff0c;都在讨论并使用这个“跨时代的”的技术。有人拿它聊天&…

前端已死?金三银四?你收到offer了吗?

目录 一、前言 二、“唱衰” 三、不局限于框架、前端 四、打动面试官 五、正向加成 六、小结 一、前言 最近在脉脉、知乎等平台都有人在渲染前端从业人员的危机&#xff0c;甚至使用“前端已死”的字眼&#xff0c;颇有“语不惊人死不休”的意味&#xff0c;对老鸟来说&a…

惊呆了,2小时我就学会了Charles抓包的详细教程

目录 一、什么是Charles 二、下载Charles 三、设置Charles代理 四、配置设备代理 五、抓包操作 六、常见问题及解决方法 抓包不到某些应用程序 Charles抓包后网站出现异常 七、总结 一、什么是Charles Charles是一个跨平台的HTTP代理服务工具&#xff0c;可以用来查看…

Zabbix“专家坐诊”第187期问答汇总

问题一 Q&#xff1a;zabbix server 5.0有办法不通过脚本监控SSL证书到期时间么&#xff1f; A&#xff1a;目前还是流行通过脚本方式去获取。 Q&#xff1a;如果是通配符证书应该怎么监控&#xff1f; A&#xff1a;通过解析域名获取对应的过期时间的&#xff0c;或者证书在…