我不是专业的C程序员,并且我知道从另一个文件中包含.c
源文件被认为是不好的做法,但是我认为这种情况有助于维护性。
我的结构很大,包含很多元素,我使用#define
来保留索引。
#define TOTO_IND 0
#define TITI_IND 1
…
#define TATA_IND 50
static const MyElements elems [] = {
{"TOTO", 18, "French"},
{"TITI", 27, "English"},
...,
{"TATA", 45, "Spanish"}
}
由于我需要从索引访问结构,因此需要保持#define
和结构声明同步。这意味着我必须在正确的位置插入新元素,并相应地更新#define
。
这很容易出错,我不太喜欢(但是出于性能考虑,我没有找到更好的解决方案)。
无论如何,此文件还包含许多处理此结构的函数。我也想保持代码分离,避免全局变量。
为了使事情“更容易”,我正在考虑将这个“容易出错的定义”移动到仅包含该结构的单个.c
源文件中。该文件将是“危险的小心文件”,并将其包含在我实际的“正常运行”文件中。
您如何看待?包括.c
源文件是否有效?还有其他更好的方法来处理我的结构吗?
答案 0 :(得分:39)
您可以使用指定的初始值设定项来初始化elems[]
的元素,而不必知道每个索引标识符(或宏)的显式值。
const MyElements elems[] = {
[TOTO_IND] = {"TOTO", 18, "French"},
[TITI_IND] = {"TITI", 27, "English"},
[TATA_IND] = {"TATA", 45, "Spanish"},
};
数组元素将以相同的方式初始化,即使您更改它们在源代码中出现的顺序也是如此:
const MyElements elems[] = {
[TITI_IND] = {"TITI", 27, "English"},
[TATA_IND] = {"TATA", 45, "Spanish"},
[TOTO_IND] = {"TOTO", 18, "French"},
};
如果如上所述通过初始化程序自动设置数组长度(即使用[]
而不是[NUM_ELEMS]
),则该长度将比最大元素索引大一。
这使您可以将elems
数组的索引值和外部声明保存在.h文件中,并在单独的.c文件中定义elems
数组的内容。
答案 1 :(得分:33)
您应使用Ian Abbot的答案中所示的指定初始化程序。
另外,如果数组索引是相邻的(如此处所示),则可以使用枚举代替:
toto.h
typedef enum
{
TOTO_IND,
TITI_IND,
...
TATA_IND,
TOTO_N // this is not a data item but the number of items in the enum
} toto_t;
toto.c
const MyElements elems [] = {
[TITI_IND] = {"TITI", 27, "English"},
[TATA_IND] = {"TATA", 45, "Spanish"},
[TOTO_IND] = {"TOTO", 18, "French"},
};
现在您可以使用静态断言来验证整个数组的数据完整性:
_Static_assert(sizeof elems/sizeof *elems == TOTO_N,
"Mismatch between toto_t and elems is causing rain in Africa");
_Static_assert(sizeof elems/sizeof *elems == TOTO_N, ERR_MSG);
其中ERR_MSG
被定义为
#define STR(x) STR2(x)
#define STR2(x) #x
#define ERR_MSG "Mismatching toto_t. Holding on line " STR(__LINE__)
答案 2 :(得分:18)
其他答案已经以更清晰的方式涵盖了它,但是出于完整性考虑,如果您愿意走这条路线并冒着同事的怒气,这是一种x宏方法。
X宏是使用内置C预处理程序生成代码的一种形式。目标是将重复次数减少到最低,尽管有一些缺点:
首先在单独的文件中创建宏调用的列表,例如elements.inc
,但此时尚未定义宏的实际作用:
// elements.inc
// each row passes a set of parameters to the macro,
// although at this point we haven't defined what the
// macro will output
XMACRO(TOTO, 18, French)
XMACRO(TITI, 27, English)
XMACRO(TATA, 45, Spanish)
然后,每次需要包含此列表时都定义宏,以便每次调用都呈现到要创建的结构的单行中,并且通常连续重复多次,即< / p>
// concatenate id with "_IND" to create enums, ignore code and description
// (notice how you don't need to use all parameters each time)
// e.g. XMACRO(TOTO, 18, French) => TOTO_IND,
#define XMACRO(id, code, description) id ## _IND,
typedef enum
{
# include "elements.inc"
ELEMENTS_COUNT
}
Elements;
#undef XMACRO
// create struct entries
// e.g. XMACRO(TOTO, 18, French) => [TOTO_IND] = { "TOTO", 18, "French" },
#define XMACRO(id, code, description) [id ## _IND] = { #id, code, #description },
const MyElements elems[] = {
{
# include "elements.inc"
};
#undef XMACRO
哪些将被预处理为类似的内容
typedef enum
{
TOTO_IND,
TITI_IND,
TATA_IND,
ELEMENTS_COUNT
}
Elements;
const MyElements elems[] = {
{
[TOTO_IND] = { "TOTO", 18, "French" },
[TITI_IND] = { "TITI", 27, "English" },
[TATA_IND] = { "TATA", 45, "Spanish" },
};
显然,列表的频繁维护变得更容易,但代价是生成代码变得更加复杂。
答案 3 :(得分:5)
在多个文件中将const
定义为static
并不是一个好主意,因为它会创建大变量MyElements
的多个实例。这将增加嵌入式系统中的内存。 static
限定词需要删除。
以下是建议的解决方案:
在file.h
中
#define TOTO_IND 0
#define TITI_IND 1
…
#define TATA_IND 50
#define MAX_ELEMS 51
extern const MyElements elems[MAX_ELEMS];
在file.c
中
#include "file.h"
const MyElements elems [MAX_ELEMS] = {
{"TOTO", 18, "French"},
{"TITI", 27, "English"},
...,
{"TATA", 45, "Spanish"}
}
修改后,将#include "file.h"
放入所需的.c文件中。
答案 4 :(得分:1)
要解决有关将#include
与.c
文件一起使用的特定问题(其他答案提出了更好的选择,尤其是Groo提出的选择),通常没有必要。
.c
文件中的所有内容都可以在外部可见和访问,因此您可以通过函数原型和#extern
对其进行引用。因此,例如,您可以在主#extern const MyElements elems [];
文件中用.c
引用表。
或者,您可以将定义放在.h
文件中并包括该文件。这使您可以根据需要隔离代码。请记住,所有#include
所做的只是在#include
语句所在的位置插入所包含文件的内容,因此它不必具有任何特定的文件扩展名。 .h
按照惯例使用,大多数IDE会自动将.c
个文件添加到要编译的文件列表中,但是就编译器而言,命名是任意的。