【问题标题】:Why C++ variable doesn't need defining properly when it's a pointer?为什么当 C++ 变量是指针时不需要正确定义?
【发布时间】:2015-11-20 21:45:06
【问题描述】:

我对 C++ 语言完全陌生(尤其是指针,经验主要是 PHP),并且希望对以下内容进行一些解释(我已尝试寻找答案)。

两行代码如何在我的程序中完成完全相同的工作?第二行似乎与我迄今为止所学和理解的关于指针的一切背道而驰。

char disk[3] = "D:";

char* disk = "D:";

我怎样才能初始化指向内存地址以外的任何东西的指针?不仅如此,在第二行中我也没有正确声明数组 - 但它仍在工作?

【问题讨论】:

    标签: c++ arrays pointers char declaration


    【解决方案1】:

    在 C 和 C++ 中初始化数组的常用方法是:

    int a[3] = { 0, 1, 2 };
    
    另外:您可以选择省略数组绑定并从初始化器列表中推导出它,或者具有比初始化器更大的边界:
    int aa[] = { 0, 1, 2 }; // 另一个由三个整数组成的数组
    int aaa[5] = { 0, 1, 2 }; // 相当于 { 0, 1, 2, 0, 0}

    对于字符数组,有一个特殊规则允许从字符串文字初始化数组,数组的每个元素都从字符串文字中的相应字符初始化。

    您的第一个示例使用字符串文字"D:",因此数组的每个元素都将被初始化为该字符串中的一个字符,相当于:

    char disk[3] = { 'D', ':', '\0' };
    

    (第三个字符是null terminator,它隐含在所有字符串文字中)。

    另外:在这里,您也可以选择省略数组绑定并从字符串文字推导出它,或者具有比字符串长度更大的边界:
    char dd[] = "D:"; // 另一个三个字符的数组
    字符 ddd[5] = "D:"; // 等价于 { 'D', ':', '\0', '\0', '\0'}
    就像上面的 aaa 示例一样,ddd 中在字符串中没有对应字符的额外元素将被初始化为零。

    您的第二个示例有效,因为字符串文字 "D:" 将由编译器输出并作为三个字符的数组存储在可执行文件中的某个位置。当可执行文件运行时,包含数组(和其他常量)的段将被映射到进程的地址空间。因此,您的 char* 指针随后被初始化为指向该数组的位置,无论它发生在哪里。从概念上讲,它类似于:

    const char __some_array_created_by_the_compiler[3] = "D:";
    const char* disk = __some_array_created_by_the_compiler;
    

    由于历史原因(主要是 const 在 C 的早期不存在),使用非常量 char* 指向该数组是合法的,即使该数组实际上是只读的,因此 C 和第一个 C++ 标准允许您使用非 const char* 指针指向字符串文字,即使它引用的数组实际上是 const:

    const char __some_array_created_by_the_compiler[3] = "D:";
    char* disk = (char*)__some_array_created_by_the_compiler;
    

    这意味着尽管看起来你的两个示例并不完全相同,因为这仅允许用于第一个:

    disk[0] = 'C';
    

    对于第一个没问题的例子,它改变了数组的第一个元素。

    对于第二个示例,它可能会编译,但会生成undefined behaviour,因为它实际上正在修改__some_array_created_by_the_compiler 的第一个元素,它是只读的。在实践中可能会发生的情况是进程会崩溃,因为尝试写入内存的只读页面会引发分段错误。

    重要的是要了解 C++ 中有很多东西(C 中甚至更多)编译器会很高兴地编译,但会导致在执行代码时发生非常糟糕的事情。

    【讨论】:

      【解决方案2】:

      你说指针只能存储内存地址是绝对正确的。那么第二个陈述如何有效?让我解释一下。

      当您将一个字符序列放在双引号中时,屏幕后面发生的情况是该字符串存储在只读计算机内存中,并返回存储该字符串的位置的地址。所以在运行时,表达式被计算,字符串被计算为内存地址,它是一个字符指针。分配给指针变量的正是这个指针。

      那么这两种说法有什么区别呢?第二种情况的字符串是一个常量,而第一种语句声明的字符串是可以改变的。

      【讨论】:

      • 谢谢,但如果它是一个常数,那我怎么能改变它呢? char* disk = "A"; disk = "BBB"; cout << disk;
      • @JamieCole,字符串是只读的,而指针不是。您正在做的是为指针分配另一个内存地址。但是你不能 *disk ++ 将值从“A”更改为“B”。
      【解决方案3】:

      字符串字面量实际上是只读的、以零结尾的字符数组,使用字符串字面量可以为您提供指向数组中第一个字符的指针。

      所以在第二个例子中

      char* disk = "D:";
      

      您将disk 初始化为指向三个字符数组的第一个字符。


      请注意,在我上面的第一段中,我说过字符串文字是 只读 数组,这意味着有一个普通的 char* 指向这个数组可能会让你认为可以修改此数组不是(尝试修改字符串文字会导致未定义的行为)。这就是const char*常用的原因:

      const char* disk = "D:";
      

      从 C++11 开始,不使用 const char* 实际上是一个错误,但大多数编译器仍然只警告它而不是产生错误。

      【讨论】:

      • char* disk = "D:"; 至少应该是一个警告,并且根据标准应该是一个错误。 coliru.stacked-crooked.com/a/723cd7928608aed0
      • @NathanOliver,根据标准,它应该是诊断消息,定义为“属于实现输出消息的实现定义子集的消息”。警告在技术上符合这一点。
      【解决方案4】:
      char disk[3] = "D:";
      

      被视为

      char disk[3] = {'D',':','\0'};
      

      在 C++11 及更高版本中

      char* disk = "D:";
      

      这是一个错误,因为字符串文字的类型为const char[],并且不能分配给char *。不过,您可以将其分配给 const char *

      【讨论】:

      • 你不认为这个答案会让那些刚接触 C++ 并且不知道版本差异的人感到困惑,并且在他们的编译器上,代码显然正在编译吗? (或者你可能不知道这曾经是合法的)
      • @BenjaminLindley 我没有想到要补充说它曾经没问题,因为问题被标记为 C++,应该用当前标准回答。我已经添加到我的答案中,指出这是 C++11 中和之后的错误。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-02
      • 1970-01-01
      • 1970-01-01
      • 2018-10-08
      • 1970-01-01
      • 2016-09-25
      相关资源
      最近更新 更多