上篇主要介绍一些零散的改动、类、右值引用与构造函数
下篇主要介绍Lambda函数、可变模板参数和包装器
初始化列表
其实就是{ ... }这个东西。可作为函数参数。
#include <iostream>
#include <initializer_list>
using namespace std;
double sum(initializer_list<double> il);
int main()
{
double total = sum({3.14,2.22,96.3,45});
cout << total;
return 0;
}
double sum(initializer_list<double> il)
{
double sum = 0;
for(auto i=il.begin();i != il.end();i++)
{
sum += *i;
}
return sum;
}
返回后置类型
auto func (char a,double b) -> decltype(a+b)
{
return a+b;
}
auto func (char a,double b) -> int
{
return (int)(a+b);
}
template <typename T, typename U>
// 如果T*U定义的话
auto func(T &t, U &u) -> decltype(T * U)
{
//do someting
}
using别名
主要用于模板类
与typedef的不同,可用于模板的部分具体化
uisng 新别名 = 原名称
int main()
{
using Vec = std::vector<int>;
Vec vec;
//do something
using My_Int = int;
My_Int a = 10;
//do smoething
return 0;
}
template <class T>
using My_arr = std::array<T,12>;
//typedef std::array<T,12> My_arr; //error
空指针nullptr
使用nullptr可使程序更加清晰和安全
#include <iostream>
int main()
{
using namespace std;
int *p1 = 0; //可以。但是有时候可能会不方便阅读
int a = NULL; //可以,但是没有必要
int *p2 = nullptr;
//int a = (int)nullptr; //error
//int a =nullptr; //error
if(a == NULL) cout << "确定这么写的人没被打死?" << endl;
if(p2 == 0); //ok
return 0;
}
nullptr实质上是一个指针类型。
智能指针,之前写过
异常规范
C++11不建议使用 异常规范
但新增了指出函数不会发成异常的关键字
int func(int a,int b) noexcept;
不是原本有异常的代码写这个东习就不会出现异常,而是程序员处理完异常再表明:这个函数被处理的不会发生异常
#include <iostream>
int func(int a,int b) noexcept;
int main()
{
using namespace std;
int sum;
try
{
sum = func(3,-3);
}
catch(const char * &error_log)
{
cout << error_log;
}
cout << sum;
return 0;
}
//声明与定义必须对应都有noexcept
int func(int a,int b) noexcept
{
if(a == -b)
throw "error";
return 10/(a+b);
}
不会抛出异常,但是还会引发标准错误流向屏幕发送错误消息
运行结果
terminate called after throwing an instance of 'char const*'
翻译过来:在抛出 'char const*' 的实例后调用终止
作用域内枚举
使用 :: 访问
int main()
{
using namespace std;
enum A{a,b,c};
//enum B{c,d,e}; error
//or use struct
enum class B {c,d,e}; //ok
return 0;
}
显示转换
防止单参构造函数的类进行隐式转换再构造函数前用explicit修饰,不解释了。
类内初始化
#include <iostream>
class Data
{
public:
int a = 10;
int b = 20;
};
int main()
{
Data data;
using namespace std;
cout << data.a << " " << data.b << endl;
return 0;
}
运行结果
基于范围for循环
注意输入要使用引用,只要有迭代器并使用其迭代器历遍容器即可使用
这里用数组做演示
#include <iostream>
using namespace std;
int main()
{
int arr[10] {1,2,3,4,5,6,7,8,9,0}; //initialized with C++ 11
for(int x : arr)
{
cout << x << " ";
}
for(auto & x: arr)
{
cin >> x;
}
for(auto x : arr)
{
cout << x << " ";
}
return 0;
}
新的STL容器
之前的博客里有说过
forward_list
unordered_map
unordered_multimap
unordered_set
unordered_multiset
新增的模板
array
新的STL方法
cbeing()
cend()
与begin()和end()一样,只不过将容器内的元素视为常量
crbeing()
crend()
同上
valarry升级
添加了begin()和end()方法
摒弃了export
但保留其关键字,方便以后使用
模板类实例化的嵌套
尖括号可不用空格隔开
右值引用 &&
可对右值做引用
//同样传入3,4这两个右值,上面的可以,下面的不行
int func(int &&a, int &&b);
int func(int & a, int &b);
还可用于获取常量的地址
#include <iostream>
using namespace std;
int main()
{
int && rr = 13;
int * p = &rr;
cout << p << endl;
*p = 15;
cout << rr;
return 0;
}
运行结果
移动语义和右值引用
假设我们要对一个特别长的字符串进行处理,怎么办,肯定不能用值传递对吧。这样即浪费时间又浪费空间。可以考虑传递引用或者指针。
再来说一说移动语义的概念
移动语义是 语言标准 提出的概念,通过编写遵守移动语义的 移动构造函数、右值限定成员函数,逻辑上 优化 对象内资源 的转移流程
某些智能指针也用到了这个概念
简单的来说,移动语义就是对对象的所有权按的转移
理论存在,时实践开始
首先分两步
1.右值引用让编译器知道何时可以使用移动语义
2.编写移动构造函数(就是参数传递右值引用的构造函数),进行处理
为什么右值引用能让类进行移动构造?
其前面说过了,右值引用能获得右值的地址,进而用指针进行所有权的转让。
代码
#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; //method
};
//初始化
int Useless::ct = 0;
Useless::Useless()
{
++ct;
n = 0;
pc = nullptr;
cout << "default constructor called; number of objects: " << ct << endl;
ShowObject();
}
Useless::Useless(int k) : n(k)
{
++ct;
cout << "int constructor called; number of objects: " << ct << endl;
pc = new char[n]; //new一个空间存放字符串
ShowObject();
}
//将字符串全部初始化为ch
Useless::Useless(int k, char ch) : n(k)
{
++ct;
cout << "int, char constructor called; number of objects: " << 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 << "copy const called; number of objects: " << 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 << "MOVE constructor called; number of objects: " << ct << endl;
pc = f.pc; //共享地址
f.pc = nullptr; //防止重复析构右值
f.n = 0;
ShowObject();
}
Useless::~Useless()
{
cout << "destructor called; objects left: " << --ct << endl;
cout << "deleted object:\n";
ShowObject();
delete []pc;
}
Useless Useless::operator+(const Useless &f) const
{
cout << "Entering operator+()\n";
Useless temp = Useless(n+f.n); //构造函数生成一个临时的变量赋值给temp
for (int i = 0; i < n; i++)
temp.pc[i] = pc[i];
for (int i = n; i < temp.n; i++)
temp.pc[i] = f.pc[i - n];
cout << "temp object:\n";
cout << "Leaving operator+()\n";
return temp;
}
void Useless::ShowObject() const
{
cout << "Number of elements: " << n;
cout << " Data address: " << (void *) pc << endl;
}
void Useless::ShowData() const
{
if (n == 0)
cout << "(object empty)";
else
for (int i = 0; i < n; i++)
cout << pc[i];
cout << endl;
}
int main()
{
Useless one(10,'x');
cout << "-----------------"<< endl;
Useless two = one;
cout << "-----------------"<< endl;
Useless three (20,'o');
cout << "-----------------"<< endl;
Useless four (one + three); //根据上面的代码,重载+返回一个临时变量temp,也就是右值,所以one + three是一个右值,将调用移动构造函数
//将返回值(右值)内容的的所有权交给four保管
cout << "-----------------"<< endl;
cout << "object one: ";
one.ShowData();
cout << "object two: ";
two.ShowData();
cout << "object three: ";
three.ShowData();
cout << "object four: ";
four.ShowData();
cin.get();
return 0;
}
运行结果也是很有意思
笑死,根本没有调用构造函数
原因是编译器进行了优化(编译器clang),用g++也是一样的效果
编译推断重载+函数的返回值要赋值给four,直接将数据转给了four的名下。
微软自家的编译器编译
可以看见在重载+号调用结束之后进行了移动构造
注意:函数中的temp和函数的返回值的地址是不一样的
不得不说,编译器的行为还是试试比较好。
在为引入构造函数之前。用const 限定的引用接受右值
int func(const int &a, const int &b)
{
return a+b;
}
int sum = func(3,4);
移动语义的意义
上面的程序,如果删除了移动构造函数,可看看其资源消耗
又进行了一次拷贝构造,即花费了时间,又花费了空间,但是移动构造不会(因为参数是一个右值引用,只是用的对象的别名,虽然这个对象原来也没有名字)。
右值引用还可以进行重载=的操作
强制移动
如果程序要分析一个数组,并且选中其中的一个元素使用,并将数组丢弃掉。虽然数组中的某一个对象是左值,但也能使用一些方法让其作为右值进行移动构造。
可用static_cast<>将对象强制转换为 &&
或者
C++11提供了一种技术
要包含头文件<utility>
使用std::move();
#include <iostream>
#include <utility>
using namespace std;
class Useless
{
private:
int n; // number of elements
char* pc; // pointer to data
static int ct; // number of objects
void ShowObject() const;
public:
Useless();
explicit Useless(int k);
Useless(int k, char ch);
Useless(const Useless& f); // regular copy constructor
Useless(Useless&& f); // move constructor
~Useless();
Useless operator+(const Useless& f)const;
Useless& operator=(const Useless& f); // copy assignment
Useless& operator=(Useless&& f); // move assignment
void ShowData() const;
};
int Useless::ct = 0;
Useless::Useless()
{
++ct;
n = 0;
pc = nullptr;
}
Useless::Useless(int k) : n(k)
{
++ct;
pc = new char[n];
}
Useless::Useless(int k, char ch) : n(k)
{
++ct;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = ch;
}
Useless::Useless(const Useless& f) : n(f.n)
{
++ct;
pc = new char[n];
for (int i = 0; i < n; i++)
pc[i] = f.pc[i];
}
Useless::Useless(Useless&& f) : n(f.n)
{
++ct;
pc = f.pc; // steal address
f.pc = nullptr; // give old object nothing in return
f.n = 0;
}
Useless::~Useless()
{
delete[] pc;
}
//左值引用赋值
Useless& Useless::operator=(const Useless& f)
{
cout << "copy assignment operator called:\n";
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; //因该返回*this ,因为赋值表达式的值是左值的值
}
Useless& Useless::operator=(Useless&& f)
{
cout << "move assignment operator called:\n";
if (this == &f)
return *this;
delete[]pc;
n = f.n;
pc = new char[n];
pc = f.pc;
f.n = 0;
f.pc = nullptr;
return *this;
}
Useless Useless::operator+(const Useless& f)const
{
Useless temp = Useless(n + f.n);
for (int i = 0; i < n; i++)
temp.pc[i] = pc[i];
for (int i = n; i < temp.n; i++)
temp.pc[i] = f.pc[i - n];
return temp;
}
void Useless::ShowObject() const
{
std::cout << "Number of elements: " << n;
std::cout << " Data address: " << (void*)pc << std::endl;
}
void Useless::ShowData() const
{
if (n == 0)
std::cout << "(object empty)";
else
for (int i = 0; i < n; i++)
std::cout << pc[i];
std::cout << std::endl;
}
int main()
{
using std::cout;
{
Useless one(10, 'x');
Useless two = one + one; // calls move constructor
cout << "object one: ";
one.ShowData();
cout << "object two: ";
two.ShowData();
Useless three, four;
cout << "three = one\n";
three = one; // automatic copy assignment
cout << "now object three = ";
three.ShowData();
cout << "and object one = ";
one.ShowData();
cout << "four = one + two\n";
four = one + two; // automatic move assignment
cout << "now object four = ";
four.ShowData();
cout << "four = move(one)\n";
//
four = std::move(one); // forced move assignment
//
cout << "now object four = ";
four.ShowData();
cout << "and object one = ";
one.ShowData();
}
std::cin.get();
}
运行结果
可以看见有两个move assignment显示,分别在148和143这两条语句中调用。
特别的,如果没有定义移动赋值,那么move(...)会使用默认的赋值函数
如果没有提供移动构造,而又需要移动构造,那么编译器会自动提供一个,但是是逐一赋值,不是深拷贝
另外的,STL类现在都有移动赋值函数,移动构造函数。
新的类功能
特殊的成员函数
1.构造函数
2.析构函数
3.复制构造函数
4.赋值运算符
C++11新增的两个如上
5.移动构造函数
6.移动赋值函数
默认的方法和禁用的方法
default显示的声明哪个构造函数是默认版本,不能做具体的实现,只是一个空实现
A() = default;
delete可禁用编译器使用特定的方法,虽然也可放在私有作用下,不过这样做更不容易犯错,更加清晰,更加使人理解
#include <iostream>
using namespace std;
class A
{
int a;
public:
A() = default;
void show(double & p)
{
cout << "p(double) is " << p << endl;
}
void show(int) = delete; //禁止编译器用int的版本
};
int main()
{
A _a;
//_a.show(10); //error 10 is a int
return 0;
}
委托构造函数
有些类的构造函数可能键入大量重复的内容,为了编码更简洁,可用委托构造函数解决。
将当前构造函数的任务委托给另外一个构造函数
#include <iostream>
using namespace std;
class Data
{
public:
int a;
int b;
int c;
public:
Data(int x, int y, int z) : a(x), b(y), c(z) {}
Data() : Data(1,2,3) {
//do other
}
Data(int y) : Data(0,y,0)
{
//do other thing
}
};
int main()
{
Data data(2);
cout << data.a << data.b << data.c << endl;
return 0;
}
运行结果
继承构造函数
C++11提供了能派生类继承基类构造函数的方法
使用using声明可让派生类继承基类的所有构造函数,但不会使用与派生类构造函数冲突的的基类构造函数。
#include <iostream>
using namespace std;
class A
{
public:
double a;
A () : a(0) {}
A (double x) : a(x) {}
};
class B : public A
{
public:
int b;
using A::A;
//共用一个值初始化
B (double x) : A(3.0*x), b((int)x) {}
};
int main()
{
B b(3.2);
cout << b.a <<" " << b.b;
return 0;
}
运行结果
虚方法的管理
C++11中可使用虚说明符override指出要覆盖一个虚函数,将其放在参数列表后面。如果声明与基类的方法不匹配,将导致编译错误
这减少了程序员犯错误的概率,也许在重写虚方法时,可能由于马虎写成了同名函数,结果时隐藏了基类中的方法而不是重写。
final与override作用相反,它说明了派生类不允许重写基类中的哪些函数,在参数列表后面加上即可
特别一说的是,override和final并非关键字,而是标识符,编译器根据上下文来判断他们是否有特殊含义。下面的代码完全可以。
int final = 10;
double override = 3.14;