C++11新特性
复习前面提及的C++11
新类型
C++11新增了类型 long long 和 unsigned long long ,以支持64位(或更宽)的整型;新增了类型char16_t和char32_t,以支持16位和32位的字符表示,还新增了原始的字符串
统一的初始化
C++11扩大了初始化列表的使用范围,使其可以用于所有内置内置类型和用户定义的类型(类对象)。使用初始化列表=可加也可不加
int x = 5; int x{5}; int x = {5};
列表初始化语法也可以用于new表达式中
int *pa = new int[4] {1,2,3,4}
创建对象,使用 {} 调用构造函数 (若类有将模板initializer_list作为参数的构造函数,则只有该构造函数可以使用列表初始化形式)
A a(15,2);A a{15,2};A a = {15,2};
- 初始化列表语法可防止缩窄,即禁止将数值赋给无法存储它的数值变量。常规初始化允许执行可能没有意义的操作:
char c1 = 1.57e27 // double->char
char c2 = 459585821
若使用初始化列表语法,编译器将禁止进行这样的类型转换,即将值赋到比它窄的变量中。
char c1 = {1.57e27} // double->char
char c2 = {459585821} 编译不通过
但允许转为更宽的类型。另外只要值在较窄类型的取值范围内,将其转换为较窄的类型也是可以的
char c1{66};
double c2 = {66}; - std::initializer_list
C++11提供了模板类initializer_list,可将其用作构造函数的参数。若类有将模板initializer_list作为参数的构造函数,则只有该构造函数可以使用列表初始化形式。列表中的类型必须是同一种类型或可转换为同一种类型。STL容器提供了将initializer_list作为参数的构造函数
vector a{1,2,3};
vector a(10); //10个元素
vector a{10}; //一个元素为10
initializer_list除了用于构造函数外,还可以用作常规函数的参数
声明
C++11提供了多种简化声明的功能,尤其是使用模板时。
- auto自动类型初始化
- decltype,将变量的类型声明为表达式指定的类型。
decltype (x) y y is x
double x;
decltype (&x) pd pd is double *
这在定义模板时特别有用,因为只有等到模板被实例化时才能确定类型
template<typename T,typename U>
void ef(T t,U u)
{
decltype(T*U) tu; //若t为char u为short 则 tu为int,整型算术自动提升
}
decltype的工作原理比auto复杂,根据使用的表达式,指定的类型可以为引用和const。
int j = 3;
int &k = j;
const int &n = j;
decltype(n) a; a->const int &
decltype(j)b ; b int
decltype((j))b b int &
decltype(k+1)b ; b int - 返回类型后置
auto f(double,int )->double
template<typename T,typename U>
auto ef(T t,U u)->decltype(T*U) - 模板别名
using itType = vector::iterator;
也可以用于模板具体化
template
using a = std::array<T,12>;
a ta; - nullptr,表示空指针,它是指针类型,不能转换为整型。(0可以)nullptr== 0; 结果为true。可以将0传给接受int参数的函数,nullptr不行。
智能指针
c++11摒弃了auto_ptr, 而是使用 shared_ptr,unique_ptr,weak_ptr
异常规范
老式: void f() throw(bad_dog) ;引发异常
void f() throw() ; 不会引发异常
C++11摒弃,新增关键字noexcept
void f() noexcept;不会引发异常
作用域内枚举
对类的修改
1.显示转换符(explicit)禁止自动转换(构造函数,转换函数也都可以)
2.类内成员初始化
模板和STL方面的修改
- 基于范围的for循环
- 新的STL容器
右值引用
移动语义和右值引用
为何需要移动语义
vector vstr; //2000个string 每个str有1000个字符
vector v1(vstr); //调用复制构造函数,vector 使用new分配2000个string对象的内存,而每个string对象又调用string的复制构造函数,使用new分配1000个字符的内存。工作量很大,但只要妥当就行
vector allcaps(const vector &vs)
{
vector tmp;
…
return tmp;
}
vector vstr;
vector v1(vstr);
vector v2(allcaps(vstr));
allcaps()创建2000*1000个字符的临时副本,然后程序删除返回的临时对象。做了大量无用功。若将对数据的所有权给v2, 而不是复制这么数据到新地方,再删除原来的地方。和计算机移动文件的类似,实际文件还在原来的地方,只是修改了记录,这种方法称为移动语义。移动语义实际上避免了移动原始数据,而只是修改了记录。
要实现移动语义,需要采用某种方式,让编译器知道什么时候需要复制,什么时候不需要。即右值引用。定义两个构造函数,一个常规的复制构造(左值),一个移动构造函数(右值引用:只调整记录),移动构造可能修改其实参,即移动构造的参数不能是const。
移动示例
#include <iostream>
using namespace std;
class Useless {
private:
int n;
char* pc;
static int ct;
void ShowObject() const;
public:
Useless();
explicit Useless(int k);
Useless(int k,char ch);
Useless(const Useless &f);
//移动构造函数
Useless(Useless&& f);
~Useless();
Useless operator+(const Useless& f)const;
//需要 =
void ShowData() const;
};
int Useless::ct = 0;
int main()
{
{
Useless one(10, 'x');
Useless two = one;
Useless three(20, '0');
Useless four(one + three); //先调用+ 后调用移动构造函数,右值引用f将关联到+方法返回的对象
//temp中的地址与four的地址相同
cout << " 1 \n";
one.ShowData();
cout << " 2 \n";
two.ShowData();
cout << " 3 \n";
three.ShowData();
cout << " 4 \n";
four.ShowData();
}
}
void Useless::ShowObject() const
{
cout << "number of elements : " << n;
cout << " data address: " << (void*)pc << endl;
}
Useless::Useless()
{
++ct;
n = 0;
pc = nullptr;
cout << "default called, object: " << ct << endl;
ShowObject();
}
Useless::Useless(int k):n(k)
{
++ct;
cout << "Useless(int k) called , object: " << ct << endl;
pc = new char[n];
ShowObject();
}
Useless::Useless(int k, char ch) :n(k)
{
++ct;
cout << "Useless(int k, char ch) called , object: " << ct << endl;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = ch;
ShowObject();
}
Useless::Useless(const Useless& f):n(f.n)
{
++ct;
cout << "const Useless& f called , object: " << ct << endl;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = f.pc[i];
ShowObject();
}
Useless::Useless(Useless&& f) :n(f.n)
{
++ct;
cout << "Useless(Useless&& f) called , object: " << ct << endl;
pc = f.pc; //两个指向相同的数据,调用析构麻烦,不能对同一个地址delete两次
//对空指针delete没问题。这种夺取所有权的方式成为窃取。
f.pc = nullptr;
f.n = 0;
ShowObject();
}
Useless::~Useless()
{
cout << "~Useless() called , left object: " << --ct << endl;
cout << "delete object\n";
ShowObject();
delete[] pc;
}
Useless Useless::operator+(const Useless& f) const
{
cout << "enter operator+\n";
Useless tmp = Useless(n + f.n);
for (int i = 0; i < n; i++)
tmp.pc[i] = pc[i];
for (int i = n; i < tmp.n; i++)
tmp.pc[i] = f.pc[i-n];
cout << "leave operator+\n";
return tmp;
}
void Useless::ShowData() const
{
if (n == 0)
cout << "empty \n";
else
{
for (int i = 0; i < n; i++)
cout << pc[i];
}
cout << endl;
}
移动构造函数解析
1.右值引用让编译器知道何时可使用移动语义
2.编写移动构造函数
Useless two = one; one是左值,与左值引用匹配 调用复制构造函数
Useless four(one + three); one+three 是右值,与右值引用匹配
若没有移动构造函数,则调用复制构造函数,形参f指向一个临时变量,该变量为one + three的返回值。即移动构造消除了额外的临时对象的开销。
赋值
移动赋值运算符
Useless& Useless::operator=(const Useless& f)
{
if (this == &f)
return *this;
delete[]pc;
n = f.n;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = f.pc[i];
return *this;
}
//移动赋值运算符删除目标对象中的原始数据,碧昂将源对象的所有权转给目标
Useless& Useless::operator=(Useless&& f)
{
if (this == &f)
return *this;
delete[]pc;
n = f.n;
pc = f.pc;
f.pc = nullptr;
f.n = 0;
return *this;
}
强制移动move
头文件utility或使用static_cast<>将对象强转为Useless &&
int main()
{
{
Useless one(10, 'x');
Useless two = one + one; //移动构造
cout << "one \n";
one.ShowData();
cout << "two \n";
two.ShowData();
Useless three, four;
cout << "three = one\n";
three = one; // 赋值运算符
cout << "now three is \n";
three.ShowData();
cout << "and one \n";
one.ShowData();
cout << "four = one + two\n";
four = one + two; // + 移动构造 移动赋值运算符
cout << "now four is \n";
four.ShowData();
cout << "four = move(one)\n";
four = move(one); // 强制移动赋值运算符,此时one已经为空了
cout << "now four is \n";
four.ShowData();
cout << "and one \n";
one.ShowData();
}
}
注意:函数move并非一定会导致移动操作。假设Chunk是一个包含私有数据的类,而您编写了如下代码
Chunk a;
Chunk b;
b = move(a); //表达式move(a); 是右值,将调用移动赋值运算符,若没有定义,则赋值运算符,若都没有,则编译不通过
右值引用带来的主要好处并非是让他们能够编写使用右值引用的代码,而是能够利用右值引用实现移动引用的库代码。例如STL类现在都有复制构造函数,移动构造函数,复制运算符,和移动赋值运算符。
新的类功能
支持c++11的编译器默认提供默认构造函数,复制构造函数,赋值函数以及析构函数,新增移动构造函数和移动赋值函数
默认的方法和禁用的方法
委托构造函数
继承构造函数
管理虚方法:override 和 final
Lambda函数(表达式)
[&count](int x) {count += (x % 13 == 0); }
函数指针,函数符,和 Lambda函数
生成一个随机整数列表,判断有多少个能被3整除能被13整除
1.函数指针
#include <iostream>
#include<utility>
#include<vector>
#include<algorithm>
using namespace std;
bool f3(int x) { return x % 3 == 0; }
bool f13(int x) { return x % 13 == 0; }
int main()
{
//生成随机整数
vector<int>number(1000);
generate(number.begin(), number.end(), rand);
int a3 = count_if(number.begin(), number.end(), f3);
int a13 = count_if(number.begin(), number.end(), f13);
cout << a3 << " : " << a13 << endl; //324 68
}
2.函数符
//函数符是一个类对象,并非只能像函数名那样使用它
class f_mod {
private:
int dv;
public:
f_mod(int d = 1) :dv(d) {}
bool operator()(int x) { return x % dv == 0; }
};
int main()
{
//生成随机整数
vector<int>number(1000);
generate(number.begin(), number.end(), rand);
int a3 = count_if(number.begin(), number.end(), f_mod(3));
int a13 = count_if(number.begin(), number.end(), f_mod(13));
cout << a3 << " : " << a13 << endl; //324 68
}
3.Lambda函数
在C++11中,对于接受函数指针或函数符的函数,可以使用匿名函数定义(Lambda)作为其参数
int main()
{
//生成随机整数
vector<int>number(1000);
generate(number.begin(), number.end(), rand);
int a3 = count_if(number.begin(), number.end(), [](int x) { return x % 3 == 0; });
int a13 = count_if(number.begin(), number.end(), [](int x) { return x % 13 == 0; });
cout << a3 << " : " << a13 << endl; //324 68
}
为何使用Lambda
在C++中引入Lamada的主要目的是,让您能够将类似于函数的表达式用作接受函数指针或函数符的函数的参数。因此,典型的Lamada是测试表达式或比较表达式,可编写一条返回语句。使得lamada简洁易于理解,且可以自动推断返回类型。
包装器
C++提供了多个包装器(wrapper,也叫适配器【adapter】)。这些对象用于给其他编程接口提供更一致或更合适的接口。bind1st和bind2ed,它们让接受了两个参数的函数能够与这样的STL算法匹配,即它要求将接受一个参数的函数作为参数。C++11提供了其它包装器包括bind,men_fn ,reference_wrapper,function.其中bind可替代bind1st和bind2ed但更灵活;模板men_fn 让你能够将成员函数作为常规函数进行传递;reference_wrapper能够创建行为像引用,但可被复制的对象。function能够以统一的方式处理多种类似于函数的形式。
包装器function以及模板的低效性
#pragma once
#include<iostream>
template<class T,class F>
T use_f(T v, F f)
{
static int count = 0;
count++;
std::cout << " use_f count :" << count << ", &count" << &count << std::endl;
return f(v);
}
class Fp {
private:
double z_;
public:
Fp(double z = 1.0) :z_(z) {}
double operator()(double p) { return z_ * p; }
};
class Fq {
private:
double z_;
public:
Fq(double z = 1.0) :z_(z) {}
double operator()(double p) { return z_ * p; }
};
#include <iostream>
#include<utility>
#include<vector>
#include<algorithm>
#include"some.h"
using namespace std;
double dub(double x) { return 2.0 * x; }
double square(double x) { return x * x; }
int main()
{
double y = 1.21;
cout << "Function pointer dub: \n";
cout << " "<<use_f(y,dub); //F的类型 double(*)(double)
cout << endl;
cout << "Function pointer square: \n";
cout << " " << use_f(y, square);//F的类型 double(*)(double)
cout << endl;
cout << "Function object Fp: \n";
cout << " " << use_f(y, Fp(5.0));//F的类型fp
cout << endl;
cout << "Function object Fq: \n";
cout << " " << use_f(y, Fq(5.0));//F的类型fq
cout << endl;
cout << "Function Lamada 1: \n";
cout << " " << use_f(y, [](double u) {return u * u; });//F的类型Lamada使用的类型
cout << endl;
cout << "Function Lamada 2: \n";
cout << " " << use_f(y, [](double u) {return u + u/2.0; });
cout << endl;
}
输出
Function pointer dub:
use_f count :1, &count00E0D140
2.42
Function pointer square:
use_f count :2, &count00E0D140
1.4641
Function object Fp:
use_f count :1, &count00E0D144
6.05
Function object Fq:
use_f count :1, &count00E0D148
6.05
Function Lamada 1:
use_f count :1, &count00E0D14C
1.4641
Function Lamada 2:
use_f count :1, &count00E0D150
1.815
根据静态成员count的地址,确定初始化了多少次,有五个不同地址,表明模板有5个不同的实例化。
修复问题
#include <iostream>
#include<utility>
#include<vector>
#include<algorithm>
#include<functional>
#include"some.h"
using namespace std;
double dub(double x) { return 2.0 * x; }
double square(double x) { return x * x; }
int main()
{
double y = 1.21;
function<double(double)> ef1 = dub;
function<double(double)> ef2 = square;
function<double(double)> ef3 = Fq(10.0);
function<double(double)> ef4 = Fp(10.0);
function<double(double)> ef5 = [](double u) {return u * u; };
function<double(double)> ef6 = [](double u) {return u + u / 2.0; };
cout << "Function pointer dub: \n";
cout << " "<<use_f(y,ef1);
cout << endl;
cout << "Function pointer square: \n";
cout << " " << use_f(y, ef2);
cout << endl;
cout << "Function object Fp: \n";
cout << " " << use_f(y, ef3);
cout << endl;
cout << "Function object Fq: \n";
cout << " " << use_f(y, ef4);
cout << endl;
cout << "Function Lamada 1: \n";
cout << " " << use_f(y, ef5);
cout << endl;
cout << "Function Lamada 2: \n";
cout << " " << use_f(y, ef6);
cout << endl;
}
count的值表明,use_f调用了6次,只有一个实例,并调用该实例6次,缩小了可执行代码的规模
其它方式
#pragma once
#include<iostream>
#include<functional>
template<class T>
//将第二个参数声明为function包装器对象
//改为这种方式。函数调用use_f<double>(y,Fp(5.5))
T use_f(T v, std::function<T(T)> f)
{
static int count = 0;
count++;
std::cout << " use_f count :" << count << ", &count" << &count << std::endl;
return f(v);
}
class Fp {
private:
double z_;
public:
Fp(double z = 1.0) :z_(z) {}
double operator()(double p) { return z_ * p; }
};
class Fq {
private:
double z_;
public:
Fq(double z = 1.0) :z_(z) {}
double operator()(double p) { return z_ * p; }
};
#include <iostream>
#include<utility>
#include<vector>
#include<algorithm>
#include<functional>
#include"some.h"
using namespace std;
double dub(double x) { return 2.0 * x; }
double square(double x) { return x * x; }
int main()
{
double y = 1.21;
typedef function<double(double)> fdd;
cout << "Function pointer dub: \n";
cout << " "<<use_f(y,fdd(dub));
cout << endl;
cout << "Function pointer square: \n";
cout << " " << use_f(y, fdd(square));
cout << endl;
cout << "Function object Fp: \n";
cout << " " << use_f(y, fdd(Fp(10.0)));
cout << endl;
cout << "Function object Fq: \n";
cout << " " << use_f(y, fdd(Fq(10.0)));
cout << endl;
cout << "Function Lamada 1: \n";
cout << " " << use_f(y, fdd([](double u) {return u * u; }));
cout << endl;
cout << "Function Lamada 2: \n";
cout << " " << use_f(y, fdd([](double u) {return u * u/2; }));
cout << endl;
}
可变参数模板
模板和函数参数包
展开参数包
在可变参数模板函数中使用递归
// pr.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <iostream>
#include<string>
using namespace std;
void show_list() {}
template<class T>
void show_list(const T &v)
{
cout << v << endl;
}
template<typename T,typename...Args>
void show_list(const T& v,const Args&...args)
{
cout << v <<", ";
show_list(args...);
}
int main()
{
int n = 14;
double x = 17.8;
string str = "hello";
show_list(n, x);
show_list(x*x, '!',7,str);
}
c++11新增的其它功能
语言变化