在C++标准模板库(STL)中,几乎所有容器都提供了非静态成员函数begin()和end(),它们返回指向容器起始和结束的迭代器。这使得遍历容器变得非常直观。例如,使用std::vector时,可以这样遍历:
#include <iostream>
#include <vector>
int main() {
std::vector<int> v;
for (auto it = v.begin(); it != v.end(); ++it) {
std::cout << *it << std::endl;
}
return 0;
}
然而,并非所有用户定义的容器都实现了begin()和end(),这限制了它们与STL算法或其他需要迭代器的用户定义模板函数的兼容性。尤其是使用C数组时,与使用vector相比,使用标准算法的方式大不相同。
#include <iostream>
#include <algorithm>
#include <vector>
int inc(int n) {
return n + 1;
}
int main() {
int a[] = {1, 2, 3, 4, 5};
std::transform(&a[0], &a[0] + sizeof(a)/sizeof(a[0]), &a[0], inc);
std::vector<int> v(&a[0], &a[0] + sizeof(a)/sizeof(a[0]));
std::transform(v.begin(), v.end(), v.begin(), inc);
return 0;
}
非成员版本的begin()和end()方法是可扩展的,这意味着它们可以为任何类型(包括C数组)重载。Herb Sutter在他的文章《Elements of ModernC++Style》中主张,应该始终优先使用非成员版本。它们促进了一致性,允许更通用的编程。
总是使用非成员begin(x)和end(x)(而不是x.begin()和x.end()),因为begin(x)和end(x)是可扩展的,并且可以适应所有容器类型——甚至是数组——而不仅仅是遵循STL风格的容器,它们提供了x.begin()和x.end()成员函数。
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v {1, 2, 3, 4, 5};
for (auto it = begin(v); it != end(v); ++it) {
std::cout << *it << std::endl;
}
std::transform(begin(v), end(v), begin(v), [](int n) {
return n + 1;
});
return 0;
}
对于C数组,可以这样重载begin()和end():
template <typename T, size_t size>
T* begin(T (&c)[size]) {
return &c[0];
}
template <typename T, size_t size>
T* end(T (&c)[size]) {
return &c[0] + size;
}
有了这些重载,可以这样写:
#include <iostream>
#include <algorithm>
int main() {
int a[] = {1, 2, 3, 4, 5};
std::transform(begin(a), end(a), begin(a), [](int n) {
return n + 1;
});
for (auto it = begin(a); it != end(a); ++it) {
std::cout << *it << std::endl;
}
return 0;
}
如果认为非成员begin()和end()破坏了封装性,那么建议阅读Scott Meyers的《How Non-Member Functions Improve Encapsulation》,其中他解释了相反的观点。