我有一個解決方案,我在C中實現了一段時間。它的設置深度達到8層,但技術上沒有深度限制。它適用於嵌入式應用程序,因此它爲所有應用程序使用靜態內存,但可以輕鬆修改它以使用動態分配。您可以在my github page上找到源代碼,但我會在這裏稍微介紹一下。
菜單是圍繞作爲二叉樹(b-tree)實現的k-tree樹(k-tree)構建的。與常規k-樹不同,節點可以有多個孩子,這對於菜單系統來說是有意義的,因爲系統是內存和資源受限的,二叉樹更多。它允許靜態分配節點,而不使用固定大小的數組來存儲子節點。這個實現起初有點令人困惑,但實際上在一些資源敏感的系統(如Linux內核)中使用。
定期K-樹表示爲:
struct t {
int n;
int numKids;
struct t **kids;
}
和結果樹struture樣子:
TOP
|
[ list of kids ]
| | |
k1 k2 k3
|
|
[list of kids]
| | |
k1.1 k1.2 k1.3
,如果你有事情malloc之類,但一個嵌入式 系統上這表示工作在使用malloc的時候,如果你想添加另一個孩子,除了必須在結構中記錄數組中孩子的數量 之外,還可以使用有限長度的靜態數組。
相反,下面的結構被用於:
struct t {
int n;
struct t *firstChildNode;
struct t *firstSiblingNode;
struct t *trueParentNode;
struct t *fakeParentNode;
};
和樹表示看起來像:
[TOP NODE ] <-
----------- \
^|^ \
|| | \ fP
|| |tP, fP \
|| |_______ fS \__________ fS
|| [NODE 2]------>[NODE 2.1]---------->NULL
|| ------- <------ ---------
|| | tP |
|| |fC |fC
||fC V V
|| NULL NULL
||
|tP, fP
||
|V_____
[NODE1]
-------
^
|tP, fp
|_________ fS
[NODE 1.1] -----> NULL
|
|fC
|
V
NULL
其中:
- FP = fakeParentNode - 這個指針用於確定實際的父親,因爲它會出現在經典的k-tree樹中。
- tP = trueParentNode - 該指針用於顯示此節點實際連接的位置,因爲它可以是另一個 節點或同級的子節點。
- fC = firstChildNode - 指向第一個子節點的指針。
- fS = firstSiblingNode - 指向第一個兄弟節點的指針。
從任何一個節點,你只能訪問第一個孩子或第一個兄弟,使之成爲一個經典的B樹。從第一個孩子,你可以訪問它的 兒童和兄弟姐妹。同樣的第一個兄弟指針。這樣所有的節點都被存儲在一個列表中,該列表可以包含靜態分配的 結構。真假父指針用於輕鬆追蹤每個子節點的 祖先。假父指針用於在此b樹上映射經典k樹結構。
有幾個優點這看似混亂的方法:
- 每個節點是一個固定的大小,並且可以靜態地分配。
- 表示子節點不需要數組。
- 許多算法可以很容易地表達,因爲它只是一個B樹。
菜單的實現是有限的,因爲刪除節點的功能是不存在的,因爲這似乎並沒有成爲一個菜單的必要功能。添加菜單或菜單項非常簡單。見menu_top.c例如
這種設計的https://blog.mozilla.org/nnethercote/2012/03/07/n-ary-trees-in-c/
代碼ktree.c啓發:
/* Includes ------------------------------------------------------------------*/
#include "ktree.h"
#include "qp_port.h" /* for QP support */
#include "project_includes.h"
/* Compile-time called macros ------------------------------------------------*/
Q_DEFINE_THIS_FILE /* For QSPY to know the name of this file */
DBG_DEFINE_THIS_MODULE(DBG_MODL_DBG);/* For debug system to ID this module */
/* Private typedefs ----------------------------------------------------------*/
/* Private defines -----------------------------------------------------------*/
/* Private macros ------------------------------------------------------------*/
/* Private variables and Local objects ---------------------------------------*/
static treeNode_t Menu1;
char *const Menu1Txt = "Menu 1";
static treeNode_t MenuItem1;
char *const MenuItem1_1Txt = "Menu Item 1.1";
static treeNode_t MenuItem2;
char *const MenuItem1_2Txt = "Menu Item 1.2";
static treeNode_t MenuItem3;
char *const MenuItem1_3Txt = "Menu Item 1.3";
static treeNode_t MenuItem4;
char *const MenuItem1_4Txt = "Menu Item 1.4";
static treeNode_t Menu2;
char *const Menu2Txt = "Menu 2";
static treeNode_t MenuItem2_1;
char *const MenuItem2_1Txt = "Menu Item 2.1";
static treeNode_t MenuItem2_2;
char *const MenuItem2_2Txt = "Menu Item 2.2";
static treeNode_t MenuItem2_3;
char *const MenuItem2_3Txt = "Menu Item 2.3";
static treeNode_t MenuItem2_4;
char *const MenuItem2_4Txt = "Menu Item 2.4";
static treeNode_t Menu3;
char *const Menu3Txt = "Menu 3";
static treeNode_t Menu3_1;
char *const Menu3_1Txt = "Menu 3_1";
static treeNode_t MenuItem3_1_1;
char *const MenuItem3_1_1Txt = "Menu Item 3.1.1";
static treeNode_t MenuItem3_1_2;
char *const MenuItem3_1_2Txt = "Menu Item 3.1.2";
static treeNode_t MenuItem3_1_3;
char *const MenuItem3_1_3Txt = "Menu Item 3.1.3";
static treeNode_t MenuItem3_1_4;
char *const MenuItem3_1_4Txt = "Menu Item 3.1.4";
static treeNode_t Menu3_2;
char *const Menu3_2Txt = "Menu 3_2";
static treeNode_t MenuItem3_2_1;
char *const MenuItem3_2_1Txt = "Menu Item 3.2.1";
static treeNode_t MenuItem3_2_2;
char *const MenuItem3_2_2Txt = "Menu Item 3.2.2";
static treeNode_t MenuItem3_2_3;
char *const MenuItem3_2_3Txt = "Menu Item 3.2.3";
static treeNode_t MenuItem3_2_4;
char *const MenuItem3_2_4Txt = "Menu Item 3.2.4";
static treeNode_t Menu3_3;
char *const Menu3_3Txt = "Menu 3_3";
static treeNode_t Menu3_3_1;
char *const Menu3_3_1Txt = "Menu 3_3_1";
static treeNode_t MenuItem3_3_1_1;
char *const MenuItem3_3_1_1Txt = "Menu Item 3.3.1.1";
static treeNode_t MenuItem3_3_1_2;
char *const MenuItem3_3_1_2Txt = "Menu Item 3.3.1.2";
static treeNode_t MenuItem3_3_1_3;
char *const MenuItem3_3_1_3Txt = "Menu Item 3.3.1.3";
static treeNode_t Menu3_3_2;
char *const Menu3_3_2Txt = "Menu 3_3_2";
static treeNode_t MenuItem3_3_2_1;
char *const MenuItem3_3_2_1Txt = "Menu Item 3.3.2.1";
static treeNode_t MenuItem3_3_2_2;
char *const MenuItem3_3_2_2Txt = "Menu Item 3.3.2.2";
static treeNode_t MenuItem3_3_2_3;
char *const MenuItem3_3_2_3Txt = "Menu Item 3.3.2.3";
static treeNode_t Menu3_3_3;
char *const Menu3_3_3Txt = "Menu 3_3_3";
static treeNode_t MenuItem3_3_3_1;
char *const MenuItem3_3_3_1Txt = "Menu Item 3.3.3.1";
static treeNode_t MenuItem3_3_3_2;
char *const MenuItem3_3_3_2Txt = "Menu Item 3.3.3.2";
static treeNode_t MenuItem3_3_3_3;
char *const MenuItem3_3_3_3Txt = "Menu Item 3.3.3.3";
static treeNode_t Menu3_3_4;
char *const Menu3_3_4Txt = "Menu 3_3_4";
static treeNode_t MenuItem3_3_4_1;
char *const MenuItem3_3_4_1Txt = "Menu Item 3.3.4.1";
static treeNode_t MenuItem3_3_4_2;
char *const MenuItem3_3_4_2Txt = "Menu Item 3.3.4.2";
static treeNode_t MenuItem3_3_4_3;
char *const MenuItem3_3_4_3Txt = "Menu Item 3.3.4.3";
/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/
/******************************************************************************/
void KTREE_nodeCtor(treeNode_t *node)
{
/* Initialize the root node */
node->firstChildNode = NULL;
node->firstSiblingNode = NULL;
node->fakeParentNode = NULL;
node->trueParentNode = NULL;
}
/******************************************************************************/
uint8_t KTREE_findDepth(treeNode_t *node, uint8_t currDepth)
{
/* Make sure the node is not null. If this happens, it's a bug. */
if (NULL == node) {
ERR_printf("!!!Node is null\n");
return(0);
}
if (NULL == node->fakeParentNode) {
/* If no fake parents exist, this is the top. Return the calculated
* depth */
return(currDepth);
} else {
/* Parent exists; recurse after incrementing the depth */
return (KTREE_findDepth(node->fakeParentNode, ++currDepth));
}
}
/******************************************************************************/
void KTREE_addChild(
treeNode_t *childToAdd,
treeNode_t *trueParentNode,
treeNode_t *fakeParentNode
)
{
/* Find the parent node */
if (NULL == trueParentNode) {
childToAdd->trueParentNode = NULL;
childToAdd->fakeParentNode = NULL;
return;
} else if (NULL == trueParentNode->firstChildNode) {
/* If the node where we want to add a child currently has no children,
* add the child node here to the childNode field. */
trueParentNode->firstChildNode = childToAdd;
// dbg_slow_printf("whereToAddChild=0x%08x\n", (uint32_t)whereToAddChild);
} else {
/* Otherwise, add a sibling to the child of this node */
KTREE_addNextSibling(childToAdd, trueParentNode->firstChildNode, fakeParentNode);
// dbg_slow_printf("Adding a sibling to the child of node '%s'\n", trueParentNode->text);
}
}
/******************************************************************************/
void KTREE_addNextSibling(
treeNode_t *siblingToAdd,
treeNode_t *trueParentNode,
treeNode_t *fakeParentNode
)
{
if(NULL == trueParentNode->firstSiblingNode) {
/* In the node being added, set its parents. */
siblingToAdd->fakeParentNode = fakeParentNode;
siblingToAdd->trueParentNode = trueParentNode;
/* Set the empty firstSiblingNode pointer to new node being added. */
trueParentNode->firstSiblingNode = siblingToAdd;
// dbg_slow_printf("Added node '%s' as a sibling to node '%s'\n", siblingToAdd->text, trueParentNode->text);
} else {
// dbg_slow_printf("Sibling '%s' already exists here. Trying next one\n", trueParentNode->text);
KTREE_addNextSibling(siblingToAdd, trueParentNode->firstSiblingNode, fakeParentNode);
}
}
/******************************************************************************/
void KTREE_printTree(treeNode_t *node, uint8_t level)
{
if (NULL == node) {
// dbg_slow_printf("Node at level %d is null, not printing\n", level);
return;
} else {
KTREE_printNode(node, level);
}
if (NULL != node->firstChildNode) {
// dbg_slow_printf("Child exists, descending one level\n");
KTREE_printTree(node->firstChildNode, level+1);
}
if (NULL != node->firstSiblingNode) {
// dbg_slow_printf("Sibling exits, moving right\n");
KTREE_printTree(node->firstSiblingNode, level);
}
}
/******************************************************************************/
void KTREE_printNode(treeNode_t *node, uint8_t level)
{
for (uint8_t i = 0; i < level; i++) {
printf("*--");
}
printf("NodeText: %s\n", node->text);
for (uint8_t i = 0; i < level; i++) {
printf("*--");
}
printf("True Parent pointer: 0x%08x\n", (uint32_t)node->trueParentNode);
for (uint8_t i = 0; i < level; i++) {
printf("*--");
}
printf("Fake Parent pointer: 0x%08x\n", (uint32_t)node->fakeParentNode);
for (uint8_t i = 0; i < level; i++) {
printf("*--");
}
printf("First Sibling pointer: 0x%08x\n", (uint32_t)node->firstSiblingNode);
for (uint8_t i = 0; i < level; i++) {
printf("*--");
}
printf("First Child pointer: 0x%08x\n", (uint32_t)node->firstChildNode);
}
/******************************************************************************/
void KTREE_fakeMenuTest(void)
{
/* First Node */
KTREE_nodeCtor(&Menu1);
Menu1.text = Menu1Txt;
/* Add a child node */
KTREE_nodeCtor(&MenuItem1);
MenuItem1.text = MenuItem1_1Txt;
KTREE_addChild(&MenuItem1, &Menu1, &Menu1);
/* Add siblings to that child node */
KTREE_nodeCtor(&MenuItem2);
MenuItem2.text = MenuItem1_2Txt;
KTREE_addChild(&MenuItem2, &Menu1, &Menu1);
KTREE_nodeCtor(&MenuItem3);
MenuItem3.text = MenuItem1_3Txt;
KTREE_addChild(&MenuItem3, &Menu1, &Menu1);
KTREE_nodeCtor(&MenuItem4);
MenuItem4.text = MenuItem1_4Txt;
KTREE_addChild(&MenuItem4, &Menu1, &Menu1);
/* Add another node at top level - it gets added as a sibling to the first node */
KTREE_nodeCtor(&Menu2);
Menu2.text = Menu2Txt;
KTREE_addNextSibling(&Menu2, &Menu1, NULL);
/* Add menu items to it as children */
KTREE_nodeCtor(&MenuItem2_1);
MenuItem2_1.text = MenuItem1_1Txt;
KTREE_addChild(&MenuItem2_1, &Menu2, &Menu2);
/* Add siblings to that child node */
KTREE_nodeCtor(&MenuItem2_2);
MenuItem2_2.text = MenuItem2_2Txt;
KTREE_addChild(&MenuItem2_2, &Menu2, &Menu2);
KTREE_nodeCtor(&MenuItem2_3);
MenuItem2_3.text = MenuItem2_3Txt;
KTREE_addChild(&MenuItem2_3, &Menu2, &Menu2);
KTREE_nodeCtor(&MenuItem2_4);
MenuItem2_4.text = MenuItem2_4Txt;
KTREE_addChild(&MenuItem2_4, &Menu2, &Menu2);
/* Add another node at top level - it gets added as a sibling to the first node */
KTREE_nodeCtor(&Menu3);
Menu3.text = Menu3Txt;
KTREE_addNextSibling(&Menu3, &Menu1, NULL);
/* Add submenu nodes - it gets added as a sibling to the first node */
KTREE_nodeCtor(&Menu3_1);
Menu3_1.text = Menu3_1Txt;
KTREE_addChild(&Menu3_1, &Menu3, &Menu3);
/* Add menu items to it as children */
KTREE_nodeCtor(&MenuItem3_1_1);
MenuItem3_1_1.text = MenuItem3_1_1Txt;
KTREE_addChild(&MenuItem3_1_1, &Menu3_1, &Menu3_1);
KTREE_nodeCtor(&MenuItem3_1_2);
MenuItem3_1_2.text = MenuItem3_1_2Txt;
KTREE_addChild(&MenuItem3_1_2, &Menu3_1, &Menu3_1);
KTREE_nodeCtor(&MenuItem3_1_3);
MenuItem3_1_3.text = MenuItem3_1_3Txt;
KTREE_addChild(&MenuItem3_1_3, &Menu3_1, &Menu3_1);
KTREE_nodeCtor(&MenuItem3_1_4);
MenuItem3_1_4.text = MenuItem3_1_4Txt;
KTREE_addChild(&MenuItem3_1_4, &Menu3_1, &Menu3_1);
KTREE_nodeCtor(&Menu3_2);
Menu3_2.text = Menu3_2Txt;
KTREE_addChild(&Menu3_2, &Menu3, &Menu3);
/* Add menu items to it as children */
KTREE_nodeCtor(&MenuItem3_2_1);
MenuItem3_2_1.text = MenuItem3_2_1Txt;
KTREE_addChild(&MenuItem3_2_1, &Menu3_2, &Menu3_2);
KTREE_nodeCtor(&MenuItem3_2_2);
MenuItem3_2_2.text = MenuItem3_2_2Txt;
KTREE_addChild(&MenuItem3_2_2, &Menu3_2, &Menu3_2);
KTREE_nodeCtor(&MenuItem3_2_3);
MenuItem3_2_3.text = MenuItem3_2_3Txt;
KTREE_addChild(&MenuItem3_2_3, &Menu3_2, &Menu3_2);
KTREE_nodeCtor(&MenuItem3_2_4);
MenuItem3_2_4.text = MenuItem3_2_4Txt;
KTREE_addChild(&MenuItem3_2_4, &Menu3_2, &Menu3_2);
KTREE_nodeCtor(&Menu3_3);
Menu3_3.text = Menu3_3Txt;
KTREE_addChild(&Menu3_3, &Menu3, &Menu3);
KTREE_nodeCtor(&Menu3_3_1);
Menu3_3_1.text = Menu3_3_1Txt;
KTREE_addChild(&Menu3_3_1, &Menu3_3, &Menu3_3);
/* Add menu items to it as children */
KTREE_nodeCtor(&MenuItem3_3_1_1);
MenuItem3_3_1_1.text = MenuItem3_3_1_1Txt;
KTREE_addChild(&MenuItem3_3_1_1, &Menu3_3_1, &Menu3_3_1);
KTREE_nodeCtor(&MenuItem3_3_1_2);
MenuItem3_3_1_2.text = MenuItem3_3_1_2Txt;
KTREE_addChild(&MenuItem3_3_1_2, &Menu3_3_1, &Menu3_3_1);
KTREE_nodeCtor(&MenuItem3_3_1_3);
MenuItem3_3_1_3.text = MenuItem3_3_1_3Txt;
KTREE_addChild(&MenuItem3_3_1_3, &Menu3_3_1, &Menu3_3_1);
KTREE_nodeCtor(&Menu3_3_2);
Menu3_3_2.text = Menu3_3_2Txt;
KTREE_addChild(&Menu3_3_2, &Menu3_3, &Menu3_3);
/* Add menu items to it as children */
KTREE_nodeCtor(&MenuItem3_3_2_1);
MenuItem3_3_2_1.text = MenuItem3_3_2_1Txt;
KTREE_addChild(&MenuItem3_3_2_1, &Menu3_3_2, &Menu3_3_2);
KTREE_nodeCtor(&MenuItem3_3_2_2);
MenuItem3_3_2_2.text = MenuItem3_3_2_2Txt;
KTREE_addChild(&MenuItem3_3_2_2, &Menu3_3_2, &Menu3_3_2);
KTREE_nodeCtor(&MenuItem3_3_2_3);
MenuItem3_3_2_3.text = MenuItem3_3_2_3Txt;
KTREE_addChild(&MenuItem3_3_2_3, &Menu3_3_2, &Menu3_3_2);
KTREE_nodeCtor(&Menu3_3_3);
Menu3_3_3.text = Menu3_3_3Txt;
KTREE_addChild(&Menu3_3_3, &Menu3_3, &Menu3_3);
/* Add menu items to it as children */
KTREE_nodeCtor(&MenuItem3_3_3_1);
MenuItem3_3_3_1.text = MenuItem3_3_3_1Txt;
KTREE_addChild(&MenuItem3_3_3_1, &Menu3_3_3, &Menu3_3_3);
KTREE_nodeCtor(&MenuItem3_3_3_2);
MenuItem3_3_3_2.text = MenuItem3_3_3_2Txt;
KTREE_addChild(&MenuItem3_3_3_2, &Menu3_3_3, &Menu3_3_3);
KTREE_nodeCtor(&MenuItem3_3_3_3);
MenuItem3_3_3_3.text = MenuItem3_3_3_3Txt;
KTREE_addChild(&MenuItem3_3_3_3, &Menu3_3_3, &Menu3_3_3);
KTREE_nodeCtor(&Menu3_3_4);
Menu3_3_4.text = Menu3_3_4Txt;
KTREE_addChild(&Menu3_3_4, &Menu3_3, &Menu3_3);
/* Add menu items to it as children */
KTREE_nodeCtor(&MenuItem3_3_4_1);
MenuItem3_3_4_1.text = MenuItem3_3_4_1Txt;
KTREE_addChild(&MenuItem3_3_4_1, &Menu3_3_4, &Menu3_3_4);
KTREE_nodeCtor(&MenuItem3_3_4_2);
MenuItem3_3_4_2.text = MenuItem3_3_4_2Txt;
KTREE_addChild(&MenuItem3_3_4_2, &Menu3_3_4, &Menu3_3_4);
KTREE_nodeCtor(&MenuItem3_3_4_3);
MenuItem3_3_4_3.text = MenuItem3_3_4_3Txt;
KTREE_addChild(&MenuItem3_3_4_3, &Menu3_3_4, &Menu3_3_4);
KTREE_printTree(&Menu1, 0);
}
代碼ktree.h:
#ifndef KTREE_H_
#define KTREE_H_
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32f4xx.h" /* For STM32F4 support */
#include "Shared.h"
/* Exported defines ----------------------------------------------------------*/
/* Exported types ------------------------------------------------------------*/
typedef void (*pMenuFunction) (
const char* dataBuf,
uint16_t dataLen,
CBMsgRoute dst
);
typedef struct treeNode {
struct treeNode *fakeParentNode;
struct treeNode *trueParentNode;
struct treeNode *firstChildNode;
struct treeNode *firstSiblingNode;
char *text;
char *selector;
pMenuFunction actionToTake;
} treeNode_t;
/* Exported macros -----------------------------------------------------------*/
/* Exported constants --------------------------------------------------------*/
/* Exported variables --------------------------------------------------------*/
/* Exported functions --------------------------------------------------------*/
/**
* @brief Initialize the menu memory space with the contents of the menu
*
* This function initializes the menu application.
*
* @param [in] iBus: I2C_Bus_t identifier for I2C bus to initialize
* @arg
* @return: None
*/
void KTREE_nodeCtor(treeNode_t *node);
/**
* @brief Counts the depth (via fakeParentNode pointer) of the passed in
* pointer to a node.
*
* Recursive function that counts it's own depth by traversing the tree up via
* the fakeParentNode pointer and counting.
*
* @param [in] *node: treeNode_t pointer to the node
* @parem [in] currDepth: uint8_t value of the current depth. Unless you want
* to for some reason offset where you start counting from, pass in 0 here.
* @return: uint8_t depth of the current node.
*/
uint8_t KTREE_findDepth(treeNode_t *node, uint8_t currDepth);
/**
* @brief Adds a child node to a passed in parent node.
*
* This function adds a child node to a passed in parent node after doing some
* initial checking of both nodes.
*
* @param [in] childNode: treeNode_t* pointer to the node to be added as a child
* @parem [in] trueParentNone: treeNode_t* pointer to the node that the child
* node will be directly attached to because it may appear as a sibling node or
* an actual child node.
* @parem [in] fakeParentNone: treeNode_t* pointer to the node that indicates
* the "regular" tree depth of this child node.
* @return: None
*/
void KTREE_addChild(
treeNode_t *childToAdd,
treeNode_t *trueParentNode,
treeNode_t *fakeParentNode
);
/**
* @brief Adds a sibling node to a passed in parent node.
*
* This function adds a sibling node to a passed in parent node by checking if
* the child node of the trueParentNode is used up and if it is, it recursively
* calls itself with the used node as a trueParentNode parameter until it finds
* the last sibling. It then adds the sibling node there and sets the
* fakeParentNode.
*
* @param [in] childNode: treeNode_t* pointer to the node to be added as a child
* @parem [in] trueParentNone: treeNode_t* pointer to the node that the child
* node will be directly attached to because it may appear as a sibling node or
* an actual child node.
* @parem [in] fakeParentNone: treeNode_t* pointer to the node that indicates
* the "regular" tree depth of this child node.
* @return: None
*/
void KTREE_addNextSibling(
treeNode_t *siblingToAdd,
treeNode_t *trueParentNode,
treeNode_t *fakeParentNode
);
void KTREE_printTree(treeNode_t *node, uint8_t level);
void KTREE_printNode(treeNode_t *node, uint8_t level);
void KTREE_fakeMenuTest(void);
我不想要在這裏發佈剩餘的代碼(這已經很多了),但是看看我的鏈接中的其他目錄,看看它是如何使用的。 menu.c/h使用ktree來設置一個實際的菜單並使用它。
我不認爲靜態數組是這種情況下使用的寫入數據結構。看看[std :: list](http://en.cppreference.com/w/cpp/container/list)和[std :: vector](http://en.cppreference.com/w/cpp /容器/載體)。 –
你也可以使用樹形數據結構來組織你的菜單和項目,但是這將需要遞歸搜索來訪問數據 – Ediac
@AustinBrunkhorst我不認爲靜態數組* * *; *) – twalberg