在C语言中,数组、指针和字符串有什么区别?

9

我有一个问题困扰着我。

我知道在C语言中,指针数组是不同的,因为指针存储一个地址,而数组存储'实际'的值

但当涉及到字符串时,我感到困惑。

char *string = "String";

我看到这行代码做了几件事情:

编译器创建了一个 字符数组,并将其值设置为 String

然后,将此数组视为 指针,程序将指向由编译器创建的数组的 第一个元素 的指针分配给字符串指针。

这意味着,数组 被视为 指针

因此,这个结论是真还是假,为什么?

如果是假的,那么 指针数组 之间有什么区别呢?谢谢。


你可能会发现这个链接很有帮助。 - Eugene Sh.
3
在C语言中,字符串字面值从来都不是“const”,现在也不是。然而在C++中,它们是“const”。 - Keith Thompson
2
@πάνταῥεῖ:在C语言中,字符串字面量不是const - Keith Thompson
1
几乎是 什么是数组衰减? 的重复,该问题提供了适当的答案。 - Edgar Bonet
@EdgarBonet 我最初将此标记为"C风格字符串,指针,数组"的重复。 - πάντα ῥεῖ
显示剩余10条评论
6个回答

10

指针包含对象的地址(或是一个不指向任何对象的空指针)。指针有特定类型,表示它所指向的对象的类型。

数组是一系列有序相邻元素的集合;每个元素都是一个对象,所有元素都是同一类型。

字符串被定义为“由第一个空字符终止并包括在内的一系列连续字符”。C 没有字符串类型。字符串是一种数据布局,而不是数据类型。

数组和指针之间的关系可能会令人困惑。我知道的最好的解释是由comp.lang.c FAQ第6节提供的。最重要的是要记住:数组不是指针

在 C 和 C++ 中,数组在某种程度上是“二等公民”。它们不能被赋值、作为函数参数传递或用于比较相等性。处理数组的代码通常使用指向数组各个元素的指针,并使用某些显式机制来指定数组的长度。

混淆的一个主要来源是事实:在大多数情况下,具有数组类型的表达式(例如数组对象的名称)会被隐式转换为指针值。转换后的指针指向数组的初始(零)元素。如果数组是以下情况之一,则不会发生这种转换:

  • sizeof 的操作数(sizeof array_object 返回数组的大小,而不是指针的大小);

  • 一元 & 的操作数(&array_object 返回整个数组对象的地址);或者

  • 初始化数组对象所使用的字符串字面量。

    char *string = "String";

为避免混淆,在您的示例中我会做出一些更改:

const char *ptr = "hello";

字符串字面值"hello"在C中创建一个类型为char[6]的匿名对象,或在C++中创建一个类型为const char[6]的匿名对象,包含字符{ 'h', 'e', 'l', 'l', 'o', '\0' }

在这种情况下,该表达式的求值产生该数组初始字符的指针。这是一个指针;没有隐式创建指针对象。该指针值用于初始化指针对象ptr

在任何时候都不会将数组“视为”指针。数组表达式被转换为指针类型。

另一个引起困惑的来源是看起来是数组类型的函数参数实际上是指针类型;类型在编译时调整。例如:

void func(char param[10]);

真正的意思是:

void func(char *param);

数字 10 会被静默地忽略。因此你可以写出如下的代码:

void print_string(char s[]) {
    printf("The string is \"%s\"\n", s);
}
// ...
print_string("hello");

看起来像是对数组进行操作,但实际上字符串"hello"被转换为指针,并将该指针传递给函数print_string


5

那么,这个结论是真还是假?为什么?

你的结论是错误的。

数组和指针是不同的。comp.lang.c FAQ list · Question 6.8解释了数组和指针之间的区别:

数组是一个单一的、预先分配的连续元素块(都是相同类型的),大小和位置固定。指针是对任何数据元素(特定类型的)的引用,可以在任何地方分配。指针必须被赋值以指向其他地方分配的空间,但它可以在任何时候重新分配(如果从malloc派生,则空间可以调整大小)。指针可以指向数组,并且可以模拟(与malloc一起)动态分配的数组,但指针是一个更通用的数据结构。

当你执行以下操作时:

char *string = "String";  

当C编译器遇到这个时,它会为字符串字面量"String"分配7个字节的内存。然后将指针"string"设置为所分配内存的起始位置。

enter image description here

当你声明时
char string[] = "String";    

当C编译器遇到这种情况时,它会为字符串字面值“String”分配7个字节的内存。然后给出该内存位置的名称,即第一个字节,即“string”。

enter image description here

所以,

  • 在第一种情况下,string是一个指针变量,在第二种情况下它是一个数组名。
  • 存储在第一种情况下的字符不能被修改,而在数组版本中可以被修改。

这意味着在C语言中数组不被视为指针,但是它们在某种程度上与指针密切相关,因为指针算术和数组索引在C语言中是等价的,指针和数组是不同的。


0
我们可以创建一个名为'string'的数组。
char string[] = "Hello";

我们可以将一个指针分配给该字符串。
char* stringPtr = string;

数组名被转换为指针

因此,数组名类似于指针。但是,它们并不相同,因为数组是一块连续的内存块,而指针只引用内存中的单个位置(地址)。

char *string = "String";

这个声明创建了数组并将指针的地址设置为用于存储数组的内存块的地址。

这意味着,数组被视为指针。那么,这个结论是真还是假?

假的,数组不是指针。然而,为了混淆(!),由于解引用运算符[],指针可以看起来像数组。

char *string = "String";
char letter = string[2];

在这种情况下,string[2],字符串首先被转换为指向数组第一个字符的指针,然后使用指针算术运算返回相关项。

你怎么知道数组在堆栈上? :-) - Peter - Reinstate Monica
@PeterSchneider,我们没有使用 malloc(或 c++ 中的 new),难道我漏掉了什么吗? - TheDarkKnight
如果定义在函数之外,字符串将不会在堆栈上。 - Peter - Reinstate Monica
我认为技术术语应该是“静态存储期”,可能具有外部链接。 - Peter - Reinstate Monica
@PeterSchneider,再次感谢,我会研究一下的 :-) - TheDarkKnight
显示剩余2条评论

0

你必须理解这里内存中发生了什么。

字符串是一块连续的内存单元,以特殊值(空终止符)结尾。如果你知道这块内存的起始位置,并且知道它何时结束(通过告诉你内存单元的数量或者读取它们直到遇到空终止符),那么你就可以开始操作了。

指针不过是内存块的起始位置,也就是第一个内存单元的地址,或者说是第一个元素的指针。所有这些术语都是指同一件事情。就像电子表格中的单元格引用一样,如果你有一个巨大的网格,你可以通过它的X-Y坐标来确定特定的单元格,因此单元格B5告诉你一个特定的单元格。在计算机术语中(而不是电子表格),内存实际上是一个非常非常长的单元格列表,类似于一维电子表格,单元格引用看起来像0x12345678而不是B5。

最后一点是要理解计算机程序是一块数据块,由操作系统加载到内存中,编译器将会计算出字符串相对于程序起始位置的位置,因此你自动知道它位于哪个内存块中。

这与在堆上分配一块内存(它只是巨大内存空间的另一部分)或栈上(同样,是为本地分配保留的内存块)完全相同。您拥有字符串所在的第一个内存位置的地址。

因此

char* mystring = "string";

char mystring[7];
copy_some_memory(mystring, "string", 7);

char* mystring = new char(7);
copy_some_memory(mystring, "string", 7);

它们都是同一件事情。mystring是第一个字节的内存位置,其中包含值“s”。语言可能使它们看起来不同,但那只是语法。因此,数组就是指针,只是语言使其看起来不同,并且您可以使用略微不同的语法对其进行操作,以使对其的操作更安全。

(注意:第一个示例和其他示例之间的重大区别在于编译器设置的数据集是只读的。如果您可以更改该字符串数据,则可以更改程序代码,因为它也只是存储在保留用于程序数据的内存部分中的CPU指令块。出于安全原因,这些特殊的内存块对您是受限制的)。


strncpy()? - Keith Thompson
我打字速度很快 - 这不是真正的代码,只是为了理解而提供的示例。 - gbjbaanb

0

这里有另一种看待它们的方式:

首先,内存是可以存储数据的地方。

其次,地址是某个内存位置的位置。由地址引用的内存可能存在,也可能不存在。你不能把任何东西放在一个地址中,只能在地址处存储数据 - 你只能将数据存储在地址所指向的内存中。

数组是内存中连续的位置 - 它是特定类型的一系列内存位置。它存在,并且可以放入真实数据。像内存中的任何实际位置一样,它一个地址

指针包含一个地址。该地址可以来自任何地方。

字符串是以NUL结尾的字符数组

这样看待它:

内存 - 一座房子。你可以把东西放进去。这座房子一个地址

数组 - 一排房子,一个挨着一个,都是一样的。

指针 - 一张纸片,你可以在上面写一个地址。你不能在纸片本身(除了地址)存储任何东西,但你可以把东西放到你在纸上写的地址所对应的房子里。


0
然后,这个数组被视为指针,程序将为指向编译器创建的数组第一个元素的指针分配给指向字符的变量(一般为左值)一个指针至第一个元素的值(右值)。这是唯一的中间/非存储的指针值,因为编译器和链接器在编译链接时知道数组的地址。但是,你无法直接访问该数组,因为它是匿名字面量。如果你改为使用文字初始化一个数组,那么文字将会消失(类似于优化掉作为单独实体),而数组将可以通过其预计算地址直接访问。
char s[] = "String"; // char[7]
char *p = s;
char *p = &s[0];

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