【wxWidgets 教程】工具类篇:容器(八)

参考文献:
https://docs.wxwidgets.org/latest/overview_container.html
https://docs.wxwidgets.org/latest/classwx_list_3_01_t_01_4.html
https://docs.wxwidgets.org/latest/classwx_array_3_01_t_01_4.html
https://docs.wxwidgets.org/latest/classwx_array_string.html

前言

本文介绍的 wxWidgets 版本并非是之前一直采用的 3.2 版,这是因为 3.23.3+ 的版本至少在容器上的设计是不一样的,至于哪些地方存在区别,下文会详细解答。

一、容器简介

wxWidgets以前使用自己的容器类,但现在已经与标准库更为接近,并建议在新代码中优先使用标准库。

  1. 历史背景:由于早期没有标准库,wxWidgets 创造了自己的容器类。
  2. 版本更新:从 3.3.0 版本开始,wxWidgets 的容器如 wxList<T>wxArray<T>std::list<T*>std::vector<T> 变得相似。
  3. 使用示例:尽管有些函数还显示使用 wxWidgets 容器,我们可以像使用标准库那样处理。例如,someFrame->GetChildren() 返回 wxWindowList,我们可以像使用 std::list<wxWindow*> 那样使用它。
  4. 编程建议:虽然某些函数可能需要我们使用 wxWidgets 的容器,但新代码应使用标准库,因为 wxWidgets 的容器主要为了兼容旧代码。

二、遗留的容器类

wxWidgets 有一系列的传统容器类,使用宏进行定义,虽然有些复杂,但只要按照上面的建议把wxWidgets 容器当做标准容器来用,我们大多数情况下不需要深入了解它们。

1. 目的:

这部分介绍的传统容器类主要是为了维护使用它们的旧代码

2. 宏定义:

  • 这些类使用宏机制来定义基于通用实现的类型安全类。
  • 通常先使用一个宏声明一个特定的“实例”,然后使用另一个宏定义它。例如,对于链表有 WX_DECLARE_LIST()WX_DEFINE_LIST(),对于包含对象的数组有 WX_DECLARE_OBJARRAY()WX_DEFINE_OBJARRAY()
  • 存储基本类型的数组类(如 int)不需要分开声明和定义,所以只存在WX_DEFINE_ARRAY_INT(),没有相应的 DECLARE 宏。

3. 宏的用途:

  • DECLARE 宏用于在头文件中声明一个新的容器类,不需要完整声明所包含的类型。
  • DEFINE 宏必须在容器元素类的完整声明在作用域内的地方使用(即不仅仅是前向声明)。

4. 预定义容器:

  • wxWidgets 预定义了一些常用的容器类。
  • wxList 定义为与很早期版本的 wxWidgets 兼容,作为一个包含wxObjects的列表,wxStringList 是一个包含C风格字符串char *)的列表。
  • 还有一些预定义的数组类,如 wxArrayInt, wxArrayLong, wxArrayPtrVoidwxArrayString,它们与 std::vector 的实例化对应于 intlongvoid*wxString

三、新的实现(3.3.0+)

自 wxWidgets 3.3.0 起,所有的传统容器类都基于标准库类进行实现。虽然大部分与 wxWidgets 之前的版本相兼容,但仍有差异。为了解决这些兼容性问题,可以选择使用旧的实现,或者更新代码使其适应新的实现。

1. 实现变更:

  • wxWidgets 3.3.0 的容器类基于标准库实现,与之前版本的 wxWidgets 默认使用的实现有所不同。

2. 切换回旧实现:如果遇到兼容性问题,可以:

  • 手动编辑 wx/msw/setup.h 来更改 wxUSE_STD_CONTAINERS 选项为 0
  • 在 Unix-like 系统下编译 wxWidgets 时使用 --disable-std_containers 选项。
  • 在使用 CMake 构建系统时使用 -DwxUSE_STD_CONTAINERS=OFF

3. 更新代码:

  • 更好的方法是更新旧代码使其与新的容器类实现相兼容。这样做的话,代码既能适用于新的实现,也能适用于旧的实现。
  • 例如,遍历列表时,应使用 wxList::compatibility_iterator 代替 wxList::Node*
  • wxSortedArrayStringwxArrayString 现在是分开的类,前者不再从后者派生。需要将排序数组转换为常规数组时,必须复制所有元素
  • 由于 std::vector<bool> 与通用的 std::vector<> 类的差异,WX_DEFINE_ARRAY_INT(bool) 不能使用。建议直接使用 std::vector<bool>wxArrayInt。这个差异对于熟悉C++的小伙伴都应该清楚,如果不懂的可以去查查~~

四、“wxList<T>” 容器

wxList<T> 是一个传统的类,与 std::list<T*> 类似,但有其特有的特点和用法。

1. 基本信息:

  • wxList<T> 是一个传统类,与标准库中的 std::list 类似。
  • 这个类总是存储对象的指针,而不是对象本身,即其 value_type 定义为 T*
  • 默认情况下,它不管理其项目的内存只有在调用 wxList<T>::DeleteContents 后删除对象时,才会销毁对象。

2. 模板与宏:

  • wxList<T> 不是真正的模板,需要使用宏 WX_DECLARE_LISTWX_DEFINE_LIST 声明和定义每一个 wxList<T> 类。
  • 希望(注意目前只是一个希望)未来提供真正的模板类,同时支持 STL std::list 和旧的 wxList API。

3. 使用方法:

  • 可以使用标准库的 std::list 文档作为参考来使用这个类。
  • 下面提供了两种方法来遍历列表:STL 语法和旧 wxList 类的遗留 API。
// STL语法
MyList::iterator iter;
for (iter = list.begin(); iter != list.end(); ++iter) {
    MyListElement *current = *iter;
    ... // 处理当前元素
}

// 旧`wxList`类的遗留API
MyList::compatibility_iterator node = list.GetFirst();
while (node) {
    MyListElement *current = node->GetData();
    ... // 处理当前元素
    node = node->GetNext();
}

4. 兼容性:

  • wxListwxStringList 类仍然被定义,但它们已被弃用,并将在未来版本中完全消失
  • 特别是不建议使用 wxStringList,因为它不仅不安全,而且效率比 wxArrayString低得多

5. 模板参数:

  • T:存储在 wxList 节点中的类型。

五、“wxArray<T>” 容器

wxArray<T> 是一个动态数组类,仅用于兼容性,不建议在新代码中使用。

1. 基本信息:

  • wxArray<T> 是一个传统的动态数组,主要是为了与旧版本代码兼容
  • 这是一个类似C数组的类型安全数据结构,成员访问时间是恒定的,与列表的线性时间不同。
  • 尽管如此,这些数组是动态的,如果没有足够的内存添加新元素,它们会自动分配更多的内存。此外,它们仅在调试模式下进行索引值的范围检查,所以务必在调试模式下编译应用程序。
  • 与某些其他语言的数组不同,尝试访问数组范围之外的元素并不会自动扩展数组,而是在调试版本触发断言失败,而在发布版本中可能会崩溃

2. 效率:

  • wxArray<T> 设计为在运行时速度内存消耗可执行文件大小方面都相对高效
  • 项目访问速度是常数,比链接列表(wxList)更高效。
  • 向数组中添加项目也在近乎恒定的时间内实现,但代价是预先分配内存。关于优化 wxArray 内存使用的一些建议可以在“内存管理”功能部分找到。

3. wxWidgets的数组种类:

  • wxArray:适合存储整数类型和指针,它不将它们视为对象。这个类有一个严重的局限性:它只能用于存储整数类型或指针。试图使用大于 sizeof(long) 的对象的 wxArray 会触发运行时断言失败。
  • wxSortedArray:当在数组中频繁搜索时应使用的 wxArray 变体。它需要定义一个额外的函数来比较数组元素类型的两个元素,并始终按此函数的排序顺序存储其项目。
  • wxObjArray:将其元素视为“对象”的类。它可以在从数组中删除它们时删除它们,并使用对象的复制构造函数复制它们。

4. 如何定义数组:

  • 使用宏定义数组类。这是使用以下部分中的宏完成的:WX_DEFINE_ARRAY()WX_DEFINE_SORTED_ARRAY()WX_DECLARE_OBJARRAY()WX_DEFINE_OBJARRAY()
  • 对于基本类型,应使用与值的 sizeof 对应的宏,例如 WX_DEFINE_ARRAY_INT()
  • 注意,使用这些宏定义的数组迭代器默认定义的操作符“->”只有在数组元素类型不是指针本身时才有意义。

5. 预定义的数组类型:

  • wxArrayShort
  • wxArrayInt
  • wxArrayDouble
  • wxArrayLong
  • wxArrayPtrVoid

要使用它们,只需要包含 dynarray.h,不需要任何宏。

6. 内存管理:

  • 自动数组内存管理相当简单:数组开始时预先分配一些最小量的内存(由 WX_ARRAY_DEFAULT_INITIAL_SIZE 定义),当新项耗尽已分配的内存时,它会重新分配内存,增加当前分配量的 50%,但不超过由 ARRAY_MAXSIZE_INCREMENT 常量定义的最大数量。
  • 这可能导致一些内存被浪费(在最坏的情况下,即当前实现中的 4Kb),因此提供了 Shrink() 函数来释放额外的内存。如果我们提前知道要在数组中放入多少项,Alloc() 函数也可能非常有用,它将防止数组代码重新分配多次不必要的内存

7. 总结和建议:

尽管 wxArray<T> 提供了一个动态数组的实现,但由于它主要是为了向后兼容而设计的,因此不建议在新的项目中使用。对于新项目,建议使用标准库中的容器,如 std::vector<T>

六、“wxArrayString” 容器

wxArrayString 是一个与 std::vector<wxString> 类似的传统类

1. 基本信息:

  • wxArrayString 是一个传统类,与标准库中的 std::vector<wxString> 类似。
  • 虽然通常不应在新代码中使用这些传统的容器类,但在传递多个项目给 wxWidgets API 中的各种函数时,仍然需要它们,尤其是各种 GUI 控件类的构造函数。
  • 通常,即使在这种情况下,通常也不需要显式地使用它,因为当使用初始化器列表或字符串向量时,wxArrayString 会被隐式地创建。例如,可以直接传递其中之一,替代 wxArrayString
// wxListBox 构造函数的文档说明它接受 wxArrayString,但可以直接传递一个初始化器列表给它:
auto listbox = new wxListBox(parent, wxID_ANY,
                             wxDefaultPosition, wxDefaultSize,
                             { "some", "items", "for", "the", "listbox" });

// 同样,如果程序中的其他地方已经有一个填满字符串的向量,可以直接传递它:
std::vector<std::string> countries = GetListOfCountries();
auto choices = new wxChoice(parent, wxID_ANY,
                            wxDefaultPosition, wxDefaultSize,
                            countries);

2. 使用方法:

  • 使用返回此类对象的 wxWidgets 函数时,可以将其视为 std::vector<wxString> 来使用,因为这个类具有所有向量方法,或者使用它的 AsVector() 真正地将其转换为这样的向量。
wxArrayString files;
wxDir::GetAllFiles("/some/path", &files);

// 可以使用通常的访问器:
if ( !files.empty() ) {
    auto first = files[0];
    auto total = files.size();
    ...
}

// 也可以像遍历向量一样遍历它
for ( const wxString& file: files ) {
    ...
}

// 或者只是将其转换为“真正的”向量:
const std::vector<wxString>& vec = files.AsVector();

七、“wxVector<T>” 容器

wxVector<T> 是一个模板类,实现了 std::vector 类的大部分功能,可以像它一样使用。

1. 基本信息:

  • wxVector<T> 是一个模板类,实现了标准库中的 std::vector 的大部分功能。
  • 如果 wxWidgets 以 STL 模式编译,则 wxVector 将仅仅是 std::vector 的一个 typedef
  • 就像 std::vector 一样,存储在 wxVector<T> 中的对象需要是可分配的,但不必是 “默认可构造的”。

2. 使用方法:

  • 请参考 STL 文档以获取更多信息。

八、“wxNode<T>” 节点结构

wxNode<T> 是在链表(参见 wxList)及其派生类中使用的节点结构。

1. 基本信息:

  • wxNode<T> 是用于链表中的节点结构
  • 永远不应直接使用 wxNode<T> 类,因为它与无类型的(void *)数据一起工作,这是不安全的。我们应该使用由 WX_DECLARE_LISTWX_DEFINE_LIST 宏自动定义的 wxNode<T>-派生类,如 wxList 文档中所述(请参见其中的示例)。
  • 请注意,虽然有一个名为 wxNode 的类,但它只是为了向后兼容而定义的,强烈不建议使用这个类。

2. 注意事项:

  • 在下面的文档中,类型 T 应被视为“模板”参数:这是存储在链表中的数据类型,或者换句话说,是 WX_DECLARE_LIST 宏的第一个参数。此外,尽管 wxNode 不是一个真的模板类,但将其写为 wxNodeT 有助于将其视为模板类。

3. 模板参数:

  • T:存储在 wxNode 中的类型。

九、总结

在本文中,我们涵盖了多个关键点,深入介绍了 wxWidgets 容器类的不同方面:

  • 传统容器类:早期版本的 wxWidgets 引入了自己的容器类,如 wxListwxArray,用于维护旧代码的兼容性。
  • 新的实现(3.3.0+):从 wxWidgets 3.3.0 版本开始,容器类基于标准库实现,建议在新代码中使用标准库容器,但也可以更新代码以适应新实现。
  • wxList<T> 容器:介绍了 wxList<T>,类似于 std::list<T*>,以及如何使用它来遍历列表。
  • wxArray<T> 容器:介绍了 wxArray<T>,作为传统的动态数组,建议在新项目中使用标准库的容器,如 std::vector
  • wxArrayString 容器:讨论了 wxArrayString,类似于 std::vector<wxString>,用于在 wxWidgets API 中传递多个字符串。
  • wxVector<T> 容器:介绍了 wxVector<T>,作为模板类,实现了大部分 std::vector 的功能。
  • wxNode<T> 节点结构:说明了 wxNode<T>,用于链表的节点结构,但应避免直接使用它。

综上所述,根据项目需求和代码现状,选择适当的容器类是提高代码效率和可维护性的关键一步。无论是采用传统的 wxWidgets 容器类还是使用标准库容器,都需要权衡各自的优劣并做出明智的决策。


【wxWidgets 教程】工具类篇:容器(八) 至此完毕,欢迎大家指正!还请大家点点赞,给我点动力~~

上一篇:【wxWidgets 教程】工具类篇:wxString(七)
下一篇:【wxWidgets 教程】工具类篇:日期和时间(九)

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xiao_Ley

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值