При работе с указателями и массивами в C или C ++ это действительно помогает распознать их как очень четкие конструкции (я думаю, что одной из лучших книг, объясняющих это различие, является книга под названием «Секреты Deep C», если я правильно помню). Что мутит воду, так это тот факт, что существует одностороннее бесшумное преобразование из имен массивов в указатели (несоответствие в обработке имен переменных в языке), но очень важно не интерпретировать существование этого явления распада как подразумевающего эквивалентность.
Чтобы помочь нам рассуждать об этом, давайте введем идею «ячейки памяти». Мы моделируем «ячейку памяти» как имеющую два атрибута:
a) value
b) address
Затем мы можем смоделировать простую переменную C ++ как имеющую два атрибута (нам не нужны типы на этом низком уровне абстракции):
c) name
d) memory cell
Как и большинство моделей, у него есть некоторые недостатки (не касается массива с более чем одним элементом, но этого достаточно для наших целей).
Так, например:
// non-array variable: name 'i', and memory cell: value=3, address=0x0A
int i = 3;
// non-array variable: name 'p', and memory cell: value=0x0A, address=0x0B
int *p = &i;
// array variable: name 'a', and memory cell: vale=4, address=0x0C
int a[1] = { 4 };
// non-array variable: name 'b', and memory cell: value=0x0C, address = 0x0D
int (*b)[1] = &a;
// non-array variable: name 's', and memory cell: value=0x0C, address = 0x0E
int *s = &a[0];
// non-array variable: name 't', and memory cell: value=0x0C, address = 0x0F
int *t = a; // Here is the key difference! read on...
Теперь вот основное отличие между переменной массива и переменной C ++, не являющейся массивом (указателем):
Когда оценивается имя переменной в C ++, оно всегда вычисляется по значению своей ячейки памяти с одним исключением: если переменная называет переменную массива.
Если переменная является именем массива, она оценивается по адресу ячейки памяти.
Вышеприведенные две строки заслуживают повторного прочтения.
Вот несколько примеров, которые помогут прояснить последствия (см. Вышеупомянутые переменные):
int k = i; // the 'i' name evaluates to the value of its cell, so 'k' is set to 3
int *q = p; // 'p' evaluates to the value of its cell, so 'q' is set to 0x0A
int *r = a; // 'a' evaluates to the *address* of its cell, so 'r' is set to 0x0C
int (*c)[1] = b; // 'c' is set to 0x0D
Это никоим образом не должно означать, что переменная массива является такой же как переменная-указатель.
Они по своей природе имеют разные типы, и любая попытка трактовать их как то же самое (т.е. определять имя переменной как массив в одной единице перевода и как указатель в другой) приведет к возникновению плохих вещей.
Так, например, не делайте этого:
// myproj_file1.cpp
int array[100] = { 0 }; // here 'array' evaluates to the *address* of the first memory cell
// myproj_file2.cpp
extern int* array; // here 'array' evaluates to the *value* of the first memory cell
// Assuming the linker links the two
// what it does if you read the assembly, is something like this:
// extern int* array = (int*) array[0];
// but it doesn't have to, it can do anything, since the behavior is undefined
Надеюсь, это поможет.
Если вам все еще кажется, что дальнейшие разъяснения могут помочь, пожалуйста, задайте дополнительный вопрос, и не стесняйтесь получить копию (библиотеку?) Этой книги "Deep C Secrets":)
-
постскриптум Типы функций, их имена и их распад не имеют отношения к большей части этого поста
постскриптум Я также намеренно не учел, что преобразование массива в указатель не происходит, когда массивы связаны с ссылочными типами