thread_local
该关键字修饰的变量具有线程周期属性,首先同static类似,都只初始化一次,但是thread_local是在某个线程的生命周期内只初始化一次,如果在函数中声明了该类型变量,当第二个线程执行该函数时,那么这个变量会第二次重新被初始化。
static void* func(void*){//类成员函数作为线程运行函数必须有static,普通函数无要求
for(int i=0; i<5; i++){
thread_local int a = 0;
a++;
cout<<a<<" ";
}
return 0;
}
int main(){
pthread_t t1;
//一个线程周期
pthread_create(&t1,nullptr,&func,nullptr);
pthread_join(t1,nullptr);//阻塞直到子线程运行完毕,回收子线程资源
//一个线程周期
pthread_create(&t1,nullptr,&func,nullptr);
pthread_join(t1,nullptr);
return 0;
}
//输出结果:1 2 3 4 5 1 2 3 4 5
右值引用 &&
当一个函数的参数为引用时,如果此时使用该函数,那么这个参数必须为左值,该参数在内存中需要有实际的地址,才能作为引用参数传入函数,当该值为一个右值时,那么引用就会出错,因为右值在内存中无实际的地址,此时就有右值引用。
使用:
void fun1(int& a){cout<<a<<endl;}
void fun2(int&& a){cout<<a<<endl;}
int main(){
int a = 10;
func1(10);//出错
func1(a);//正确
func2(10);//正确,右值引用
}
shared_ptr自定义释放规则
在初始化 shared_ptr 智能指针时,可以自定义所指堆内存的释放规则,这样当堆内存的引用计数为 0 时,会优先调用我们自定义的释放规则。
在某些场景中,自定义释放规则是很有必要的。比如,对于申请的动态数组来说,shared_ptr 指针默认的释放规则是不支持释放数组的,只能自定义对应的释放规则,才能正确地释放申请的堆内存。
对于申请的动态数组,释放规则可以使用 C++11 标准中提供的 default_delete 模板类,我们也可以自定义释放规则:
//指定 default_delete 作为释放规则
std::shared_ptr<int> p6(new int[10], std::default_delete<int[]>());
//自定义释放规则
void deleteInt(int*p) {
delete []p;
}
//初始化智能指针,并自定义释放规则
std::shared_ptr<int> p7(new int[10], deleteInt);
//自定义释放规则可以采用lambda表达式
std::shared_ptr<int> p7(new int[10], [](int* p) {delete[]p; });
自定义释放规则不一定用在释放内存上,也可以用于其他方面,比如文件的打开:
shared_ptr<FILE> fp(fopen("./1.txt","r"), fclose);
size_t len = fread(buff,1,10,fp.get());
//在智能指针作用域内读写一次文件智能指针就会关闭该文件,关闭文件的界限太模糊,不建议使用
attribute ((packed))
attribute ((packed))跟在结构体后面,目的是让结构体的成员以紧凑的方式进行内存布局,不会去进行内存对齐,可以用在一段二进制数据的序列化中,比如在buff中存储了int+double+int,那么就可以直接将这段buff memcpy到示例中的test2结构体中
struct test1
{
int a;//4字节 + 自动对齐(+4字节)=8字节
double b;//8字节
int c;//4字节 + 自动对齐(+4字节)=8字节
};
struct test2
{
int a;//4字节
double b;//8字节
int c;//4字节
}__attribute__((__packed__));
int len = sizeof(test1);//len = 24
int len = sizeof(test2);//len = 16
test2 t2;
char* buff[16];//假设里面已经存储了int+double+int的三个数据
memcpy(&t2,buff,sizeof(test2));//这样就可以直接反序列化出一段内存的数据,不需要使用指针去一个一个偏移取出
函数指针
用于指向一个函数的指针,其值为函数的入口地址
//定义一个函数指针(*func),该指针可以用于指向参数为(int, int)、返回值为int的函数
typedef int (*func)(int,int);
int add(int a,int b)
{
return a+b;
}
int main()
{
func f = add;
int a = f(1,2);
cout<<a<<endl;//输出3
return 0;
}
std::function
std::function是一个函数包装器,可以用来包装任何可调用对象,包括普通函数,函数指针,静态成员函数,lambda表达式,仿函数等。std::function<>,<>中接收形如void(int)的参数,表示返回void,参数为一个int的函数,如std::function<int(void*,int>表示该function可以包装一个返回值为int,参数为void*和int的可调用对象。
//包装函数
void func1(int a){cout<<a<<endl;}
//包装静态成员函数
class A{
public:
static void func2(int a){cout<<a<<endl;}
};
//包装模板函数
template<typename T>
void func3(T a){cout<<a<<endl;}
//包装函数指针
typedef void (*fun_ptr)(int);
void func4(int a){cout<<a<<endl;}
//包装仿函数
struct Func5{
void operator()(int a){cout<<a<<endl;}
};
//包装lambda表达式
auto func6 = [](int a){cout<<a<<endl;};
int main()
{
std::function<void(int)> callback;
//全输出1
callback = func1; callback(1);
callback = A::func2; callback(1);
callback = func3<int>; callback(1);
fun_ptr f_p= func4;
callback = f_p; callback(1);
Func5 f5;
callback = f5; callback(1);
callback = func6; callback(1);
}
std::bind
C++11中std::bind函数的意义就如字面上的意思一样,用来绑定函数调用的某些参数。std::bind的思想其实是一种延迟计算的思想,将可调用对象保存起来,然后在需要的时候再调用。而且这种绑定是非常灵活的,不论是普通函数还是函数对象还是成员函数都可以绑定,而且其参数可以支持占位符。
这里的std::placeholders::_1是一个占位符,且绑定第一个参数,若可调用实体有2个形参,那么绑定第二个参数的占位符是std::placeholders::_2,依次类推3 4,示例如下。
//包装类成员函数
class C
{
public:
void func7(int a){cout<<a<<endl;}
};
void output(int a,int b){cout<<a<<" "<<b<<endl;}
int main()
{
std::function<void(int)> callback;
C c;
callback = std::bind(&C::func7,&c,std::placeholders::_1);
callback(1);//输出1
std::bind(output,1,2)(1,2);//输出1 2
std::bind(output,1,2)(2,2);//输出1 2,第一个2被忽略
//std::bind(output,1,std::placeholders::_2)(2);//出错,使用时传参缺少参数一
std::bind(output,2,std::placeholders::_1)(1);//输出2 1
std::bind(output,1,std::placeholders::_2)(1,2);//输出1 2
std::bind(output,std::placeholders::_1,placeholders::_2)(1,2);//输出1 2
std::bind(output,placeholders::_2,placeholders::_1)(1,2);//输出2 1
}
lambda表达式
就地匿名定义函数对象,优点 方便 简洁 好维护 功能闭包,缺点 调试麻烦
格式:
[捕获列表] (参数表) opt -> ret{ 函数体 };
opt是函数选项,一般可忽略,ret是函数体返回类型,lambda中可以由return推断出类型,故ret也可以省略。
捕获用法:
[ ] : 不捕获任何变量
[=]:按值捕获外部作用域的所有变量
[&]:按引用捕获外部作用域所有变量
int a,b;
[a]:只按值捕获a
[a,&b]:按值捕获a,按引用捕获b
int c;
[=,&c]:按值捕获所有变量,按引用捕获c
[this]:捕获this指针
//一个完整的lambda表达式,按值捕获外部变量n,函数参数为一个int类型,返回int类型
int n=1;
auto f = [n](int a)->int{return a+n;};
f(1);//返回2
//省略写法
auto f = [n](int a){return a+n;};
f(1);//返回2
//按值捕获的变量在经够lambda表达式后就已经被复制了一份,如下
int a = 0;
auto f = [=](){return a;};
a += 1;
f();//输出0
//如果想让lambda表达式中捕获的变量跟随外部一起变化,就需要按引用捕获
auto f = [&](){return a;};
a += 1;
f();//输出1
注意:按值捕获的变量不可以在lambda表达式内对其修改,非要修改就要声明为mutable
int a=0;
auto f = [=](){return ++a;};//error 值捕获不可修改
auto f = [&](){return ++a;};//OK
//声明为mutable也可以修改按值捕获的变量
//lambda表达式内的修改不会影响外部的变量a,因为a仍然是复制了一份
auto f = [=]() mutable {return ++a;};//OK
tuple
创建tuple和使用tuple
tuple<int,string> tp{1,"qiang"};
tuple<int,string> t = make_tuple(1,"qiang");//推荐使用
int a = get<0>(tp); //1
string b = get<1>(tp); // qiang
//也可以使用std::tie创建tuple和解tuple,用的少