char a[] = ?string?;和char *p = ?string?;有什么区别?

53

正如标题所说,

“What is the difference between”之间的差异是什么?

char a[] = ?string?; and 
char *p = ?string?;  

这个问题是在面试中问我的,我甚至不理解这个陈述。

char a[] = ?string?

问号操作符?是什么?它是字符串的一部分还是有特定的含义?


14
面试官的意思可能是“使用”而不是“?”。问号符号用于三元运算符,但这不是其有效的语法。 - user406009
12
这可能是一个乱码的情况。这不是C++。 - André Caron
9
可能这个问题使用了开头/结尾引号,但是由于某些原因你的字体找不到它们,所以将它们呈现为“?”。 - Nicol Bolas
6
我的猜测是:代码被复制到了微软 Word 中,引号被转换后再进行了某种方式的还原。或者缺少了一个 #define ? "。不知道是否可以编译通过。 - Residuum
3
这篇文章讨论了C++中“char”、“string”、“const char”和“const string&”这四个类型之间的区别。简而言之,“char”是表示单个字符,而“string”是表示一组字符的集合(即字符串)。 “const char”和“const string&”都可以用于表示字符串,但它们的底层实现方式不同,因此在使用时需要注意。 - Bo Persson
显示剩余5条评论
8个回答

105

?似乎是一个拼写错误,它并不符合语义规范。因此,答案假设?是一个打字错误,并解释了面试官可能实际想要询问的内容。


首先,两者截然不同:

  1. 第一种创建指针。
  2. 第二种创建数组。

继续阅读以获取更详细的解释:

数组版本:

char a[] = "string";  

创建一个足够大的数组来容纳字符串常量"string",包括它的NULL终止符。数组string被初始化为字符串常量"string"。此后,数组可以在以后的时间内修改。同时,即使在编译时,数组的大小也是已知的,因此可以使用sizeof运算符来确定其大小。

指针版本:

char *p  = "string"; 

创建一个指向字符串字面值"string"的指针。与数组版本相比,这更快,但是指针指向的字符串不应该被修改,因为它位于只读实现定义的内存中。修改这样的字符串字面值会导致未定义的行为

事实上,C++03已经[Ref 1]弃用了没有带有const关键字的字符串字面值的使用。所以声明应该是:

const char *p = "string";

此外,您需要使用 strlen() 函数来查找字符串的大小,而不是使用 sizeof,因为 sizeof 运算符只会给出指针变量的大小。
哪个版本更好,我应该使用哪个?

取决于用途。

  • 如果您不需要对字符串进行任何更改,请使用指针版本。
  • 如果您想要更改数据,请使用数组版本。

注意:这不是 C++,而是 C 专用。

请注意,在 C 中,使用字符串字面值而没有 const 关键字是完全有效的。 然而,在 C 中修改字符串字面值仍然是未定义行为[Ref 2]

这带来了一个有趣的问题,
在 C 中,char* 和 const char* 在使用字符串字面值时有什么区别?


对于 Standerdese 粉丝:
[Ref 1]C++03 标准:§4.2/2

一个非宽字符串字面值(2.13.4)可以转换为类型为“指向 char 的指针”的 rvalue;一个宽字符串字面值可以转换为类型为“指向 wchar_t 的指针”的 rvalue。在任一情况下,结果都是数组的第一个元素的指针。只有当存在显式适当的指针目标类型时才考虑此转换,而不是当存在从 lvalue 转换为 rvalue 的一般需要时。[注意:此转换已被弃用。请参见附录 D。] 为了在重载分辨率(13.3.3.1.1)中进行排名,这种转换被认为是数组到指针转换,然后是限定符转换(4.4)。[例如:“abc”被转换为“指向 const char 的指针”,作为数组到指针的转换,然后作为限定符转换而转换为“指向 char 的指针”。]

C++11 只是删除了上述引文,这意味着它在 C++11 中是非法代码。

[Ref 2]C99 标准 6.4.5/5 "String Literals - Semantics":

在第七个翻译阶段,对于由字符串字面量或多个字面量组成的多字节字符序列,会在其末尾添加一个值为零的字节或代码。然后使用这个多字节字符序列来初始化一个具有静态存储期和长度足以容纳该序列的数组。对于字符字符串字面量,数组元素的类型为char,并用多字节字符序列的各个字节进行初始化;对于宽字符串字面量,数组元素的类型为wchar_t,并用宽字符序列进行初始化...
未指定这些数组是否是不同的,只要它们的元素具有适当的值即可。如果程序试图修改这样的数组,则行为是未定义的。

2
非常感谢任何有关投票反对的技术理由。 - Alok Save
“这是更快”是什么意思? - Karlis Olte
我不同意“如果您不需要对字符串进行任何更改,请使用指针版本。” - 如果您不需要对字符串进行任何更改,则可能希望使用const char a [] =“string”;,即只需添加一个const。这样可以避免在启动时(至少在Linux上)动态链接器执行其工作时进行重定位。有关更长的讨论,请参见如何编写共享库第2.4.1节。 - Frerich Raabe

90
第一个是数组,另一个是指针。

The array declaration char a[6]; requests that space for six characters be set aside, to be known by the name a. That is, there is a location named a at which six characters can sit. The pointer declaration char *p; on the other hand, requests a place which holds a pointer. The pointer is to be known by the name p, and can point to any char (or contiguous array of chars) anywhere.

The statements

 char a[] = "string";
 char *p = "string"; 

would result in data structures which could be represented like this:

     +---+---+---+---+---+---+----+
  a: | s | t | r | i | n | g | \0 |
     +---+---+---+---+---+---+----+
     +-----+     +---+---+---+---+---+---+---+ 
  p: |  *======> | s | t | r | i | n | g |\0 |    
     +-----+     +---+---+---+---+---+---+---+ 

It is important to realize that a reference like x[3] generates different code depending on whether x is an array or a pointer. Given the declarations above, when the compiler sees the expression a[3], it emits code to start at the location a, move three elements past it, and fetch the character there. When it sees the expression p[3], it emits code to start at the location p, fetch the pointer value there, add three element sizes to the pointer, and finally fetch the character pointed to. In the example above, both a[3] and p[3] happen to be the character l, but the compiler gets there differently.

Source: comp.lang.c FAQ list · Question 6.2


我敢打赌@Sachin考虑了这样的情况: const char *p = "string"; p = "another string"; printf("%c", p[3]); (是的,char *p = "string"; 将会出现编译错误)。 - nodakai

12
char a[] = "string";

这会在堆栈上分配字符串。

char *p = "string";

这在堆栈上创建一个指向进程数据段中字面值的指针。

? 是指谁写的不知道自己在做什么。


6
考虑到问题询问某个琐碎细节,我认为答案应该更深入地探讨可能性。具体来说,“char a[]...在栈上分配...”假设它在一个函数内而不是全局,并且进一步指的是 a[],但没有提到在函数内部实际上有一个运行时从常量数据段到栈的整个文本的副本。使用创建非“const”指针的 char* - 在堆栈上或作为数据段中的全局变量 - 并在运行时或(很可能)编译时初始化以寻址常量文本。 - Tony Delroy
1
这个答案是错误的。第二段代码片段会创建一个编译错误 :P - BЈовић
2
@VJovic:确实如此。在C++03中,声明指向字符串字面值的指针而没有使用const限定符已经被弃用了,因此第二个代码片段不是合法的C++代码。 - Alok Save
1
它可以在某些编译器上编译通过(例如Microsoft Visual C++ 2010),因此当说它创建编译错误时,您应该更具体地写出编译器版本,或者(如其他答案中提到的)说明这违反了C++标准(C++03 C++11)。 - Dainius

8
堆栈、堆、数据段(以及BSS)和文本段是进程内存的四个部分。所有定义的局部变量将位于堆栈中。使用malloc和calloc动态分配的内存将在堆中。所有全局和静态变量将位于数据段中。文本段将包含程序的汇编代码和一些常量。
在这4个段中,文本段是只读段,在其他三个段中,可读可写。
char a[] = "string"; - 这个语句会在堆栈中为7个字节分配内存(因为是局部变量),并将所有6个字符(s、t、r、i、n、g)以及结尾的NULL字符(\0)保存在其中。
char *p = "string"; - 这个语句会在堆栈中为4个字节(如果是32位机器)分配内存(因为这也是一个局部变量),并保存常量字符串“string”的指针值。这6个字节的常量字符串将在文本段中。这是一个常量值。指针变量p只是指向那个字符串。
现在a [0](索引可以是0到5)表示它将访问堆栈中的该字符串的第一个字符。所以我们也可以对此位置进行写操作。a [0] ='x'。这个操作是允许的,因为我们在堆栈中有读写访问权限。
但是p [0] ='x'会导致崩溃,因为我们只有对文本段的读取权限。如果我们在文本段上进行任何写操作,将发生分段错误。
但是您可以更改变量p的值,因为它是堆栈中的局部变量。如下所示。
char *p = "string";
printf("%s", p);
p = "start";
printf("%s", p);

这是允许的。在这里,我们正在更改指针变量p中存储的地址,以指向字符串start的地址(再次提醒,start也是文本段中的只读数据)。如果您想修改*p中存在的值,请选择动态分配内存。

char *p = NULL;
p = malloc(sizeof(char)*7);
strcpy(p, "string");

现在允许 p[0] = 'x' 操作,因为我们现在是在堆中写入。

1
很好的解释,唯一我不确定的是,字符串字面值将存储在文本段还是数据段中? - ADJ
字符串字面量是只读数据,因此大多数编译器将其存储在文本段中。 - rashok
@rajaashok 所以,字符串字面值的字节存储在文本段中,这对于每个C编译器都是正确的吗?那C++呢? - Wyvern666

6

char *p = "string"; 创建一个指向只读内存的指针,其中存储着字符串字面量"string"。试图修改指向p的字符串会导致未定义的行为。

char a[] = "string"; 创建一个数组,并使用字符串字面量"string"来初始化其内容。


3

它们存在存储内存的位置不同。理想情况下,第二个应该使用const char*。

第一个

char buf[] = "hello";

创建一个自动缓冲区,大小足以容纳字符并将它们复制进去(包括空终止符)。

第二个

const char * buf = "hello";

应该使用const并简单地创建一个指向通常存储在静态空间中的内存的指针,这是不合法的。

相反(你可以安全地修改第一个而不是第二个事实的相反),从函数返回第二个指针是安全的,但不是第一个。这是因为第二个指针将在函数范围之外保持有效的内存指针,而第一个则不会。

const char * sayHello()
{
     const char * buf = "hello";
     return buf; // valid
}

const char * sayHelloBroken()
{
     char buf[] = "hello";
     return buf; // invalid
}

1

a 声明了一个由 char 值组成的数组 -- 一个以 null 结尾的 char 数组。

p 声明了一个指针,它指向一个不可变的、以 null 结尾的 C 字符串,其确切的存储位置是实现定义的。请注意,这应该是带有 const 限定符的 (例如:const char *p = "string";)。

如果你使用 std::cout << "a: " << sizeof(a) << "\np: " << sizeof(p) << std::endl; 将它打印出来,你会看到它们的大小有所不同 (注意:值可能因系统而异):

a: 7
p: 8

Here what is ? operator? Is it a part of a string or it has some specific meaning?

char a[] = ?string?

我猜它们曾经是双引号"string",可能被转换为“智能引号”,但在过程中无法表示为此类字符,因此被转换为?


@Sachan 只有在必须修改 char 缓冲区时才会很少使用。 - justin
如果您不需要改变char缓冲区,则应该是[static] const char a[] = "xyz";,而不是const char* p = "xyz";const char* const p = "xyz"; - 前者意味着p可能被移动到其他地方,如果这不是预期的结果,则最好不要允许这种可能性,并且两者都要求编译器为指针和文本分配空间 - 在我看来 - 只是显示了对编译器所要求的准确模型的缺乏,浪费了空间和时间在未优化的构建中。 - Tony Delroy
@Justin:我没有任何侮辱性的意图或兴趣。你说我在许多情况下都是错的——我只谈到了“不需要改变上下文”,所以请解释一下我在哪些情况下是错的,以及[static] const char[]如何导致“浪费空间和时间”。 - Tony Delroy

0

C和C++具有非常相似的指针与数组关系...

我无法确定你所询问的两个语句的确切内存位置,但我发现这些文章很有趣,对于理解char指针声明与char数组声明之间的某些差异非常有用。

为了清晰明了:

C指针和数组的关系

C++中的指向数组的指针

我认为重要的是记住,在C和C++中,数组是指向数组第一个元素的常量指针。因此,您可以对数组执行指针算术运算。

char *p = “string”; <--- 这是一个指针,它指向字符字符串的第一个地址。

以下也是可能的:

char *p;
char a[] = "string";

p = a; 

此时,p 现在引用 a 的第一个内存地址(即第一个元素的地址)

因此 *p == 's'

*(p++) == 't',以此类推。 (或 *(p+1) == 't')

对于 a,同样的事情也适用:*(a++) 或 *(a+1) 也等于 't'


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