7.15 非STL容器
在标准库中有两种“非STL”容器:bitset和valarray。[1]之所以称之为“非STL”,是因为这两种容器中没有一种能够完全满足STL容器的要求。在本章前部包括了bitset容器,将二进制位打包成整数并且不允许对其成员进行直接寻址。valarray模板类是一个类-vector的容器,
该容器对有效率的数值的计算进行了优化。这两个容器都不提供迭代器。虽然可以用非数值类型来实例化valarray,但是它拥有一些用于操作数值型数据的数学函数,比如sin、cos、tan等等。
这里有一个用来打印valarray中元素的工具:
valarray的大多数函数和运算符都将valarray作为一个整体来进行操作,就像下面的例子阐明的一样:
valarray类提供了一个构造函数,该构造函数接受一个目标类型的数组和数组中的元素计数作为其参数来初始化一个新的valarray。成员函数shift()将每个valarray元素向左移动一个位置(或者,如果它的参数是个负值则向右移动),并且向移走元素后的空位中填入该类型的默认值(在这种情况下是0)。还有一个成员函数cshift(),它进行循环移动(或者称为“旋转”)。所有数学运算符和函数都进行了重载以便用来操作valarray,二进位运算符要求valarray具有相同类型和大小的参数。像transform()算法一样,成员函数apply()对每一个元素应用一个函数,但是结果被收集到一个结果valarray中。关系运算符返回大小匹配的valarray<bool>实例,该实例显示了元素与元素逐个对比的结果,例如上面的eq。大多数操作返回一个新的结果数组,但是很少,由于显而易见的原因,其中的一些操作返回一个数值,比如min()、max()、和sum()。
对valarray可以做的最有趣的事情就是引用其元素的一个子集,不仅可以提取信息,而且可以更新这些信息。valarray的一个子集被称为一个切片(slice),某些运算符使用切片来做它们的工作。下面的简单程序就使用了切片:
一个slice对象接受3个参数:起始索引、要提取的元素合计数以及“跨距”,即两个用户感兴趣的元素之间的间距。切片可以用来作为一个现有valarray的索引,并且返回一个包含了被提取元素的新的valarray。比如一个bool型的valarray,它是由表达式v>6返回的值,也可以作为另一个valarray的索引;那些符合true值所在位置的元素都被提取出来。就像看到的那样,也可以将切片和掩码作为索引用在一个赋值操作的左边。一个gslice对象(即“generalized slice”,通用切片)就像一个切片,除了合计数和跨距参数是它们自己的数组之外,这意味着可以将一个valarray解释为一个多维数组。上面的例子从v中提取了一个2乘3的数组,从v中下标为0的元素开始,到相距6个元素的位置建立第1维的元素数,所做的其他事情就是在各维中每相距两个元素的位置提取一个数,这样就有效地从v中提取出了一个矩阵:
以下是这个程序的完整输出:
在矩阵乘法中可以发现一个使用切片的实际例子。考虑如何使用数组来编写两个整数矩阵相乘的函数。
这个函数将一个m乘n的矩阵a和一个p乘q的矩阵b相乘,这里n和p应当相等。就像读者可以看到的,没有什么事情像valarray那样,必须为每个矩阵的第2维确定最大值,因为数组中的每个位置都是静态决定了的(固定的)。而且也很难通过值返回一个结果数组,因此调用者通常传递一个结果数组作为参数。
使用valarray,不仅可以传递任意大小的矩阵,而且可以容易地处理任意类型的矩阵,并且通过传值的方式返回结果。其实现方式如下所示:
在结果矩阵c中,每一个条目都是a中的某一行与b中的某一列的点积。通过使用切片,可以将这些行和列作为valarray提取出来,并使用全局的运算符和valarray提供的sum()函数进行简洁地计算。作为结果的valarray在运行时进行计算;没有必要担心数组维数的静态限制。在这里确实需要自行计算位置[i][j]的线性偏移量(参见上面的公式ibcols+j),但是为了自由地确定valarray的大小和类型,这是值得的。
[1]在前面已经提到过,在某种程度上讲vector<bool>特化也是一个非STL容器。