Что происходит с этими массивами в нотации указателя C? - PullRequest
1 голос
/ 22 августа 2011

Я немного запутался в массивах указателей, и я просто хочу убедиться, что я прав.

Когда я пишу int *arr, это просто указатель на переменную типа int, а не на массив.Я только инициализирую его (скажем, с помощью malloc), и он становится массивом.Я прав до сих пор?

Также у меня есть еще один вопрос: мне дали (в школе) небольшую функцию, которая должна возвращать массив оценок, с первой ячейкой, являющейся средней.Функция была преднамеренно неправильной: они установили

int *grades = getAllGrades();

И затем они уменьшили указатель на единицу для средней «ячейки»

*(grades - 1) = getAverage();
 return *(grades - 1)

Я знаю, что это неправильнопоскольку возвращаемое значение не является массивом, я просто не знаю, как это объяснить.Когда я устанавливаю указатель, как машина / компилятор узнает, нужен ли мне только указатель или массив?

(Если я не ясно, это потому, что я пытаюсь спросить о чем-то, что все еще неопределенноза меня мои извинения)

Ответы [ 4 ]

5 голосов
/ 22 августа 2011

как машина / компилятор узнает, нужен ли мне только указатель или массив

Это не так, и никогда не будет. Предположим, вы

int *a = malloc(3 * sizeof(int));

Вы только что нашли 12 байтов (при условии, что int равен 4). Но malloc видит только 12. Это 1 большой объект или много маленьких? Это не знает. Единственный, кто на самом деле знает, это ты;)

Теперь о вашем конкретном примере,

int *grades = getAllGrades();

На данный момент, как вы сказали, нечего сказать, указывает ли grades на массив. Но вы знаете, что это указывает на массив, и вот что важно. Или, может быть, вы знаете, что это не указывает на массив. Ключ в том, что вы должны знать, что делает getAllGrades, чтобы знать, возвращает ли он массив или указатель на 1 вещь.

*(grades - 1) = getAverage();
return *(grades - 1)

Это не обязательно неправильно, но выглядит как эскиз. Если это массив, вы ожидаете, что grades[0] == *(grade + 0) будет первым элементом, поэтому grades[-1] == *(grades - 1) выглядит так, как если бы это было до первого элемента. Опять же, это не обязательно неправильно; может быть, в getAllGrades они сделали:

int* getAllGrades() {
    int *grades = malloc(sizeof(int) * 10);
    return grades + 1;
}

то есть они затормозили запуск на 1. Это, как известно, случалось (посмотрите в Числовых Рецептах в C), но это немного странно.

3 голосов
/ 22 августа 2011

Массивы не указатели.Указатели не являются массивами.

Возможно, было бы яснее сказать, что массив объекты не являются указателями объекты , и наоборот.

Когда выобъявлять int *arr, arr является указателем объекта.Это все, что есть;это не может быть и никогда не будет массивом.

Когда вы выполняете arr = malloc(10 * sizeof *arr);, (если malloc() не дает сбоя, что вы всегда должны проверять), arr теперь указывает на intобъект.Этот объект оказывается первым элементом массива int из 10 элементов (созданного с помощью вызова malloc).Обратите внимание, что существует такая вещь, как указатель на массив, но это не так.

Массивы, в самом реальном смысле, не являются типами первого класса в C. Вы можете создавать и манипулировать массивом.объекты, как вы можете с любым другим типом объектов, но вы редко будете иметь дело с массивом значения напрямую.Вместо этого вы будете иметь дело с элементами объекта массива косвенно, через указатели на эти элементы.А в случае с объявлением arr, приведенным выше, вы можете выполнить арифметику с указателем на первый элемент, чтобы получить указатели на другие элементы (и у вас должен быть какой-то другой механизм, чтобы запомнить, сколько там элементов).

Любое выражение типа массива в большинстве случаев неявно преобразуется в указатель на первый элемент массива (исключения: операнд унарного оператора &, операнд оператора sizeof истроковый литерал в инициализаторе, используемый для инициализации массива (под) объекта).Это правило заставляет казаться так, как будто массивы и указатели взаимозаменяемы.

Оператор индексации массива [] фактически определен для работы с указателями, а не с массивами.a[b] - это просто другой способ написания *((a)+(b)).Если a является именем объекта массива, он сначала преобразуется в указатель, как я описал выше.

Я настоятельно рекомендую прочитать раздел 6 comp.lang.c FAQ .(Ссылка на первую страницу, а не непосредственно на раздел 6, потому что я хотел бы призвать людей просмотреть все это.)

Я упомянул, что есть указатели на массивы.Учитывая, что int foo[10];, &foo[0] является указателем на int, но &foo является указателем на весь массив.Оба указывают на одно и то же место в памяти, но они разных типов и ведут себя совершенно по-разному в арифметике указателей.

0 голосов
/ 22 августа 2011

Вы правы, единственная разница между массивами и указателями - это условность. Вот изображение того, как должна выглядеть память, когда возвращается функция getAllGrades():

                          | secret malloc() stuff |
                          +-----------------------+
                          | average value         |
                          +-----------------------+  ----\
grades* points here --->  | grade at index 0      |      |  by convention
                          +-----------------------+      |  this stuff is
                          | grade at index 1      |      |  called grades[]
                          +-----------------------+      |
                          | grade at index 2      |      |
                          +-----------------------+      .
                          | ...                   |      .

Теперь нет никакой разницы между массивом и указателем. Таким образом, когда компилятор видит *(grades - 1), он сначала вычитает 1 из указателя оценок. Это специальная арифметика указателей, поэтому она знает, что нужно пройти один целый блок int вверх и указать среднее значение. Затем он может работать с этим значением, например, чтобы установить среднее значение с помощью *(grades - 1) = getAverage().

Отдельно от индексации массива: индексация массива компилируется точно так же, как арифметика указателя. Например, grades[2] компилируется до *(grades + 2), что делает арифметику указателей для перемещения вниз на 2 блока к адресу памяти, помеченному как «оценка по индексу 2» на моей картинке. Это означает, что вы можете изменить *(grades - 1) = getAverage() на grades[-1] = getAverage(), и он будет работать точно так же.

Если вы хотите поэкспериментировать, вы можете сделать (-1)[grades], который компилируется до *(-1 + grades) и работает также, но это глупо, так что не делайте этого:)

0 голосов
/ 22 августа 2011

Когда я пишу int *arr, это просто указатель на переменную типа int, а не массив еще. Я только инициализирую его (скажем, с помощью malloc), это становится массивом. Я прав до сих пор?

Ну да и нет :). arr - указатель на (или адрес) некоторого блока памяти. До тех пор, пока arr не будет инициализирован, он, вероятно, будет указывать на неверный или бессмысленный адрес памяти. Так что может показаться странным, что arr является указателем на переменную int. Например, до malloc вы не можете хранить целое число в том месте, на которое оно указывает.

Кроме того, может быть легче понять, если вы скажете, что после malloc, arr указывает на массив, он не "становится" массивом. До malloc, arr указывает на какое-то случайное бессмысленное местоположение.

Когда я устанавливаю указатель, как машина / компилятор узнает, хочу ли я просто указатель или массив?

Если вы установите указатель (например, arr = <something>), вы просто изменяете, куда указывает указатель. Это может быть то, что вы хотите. Если вы не хотите менять, куда указывает arr, но хотите изменить значения, хранящиеся в памяти, куда он указывает, вы должны делать это по одному элементу за раз (например, с циклом for, который перебирает каждый элемент в массив).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...