从C到C++___STL入门(五)初始化列表和其他
1. initializer_list模板类
C++11中新加入的initializer_list模板的设计目的是:
- 使得初始化语法更加简单
- 使得传递参数更加方便
1.1 C++11的初始化语法
在C++11以前我们的初始化语法是:
int a=10;
int a(10);
所有的C++基本类型都有以上两种等价的初始化语句 而对于结构体和对象来说,我们的初始化语句只能调用构造函数。
但是在C++11中,我们引入了更简单通用的初始化语法:
double a{100.0};
vector<double> c{0.22,0.3,11};//等价于vector<double> c({0.22,0.3,11});
以上就是新的初始化语法。对于C++基本类型来说,double a{100.0} 成立的原因是,{} 被视为() ;而对于类对象来说,vector<double> c{0.22,0.3,11} 成立的原因是在vector 类中存在一个构造函数,形如vector<T>(initializer_list<T> il) 。所以说在这里{0.22,0.3,11} 相当于是initializer_list<double>{0.22,0.3,11} 。
{} 和() 的关系
而且狠crazy的一件事是,这里{} 被视为() 并不是强行解释:
struct A{
int x;
double y;
};
A a{12,13};
上面这一语法是正确的,但是我们知道对于结构体A来说,它并没有定义构造函数A(initializer_list<int>) ;但是上面这一初始化语法居然是正确的,所以唯一解释是,A a(12,13); 即调用的隐式默认构造函数进行初始化。
vector<double> a{10};
现在,上面这一语句是调用构造函数vector<T>(size n) 还是vector<T>(initializer_list<T> il) ;答案是后者,我们得明确这一事实:如果类中存在initializer_list的构造函数,那么它会优先将由初始化列表初始化的对象,解释为使用该构造函数,如果不能解释,它才会将{} 视为() ,以调用其他构造函数。
所以说,在今后的编程中能使用{} 就是说我显式使用初始化列表语句,使用() 就代表我使用的是某明确的构造函数。这实现了初始化语句的统一性。
initializer_list 不能进行narrowing
vector<int> a{1.0,2.3};
上述初始化语句是错误的,即说明initializer_list<double> 不能进行隐式narrowing 为initializer_list<int> 。
1.2 使用initializer_list 进行参数传递
如果要在代码中使用initializer_list 模板类,必须包含头文件initializer_list 。 实际上,initializer_list 的更多的用处在于传递可变长的参数(相同类型)。但是很多人可能会使用vector<> 来实现同样的目的,但是initializer_list 更加轻量级,传参更快。
#include<initializer_list>
#include<iostream>
using namespace std;
template<class T>
T sum(initializer_list<T> a)
{
T ret=0;
for(auto &x :a)
ret+=x;
return ret;
}
int main()
{
cout<<sum({1,2,3,5,6,7})<<endl;
}
如上所示就是可变长参数的函数,实际上也可以使用vector 来替代initializer_list 。
而且我们使用按值传递,不采用引用传递,这是因为initializer_list 对象本身很小,没必要传引用,而且这类的设计初衷就是为了传参,而且他这种容器,里面的元素都是const 的。同样的,initializer_list 容器也由一些类方法,例如begin() 和end() 。值得注意的是,在STL容器中,所有的容器的接受initializer_list 的构造函数都是采用的按值传递。
2.其他STL知识
2.1 全排列算法
看一个好玩的STL函数:next_permutation() ,全排列算法,这是一种排序算法,他会对容器中的内容进行排序,生成下一个排序(从小到大紧挨着的下一个排序)。如果发现容器中的元素已经是最大排列,那就返回false,否则返回true
#include<string>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
int main()
{
string a{"abcd"};
vector<int> b{1,2,3,4};
cout<<a<<endl;
while(next_permutation(a.begin(),a.end()))
{
cout<<a<<endl;
}
for(auto&x:b)cout<<x<<" ";
cout<<endl;
while (next_permutation(b.begin(),b.end()))
{
for(auto&x:b)cout<<x<<" ";
cout<<endl;
}
}
abcd
abdc
acbd
acdb
adbc
adcb
bacd
badc
bcad
bcda
bdac
bdca
cabd
cadb
cbad
cbda
cdab
cdba
dabc
dacb
dbac
dbca
dcab
dcba
1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
2 1 3 4
2 1 4 3
2 3 1 4
2 3 4 1
2 4 1 3
2 4 3 1
3 1 2 4
3 1 4 2
3 2 1 4
3 2 4 1
3 4 1 2
3 4 2 1
4 1 2 3
4 1 3 2
4 2 1 3
4 2 3 1
4 3 1 2
4 3 2 1
2.2 vector ,array 和valarray
在C++中提供3中数组模板:vector ,array 和valarray 。但是它们的区别很大,它们完全是由不同的小组设计开发的,其中valarray 不是STL容器,其他两个都是STL容器;vector 是可变长数组,而array 才是真正纯粹上的C数组替代品,它是固定长度的数组,valarray 是一种数值计算的数组。
vector<int> vec(10);
array<int,10> arr;
valarray<int> val(10);
以上是3种初始化语句,可以发现,vector 和valarray 的初始化类似,而array 由于是不可变长的数组,它使用模板参数中的非类型参数来初始化数组长度。
array 的应用情形就是替代C数组,而vector 的应用情形就是可变长数组,而valarray 的数值计算优势明显。
例如,我们想让array 或者vector 中的每一个元素乘以2.5
transform(x.begin(),x.end(),out.begin(),bind1st(multiplies<double>(),2.5));
而使用valarray 就会简单很多
x*=2.5;//x=x*2.5
除此之外,valarray 还重载了符号+ ,- ,% 等运算符。更通用的,它还具有方法apply() 用以接受一个一元函数符。例如x.apply(log) ,即为容器中每一个元素做取对数运算。
而且valarray 的通用性不好,因为它不是STL容器,它甚至没有begin() 和end() 成员。而且它虽然做数值运算狠方便,但是并不意味着它的效率更高,他和vector 的效率是一样的。
所以说,valarray 是不太常用的容器。
|