1.列表初始化
2.变量类型推导
3.final和override
4.智能指针
5.默认成员函数控制
6.右值引用
7.lambda表达式
8.可变参数模板
9.包装器
1.列表初始化
C++11扩大了用大括号括起的列表(初始化列表)的使用范围,使其可用于所有的内置类型和用户自 定义的类型,使用初始化列表时,可添加等号(=),也可不添加。
struct Point
{
int _x;
int _y;
};
int main()
{
int x1 = 1;
int x2{ 2 };
int array1[]{ 1, 2, 3, 4, 5 };
int array2[5]{ 0 };
Point p{ 1, 2 };
int* pa = new int[4]{ 0 };
return 0;
}
创建对象时也可以使用列表初始化方式调用构造函数初始化。
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{
cout << "Date(int year, int month, int day)" << endl;
}
private:
int _year;
int _month;
int _day;
}
int main()
{
Date d2{ 2022, 1, 2 };
Date d3 = { 2022, 1, 3 };
return 0;
}
2.变量类型推导
C++11中废弃auto原来的用法,将其用于实现自动类型推断。这样要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。
关键字decltype将变量的类型声明为表达式指定的类型。
int main()
{
int i = 10;
auto p = &i;
auto pf = strcpy;
cout << typeid(p).name() << endl;
cout << typeid(pf).name() << endl;
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;
map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };
auto it = dict.begin();//map<string, string>::iterator it = dict.begin();
return 0;
}
3.final和override
关键字final可以修饰类,使其不能被继承;或者可以修饰虚函数,使其不能被重写。
关键字override可以修饰父类的虚函数,检查是否完成重写。
4.智能指针
RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内 存、文件句柄、网络连接、互斥量等等)的简单技术,简单来说是一种思想。 在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。
这种做法有两大好处: 不需要显式地释放资源。采用这种方式,对象所需的资源在其生命期内始终保持有效。
智能指针的原理:RAII特性和像指针一样的行为(重载operator*和operator->)。
即智能指针就是用一个“指针”去管理一个对象,但是这个“指针”又跟平常的指针不同,平常的指针需要指明类型和及时释放,但是智能指针因为是用类实现的,可以自己释放并且可以自定义类型。
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr = nullptr)
:
_ptr(ptr)
{}
~SmartPtr()
{
if(_ptr)
delete _ptr;
}
T& operator*() {return *_ptr;}
T* operator->() {return _ptr;}
private:
T* _ptr;
};
下面介绍一下std库里的智能指针:auto_ptr:对象会悬空。
unique_ptr:不能拷贝。
shared_ptr:支持拷贝,但因为其内部实现使用了计数的方 法,会有循环引用的风险。
int main()
{
std::auto_ptr<int> sp1(new int);
std::auto_ptr<int> sp2(sp1); // 管理权转移
// sp1悬空
*sp2 = 10;
cout << *sp2 << endl;
cout << *sp1 << endl;
std::unique_ptr<int> sp1(new int);
//std::unique_ptr<int> sp2(sp1);
return 0;
}
循环引用:
struct ListNode
{
int _data;
shared_ptr<ListNode> _prev;
shared_ptr<ListNode> _next;
~ListNode(){ cout << "~ListNode()" << endl; }
};
int main()
{
shared_ptr<ListNode> node1(new ListNode);
shared_ptr<ListNode> node2(new ListNode);
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
node1->_next = node2;
node2->_prev = node1;
}
cout << node1.use_count() << endl;
cout << node2.use_count() << endl;
}
node2的delete需要node1的next的delete,node1的next的delete需要node1的delete,node1的delete需要node2的prev的delete,node2的prev的delete需要node2的delete。
5.默认成员函数控制
C++类中,有8个默认成员函数: 1. 构造函数 2. 析构函数 3. 拷贝构造函数 4. 拷贝赋值重载 5. 取地址重载 6. const 取地址重载 7.移动构造函数 8.移动赋值运算符重载
如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任 意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类 型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造, 如果实现了就调用移动构造,没有实现就调用拷贝构造。
如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中 的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内 置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋 值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)
如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。
强制生成默认函数的关键字default。
禁止生成默认函数的关键字delete。
6.右值引用和移动语义
左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋 值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左 值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。
右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引 用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能 取地址。右值引用就是对右值的引用,给右值取别名。
无论左值引用还是右值引用,都是给对象取别名。
注意:1. 左值引用只能引用左值,不能引用右值。 2. 但是const左值引用既可引用左值,也可引用右值。3. 右值引用只能右值,不能引用左值。 4. 但是右值引用可以move以后的左值。
int main()
{
// 左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 对左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;
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);
return 0;
}
左值引用的短板:当函数返回对象是一个局部变量,出了函数作用域就不存在了,就不能使用左值引用返回, 只能传值返回。
右值引用的应用和移动语义:移动构造本质是将参数右值的资源窃取过来,占位已有,那么就不 用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
万能引用:模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。
template<typename T>
void A(T&& t)
{
Fun(t);
}
int main()
{
A(10);
int a;
A(a);
// 右值
// 左值
A(std::move(a)); // 右值
const int b = 8;
A(b);
// const 左值
A(std::move(b)); // const 右值
return 0;
}
完美转发: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; }
template<typename T>
void A(T&& t)
{
Fun(std::forward<T>(t));
}
int main()
{
A(10)//右值
int a;
A(a); //左值
A(std::move(a)); // 右值
const int b = 8;
A(b);// const 左值
A(std::move(b)); // const 右值
return 0;
}
7.lambda表达式
lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement }
1. lambda表达式各部分说明 :[capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来 判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda 函数使用。
(parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以连同()一起省略。
mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量性,跃变可以省略。使用该修饰符时,参数列表不可省略(即使参数为空)。
->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推导。
{statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量。 注意: 在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为 空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。
int main()
{
//最简单的lambda表达式, 该lambda表达式没有任何意义
[]{};
//省略参数列表和返回值类型,返回值类型由编译器推导为int
int a = 3, b = 4;
[=]{return a + 3; };
//省略了返回值类型,无返回值类型
auto fun1 = [&](int c){b = a + c; };
fun1(10)
// 各部分都很完善的lambda函数
auto fun2 = [=, &b](int c)->int{return b += a+ c; };
return 0;
}
2.捕获列表说明:捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。
[var]:表示值传递方式捕捉变量var。
[=]:表示值传递方式捕获所有父作用域中的变量(包括this)。
[&var]:表示引用传递捕捉变量var。
[&]:表示引用传递捕捉所有父作用域中的变量(包括this)。
[this]:表示值传递方式捕捉当前的this指针。
注意: a. 父作用域指包含lambda函数的语句块
b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。 比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量 [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量。
c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。 比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复。
d. 在块作用域以外的lambda函数捕捉列表必须为空。
e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
f. lambda表达式之间不能相互赋值,即使看起来类型相同。
8.可变参数模板
性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,类模版和函数模版中只能含固定数量的模版参数。
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
若要展开参数包,可使用递归函数和逗号表达式。
//递归展开参数包
// 递归终止函数
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...);
}
//逗号展开参数包
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.包装器
function包装器也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。他可以调用函数指针,仿函数,lambda表达式,类成员函数。主要用于解决模板的效率地下,实例化多份的问题。
function<返回类型(参数类型及个数)>
#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;
}