lambda
lambda是c++11非常重要也是最常用的特性之一,他有以下优点:
● 可以就地匿名定义目标函数或函数对象,不需要额外写一个函数
● lambda是一个匿名的内联函数
lambda 表达式定义了一个匿名函数,语法如下:
[ capture ]( params ) -> ret {body;};
其中capture是捕获列表,params是参数列表,ret是返回值类型,body是函数体。
捕获列表[]:捕获一定范围内的变量
参数列表(): 和普通函数的参数列表一样,如果没有参数参数列表可以省略不写
auto fun = [](){return 0;};
auto fun = []{return 0;};
捕获列表
● [ ] 不捕获任何变量
● [&] 捕获外部作用域中的所有变量,并且按照引用捕获
● [=]捕获外部作用域的所有变量,按照值捕获,拷贝过来的副本在函数体内是只读的
● [= ,&a] 按值捕获外部作用域中的所有变量,并且按照引用捕获外部变量 a
● [bar] 按值捕获bar变量,不捕获其他变量
● [this] 捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限
int main()
{
int a = 10, b = 20;
auto f1 = [] {return a; }; // 错误,没有捕获外部变量,因此无法访问变量 a
auto f2 = [&] {return a++; }; // 正确,使用引用的方式捕获外部变量,可读写
auto f3 = [=] {return a; }; // 正确,使用值拷贝的方式捕获外部变量,可读
auto f4 = [=] {return a++; }; // 错误,使用值拷贝的方式捕获外部变量,可读不能写
auto f5 = [a] {return a + b; }; // 错误,使用拷贝的方式捕获了外部变量 a,没有捕获外部变量 b,因此无法访问变量 b
auto f6 = [a, &b] {return a + (b++); }; // 正确,使用拷贝的方式捕获了外部变量 a,只读,使用引用的方式捕获外部变量 b,可读写
auto f7 = [=, &b] {return a + (b++); }; // 正确,使用值拷贝的方式捕获所有外部变量以及 b 的引用,b 可读写,其他只读
return 0;
}
class Test
{
public:
void output(int x, int y)
{
auto x1 = [] {return m_number; }; // 错误,没有捕获外部变量,不能使用类成员 m_number
auto x2 = [=] {return m_number + x + y; }; // 正确,以值拷贝的方式捕获所有外部变量
auto x3 = [&] {return m_number + x + y; }; // 正确,以引用的方式捕获所有外部变量
auto x4 = [this] {return m_number; }; // 正确,捕获 this 指针,可访问对象内部成员
auto x5 = [this] {return m_number + x + y; }; // 错误,捕获 this 指针,可访问类内部成员,没有捕获到变量 x,y,因此不能访问。
auto x6 = [this, x, y] {return m_number + x + y; }; // 正确,捕获 this 指针,x,y
auto x7 = [this] {return m_number++; }; // 正确,捕获 this 指针,并且可以修改对象内部变量的值
}
int m_number = 100;
};
返回值
一般情况下,不指定lambda表达式的返回值,编译器会根据return语句自动推导返回值类型,但是需要注意的是lambda表达式不能通过列表初始化自动推导出返回值类型。
// 可以自动推导出返回值类型
auto f = [](int i)
{
return i;
}
// 不能推导出返回值类型
auto f1 = []()
{
return { 1, 2 }; // 基于列表初始化推导返回值,错误
}
// 正确显示声明了函数的返回值类型
auto f1 = []()-> vector<int>
{
return { 1, 2 }; // 基于列表初始化推导返回值,错误
};
用法
与STL搭配使用:
#include <iostream>
using namespace std;
#include<vector>
#include<algorithm>
int main()
{
vector<int> vec = {1,2,3,4,5,6};
sort(vec.begin(), vec.end(), [](int a, int b)
{
return a > b;
});
for (auto it : vec)
{
cout << it << " ";
}
}
#include <vector>
#include <algorithm>
using namespace std;
#include<iostream>
vector<int> nums;
vector<int> largeNums;
const int ubound = 10;
inline void LargeNumsFunc(int i) {
if (i > ubound)
largeNums.push_back(i);
}
void Above() {
//传统的for循环
for (auto itr = nums.begin(); itr != nums.end(); ++itr) {
if (*itr >= ubound)
largeNums.push_back(*itr);
}
//使用函数指针
for_each(nums.begin(), nums.end(), LargeNumsFunc);
//使用lambda和算法for_each
for_each(nums.begin(), nums.end(), [=](int i) {
if (i > ubound)
largeNums.push_back(i); });
}
那么我们再比较一下函数指针方式以及lambda方式。函数指针的方式看似简洁,不过却有很大的缺陷。
第一点是函数定义在别的地方,比如很多行以前(后)或者别的文件中,这样的代码阅读起来并不方便。
第二点则是出于效率考虑,使用函数指针很可能导致编译器不对其进行inline优化( inline对编译器而言并非强制),在循环次数较多的时候,内联的 lambda和没有能够内联的函数指针可能存在着巨大的性能差别。因此,相比于函数指针,lambda拥有无可替代的优势。
final
c++增加了final 关键字来限制某个类不能被继承或者某个虚函数不能被重写,如果final修饰函数只能修饰虚函数,并且要把final关键字放到类或者函数的后面。
修饰函数
如果使用final修饰函数,只能修饰虚函数,这样就可以组织子类重写父类这个函数
class Base
{
public:
virtual void test()
{
cout << "Base class...";
}
};
class Child : public Base
{
public:
void test() final
{
cout << "Child class...";
}
};
class GrandChild : public Child
{
public:
// 语法错误, 不允许重写
void test()
{
cout << "GrandChild class...";
}
};
修饰类
使用final关键字修饰过得类不允许被继承,也就是说这个类不能有子类
class Base
{
public:
virtual void test()
{
cout << "Base class...";
}
};
class Child final : public Base
{
public:
void test()
{
cout << "Child class...";
}
};
// 语法错误
class GrandChild : public Child
{
public:
};
override
override关键字明确的表明将会重写父类的虚函数,和final的用法相同,放在函数后面。提高了程序的正确性,降低了出错概率。
class Base
{
public:
virtual void test()
{
cout << "Base class...";
}
};
class Child : public Base
{
public:
//正确,重写了父类的虚函数
void test() override
{
cout << "Child class...";
}
};
class GrandChild : public Child
{
public:
//报错,父类中没有相同的虚函数可以被重写
void test(int a) override
{
cout << "Child class...";
}
};
=default
可以在类内部修饰满足条件的类函数为显示默认函数,也可以在类定义之外修饰成员函数为默认函数。
class Base
{
public:
//指定无参构造为默认函数
Base() = default;
//指定拷贝构造函数为默认函数
Base(const Base& obj) = default;
//指定移动构造函数为默认函数
Base(Base&& obj) = default;
//指定复制赋值操作符重载函数为默认函数
Base& operator= (const Base& obj) = default;
//指定移动赋值操作符重载函数为默认函数
Base& operator= (Base&& obj) = default;
//指定析构函数为默认函数
~Base() = default;
};
默认函数除了在类定义的内部指定,也可以在类的外部指定。
// 类定义
class Base
{
public:
Base();
Base(const Base& obj);
Base(Base&& obj);
Base& operator= (const Base& obj);
Base& operator= (Base&& obj);
~Base();
};
// 在类定义之外指定成员函数为默认函数
Base::Base() = default;
Base::Base(const Base& obj) = default;
Base::Base(Base&& obj) = default;
Base& Base::operator= (const Base& obj) = default;
Base& Base::operator= (Base&& obj) = default;
Base::~Base() = default;
class Base
{
public:
Base() = default;
Base(const Base& obj) = default;
Base(Base&& obj) = default;
Base& operator= (const Base& obj) = default;
Base& operator= (Base&& obj) = default;
~Base() = default;
// 以下写法全部都是错误的
Base(int a = 0) = default; //自定义带参构造,不允许使用 =default 修饰(即使有默认参数也不行)
Base(int a, int b) = default; //自定义带参构造,不允许使用 =default 修饰
void print() = default; //自定义函数,不允许使用 =default 修饰
//下面两行不是移动、复制赋值运算符重载,不允许使用 =default 修饰
bool operator== (const Base& obj) = default;
bool operator>=(const Base& obj) = default;
};
后两行vs报错如下:
=delete
delete关键字标识显示删除,显示删除可以避免用户使用一些不应该使用的类的成员函数。
禁止使用默认生成的函数
class Base
{
public:
Base() = default;
Base(const Base& obj) = delete; //禁用拷贝构造函数
Base& operator= (const Base& obj) = delete; //禁用 = 进行对象复制/
};
int main()
{
Base b;
Base tmp1(b); // 报错 拷贝构造函数已被显示删除,无法拷贝对象
Base tmp = b; // 报错
return 0;
}
禁止使用自定义函数
class Base
{
public:
Base(int num) : m_num(num) {}
Base(char c) = delete; //禁用带 char 类型参数的构造函数,防止隐式类型转换(char 转 int)
void print(char c) = delete; //禁止使用带 char 类型的自定义函数,防止隐式类型转换(char 转 int)
void print()
{
cout << "num: " << m_num << endl;
}
void print(int num)
{
cout << "num: " << num << endl;
}
private:
int m_num;
};
int main()
{
Base b(97);
Base b1('a'); // 'a' 对应的 acscii 值为97 报错 对应的构造函数被禁用,因此无法使用该构造函数构造对象
b.print();
b.print(97);
b.print('a'); // 报错 对应的打印函数被禁用,因此无法给函数传递 char 类型参数
return 0;
}
委托构造
委托构造函数允许使用同一个类中的一个构造函数调用其他的构造函数,从而简化相关变量的初始化。
class Test
{
public:
int min;
int mid;
int max;
Test(int min)
{
this->min = min;
}
Test(int min, int max):Test(min)
{
this->max = max;
}
Test(int min, int mid, int max):Test(min,max)
{
this->mid = mid;
}
};
int main()
{
Test t(1,2,3);
cout << t.min << " " << t.mid << " " << t.max << endl;
return 0;
}
- 链式的调用委托构造不能形成一个闭环。
- 在初始化列表调用了委托构造,就不能再初始化列表中初始化其他变量了
继承构造
c++11提供的继承构造函数可以让派生类直接使用基类的构造函数,而不需要自己再写构造函数,尤其是在基类有很多构造函数的情况下,可以极大的简化派生类构造函数的编写。
- 没有继承构造的处理方式
#include <iostream> using namespace std; class father { int min; int mid; int max; public: father(int i) :min(i) {} father(int i, int j) :min(i), mid(j) {} father(int i, int j, int k) :min(i), mid(j), max(k) {} }; class Child : public father { public: Child(int i):father(i){ } Child(int i, int j, int k) :father(i, j, k) {} }; int main() { Child c(1,2,3); return 0; }
● 在子类中初始化父类中的私有成员变量,需要在子类中的初始化列表中加入基类的构造函数。
● 继承构造语法:using 类名::构造函数名
● 在子类中不添加任何构造函数,只添加using 类名::构造函数名,这样就可以在子类中直接继承父类的所有构造函数,通过他们去构造子类对象,父类没有默认构造函数,子类也没有默认构造除非自己实现。
#include <iostream> using namespace std; class father { int min; int mid; int max; public: father(int i) :min(i) {} father(int i, int j) :min(i), mid(j) {} father(int i, int j, int k) :min(i), mid(j), max(k) {} }; class Child : public father { public: using father::father; }; int main() { Child c(1,2,4); return 0; }
● 如果子类中隐藏了父类中的同名函数,也可以通过using的方式在子类中使用基类中的这些父类函数。
#include <iostream> using namespace std; #include<queue> #include<string> class father { int min; int mid; int max; public: father(int i) :min(i) {} father(int i, int j) :min(i), mid(j) {} father(int i, int j, int k) :min(i), mid(j), max(k) {} void fun(int a) { cout << "父类中的一个int" << endl; } void fun(int a,int b) { cout << "父类中的两个int" << endl; } }; class Child : public father { public: using father::father; using father::fun; void fun() { cout << "子类中的没有int" << endl; } }; int main() { Child c(1,2,4); c.fun(); //子类中的fun c.fun(1); //如果没有using father::fun; 这句话会报错 return 0; }