【问题标题】:Are pointers arrays?指针是数组吗?
【发布时间】:2016-04-21 01:18:55
【问题描述】:

这是我难以理解的代码:

char* myPtr = "example";
myPtr[1] = 'x';

如何允许我使用myPtr[1]?为什么我可以选择像 do on arrays 这样的位置? myPtr 甚至不是一个数组。

观察。我知道查找表、文字池和字符串文字,我关心的是它是如何编译的。我很少使用指针。

谁能帮忙?

【问题讨论】:

  • 在现代 C++ 中实际上不允许这样做,其中字符文字为 const char *s。任何独立的现代 C++ 编译器都将拒绝编译此代码。但是,假设我们谈论的是const char *,数组实际上只是指向数组第一个元素的指针,所以数组和指针真的就像异卵双胞胎。
  • 这很有帮助。谢谢。 :p
  • operator[]其实是指针操作,不是数组操作。它在数组上工作(或似乎工作)是数组到指针衰减的结果。
  • 指针是不是数组。阅读comp.lang.c FAQ 的第 6 节。

标签: c++ c arrays pointers


【解决方案1】:

显然,您假设[] 运算符对某事物的适用性必然意味着“某事物”是一个数组。这不是真的。内置的[] 运算符与数组没有直接关系。 [] 只是*+ 运算符组合的简写:根据定义a[b] 表示*(a + b),其中一个操作数必须是指针,而另一个操作数必须是整数。

此外,当您将[] 运算符应用于实际数组时,该数组首先会被隐式转换为指针类型,然后结果指针才能充当[] 运算符的操作数。这实际上意味着与您最初假设的相反:operator [] never 适用于数组。当我们到达[] 时,数组已经衰减为一个指针。

作为一个相关的旁注,后一个细节体现在第一个 C 语言标准的一个模糊特性中。在 C89/90 中,右值数组不允许数组到指针的转换,这也阻止了 [] 运算符处理此类数组

struct S { int a[10]; };

struct S foo(void) { struct S s = { 0 }; return s; }

int main() 
{
  foo().a[5]; 
  /* ERROR: cannot convert array to pointer, and therefore cannot use [] */

  return 0;
}

C99 扩展了该转换的适用性,从而使上述代码有效。

【讨论】:

  • "内置的[] 运算符与数组绝对没有任何直接关系。" 也许,但它有一个非常强大的间接关系,因为指针必须指向数组对象的元素。
  • @Keith Thompson:是的,但它也可以与指向独立对象(而不是数组)的指针一起使用,因为对于指针算术来说,独立对象充当大小为 1 的数组。并声称任何独立对象可以被视为一个大小为 1 的数组无论出于何种目的可能太多了(即使它是真的)。​​
  • 是的,同样,它实际上必须是一个数组对象。
【解决方案2】:

它根据 C++ 标准的 §5.2.1/1 [expr.sub] 进行编译:

后缀表达式后跟方括号中的表达式是后缀表达式。其中一个表达式应具有“T 数组”或“指向 T 的指针”类型,而另一个应具有无作用域枚举或整数类型。结果是类型“T”。类型“T”应该是一个完全定义的对象类型。

表达式E1[E2]*((E1)+(E2)) 相同(根据定义),除了在数组操作数的情况下,如果该操作数为左值,则结果为左值,否则为xvalue。

由于"example" 的类型为char const[8],它可能会衰减为char const*(它曾经也衰减为char*,但它主要是过去的遗留物),这使它成为一个指针。

此时myPtr[1] 的表达式变为*(myPtr + 1),这是定义明确的。

【讨论】:

  • 在 C 中,字符串文字仍然不是const,尽管尝试修改一个具有未定义的行为。
  • 我使用的是 VC 版本 19.0 并且编译得很好。相当复杂的东西,但我现在明白了。谢谢。
  • @KeithThompson 甚至没有注意到 C 标签。
【解决方案3】:

指针保存它们被分配保存的特定数据类型的变量的内存位置地址。正如其他人指出的那样,它的反直觉方法需要一些学习曲线才能理解。

注意字符串"example"本身是不可变的,但是编译器不会阻止指针变量的操作,它的新值被更改为字符串'x'的地址(这与@的地址不同987654324@'example'),

char* myPtr = "example";
myPtr[1] = 'x';

由于 myPtr 在程序运行时引用不可变数据,它会崩溃,尽管它编译没有问题。

从 C 的角度来看,在这里,您取消了对可变变量的引用。 默认情况下,在 C 中,char 指针被定义为可变的,除非通过关键字 const 明确声明为不可变,在这种情况下,绑定变得不可分割,因此在定义指针变量后不能将任何其他内存地址分配给指针变量。

假设您的代码看起来像这样,

const char *ptr ="example";
ptr[1] = 'x';

现在编译会失败,你不能修改值,因为这个指针变量是不可变的。

您应该只使用 char 指针来访问字符串中的单个字符。

如果你想做字符串操作,那么我建议你声明一个int 来存储标准输入输出中每个字符的 ASCII 值,就像这里提到的那样,

#include<stdio.h>
int main()
{
    int countBlank=0,countTab=0,countNewLine=0,c;
    while((c=getchar())!=EOF)
    {
        if(c==' ')
            ++countBlank;
        else if(c=='\t')
            ++countTab;
        else if(c=='\n')
            ++countNewLine;
        putchar(c);
    }
    printf("Blanks = %d\nTabs = %d\nNew Lines = %d",countBlank,countTab,countNewLine);
}

查看整数如何获取 ASCII 值,以便使用 getchar() 和 putchar() 获取和打印单个字符。

特别感谢 Keith Thompson 今天在这里学到了一些有用的东西。

【讨论】:

  • C++ 中的字符串文字(不是字符文字)的类型为const char[N],而不是const char*,其中N 是文字的长度加1 表示终止'\0'。跨度>
  • @KeithThompson 问题中有一个 C 标签。所以我用我的 const char *
  • 在 C 中,字符串文字的类型为 char[N]。在 C++ 中,它们的类型为 const char[N]。在这两种情况下,N 是长度加 1。尝试打印 sizeof "hello, world" 的值;它是数组的大小,而不是指针的大小。
  • @Keith 我想指出 const char * -C 和 C++ 的字符串数组或对象的类型分别阻止询问者使用 myPtr[1] = 'x'; 进行修改如果你觉得有必要改正,也许你可以从这里开始,stackoverflow.com/questions/4949254/…
  • 指针对象或数组对象都可以存储在堆栈上(严格来说:在具有自动存储持续时间的对象中),在堆上(严格来说:在具有分配存储持续时间的对象中) ,或在静态数据中。您在第一段中所做的区分实际上并不存在。
【解决方案4】:

要记住的最重要的事情是:

数组不是指针。

但是 C 和 C++ 中都有一些语言规则可以使它们看起来好像是同一件事。在某些上下文中,数组类型的表达式或指针类型的表达式是合法的。在这些上下文中,数组类型的表达式被隐式转换以产生指向数组初始元素的指针。

char an_array[] = "hello";
const char *a_pointer = "goodbye";

an_array 是一个数组对象,类型为char[6]。字符串文字"hello" 用于对其进行初始化。

a_pointer 是一个指针对象,类型为const char*。您需要const,因为用于初始化它的字符串文字是只读的。

当数组类型的表达式(通常是数组对象的名称)出现在表达式中时,它通常被隐式转换为指向其初始(第 0 个)元素的指针。因此,例如,我们可以这样写:

char *ptr = an_array;

an_array 是一个数组表达式;它被隐式转换为char* 指针。以上完全等价于:

char *ptr = &(an_array[0]); // parentheses just for emphasis

数组表达式转换为指针值的情况有3种:

  1. 当它是 sizeof 运算符的操作数时。 sizeof an_array 产生数组的大小,而不是指针的大小。

  2. 当它是一元 &amp; 运算符的操作数时。 &amp;an_array 产生整个数组对象的地址,而不是某些(不存在的)char* 指针对象的地址。它的类型是“指向 6 个chars 数组的指针”或char (*)[6]

  3. 当它是用作数组对象的初始值设定项的字符串文字时。在上面的例子中:
    char an_array[] = "hello";
    字符串文字"hello" 的内容被复制到an_array;它不会衰减为指针。

最后,还有一个语言规则可以使数组看起来好像是“真正的”指针:用数组类型定义的参数是调整,因此它实际上是指针类型。您可以定义如下函数:

void func(char param[10]);

真正的意思是:

void func(char *param);

10 被忽略。

[] 索引运算符需要两个操作数,一个指针和一个整数。指针必须指向数组对象的一个​​元素。 (独立对象被视为 1 元素数组。)表达式

arr[i]

根据定义等价于

*(arr + i)

将整数添加到指针值会产生一个新指针,该指针在数组中向前i 元素。

comp.lang.c FAQ 的第 6 节对所有这些内容都有很好的解释。 (它适用于 C++ 和 C;这两种语言在这方面的规则非常相似。)

【讨论】:

    【解决方案5】:

    在 C++ 中,您的代码在编译期间会生成警告:

    {
      //char* myPtr = "example";  // ISO C++ forbids converting a string 
                                  // constant to ‘char*’ [-Wpedantic]
    
      // instead you should use the following form
      char myPtr[] = "example";  // a c-style null terminated string
    
      // the myPtr symbol is also treated as a char*, and not a const char*
    
      myPtr[1] = 'k';  // still works,  
    
      std::cout << myPtr << std::endl;  // output is 'ekample'
    }
    

    另一方面,std::string 更加灵活,并且具有更多功能:

     {
       std::string myPtr = "example";
    
       myPtr[1] = 'k';  // works the same
    
       // then, to print the corresponding null terminated c-style string
       std::cout << myPtr.c_str() << std::endl;
    
       //  ".c_str()" is useful to create input to system calls requiring
       //   null terminated c-style strings
     }
    

    【讨论】:

    • 查看 cppreference.com 以获得 std::string 函数的完整列表。
    【解决方案6】:

    abc[x] 的语义 是“将 x*sizeof(type)”添加到 abc,其中 abc 是任何内存指针。数组变量的行为类似于内存指针,它们只是指向分配给数组的内存位置的开头。

    因此将 x 添加到数组或指针变量都将指向与指向的变量相同的内存 + x*sizeof(数组包含或指针指向的类型,例如在 int 指针或 int 数组的情况下为 4)

    数组变量与 Keith 在评论中所说的指针不同,因为数组声明将创建固定大小的内存块,并且任何算术都将使用数组的大小而不是该数组中的元素类型。

    【讨论】:

    • 不错。很好的解释。非常感谢。
    • "数组变量的行为类似于内存指针,它们只是指向分配给数组的内存位置的开头。"。不,数组和指针是完全不同的东西。请查看我的回答和comp.lang.c FAQ 的第 6 部分。
    • 你了解表现得像做不同的事情之间的区别吗?
    • 是的,我愿意。数组的行为不像指针。在大多数情况下,数组表达式被隐式转换为指向数组初始元素的指针。结果指针的行为就像一个指针。
    • 我在说什么表现得像一样
    猜你喜欢
    • 1970-01-01
    • 2010-12-11
    • 1970-01-01
    • 1970-01-01
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多