1.简洁的编程方式
1.关键字
1.auto
在C++11标准中,auto被赋予了新的功能,使用它可以让编译器自动推导出变量的类型。
cpp
auto x=10;
在上述代码中,使用auto定义了变量x,并赋值为10,则变量x的类型由它的初始化值决定。由于编译器根据初始化值推导并确定变量的类型,因此auto修饰的变量必须初始化。除了修饰变量,auto还可作为函数的返回值。
cpp
auto func()
{
//.....
return 1;
}
auto可以修饰函数的返回值,但是不能修饰函数参数。
auto最大的用途是简化模板编程中的代码。
cpp
map<string,vector<int>> m;
for(auto value=m.begin();value!=m.end();value++)
{
//...
}
如果不使用auto,则如下:
cpp
map<string,vector<int>> m;
map<string,vector<int>>::iterator value;
for(value=m.begin();value!=m.end();value++)
{
//...
}
在模板编程中,变量的类型依赖于模板参数,有时很难确定变量的类型。
cpp
template<class T1,class T2>
void multiply(T1 x,T2 y)
{
auto result=x*y;
}
2.decltype
decltype的功能和auto关键字类似,使用格式如下:
cpp
decltype(表达式)
decltype会根据表达式的结果推导出数据类型,但它不会真正的计算出表达式的值,表达式不能是具体的数据类型,可以用它推导出的类型定义新变量。它可以和auto关键字结合使用推导函数返回值类型,auto作为返回值占位符,->decltype()放在函数后面用于推导函数返回值类型。
cpp
template<class T1,class T2>
auto multiply(T1 x,T2 y)->decltype(x*y)
{
}
这种方式称为追踪返回类型,也称尾推导。
3.nullptr
在C语言中,为了避免野指针的出现,通常使用NULL为指针赋值,定义如下:
cpp
#define NULL ((void*)0)
NULL是一个void*类型的指针,其值为0,在使用NULL给其他指针赋值时,将void*类型指针转换成为要赋值的指针类型,使用它有时会发生一些错误,有两个重载函数传入参数为NULL时,它会调用参数类型为int的函数而不是int*,除非进行强制类型转换。
所以C++11引入了nullptr,它是一个有类型的空指针常量,当使用它给指针赋值时,nullptr可以隐式类型转换成等号左侧的指针类型,它不能转换为非指针类型。
4.=default与=delete
在类中显式定义构造,析构,拷贝构造函数时,编译器不再提供默认的版本。在默认函数声明后面添加=default,显式的指示编译器生成该函数的默认版本。
cpp
class Animal
{
public:
Animal()=default;
Animal(string name);
private:
string _name;
};
有时,我们不希望类的某些成员函数在类外被调用,C++11标准在函数的声明后面加上=delete,编译器会禁止函数在类外调用。
cpp
class Animal
{
public:
Animal(const Animal&)=delete;
};
2.基于范围的for循环
cpp
for(变量:对象)
{
}
用法实例:
cpp
vector<int> v={1,2,3,4,5,6};
for(auto i:v)
{
cout<<i<<" ";
}
3.lambda表达式
lambda表达式用于定义匿名函数,格式如下:
cpp
[捕获列表](参数列表)->返回值类型{函数体}
捕获列表能够捕获lambda表达式上下文中的变量,以供其使用。捕获形式如下:
- []:空捕获,表示不捕获任何变量
- [var]:表示捕获局部变量var
- [&var]:表示以引用方式捕获局部变量
- [=]:表示捕获所有得局部变量
- [&]:表示以引用方式捕获所有得局部变量
cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int main()
{
int num=100;
auto f=[num](int x)->int{return x+num;};
cout<<f(10)<<endl;
vector<int> v={54,148,3,848,2,89};
for_each(v.begin(),v.end(),[](auto n)
{
cout<<n<<" ";
});
return 0;
}
cpp
110
54 148 3 848 2 89
2.智能指针
在C++中,如果使用new手动申请了内存,必须要用delete手动释放,C++98提供了auto_ptr解决,但是它有很多缺点,不能调用delete[],C++11提供了三个新的智能指针,这些模板类定义了一个以堆内存空间指针为参数的构造函数,创建智能指针对象时,将new返回的指针作为参数,他、也定义了析构来释放内存。
1.unique_ptr
unique_ptr用法与auto_ptr相同,格式如下:
cpp
unique_ptr<T> 智能指针对象名称(指针);
unique_ptr<T>是模板类型,指针是new运算符申请堆内存空间返回的指针。
cpp
unique_ptr<int> pi(new int(10));
class A {};
unique_ptr<A> pA(new A);
该对象之间不可以赋值。当发生赋值操作时,智能指针会转让所有权,赋值成功后指针会失去所有权成为悬挂指针,如果要赋值可以调用move函数。
2.shared_ptr
它在实现的时候采用了引用计数的方式,多个指针对象可以同时管理一个new对象指针,每增加一个智能指针对象,new对象的引用计数就加1,失效时引用计数减1,引用计数为0时,释放堆内存空间。下面是几个常用的成员函数:
- get()函数:用于获取shared_ptr管理的new对象指针,声明如下:
cpp
T* get() const;
- use_count()函数:用于获取new对象的引用计数,声明如下:
cpp
long use_count() const;
- reset()函数:用于取消shared_ptr智能指针对象对new对象的引用,声明如下:
cpp
void reset();
cpp
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
int main()
{
shared_ptr<string> language1(new string("C++"));
shared_ptr<string> language2=language1;
shared_ptr<string> language3=language1;
cout<<"language1:"<<language1.get()<<endl;
cout<<"language2:"<<language2.get()<<endl;
cout<<"language3:"<<language3.get()<<endl;
cout<<"引用计数:";
cout<<language1.use_count()<<" ";
cout<<language2.use_count()<<" ";
cout<<language3.use_count()<<endl;
language1.reset();
cout<<"引用计数:";
cout<<language1.use_count()<<" ";
cout<<language2.use_count()<<" ";
cout<<language3.use_count()<<endl;
cout<<"language1:"<<language1.get()<<endl;
cout<<"language2:"<<language2.get()<<endl;
cout<<"language3:"<<language3.get()<<endl;
return 0;
}
cpp
language1:0x27a2560
language2:0x27a2560
language3:0x27a2560
引用计数:3 3 3
引用计数:0 2 2
language1:0
language2:0x27a2560
language3:0x27a2560
3.weak_ptr
它可以指向shared_ptr管理的new对象,却没有对象的所有权,无法通过它管理new对象,它最常见的用法是验证shared_ptr对象的有效性,它提供了一个成员函数lock,返回一个shared_ptr对象。
cpp
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
void func(weak_ptr<string>& pw)
{
shared_ptr<string> ps=pw.lock();
if(ps!= nullptr)
cout<<"编程语言是"<<*ps<<endl;
else
cout<<"shared_ptr智能指针失效!"<<endl;
}
int main()
{
shared_ptr<string> pt1(new string("C++"));
shared_ptr<string> pt2=pt1;
weak_ptr<string> pw=pt1;
func(pw);
*pt1="Java";
pt1.reset();
func(pw);
pt2.reset();
func(pw);
return 0;
}
cpp
编程语言是C++
编程语言是Java
shared_ptr智能指针失效!
3.提高编程效率
1.右值引用
左值就是=左边的值,右值就是=右边的值,C++11将右值分为纯右值与将亡值,纯右值是指字面常量,运算表达式,lambda表达式等,将亡值是那些即将被销毁却可以移动的值,如函数返回值,右值引用就是定义一个标识符引用右值,符号为&&。右值引用不能引用左值。
cpp
类型&& 引用名称=右值;
cpp
int x=10,y=20;
int&& r1=100;
int&& r2=x+y;
int&& r3=sqrt(9.0);
2.移动构造
cpp
#include <iostream>
#include <vector>
#include <memory>
#include <valarray>
using namespace std;
class A
{
public:
A(){cout<<"构造函数"<<endl;}
A(const A& a){cout<<"拷贝构造函数"<<endl;}
~A(){cout<<"析构函数"<<endl;}
};
A func()
{
A a;
return a;
}
int main()
{
A b=func();
return 0;
}
在func的调用过程中,,func不会直接将对象a返回出去,而是创建一个临时对象,将对象a的值赋给临时对象,在返回时,将临时对象返回给对象b,func函数在返回过程中经过了两次拷贝,右值引用,在构造对象b时,让对象b指向临时对象的内存空间,即引用临时对象。延长了临时对象的生命周期,减少了对象拷贝,析构的次数。移动构造函数如下:
cpp
class 类型
{
public:
移动构造函数名(类名&& 对象名)
{
}
};
cpp
include <iostream>
using namespace std;
class A
{
public:
A(int n);
A(const A& a);
A(A&& a);
~A();
private:
int *p;
};
A::A(int n):p(new int(n))
{
cout<<"构造函数"<<endl;
}
A::A(const A &a)
{
p=new int(*(a.p));
cout<<"拷贝构造函数"<<endl;
}
A::A(A &&a)
{
p=a.p;
a.p= nullptr;
cout<<"移动构造函数"<<endl;
}
A::~A()
{
cout<<"析构函数"<<endl;
}
A func()
{
A a(10);
return a;
}
int main()
{
A m=func();
return 0;
}
3.move()函数
该函数的功能就是将一个左值强制转换为右值,便可以通过右值引用使用该值。
cpp
int x=10;
int&& r=move(x);
如果类中有指针或者动态数组成员,在对象被拷贝或赋值时,可以直接调用move函数将对象转换为右值,去初始化另一个对象,使用右值进行初始化,调用的是移动构造函数,而不是拷贝构造函数。
4.完美转发
一个已经定义的右值引用其实是一个左值,这样在参数转发时就会产生一些问题,在函数嵌套调用时,外层函数接收一个右值作为参数,但外层函数将参数转发给内层函数时,参数就变成了一个左值,并不是它原来的类型了。
cpp
#include <iostream>
using namespace std;
template<typename T>
void transimit(T& t){cout<<"左值"<<endl;}
template<typename T>
void transimit(T&& t){cout<<"右值"<<endl;}
template<typename U>
void test(U&& u)
{
transimit(u);
transimit(move(u));
}
int main()
{
test(1);
return 0;
}
第一次调用transimit函数时,右值变为左值,提供了一个forward函数,使转发的参数类一直保持不变。
cpp
transimit(forward<U>(u));
forward函数实现完美转发时遵循引用折叠规则,该规则通过形参和实参的类型推导出内层函数接收到的参数的实际类型。
5.函数包装
C++11提供了一个函数包装器function,他是一个类模板,它能够为多种类似的函数提供统一的调用接口,即对函数进行包装,function可以包装除类成员之外的所有函数,包括普通函数,函数指针,lambda表达式和仿函数。
cpp
#include <iostream>
using namespace std;
template<typename T,typename U>
T func(T t,U u)
{
static int count = 0;
count++;
cout << "count=" << count << ",&count=" << &count<<endl;
return u(t);
}
int square(int a)
{
return a*a;
}
class Student
{
private:
int _id;
public:
Student(int id=1001):_id(id){}
int operator()(int num){return _id+num;}
};
int main()
{
int x=10;
cout<<"square()函数:"<<func(x, square)<<endl;
cout<<"Student类:"<<func(x,Student(1002))<<endl;
cout<<"lambda表达式:"<<func(x,[](int b){return b/2;})<<endl;
return 0;
}
cpp
function<int(int)> fi1= square;
function<int(int)> fi2=Student(1002);
function<int(int)> fi3=[](int b){return b/2;};
三个函数都有一个int类型的参数,返回值都为int类型,它们的调用特征标相同int(int)。function为这些函数提供统一的接口。