std::function
std::function的实例可以对任何可以调用的目标实体进行存储、复制和调用操作。这些目标实体包括普通函数、Lambda表达式、函数指针以及其它函数对象等。(升级版函数指针)
//普通函数
int MyFunc1(int a,int b){}
function<int(int,int)> functor1 = MyFunc1;
functor1(7,2);
//lambda
auto MyFunc2 =[](int a,int b){return a + b;};
function<int(int,int)> functor2 = MyFunc2;
functor2(2,5);
//仿函数
struct MyFunc3{
int operator() (int a,int b){}
};
function<int(int,int)> functor3 = MyFunc3;
functor2(2,9);
智能指针
new分配内存,智能指针可以自动释放。而传统指针需要手动delete。
智能指针对普通的指针进行封装,负责自动释放所指的对象,这样的一层封装机制的目的是为了使得智能指针可以方便的管理一个对象的生命期。
- auto_ptr(c++11弃用)
auto_ptr<string> p1(new string("I reigned loney as a cloud."));
auto_ptr<string> p2;
p2=p1; //auto_ptr不会报错
此时p2剥夺p1所有权。访问p1会报错。(存在内存崩溃的风险)
-
unique_ptr
实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。避免资源泄露
unique_ptr<string> p3(new string("I reigned loney as a cloud."));
unique_ptr<string> p4;
p4 = p3; //error
/*****************************
当将一个 unique_ptr 赋值给另一个
如果源 unique_ptr 是个临时右值
编译器允许这么做
否则 报错
*****************************/
unique_ptr<string> ps1, ps2;
ps1 = make_unique<int>(200);
ps2 = move(ps1);
-
shared_ptr
实现
共享式
拥有概念。多个智能指针可以指向相同对象,该对象和其相关资源会在“最后一个引用被销毁”时候释放。用计数机制来表明资源被几个指针共享。多个线程同时读同一个shared_ptr对象是线程安全的,但是如果是多个线程对同一个shared_ptr对象进行读和
写
,则需要
加锁
。
多线程读写shared_ptr所指向的同一个对象,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,也需要加锁保护。shared_ptr拥有成员函数如下:
- use_count — 引用计数的个数
- unique — 是否独占
- swap — 交换两个shared_ptr所拥有的对象
- get — 返回内部对象
-
weak_ptr
- 尝尝与shared_ptr搭配使用,为shared_ptr的观察者。
- 不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象。
- 当 shared_ptr 管理的资源被释放时,weak_ptr 会自动变成 nullptr。
- 只是提供了对管理对象的一个访问手段。用来解决shared_ptr相互引用时的死锁问题
类型转换
-
const_cast
— 将
const type
转化为
type
(type为
指针或引用
) -
static_cast
— 可以用于各种编译器认可的隐式转换(类对象向上转换) -
dynamic_cast
— 用于含有虚函数的类的转换,用于类向上和向下转换 -
reinterpret_cast
— 任意类型转换,不保证成功。
//static_cast
int a = 5;
double b = static_cast<int>(a);
class Base{ };
class Derive:Base{ };
Base* ptr;
Derive *temp = new Derive();
ptr = static_cast<Base*>(temp);
//const_cast
const int a = 1;
int *p = const_cast<int*>(&a);
Variadic Template
数量不定的模板参数。
...
用来表示一包
pack
。表示模板参数、函数参数、函数参数类型的数量不确定。(用来递归)
void print() {}//递归出口
template<typename T,typename... Types>
void print(constT& firstArg,const Types&... args) {
cout <<firstArg<<endl;
print(args...);
//sizeof...(args)可以知道当前剩余几个
}
//tuple
template<typename... Values>class tuple;
template<> class typle<>{};
template<typename Head,typename... Tail>
class tuple<Head,Tail...>
:private tuple<Tail...>
{
protected:
Head m_head;
};
/*******************************************
X<T1,T2,T3> ==> X<T2,T3> ==> X<T3> ==> X<>
T1 t1 ==> T2 t2 ==> T3 t3 ==> NULL
*******************************************/
作用域
- 函数模板
- 类模板
变化的是模板参数
- 参数个数(参数个数逐一递减,实现递归)
- 参数类型(参数类型随参数个数也变化)
重写printf
//无参数版本
void printf(const char* s) {
while(*s) {
if(*s == '%' && *(++s)!='%')
//还有参数,则抛出异常
throw std::runtime_error("xxx");
std::cout<<*s++;
}
}
template<typename T,typename... Args>
void printf(const char*,T value,Args... args){
while(*s){
if(*s == '%' && *(++s)!='%') {//拿类型
std::cout << value; //输出
printf(++s,args...);//跳过 d,s,f等 继续递归
return;
}
std::cout<<*s++;
}
throw std::logic_error("xxx");
}
int *pi = new int;
print("%d%s%p%f\n",15,"Hello World",pi,3.1415926);
递归继承 — tuple
Spaces in Template Expressions
模板表达式中的空格。
在过去的版本中,
vector<vector<int> > res
,右侧的
> >
中间必须有空格,不然编译器会以为是个操作符。该版本之后中间的空格可以取消。
nullptr
C++11支持使用
nullptr
代替
0 NULL
,其类型为
std::nullptr_t
void func(int);
void func(void*);
func(0);//调用func(int)
/************************
c++11之前, #define NULL 0
#define NULL ((void *)0)
*************************/
func(NULL);
func(nullptr);//调用func(void*)
//nullptr定义
typedef decltype(nullptr) nullptr_t;
Automatic Type Deduction
auto
自动类型推断。在模板的应用中,我们已经知道编译器可以推导实参的类型,在此将此能力表现。
名称特别长懒得打字、类型复杂一时想不出时推荐使用。
auto i = 42;
vector<int> vec;
auto pos = vec.begin();
auto tmp = [](int x)->bool{};
Uniform Initialization
一致性初始化。
在C++11之前,初始化可能会发生在
()、{}、=
操作中。程序员很容易困惑于:初始化变量、对象时怎么写?。基于此原因,导入
uniform initialization
。保证任何初始化都可以使用一种语法
{}
//一致性初始化
int value{1,2,35,4};
vector<string> vec{"asfg","asgg","ewgs"};
complex<double> comp{4.0,3.0};
initializer_list
编译器看到
{..}
便生成
initializer_list<T>
,其内部关联到容器
array<T,n>
。调用构造函数时(初始化时调用的是构造函数
ctor
),该
array
内的元素可被编译器分解,逐一传给函数。
-
函数参数如果是
initializer_list<T>
,调用者不能传入多个
initializer_list<T>
。 -
可以利用
{}
来赋空值
int i{}; // i == 0
int* ptr{}; // ptr == nullptr
-
不推荐向低精度转化
int x{5.0}
自定义函数时,如果想通过简单的方式来接收任意个数参数,可以使用
initializer_list
。
比起
...
参数的类型比较严格。
void print(std::initializer_list<int> vals) {
for(auto p = vals.begin();p!=vals.end();p++) {
std::cout << *p << " ";
}
}
//调用
print({12,53,26,57,14});
样例
class P
{
public:
P(int a,int b){} // func1
P(initializer_list<int> initlist){} // func2
};
//调用
P a(10,20); //调用func1
P b{5,7}; //func2
P c{2,23,62}; //func2
P d={3,5}; //func
上述样例中如果没有
func2
,则对象
b d
仍会创建,调用
func1
源码
template<class _E>
class initializer_list
{
public:
typedef _E value_type;
typedef const _E& reference;
typedef const _E& const_reference;
typedef size_t size_type;
typedef const _E* iterator;
typedef const _E* const_iterator;
private:
iterator _M_array;
size_type _M_len;
//编译器看到{}时会调用该私有构造
// The compiler can call a private constructor.
constexpr initializer_list(const_iterator __a, size_type __l)
: _M_array(__a), _M_len(__l) { }
public:
constexpr initializer_list() noexcept
: _M_array(0), _M_len(0) { }
// Number of elements.
constexpr size_type
size() const noexcept { return _M_len; }
// First element.
constexpr const_iterator
begin() const noexcept { return _M_array; }
// One past the last element.
constexpr const_iterator
end() const noexcept { return begin() + size(); }
};
多个同类型的参数max/min通过initializer_list实现
Explicit
作用于含有
一个以上
实参的构造函数,防止编译器进行隐式类型转换。
C++中
nonexplicit one argument ctor
才能做隐式转换。
range-base
for
for
for-loop`本来的格式有三段,现可以简化为两段`for(decl : coll)
vector<int>vec{2,5,547,47,85,373,856,86};
//以前
for(int i = 0;i<vec.size();++i) { cout <<vec[i];}
//现在
for(auto& elem:vec){cout<<elem;}
//源码
for(decl:coll)
for(auto _pos=coll.begin();_pos !=coll.end();++_pos){decl = *_pos;}
=default/=delete
如果有定义构造函数(ctor),则编译器不会再提供默认构造函数(default ctor),如果强制加上
=default
,可以重新获得并使用
default ctor
。主要用于构造函数、拷贝构造、拷贝赋值、析构函数(不写的话,编译器会自动增加的函数)
class Zoo{
Zoo(const Zoo&) = delete;
Zoo(Zoo&&) = default;
Zoo& operator=(const Zoo&) = default;
Zoo& operator=(const Zoo&) = delete;
};
=delete
禁用成员函数的使用。删除特殊成员函数提供了一种更简洁的方法来防止编译器生成我们不想要的特殊成员函数。
Alias Template
模板别名。在专门化别名模板时生成的类型不允许直接或间接地使用其自己的类型:
template<T>
using Vec = std::vector<T,MyAlloc<T>>;
Vec<int> coll;
#define
typedef
无法达到相同效果。
#define
会改变模板中的参数,从而形成类似于偏特化的东西
typedef
不接收参数,也无法达到预期效果。
template template parameter
模板模板参数则是模板的参数又是一个模板,如下
template<typename T, template<typename U> typename Container>
class XCls{
private:
Container<T> c;
};
//调用 tmp为一个模板类
template<typename T>
class Tmp{};
XCls<std::string, Tmp> test1;
//调用容器
XCls<string,vector> test2; //error
//由于vector会有第二参数(构造器),需要指定
template<typename T>
using Vec = std::Vec<T, std::allocator<T>>;
XCls<string,Vec> test2;
注意事项
以下不为模板模板参数
template<typename T, typename Sequence = list<T>>
class stack
{
private:
Sequence c;
};
stack<int, deque<int>> s2;
这里是容器指定底层容器,虽然使用了模板参数,但两个参数一旦指定前一个,后一个随之确定。
而模板模板参数两个参数之间没有任何关系。
Type Alias
类型别名,类似于
typedef
。
typedef void (*func)(int,int); //参数为int,int返回值为void的函数指针
using func = void(*)(int,int);
//需要注意 = 左侧为重命名后的
template<typename T>
class Tmp{
using value_type = T;
//typedef T value_type;
};
Tmp::value_type xxx;
using
-
using-directives 命名空间(
using namespace std
)、using-declarations 某个函数(
using std::function
) - using-declarations成员函数(using _Class::xxx)功能同上
- type alias 、alias template去替换
noexcept
不抛出异常。C++中的异常处理是在运行时而不是编译时检测的。为了实现运行时检测,编译器创建额外的代码,然而这会妨碍程序优化。
void func() noexcept(true);//满足条件时不抛出异常
void func() noexcept;//一定不抛出异常
override
重写。子类在想要重写父类的虚函数时,声明为
override
(防止子类写错,不是重写虚函数则报错)可以显式的在派生类中声明,哪些成员函数需要被重写,如果没被重写,则编译器会报错。如果不小心漏写了虚函数重写的某个苛刻条件,也可通过编译器的报错,快速定位错误
class Base {
public:
virtual void func(int x);
};
class Derived: public Base {
public:
virtual void func(unsigned int x) override;//error 非重写
virtual void func(int x) override;
};
final
用于修饰类、成员变量和成员函数。
- final修饰的类,不能被继承,其中所有的函数都不能被重写。
- final修饰的成员函数不能被重写。
- final修饰的变量不能更改。
decltype
类型自动推导
- 用来声明返回值类型。
template<typename T1,typename T2>
auto add(T1 x,T2 y)->decltype(x+y); //取决于T1 T2两个类的设计
- 函数模板设计中获取某对象类型
template<typename T>
void func(T obj){
typedef typename decltype(obj)::iterator iType;
//防止编译不通过
}
-
获取
lambda
表达式的返回值的类型(用来声明)
auto cmp = [](const Person1& p1,const Person2& p2){...};
set<Person,decltype(cmp)>Myset(cmp);
Lambda
无名仿函数。允许定义内联函数,用来当成参数、对象使用。是一组功能的定义,可以被定义在表达式里。
声明
[CaptureList] (ParamsList) mutable exception-> ReturnType { FunctionBody }
-
CaptureList
— 捕获外部变量列表 -
ParamsList
— 形参列表 -
mutable
— 用来说明是否可以修改捕获的变量 -
exception
— 异常设定 -
ReturnType
— 返回类型 -
FunctionBody
— 函数体
省略形式
序号 | 格式 |
---|---|
1 |
[
] (
) ->
{
} |
2 |
[
] (
) {
} |
3 |
[
] {
} |
-
格式1声明了
const
类型的表达式,这种类型的表达式
不能修改
捕获列表中的值。 -
格式2
省略了返回值类型
,但编译器可以
根据以下规则推断
出Lambda表达式的返回类型-
如果
FunctionBody
中
存在
return
语句,则该Lambda表达式的返回类型
由
return
语句的返回类型
确定
。 -
如果
FunctionBody
中
没有
return
语句,则返回值为
void
类型。
-
如果
- 格式3中省略了参数列表,类似普通函数中的无参函数。
参数详解
CaptureList
Lambda表达式与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过捕获列表访问一些上下文中的数据。
- []、[var]、[&var]表示不捕获、值传递、引用传递
-
[=]表示
值传递
方式捕获
所有
父作用域的变量 -
[&]表示
引用传递
方式捕捉
所有
父作用域的变量
ParamsList
除了捕获列表之外,lambda还可以接受输入参数。参数列表是
可选
的。
mutable
mutable
修饰符
, 默认情况下Lambda函数总是一个
const
函数,
mutable
可以取消其常量性。
在使用该修饰符时,参数列表不可省略。
exception
指示 Lambda 表达式不会引发任何异常。
ReturnType
返回类型会
自动推导
FunctionBody
可以包含普通方法或函数的主体可以包含的任何内容。
样例
vector<int> myvec{ 3, 2, 5, 7, 3, 2 };
vector<int> lbvec(myvec);
//仿函数
bool cmp(int a, int b)
{
return a < b;
}
sort(myvec.begin(), myvec.end(), cmp);
// Lambda表达式
sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool { return a < b; });
//特例
int id = 0;
auto func = [id]()mutable{
cout<<"id = "<< id << endl;
++id;//如果没有mutable id不可更改
}
id = 200;
func();
func();
func();
cout <<"id = " << id<< endl;
/*
0
1
2
200
*/
Rvalue reference
右值引用。
通俗来说,可以取地址、有名字的为左值。(在内存中有实际地址的值、可以出现在赋值左侧的值)
右值引用就是对一个右值进行引用的类型。左值引用就是对一个左值进行引用的类型。
为了解决非必要的拷贝。当赋值的右侧为一个右值时,左侧可以在右侧偷出值,而不需要调用构造器。
左值引用
左值引用包括常量左值引用和非常量左值引用。非常量左值引用只能接受左值,不能接受右值;
int &a = 2; // 非常量左值引用 绑定到 右值,编译失败
int b = 2; // b 是非常量左值
const int &c = b; // 常量左值引用 绑定到 非常量左值,编译通过
const int d = 2; // d 是常量左值
const int &e = d; // 常量左值引用 绑定到 常量左值,编译通过
const int &f =2; // 常量左值引用 绑定到 右值,编译通过
右值引用
右值引用独立于左值和右值。即,右值引用类型的变量可能是左值也可能是右值。
int&& var1 = x;
var1类型为右值引用,但var1本身是左值,因为具名变量都是左值。
T&& 并不一定表示右值,它绑定的类型是未定的,既可能是左值又可能是右值。
template<typename T>
void f(T&& param){}
f(10); //param是右值
int x = 10;
f(x); //param是左值
/*****************
参数为右值10时
param 被一个右值初始化
那么 param 就是右值
当参数为左值 x 时
param 被一个左值初始化
那么 param 就是一个左值
*****************/
std::move()
该函数并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。从实现上讲,std::move基本等同于一个类型转换
static_cast<T&&>(Lvalue);
Perfect forwarding
nonperfect forwarding
— 当我们对一个右值调用函数时,右值会调用
move
函数,然后调用函数时调用相应的左指的函数。此时即为一个不完美的交付
void process(int& i){ cout<<"process(&)"<<i<<endl;}
void process(int&& i){ cout<<"process(&&)"<<i<<endl;}
void forward(int&& i){
cout<<"forward(&&)"<<i<<endl;
process(i);
}
forward(2);
/*************
首先2是右值 调用相应函数
调用之后在forward(int &&)中
i变为一个左值,调用process(int &)
而理想状态应该调用process(int &&)
故,为一个不完美的交付
**************/
标准库中提供函数
std::foward
来实现完美的交付
容器array
没有
ctor
,没有
dtor
。封装成类的数组。创建时指定大小,不可扩容。
Tuple
元组是将不同类型的元素打包到一个对象中使用,就像
pair
适用于成对的元素,但
tuple
可以泛化为任意数量的元素。相关函数如下
-
tuple_size()
— 获取元组中元素的个数 -
tuple_element()
— 访问tuple中指定位置的元素 -
make_tuple()
— 构造包含指定内容的元组 -
get<index>()
— 获取元组中第index个元素
tuple<int,double,string> temp(11,42.2,"xxx");//手动建元组
get<id>(temp);//获取元组temp中的第id个元素
auto temp2 = make_tuple(15,41.3,"sss");//把括号里的一包xxx生成为tuple
cout <<tuple_size<decltype(temp)>::value;//元组中元素个数 3
cout <<tuple_element<0,decltype(temp)>::type;//第0个元素
cout << temp <<endl;//tuple可以直接cout
get<1>(temp) = get<1>(temp2);//取元素赋值
temp = temp2;//直接赋值
int myint;
double mydouble;
string mystring;
tie(myint,mydouble,mystring) = temp;//tie对应赋值