【问题标题】:Differences between arrays, pointers and strings in CC中数组、指针和字符串的区别
【发布时间】:2015-08-19 07:02:04
【问题描述】:

请记住一个让我感到困扰的问题。

我知道 pointersarrays 在 C 中是不同的,因为 pointers 存储 addressarrays 存储 'real' values

但我对string 感到困惑。

char *string = "String";

我读到这条线做了几件事:

一个字符数组由编译器创建,其值为String

然后,这个数组被认为是一个pointer,程序为指针字符串分配一个指针,该指针指向编译器创建的数组的first element

这意味着,arrays 被视为pointers

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

如果为 false,那么 pointersarrays 之间有什么区别? 谢谢。

【问题讨论】:

  • char *string = "String"; 这应该会给出编译器警告,因为您将 const char* 分配给 char*
  • @πάνταῥεῖ 字符串文字并不总是const。然而,UB 一直在尝试改变它们。
  • @Quentin:在 C 中,字符串文字从来都不是const,现在也不是。在 C++ 中,它们是。
  • @πάνταῥεῖ:字符串文字不是 C 中的 const
  • 几乎是 what is array decaying? 的副本,它提供了适当的答案。

标签: c arrays string pointers


【解决方案1】:

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

数组是元素的连续有序序列;每个元素都是一个对象,数组的所有元素都是同一类型。

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

数组和指针之间的关系可能会令人困惑。 comp.lang.c FAQ 的第 6 节给出了我所知道的最佳解释。最重要的是要记住 数组不是指针

数组在某种意义上是 C 和 C++ 中的“二等公民”。它们不能被赋值、作为函数参数传递或比较是否相等。操作数组的代码通常使用指向数组各个元素的指针来执行此操作,并使用一些明确的机制来指定数组的长度。

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

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

  • 一元操作数&&array_object产生整个数组对象的地址);或

  • 用于初始化数组对象的初始化器中的字符串文字。

    char *string = "String";

为避免混淆,我将对您的示例进行一些更改:

const char *ptr = "hello";

字符串文字 "hello" 创建一个类型为 char[6](在 C 中)或 const char[6](在 C++ 中)的匿名对象,其中包含字符 { '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函数的东西。

【讨论】:

    【解决方案2】:

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

    你的结论是错误的。

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

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

    当你这样做时

    char *string = "String";  
    

    当 C 编译器遇到这种情况时,它会为字符串文字 String 留出 7 字节的内存。然后设置指针string指向分配内存的起始位置。

    当你声明时

    char string[] = "String";    
    

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

    所以,

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

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

    【讨论】:

      【解决方案3】:

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

      字符串是一个连续的内存单元块,以一个特殊的值(空终止符)终止。如果您知道这块内存的开始,并且知道它的结束位置(通过被告知内存单元的数量或通过读取它们直到到达空值),那么您就可以开始了。

      指针只不过是内存块的开始,它是第一个内存单元的地址,或者它是一个指向第一个元素的指针。所有这些术语的意思都是一样的。它就像电子表格中的单元格引用,如果你有一个巨大的网格,你可以通过它的 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 指令块存储在为程序数据保留的一段内存中。出于安全原因,这些特殊的内存块仅限您使用。

      【讨论】:

      • 我打字很快——它不应该是真正的代码,只是为了理解的例子。
      【解决方案4】:

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

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

      其次,address 是一些内存的位置。 address 引用的内存可能存在也可能不存在。您不能在address in 中放入任何内容,只能在address 中放入任何内容 - 您只能将数据存储在memory 中的address 所指的位置。

      array 是内存中的连续位置 - 它是一系列特定类型的内存位置。它存在,并且可以将真实数据放入其中。与memory 中的任何实际位置一样,它一个address

      pointer 包含 address。该地址可以来自任何地方。

      string 是一个以 NUL 结尾的 array 字符。

      这样看:

      memory - 房子。你可以把东西放进去。房子address

      array - 一排房子,一个挨一个,都一样。

      pointer - 一张纸,您可以在上面写上address。你不能在纸上存储任何东西(地址除外),但你可以把东西放在你写在纸上的地址的房子里。

      【讨论】:

        【解决方案5】:

        我们可以创建一个名为'string'的数组

        char string[] = "Hello";
        

        我们可以分配一个指向那个字符串的指针

        char* stringPtr = string;
        

        数组名是converted to a pointer

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

        char *string = "String";
        

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

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

        错,数组不是指针。但是,为了混淆(!),由于取消引用运算符 []

        ,指针可能看起来是数组
        char *string = "String";
        char letter = string[2];
        

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

        【讨论】:

        • 怎么知道数组在栈上? :-)
        • @PeterSchneider,我们没有使用 malloc(或新的 c++),还是我遗漏了什么?
        • 如果定义在函数之外,字符串将不在堆栈中。
        • @PeterSchneider,如果您指的是全局变量,那么是的,我同意它是neither on the heap nor the stack,所以我假设这是在一个函数中。这些天我通常使用 C++,所以我没有考虑这个。感谢您指出。
        • 我认为技术术语是静态存储持续时间,可能带有外部链接。
        【解决方案6】:

        然后,这个数组被认为是一个指针,程序为指针字符串分配一个指针,该指针指向编译器创建的数组的第一个元素。

        这里的措辞不是很好。数组仍然是一个数组,并且被认为是这样的。该程序将指向第一个元素的值(右值)分配给指向字符的指针变量(通常为左值)。这是这里唯一的中间/非存储指针值,因为编译器和链接器在编译链接时知道数组的地址。但是,您不能直接访问该数组,因为它是匿名文字。如果您改为使用文字初始化数组,那么文字将消失(将优化视为单独的实体)并且数组将可以通过其预先计算的地址直接访问。

        char s[] = "String"; // char[7]
        char *p = s;
        char *p = &s[0];
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2012-02-04
          • 1970-01-01
          • 1970-01-01
          • 2010-12-20
          • 2013-12-16
          • 2021-06-28
          • 2016-03-16
          相关资源
          最近更新 更多