通过阅读有关 CI 中的指针和数组的一些详细信息,有些困惑。一方面,数组可以看作是一种数据类型。另一方面,数组往往是不可修改的左值。我想编译器会做一些事情,比如用一个常量地址和一个表达式替换数组的标识符,以计算运行时索引给出的位置。
myArray[3] -(compiler)-> AE8349F + 3 * sizeof(<type>)
当说数组是一种数据类型时,这到底是什么意思?我希望你能帮助我澄清我对数组到底是什么以及编译器如何处理它的困惑理解。
当谈到数组是一种数据类型时,这到底是什么意思?
数据类型是一组数据,其值具有预定义的特征。数据类型的示例有:整数、浮点单元数、字符、字符串和指针
数组是一组内存位置,它们都具有相同的名称和相同的类型。
如果您想知道为什么数组不可修改,那么我读过的最好的解释是;
C 并不是从丹尼斯·里奇的头脑中完全形成的;它源自一种称为 B 的早期语言(源自 BCPL)。1 B 是一种“无类型”的语言;对于整数、浮点数、文本、记录等,它没有不同的类型。相反,一切都只是一个固定长度的单词或“单元格”(本质上是一个无符号整数)。记忆被视为细胞的线性阵列。当您在 B 中分配一个数组时,例如
auto V[10];
编译器分配了 11 个单元格;数组本身的 10 个连续单元格,加上一个绑定到 V 的单元格,其中包含第一个单元格的位置:
+----+
V: | | -----+
+----+ |
... |
+----+ |
| | <----+
+----+
| |
+----+
| |
+----+
| |
+----+
...
当 Ritchie 在struct
C 中添加类型时,他意识到这种安排给他带来了一些问题。例如,他想创建一个结构类型来表示文件或目录表中的条目:
struct {
int inumber;
char name[14];
};
他希望该结构不仅以抽象的方式描述条目,而且还表示实际文件表条目中的位,该条目没有额外的单元格或单词来存储数组中第一个元素的位置。所以他摆脱了它——他没有留出一个单独的位置来存储第一个元素的地址,而是编写了 C 语言,以便在计算数组表达式时计算第一个元素的地址。
这就是为什么你不能做类似的事情
int a[N], b[N];
a = b;
因为在该上下文中,两者a
都b
评估为指针值;相当于写作3 = 4
。内存中没有任何东西实际存储数组中第一个元素的地址;编译器只是在翻译阶段计算它。
有关更多详细信息,您可能想阅读此答案。
编辑:为了更清楚;可修改左值、不可修改左值和右值之间的区别(简而言之);
这些表达式之间的区别在于:
- 可修改的左值是可寻址的(可以是一元 & 的操作数)和可赋值的(可以是 = 的左操作数)。
- 不可修改的左值是可寻址的,但不可赋值。
- r 值既不可寻址也不可赋值。
数组是一个连续的内存块。这意味着它按顺序排列在内存中。假设我们定义了一个数组,如:
int x[4];
哪里sizeof(int) == 32
位。
这将像这样布置在内存中(选择一个任意的起始地址,比如说0x00000001
)
0x00000001 - 0x00000004
[element 0]
0x00000005 - 0x00000008
[element 1]
0x00000009 - 0x0000000C
[element 2]
0x0000000D - 0x00000010
[element 3]
您是正确的,编译器替换了标识符。请记住(如果您已经学会了这一点。如果没有,那么您正在学习新的东西!)数组本质上是一个指针。0x00000001
在 C/C++ 中,数组名是指向数组第一个元素的指针(或在我们的示例中指向地址的指针)。通过做这个:
std::cout << x[2];
您告诉编译器将 2 添加到该内存地址,这是指针算术。假设您使用变量来索引:
int i = 2;
std::cout << x[i];
编译器看到这个:
int i = 2;
std::cout << x + (i * sizeof(int));
它基本上将数据类型的大小乘以给定的索引,并将其添加到数组的基地址。编译器基本上采用索引运算符[]
并使用指针将其转换为加法。
如果您真的想对此有所了解,请考虑以下代码:
std::cout << 2[x];
这是完全有效的。如果你能弄清楚原因,那么你就已经掌握了这个概念。