泛型算法(一)

标准库提供的 find 运算:

// value we'll look for
int search_value = 42;

// call find to see if that value is present
vector::const_iterator result =
find(vec.begin(), vec.end(), search_value);

// report the result
cout << "The value " << search_value
<< (result == vec.end()
? " is not present" : " is present")
<< endl;

只要找到与给定值相等的元素,find 就会返回指向该元素的迭代器。如果没有匹配的元素,find 就返回它的第二个迭代器实参,表示查找失败。



由于 find 运算是基于迭代器的,因此可在任意容器中使用相同的 find 函数查找值。



类似地,由于指针的行为与作用在内置数组上的迭代器一样,因此也可以使用 find 来搜索数组:

int ia[6] = {27, 210, 12, 47, 109, 83};
int search_value = 83;

int *result = find(ia, ia + 6, search_value);

cout << "The value " << search_value
<< (result == ia + 6
? " is not present" : " is present")
<< endl;


如果需要传递一个子区间,则传递指向这个子区间的第一个元素以及最后一个元素的下一位置的迭代器(或指针)。

// only search elements ia[1] and ia[2]
int *result = find(ia + 1, ia + 3, search_value);




所有迭代器都支持自增操作符,从一个元素定位下一个元素,并提供解引用操作符访问元素的值。

除了 第 11.3.5 节将介绍的一个例外情况之外,迭代器还支持相等和不等操作符,用于判断两个迭代是否相等。



元素值的比较,有两种解决方法。默认情况下,find 操作要元素类型定义了相等(==)操作符,算法使用这个操作符比较元素。如果元素类型不支持相等(==)操作符,或者打算用不同的测试方法来比较元素,则可使用第二个版本的 find 函数。这个版本需要一个额外的参数:实现元素比较的函数名字。



这些算法从不使用容器操作,因而其实现与类型无关,元素的所有访问和遍历都通过迭代器实现。实际的容器类型未知(甚至所处理的元素是否存储在容器中也是未知的)。





关键概念:算法永不执行容器提供的操作

泛型算法本身从不执行容器操作,只是单独依赖迭代器和迭代器操作实现。算法基于迭代器及其操作实现,而并非基于容器操作。这个事实也许比较意外,但本质上暗示了:使用“普通”的迭代器时,算法从不修改基础容器的大小。正如我们所看到的,算法也许会改变存储在容器中的元素的值,也许会在容器内移动元素,但是,算法从不直接添加或删除元素。

第 11.3.1 节将介绍标准库提供的另一种特殊的迭代器类:插入器(inserter),除了用于遍历其所绑定的序列之外,还可实现更多的功能。在给这类迭代器赋值时,在基础容器上将执行插入运算。如果算法操纵这类迭代器,迭代器将可能导致在容器中添加元素。但是,算法本身从不这么做。


使用泛型算法必须包含 algorithm 头文件

#include

标准库还定义了一组泛化的算术算法(generalized numeric algorithm),其命名习惯与泛型算法相同。使用这些算法则必须包含 numeric 头文件

#include




除了少数例外情况,所有算法都在一段范围内的元素上操作,我们将这段范围称为“输出范围(input range)”。带有输入范围参数的算法总是使用头两个形参标记该范围。这两个形参是分别指向要处理的第一个元素和最后一个元素的下一位置的迭代器。



只读算法是 accumulate,该算法在 numeric 头文件中定义。假设 vec 是一个 int 型的 vector 对象,下面的代码:

// sum the elements in vec starting the summation with the value 42
int sum = accumulate(vec.begin(), vec.end(), 42);
将 sum 设置为 vec 的元素之和再加上 42。第三个形参则是累加的初值。

用于指定累加起始值的第三个实参是必要的,因为 accumulate 对将要累加的元素类型一无所知,因此,除此之外,没有别的办法创建合适的起始值或者关联的类型。

accumulate 对要累加的元素类型一无所知,这个事实有两层含义。首先,调用该函数时必须传递一个起始值,否则,accumulate 将不知道使用什么起始值。其次,容器内的元素类型必须与第三个实参的类型匹配,或者可转换为第三个实参的类型。



考虑下面的例子,可以使用 accumulate 把 string 型的 vector 容器中的元素连接起来:

// concatenate elements from v and store in sum
string sum = accumulate(v.begin(), v.end(), string(""));

这个函数调用的效果是:从空字符串开始,把 vec 里的每个元素连接成一个字符串。注意:程序显式地创建了一个 string 对象,用作该函数调用的第三个实参。传递一个字符串字面值,将会导致编译时错误。因为此时,累加和的类型将是 const char*,而 string 的加法操作符所使用的操作数则分别是 string 和 const char* 类型,加法的结果将产生一个 string 对象,而不是 const char* 指针。





find_first_of 函数。这个算法带有两对迭代器参数来标记两段元素范围,在第一段范围内查找与第二段范围中任意元素匹配的元素,然后返回一个迭代器,指向第一个匹配的元素。如果找不到元素,则返回第一个范围的 end 迭代器。

假设 roster1 和 roster2 是两个存放名字的 list 对象,可使用 find_first_of 统计有多少个名字同时出现在这两个列表中:

// program for illustration purposes only:
// there are much faster ways to solve this problem
size_t cnt = 0;
list::iterator it = roster1.begin();
// look in roster1 for any name also in roster2
while ((it = find_first_of(it, roster1.end(),
roster2.begin(), roster2.end()))
!= roster1.end()) {
++cnt;
// we got a match, increment it to look in the rest of roster1
++it;
}
cout << "Found " << cnt
<< " names on both rosters" << endl;

上述程序中,roster1 和 roster2 的类型不必精确匹配:roster1 可以是 list 对象,而 roster2 则可以是 vector 对象、 deque 对象或者是其他后面要学到的序列。只要这两个序列的元素可使用相等(==)操作符进行比较即可。如果 roster1 是 list 对象,则 roster2 可以是 vector 对象,因为 string 标准库为 string 对象与 char* 对象定义了相等(==)操作符。



写入到输入序列的一个简单算法是 fill 函数,考虑如下例子:

fill(vec.begin(), vec.end(), 0); // reset each element to 0
// set subsequence of the range to 10
fill(vec.begin(), vec.begin() + vec.size()/2, 10);

如果输入范围有效,则可安全写入。这个算法只会对输入范围内已存在的元素进行写入操作。



不检查写入操作的算法

fill_n 函数带有的参数包括:一个迭代器、一个计数器以及一个值。该函数从迭代器指向的元素开始,将指定数量的元素设置为给定的值。fill_n 函数假定对指定数量的元素做写操作是安全的。初学者常犯的错误的是:在没有元素的空容器上调用 fill_n 函数(或者类似的写元素算法)。

vector vec; // empty vector
// disaster: attempts to write to 10 (nonexistent) elements in vec
fill_n(vec.begin(), 10, 0);

这个 fill_n 函数的调用将带来灾难性的后果。我们指定要写入 10 个元素,但这些元素却不存在——vec 是空的。其结果未定义,很可能导致严重的运行时错误。



对指定数目的元素做写入运算,或者写到目标迭代器的算法,都不检查目标的大小是否足以存储要写入的元素。



使用 back_inserter 的程序必须包含 iterator 头文件。



back_inserter 函数是迭代器适配器。

使用 back_inserter 可以生成一个指向 fill_n 写入目标的迭代器:

vector vec; // empty vector
// ok: back_inserter creates an insert iterator that adds elements to vec
fill_n (back_inserter(vec), 10, 0); // appends 10 elements to vec

现在,fill_n 函数每写入一个值,都会通过 back_inserter 生成的插入迭代器实现。效果相当于在 vec 上调用 push_back,在 vec 末尾添加 10 个元素,每个元素的值都是 0。





copy 带有三个迭代器参数:头两个指定输入范围,第三个则指向目标序列的一个元素。传递给 copy 的目标序列必须至少要与输入范围一样大。假设 ilst 是一个存放 int 型数据的 list 对象,可如下将它 copy 给一个 vector 对象:

vector ivec; // empty vector
// copy elements from ilst into ivec
copy (ilst.begin(), ilst.end(), back_inserter(ivec));

copy 从输入范围中读取元素,然后将它们复制给目标 ivec。

当然,这个例子的效率比较差:通常,如果要以一个已存在的容器为副本创建新容器,更好的方法是直接用输入范围作为新构造容器的初始化式:

// better way to copy elements from ilst
vector ivec(ilst.begin(), ilst.end());


算法的 _copy 版本

// replace any element with value of 0 by 42
replace(ilst.begin(), ilst.end(), 0, 42);

这个调用将所有值为 0 的实例替换成 42。如果不想改变原来的序列,则调用 replace_copy。这个算法接受第三个迭代器实参,指定保存调整后序列的目标位置。

// create empty vector to hold the replacement
vector ivec;

// use back_inserter to grow destination as needed
replace_copy (ilst.begin(), ilst.end(),
back_inserter(ivec), 0, 42);

调用该函数后,ilst 没有改变,ivec 存储 ilst 一份副本,而 ilst 内所有的 0 在 ivec 中都变成了 42。





对容器元素重新排序的算法

假设我们要分析一组儿童故事中所使用的单词。例如,可能想知道它们使用了多少个由六个或以上字母组成的单词。每个单词只统计一次,不考虑它出现的次数,也不考虑它是否在多个故事中出现。要求以长度的大小输出这些单词,对于同样长的单词,则以字典顺序输出。

1. 去除重复

// sort words alphabetically so we can find the duplicates
sort(words.begin(), words.end());
// eliminate duplicate words:
// unique reorders words so that each word appears once in the
// front portion of words and returns an iterator one past the unique range;
// erase uses a vector operation to remove the nonunique elements
vector::iterator end_unique =
unique(words.begin(), words.end());
words.erase(end_unique, words.end());
为了说清楚,使用下面这个简单的故事作为我们的输入:

the quick red fox jumps over the slow red turtle
对于这个输入,我们的程序应该产生如下输出:

1 word 6 characters or longer
- 1.1 调用 sort 后,此 vector 对象的元素按次序排列:(注意,单词 red 和 the 重复出现了。)

1 2 3 4 5 6 7 8 9 10

fox jumps over quick red red slow the the turtle

- 1.2 调用 unique 后,vector 中存储内容是:(end_unique 指向位置 9 的 "the")

1 2 3 4 5 6 7 8 9 10

fox jumps over quick red slow the turtle the turtle

注意,words 的大小并没有改变,依然保存着 10 个元素;只是这些元素的顺序改变了。调用 unique“删除”了相邻的重复值。给“删除”加上引号是因为 unique 实际上并没有删除任何元素,而是将无重复的元素复制到序列的前端,从而覆盖相邻的重复元素。unique 返回的迭代器指向超出无重复的元素范围末端的下一位置。

- 1.3 调用 erase 后,这个函数调用从 end_unique 指向的元素开始删除,直到 words 的最后一个元素也删除掉为止。调用之后,words 存储输入的 8 个不相同的元素。

值得注意的是,对没有重复元素的 vector 对象,调用 erase 也是安全的。如果不存在重复的元素,unique 就会返回 words.end(),此时,调用 erase 的两个实参值相同,都是 words.end()。两个迭代器相等这个事实意味着 erase 函数要删除的范围是空的。删除一段空的范围没有任何作用,所以即使输入中没有重复的元素,我们的程序仍然正确。

2. 定义需要的实用函数

谓词是做某些检测的函数,返回用于条件判断的类型,指出条件是否成立。

我们需要的第一个谓词将用在基于大小的元素排序中,指出第一个字符串是否比第二个短:

// comparison function to be used to sort by word length
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}

另一个所需的谓词函数将判断给出的 string 对象的长度是否不小于 6:

// determine whether a length of a given word is 6 or more
bool GT6(const string &s)
{
return s.size() >= 6;
}
3. 排序算法

// sort words by size, but maintain alphabetic order for words of the same size
stable_sort(words.begin(), words.end(), isShorter);

调用后,words 中的元素按长度大小排序,而长度相同的单词则仍然保持字典顺序:

1 2 3 4 5 6 7 8

fox red the over slow jumps quick turtle

4. 统计长度不小于 6 的单词

现在此 vector 对象已经按单词长度排序,剩下的问题就是统计长度不小于 6 的单词个数。使用 count_if 算法处理这个问题:

vector::size_type wc =
count_if(words.begin(), words.end(), GT6);

执行 count_if 时,首先读取它的头两个实参所标记的范围内的元素。每读出一个元素,就将它传递给第三个实参表示的谓词函数。此谓词函数。此谓词函数需要单个元素类型的实参,并返回一个可用作条件检测的值。count_if 算法返回使谓词函数返回条件成立的元素个数。在这个程序中,count_if 将每个单词传递给 GT6,而 GT6 返回一个 bool 值,如果单词长度不小于 6,则该 bool 值为 true。

5. 将全部程序段放在一起

了解程序的细节之后,下面是完整的程序:

// comparison function to be used to sort by word length
bool isShorter(const string &s1, const string &s2)
{
return s1.size() < s2.size();
}
// determine whether a length of a given word is 6 or more
bool GT6(const string &s)
{
return s.size() >= 6;
}
int main()
{
vector words;
// copy contents of each book into a single vector
string next_word;
while (cin >> next_word) {
// insert next book's contents at end of words
words.push_back(next_word);
}
// sort words alphabetically so we can find the duplicates
sort (words.begin(), words.end());

vector::iterator end_unique =
unique(words.begin(), words.end());
words.erase(end_unique, words.end());
// sort words by size, but maintain alphabetic order for words of the same size
stable_sort(words.begin(), words.end(), isShorter);
vector::size_type wc =
count_if (words.begin(), words.end(), GT6);
cout << wc << " " << make_plural(wc, "word", "s")
<< " 6 characters or longer" << endl;
return 0;
}

你可能感兴趣的:(泛型算法(一))