如何在C语言中声明字符串常量?

89

我知道在C语言中,将数字常量声明为enum而不是使用#define定义它们是很惯用的,或者至少是一种良好的编程风格。

/* bad style */
#define MAXLINE 1024

/* good/better style */
enum {
    MAX_LINE = 1024
};

有没有关于定义字符串常量的相应规则?

/* is this good style? */
#define HELLO "Hello World"

/* or is this better? */
const char *HELLO2 = "Howdy";

你更喜欢哪种方法?如果可能的话,请展示一些两种方法的缺点。

5个回答

117

还有一条(至少)通往罗马的路:

static const char HELLO3[] = "Howdy";

(static - 可选的 - 是为了防止它与其他文件冲突)。我更喜欢这种方式而不是const char*,因为你将能够使用sizeof(HELLO3),因此你不必等到运行时再去做编译时就可以完成的事情。

然而,定义具有编译时连接的优点(想像一下HELLO",世界!"),你也可以使用sizeof(HELLO)

但是,你也可以选择const char*并在多个文件中使用它,这将节省一小部分内存。

简而言之——这要取决于具体情况。


感谢您的解释。使用枚举而不是宏常量或const int来表示常量整数值的原因是这是唯一类型安全的方式来声明一个常量整数,可以作为可移植的数组边界使用。嗯...编译时字符串连接几乎让我选择使用#define来表示字符串,但我会坚持使用const char*来保证类型安全,并在适当的情况下使用static。简而言之,我将使用枚举来表示整数常量,使用const变量来表示其他所有内容。 - tatt
1
Tatt,我建议你不要固守任何东西。并非所有的常量都是相等的。如果你不打算连接,你就不需要#define,如果你也不需要大小,那你根本不用在意。我完全看不出const char*const char [] 在静态适用的情况下更好在哪里。但是再说一遍,如果你不需要大小,它也不会更糟糕;-) - Michael Krelin - hacker

27

定义字符串常量的一个优点(尽管非常微小)是您可以在编译时将它们连接起来:

#define HELLO "hello"
#define WORLD "world"

puts( HELLO WORLD );

我不确定这是否真的是一种优势,但这是一种在const char *中无法使用的技术。


1
字符串拼接可以通过string.h中的strcat(char *s1,const char *s2)strncat()实现。 - Chris Tang
1
@ChrisTang 说得对。我在答案中加入了“...在编译时”的短语。 - William Pursell
实际上,如果你有 char * s1 = "HELLO WOLRD",你不能使用 strcat,因为 s1 是一个常量值。你可以将其用作 s2,但是 s1 必须有足够的空间来分配给 s2。 - Cameron Monks

17
如果你想要像你的问题所说的那样一个“const字符串”,我会真正建议你选择你在问题中提到的版本:
/* first version */
const char *HELLO2 = "Howdy";
特别是,我会避免:
/* second version */
const char HELLO2[] = "Howdy";

原因:第二个版本的问题在于编译器会复制整个字符串"Howdy",而且该字符串可以修改(所以不是真正的const)。

另一方面,第一个版本是通过指针HELLO2访问的const字符串,它不能被修改。


5
我认为这个答案中有两个错误:1)第二个版本创建了一个常量字符串,它不能被修改,至少在保护其内存的系统上是如此。如果这个变量是静态的,就不会有复制。2)指针不是常量,只有它所指向的字符是常量。要获取只读指针,需要写作 const char * const HELLO2 = "Howdy"; - the busybee
IntelliSense: 此常量表达式的类型为“const char *”,而不是所需的整数或枚举类型。 - Alexis

9
#define 方法的主要缺点是每次使用字符串时都会复制一次,因此您可能会在可执行文件中拥有大量副本,从而使其变得更大。

7
只要常量在同一文件中使用,我相信编译器会优化它们,因此并不是真正的“每次使用”。因此,定义全局常量有时确实可以节省空间。 - Michael Krelin - hacker
3
如果编译器或链接器将副本合并成一个,那么这是正确的。这个特性有时也被称为“字符串池”。 - Rom
实际上,它们都指向同一个地址。尝试使用 char *foo = "HELLO WORLD" 和 char *bar = "HELLO WORLD"; 然后你会发现 foo == bar 为真。我相信这种设计模式被称为享元模式。 - Cameron Monks

2
他们有几个不同之处。
#define HELLO "Hello World"

上述语句可以与预处理器一起使用,只能在预处理器中进行更改。

const char *HELLO2 = "Howdy";

上面的语句可以通过C代码进行更改。现在,您无法像下面的语句一样更改周围的每个单独字符,因为它是常量。
HELLO2[0] = 'a'

但是你可以将它指向不同的字符串,就像下面的语句一样。
HELLO2 = "HELLO WOLRD"

这真的取决于您想如何改变变量。使用预处理器或C代码。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接