集合 (C++/CX)

在 C++/CX 程序中,你可以任意使用标准模板库 (STL) 容器或任何其他用户定义的集合类型。 但是,当你跨 Windows 运行时应用程序二进制接口 (ABI) 边界来回传递集合时,例如传递到 XAML 控件或 JavaScript 客户端时,必须使用 Windows 运行时集合类型。

Windows 运行时定义集合和相关类型的接口,并且,C++/CX 在 collection.h 头文件中提供具体的 C++ 实现。 下图显示了各个集合类型之间的关系:

C++ CX 继承树的集合类型图解。

向量用法

当类必须将序列容器传递给另一个 Windows 运行时组件时,请使用 Windows::Foundation::Collections:: IVector<T> 作为参数或返回类型,将 Platform::Collections::Vector<T> 用作具体实现。 如果你尝试在公共返回值或参数中使用 Vector 类型,则将引发编译器错误 C3986。 可以通过将 Vector 更改为 IVector来修复该错误。

重要

如果您要在自己的程序中传递序列,请使用 Vectorstd::vector ,因为它们比 IVector更高效。 在通过 ABI 传递容器时,仅使用 IVector

Windows 运行时类型系统不支持交错数组的概念,因此无法将 IVector<Platform::Array<T>> 作为返回值或方法参数传递。 要跨 ABI 传递交错数组或一系列序列,请使用 IVector<IVector<T>^>

Vector<T> 提供添加、移除和访问集合项所需的方法,并且,它可以隐式转换为 IVector<T>。 还可以对 Vector<T>的实例使用 STL 算法。 下面的示例演示一些基本用法。 此处的 begin 函数end 函数 来自 Platform::Collections 命名空间,而不是 std 命名空间。

#include <collection.h>
#include <algorithm>
using namespace Platform;
using namespace Platform::Collections;
using namespace Windows::Foundation::Collections;


void Class1::Test()
{
    Vector<int>^ vec = ref new Vector<int>();
    vec->Append(1);
    vec->Append(2);
    vec->Append(3);
    vec->Append(4);
    vec->Append(5);


    auto it = 
        std::find(begin(vec), end(vec), 3);

    int j = *it; //j = 3
    int k = *(it + 1); //or it[1]

    // Find a specified value.
    unsigned int n;         
    bool found = vec->IndexOf(4, &n); //n = 3

    // Get the value at the specified index.
    n = vec->GetAt(4); // n = 3

    // Insert an item.
    // vec = 0, 1, 2, 3, 4, 5
    vec->InsertAt(0, 0);

    // Modify an item.
    // vec = 0, 1, 2, 12, 4, 5,
    vec->SetAt(3, 12);

    // Remove an item.
    //vec = 1, 2, 12, 4, 5 
    vec->RemoveAt(0);

    // vec = 1, 2, 12, 4
    vec->RemoveAtEnd();

    // Get a read-only view into the vector.
    IVectorView<int>^ view = vec->GetView();
}

如果你具有使用 std::vector 的现有代码,而你想在 Windows 运行时组件中重用它,只需使用 Vector 构造函数之一即可,它采用一个 std::vector 或一对迭代器在你将集合传递过 ABI 之处构造一个 Vector。 下面的示例演示如何从 Vectorstd::vector移动构造函数用于高效初始化。 移动操作完成后,原始的 vec 变量不再有效。

//#include <collection.h>
//#include <vector>
//#include <utility> //for std::move
//using namespace Platform::Collections;
//using namespace Windows::Foundation::Collections;
//using namespace std;
IVector<int>^ Class1::GetInts()
{
    vector<int> vec;
    for(int i = 0; i < 10; i++)
    {
        vec.push_back(i);
    }    
    // Implicit conversion to IVector
    return ref new Vector<int>(std::move(vec));
}

如果您有一个必须在将来某个时间点通过 ABI 传递的字符串向量,则您必须确定最初是将字符串创建为 std::wstring 类型还是 Platform::String^ 类型。 如果你必须对字符串执行大量处理,请使用 wstring。 否则,请将字符串创建为 Platform::String^ 类型,这可避免稍后转换它们的开销。 你还必须确定是将这些字符串内部置于 std::vector 还是 Platform::Collections::Vector 中。 作为一种常规做法,先使用 std::vector ,然后在通过 ABI 传递容器时从其创建一个 Platform::Vector

Vector 中的值类型

要存储在 Platform::Collections::Vector 中的任何元素都必须隐式支持相等比较,或者通过使用你提供的自定义 std::equal_to 比较器。 所有引用类型和所有标量类型都隐式支持相等性比较。 对于非标量值类型(例如 Windows::Foundation::DateTime),或是需要进行自定义比较(例如 objA->UniqueID == objB->UniqueID),您必须提供自定义函数对象。

VectorProxy 元素

Platform::Collections::VectorIteratorPlatform::Collections::VectorViewIterator 允许在 IVector<T> 容器中使用 range forstd::sort 等循环和算法。 但是,通过C++指针解引用无法访问IVector元素;只能通过GetAtSetAt方法访问这些元素。 因此,这些迭代器使用代理类 Platform::Details::VectorProxy<T>Platform::Details::ArrowProxy<T>,按照标准库的要求,通过 *、-> 和 [] 运算符提供对各个元素的访问。 严格来说,给定 IVector<Person^> vec*begin(vec) 的类型为 VectorProxy<Person^>。 不过,代理对象对于你的代码几乎始终是透明的。 我们不讨论这些代理对象,因为它们仅由迭代器在内部使用,但了解该机制的工作原理将非常有用。

for 容器使用基于范围的 IVector 循环时,请使用 auto&& 以使迭代器变量正确绑定到 VectorProxy 元素。 如果使用 auto&,将引发编译器警告 C4239 ,并在 VectoryProxy 警告文本中提及。

下图演示了针对 range forIVector<Person^>循环。 请注意,执行操作在第 64 行的断点处停止。 “快速监视”窗口显示迭代器变量p实际上是一个VectorProxy<Person^>,它具有m_vm_i成员变量。 不过,当调用 GetType 时,它将返回与 Person 实例 p2相同的类型。 要点是,虽然 VectorProxyArrowProxy 可能出现在 QuickWatch、调试器中的某些编译器错误或其他位置,但通常不必为它们显式编写代码。

在基于循环的范围中调试 VectorProxy 的屏幕截图。

必须针对代理对象进行编码的一种情况是,你必须对元素执行 dynamic_cast ,例如你在 UIElement 元素集合中寻找特定类型的 XAML 对象。 在这种情况下,必须先将元素强制转换为 Platform::Object^ ,然后执行动态强制转换:

void FindButton(UIElementCollection^ col)
{
    // Use auto&& to avoid warning C4239
    for (auto&& elem : col)
    {
        Button^ temp = dynamic_cast<Button^>(static_cast<Object^>(elem));
        if (nullptr != temp)
        {
            // Use temp...
        }
    }
}

映射用法

此示例演示如何在 Platform::Collections::Map 中插入项并查找它们,然后返回 Map 为只读 Windows::Foundation::Collections::IMapView 类型。

//#include <collection.h>
//using namespace Platform::Collections;
//using namespace Windows::Foundation::Collections;
IMapView<String^, int>^ Class1::MapTest()
{
    Map<String^, int>^ m = ref new Map<String^, int >();
    m->Insert("Mike", 0);
    m->Insert("Dave", 1);
    m->Insert("Doug", 2);
    m->Insert("Nikki", 3);
    m->Insert("Kayley", 4);
    m->Insert("Alex", 5);
    m->Insert("Spencer", 6);

   // PC::Map does not support [] operator
   int i = m->Lookup("Doug");
   
   return m->GetView();
   
}

通常,对于内部映射功能,出于性能考虑,应尽可能首选 std::map 类型。 如果必须跨 ABI 传递容器,请从 std::map 构造一个 Platform::Collections::Map,并将其作为 Windows::Foundation::Collections::IMap 返回。 如果你尝试在公共返回值或参数中使用 Map 类型,则将引发编译器错误 C3986。 可以通过将 Map 更改为 IMap来修复该错误。 在某些情况下,例如,如果您不需要大量查找或插入,而需要通过 ABI 频繁传递集合,则在一开始使用 Platform::Collections::Map 可能成本较低,同时可避免转换 std::map的开销。 在任何情况下,应避免在 IMap 上执行查找和插入操作,因为这些是三种类型中性能最差的操作。 仅在你通过 ABI 传递容器之处转换为 IMap

Map 中的值类型

Platform::Collections::Map 中的元素是有序的。 要存储在某个元素 Map 中的任何元素都必须支持与严格弱排序的不相比较,无论是隐式的,还是通过使用你提供的自定义 stl::less 比较器。 标量类型隐式支持比较。 对于非标量类型(如 Windows::Foundation::DateTime)或自定义比较(如 objA->UniqueID < objB->UniqueID),你必须提供自定义比较器。

集合类型

集合分为四种类别:序列集合和关联集合各自的可修改版本和只读版本。 此外,C++/CX 通过提供简化集合访问的三个迭代器类增强集合。

可修改集合的元素可以更改,但只读集合的元素(称为 视图)只能读取。 可以使用迭代器或集合的Platform::Collections::VectorPlatform::Collections::VectorViewVector::GetAt方法和索引来访问集合的元素。 可以使用集合的 Map::Lookup 和键来访问关联集合的元素。

Platform::Collections::Map 类
可修改的关联集合。 映射元素为键/值对。 支持查找键以检索其关联值,也支持遍历所有键值对。

MapMapView<K, V, C = std::less<K>>上模板化,因此,你可以自定义比较器。 此外, VectorVectorView<T, E = std::equal_to<T>> 上模板化,以便你可以自定义 IndexOf()的行为。 这通常对于值结构的 VectorVectorView 非常重要。 例如,若要创建 Vector<Windows::Foundation::DateTime>,必须提供自定义比较运算符,因为 DateTime 不会重载 == 运算符。

Platform::Collections::MapView 类
Map的只读版本。

Platform::Collections::Vector 类
可修改的序列集合。 Vector<T> 支持常量时间随机访问和摊销常量时间 追加 操作。

Platform::Collections::VectorView 类
Vector的只读版本。

Platform::Collections::InputIterator 类
满足 STL 输入迭代器要求的 STL 迭代器。

Platform::Collections::VectorIterator 类
满足 STL 可变随机访问迭代器要求的 STL 迭代器。

Platform::Collections::VectorViewIterator 类
满足 STL const 随机访问迭代器要求的 STL 迭代器。

begin() 和 end() 函数

为了简化 STL 的处理 VectorVectorViewMapMapView和任意 Windows::Foundation::Collections 对象的使用,C++/CX 支持 开始函数结束函数 非成员函数的重载。

下表列出可用迭代器和函数。

迭代器 函数
Platform::Collections::VectorIterator<T>

(内部存储 Windows::Foundation::Collections:: IVector<T> 和 int.)
开始/ 结束Windows::Foundation::Collections:: IVector<T>
Platform::Collections::VectorViewIterator<T>

(内部存储IVectorView<T>^和int。)
开始/ 结束IVectorView<T>^)
Platform::Collections::InputIterator<T>

(内部存储 IIterator<T>^ 和 T。)
开始/ 结束IIterable<T>
Platform::Collections::InputIterator<IKeyValuePair<K, V>^>

(内部存储 IIterator<T>^ 和 T。)
开始/ 结束IMap<K,V>.
Platform::Collections::InputIterator<IKeyValuePair<K, V>^>

(内部存储 IIterator<T>^ 和 T。)
开始/ endWindows::Foundation::Collections::IMapView

集合更改事件

VectorMap 支持 XAML 集合中的数据绑定,方式是通过实现在更改或重置集合对象,或在插入、移除或更改集合的任何元素时发生的事件。 您可编写自己的支持数据绑定的类型,尽管因这些类型是密封类型而导致无法从 MapVector 继承。

Windows::Foundation::Collections::VectorChangedEventHandlerWindows::Foundation::Collections::MapChangedEventHandler 委托指定用于集合更改事件的事件处理程序的签名。 Windows::Foundation::Collections::CollectionChange 公共枚举类和 Platform::Collection::Details::MapChangedEventArgsPlatform::Collections::Details::VectorChangedEventArgs ref 类存储事件参数以确定导致事件的原因。 *EventArgs 类型是在 Details 命名空间中定义的,因为你在使用 MapVector 时无需显式构造或使用它们。

另请参阅

类型系统
C++/CX 语言参考
命名空间参考