我想避免在我的 C 文件中硬编码常量值,
所以我想知道我是否有办法初始化结构常量
直接在头文件中使用它在我包含头文件的任何地方
文件? (就像#define 对简单类型的常量起作用)
您首先需要更清楚地了解您的意思,相对于 C 语言的语义。在 C 术语中,常量是 句法 结构,表示源代码中的特定值。它们没有自己的关联存储,并且仅适用于内置类型。你想要的不是这个意义上的“常数”,或者至少,C 没有提供这种意义上的结构常数。这与 const 类型限定符无关。
在这个一般领域,C 确实提供了几项功能:
- 结构初始化器,其作用类似于用于初始化结构类型对象的常量;
- 具有不可修改 (
const) 结构类型的对象;和
-
结构类型的复合文字。
初始化器
正如他们的名字所暗示的,初始化器可以在对象声明中用于初始化声明的对象。然而,它们本身不是值,因此它们不能分配到声明后的对象或以其他方式在需要表达式的地方使用。您可以定义一个扩展为初始化程序的宏,有时会这样做。示例
header1.h
struct my_struct { int x; int y; };
#define MY_STRUCT_INITIALIZER { .x = 0, .y = 0 }
code1.c
// ...
// This is initialization, not assignment:
struct my_struct s = MY_STRUCT_INITIALIZER;
初始化器没有自己的存储空间。
不可修改的对象
与在 C++ 中一样,任何数据类型都可以通过const 限定以生成无法修改的对象类型。因此,此类对象必须从初始化程序中获取它们的值,或者是函数参数,或者以一种导致它们被默认初始化的方式声明,因为不可修改意味着没有其他方法可以定义它们的值。与初始值设定项不同,这些是具有数据类型和相关存储的真正对象,并且它们可以在与其类型兼容的任何表达式中使用。同样,与const 对象关联的标识符必须满足C 的范围和链接规则,就像其他标识符一样。特别是,尽管任何具有外部(或内部)链接的对象都可以有多个声明,但每个对象只能有一个定义。
外部对象
如果你想在“任何地方”使用相同的对象,那么这意味着外部链接。好的样式然后要求在标题中声明该对象,但不能在标题中定义它,因为如果标题包含在多个翻译单元中,则会导致重复定义。 C 通过以下成语很好地支持了这一点:
header2.h
struct my_struct { int x; int y; };
// a declaration:
extern const struct my_struct shared_struct; // NOTE: explicit "extern" and no initializer
my_struct_stuff.c
#include "header2.h"
// a definition:
const struct my_struct shared_struct = { .x = 1, .y = 2 };
other.c
#include "header2.h"
// no definition of shared_struct here
// ...
int x = shared_struct.x;
int y = shared_struct.y;
// ...
这提供了一个不可修改的对象,在您喜欢的多个翻译单元之间共享,但它不满足您将所有内容都保存在标题中的标准。可以玩条件编译游戏以使定义以词汇方式出现在标题中,但您仍然需要一个指定的源文件来提供定义。 (细节留作练习。)
内部对象
或者,如果在每个翻译单元中使用等效但不同的对象就足够了,那么您的标题可以定义一个不可修改的对象,其标识符具有内部链接。每个包含标头的翻译单元都将拥有自己独立的对象副本:
header3.h
struct my_struct { int x; int y; };
static const struct my_struct local_struct = { .x = 1, .y = 2 };
这满足了您将所有内容都保留在头文件中的标准,但您可能有理由希望为每个翻译单元提供相同的对象,即使除了存储使用方面的考虑之外,这并不能实现该目标。此外,您的编译器可能会针对包含标头但不访问 local_struct 的翻译单元发出警告。
复合文字
C 还具有结构类型的复合文字,在某些方面类似于字符串文字。它们代表真正的对象,具有数据类型和存储,并具有直接传达对象值的词汇形式。复合文字可能有const-qualified 类型,但默认情况下它们不是const,通常,可以以任何类型允许的方式修改与复合文字对应的对象。此外,与字符串文字不同,复合文字不一定具有静态存储持续时间。此外,复合文字的每次出现都代表一个单独的对象。
从词法上讲,复合文字类似于结构、联合或数组类型的强制转换运算符,应用于该类型对象的相应初始化程序:
header4.h
struct my_struct { int x; int y; };
#define MY_STRUCT_LITERAL ((struct my_struct) { .x = 42, .y = 42 })
/* or:
#define MY_STRUCT_LITERAL ((const struct my_struct) { .x = 42, .y = 42 })
*/
可以在标题中定义扩展为复合文字的宏,如图所示,但重要的是要了解此类宏的每次出现都将对应一个单独的对象。在某些情况下,编译器可能会优化保留任何实际存储,但复合文字不是与整数或浮点常量相同意义上的“常量”。
总体
上面介绍了您的主要可用替代方案。我不清楚你对让这些值在头文件中以词法方式出现,而不是在代表翻译单元的随附的专用源文件中,给予什么或多少价值。我也不清楚您认为最小化这些数据的存储需求的相对重要性,或者结构的对象身份是否需要重要。并且由于您在比较中提出了 C++,我不应该忽视复合文字是 C++ 不提供的 C 特性这一事实。这些是您在选择这些可能性时应牢记的注意事项。
或者您也可以考虑是否真的要在实际结构对象中排列数据。我之所以关注这一点,是因为这是您所问的,但是有一些基于宏的选项可以让您以相当紧凑的形式在标题中列出您的数据,并使用类似函数的宏而不是结构访问语法来访问他们。