参考文献:
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.2 和 3.3+ 的版本至少在容器上的设计是不一样的,至于哪些地方存在区别,下文会详细解答。
一、容器简介
wxWidgets以前使用自己的容器类,但现在已经与标准库更为接近,并建议在新代码中优先使用标准库。
- 历史背景:由于早期没有标准库,wxWidgets 创造了自己的容器类。
- 版本更新:从 3.3.0 版本开始,wxWidgets 的容器如
wxList<T>
和wxArray<T>
与std::list<T*>
和std::vector<T>
变得相似。 - 使用示例:尽管有些函数还显示使用 wxWidgets 容器,我们可以像使用标准库那样处理。例如,
someFrame->GetChildren()
返回wxWindowList
,我们可以像使用std::list<wxWindow*>
那样使用它。 - 编程建议:虽然某些函数可能需要我们使用 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
,wxArrayPtrVoid
和wxArrayString
,它们与std::vector
的实例化对应于int
、long
、void*
和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*
。 wxSortedArrayString
和wxArrayString
现在是分开的类,前者不再从后者派生。需要将排序数组转换为常规数组时,必须复制所有元素。- 由于
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_LIST
和WX_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. 兼容性:
wxList
和wxStringList
类仍然被定义,但它们已被弃用,并将在未来版本中完全消失。- 特别是不建议使用
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_LIST
和WX_DEFINE_LIST
宏自动定义的wxNode<T>
-派生类,如wxList
文档中所述(请参见其中的示例)。 - 请注意,虽然有一个名为
wxNode
的类,但它只是为了向后兼容而定义的,强烈不建议使用这个类。
2. 注意事项:
- 在下面的文档中,类型
T
应被视为“模板”参数:这是存储在链表中的数据类型,或者换句话说,是WX_DECLARE_LIST
宏的第一个参数。此外,尽管wxNode
不是一个真的模板类,但将其写为wxNodeT
有助于将其视为模板类。
3. 模板参数:
T
:存储在wxNode
中的类型。
九、总结
在本文中,我们涵盖了多个关键点,深入介绍了 wxWidgets 容器类的不同方面:
- 传统容器类:早期版本的 wxWidgets 引入了自己的容器类,如
wxList
和wxArray
,用于维护旧代码的兼容性。 - 新的实现(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 教程】工具类篇:日期和时间(九)