В чем разница между указателем на массив и указателем на указатель?

Я новичок в программировании и изучаю указатели в массиве. Я немного смущен прямо сейчас. Взгляните на программу ниже:

#include <stdio.h>
int fun();
int main()
{
    int num[3][3]={23,32,478,55,0,56,25,13, 80};
    printf("%d\n",*(*(num+0)+1));
    fun(num);
    printf("%d\n", *(*(num+0)+1));
    *(*(num+0)+0)=23;
    printf("%d\n",*(*(num+0)));
    return 0;
}
int fun(*p)  // Compilation error
{
    *(p+0)=0;
    return 0;
}

Это была программа, записанная в заметках моего учителя. Здесь, в функции main(), в функции printf() оператор разыменования используется два раза, потому что num является указателем на массив, поэтому первый раз оператор разыменования даст указатель на int, а затем второй даст значение, на которое указывает указатель.

Мой вопрос в том, что когда я передаю имя массива в качестве аргумента функции fun(), то почему используется *p; почему бы не **p, поскольку num является указателем на массив?

Во-вторых, почему *(p+0) используется для изменения значения нулевого элемента массива; почему не *(*(p+0)+0)=0 как в main() функция *(*(num+0)+0) используется для изменения значения нулевого элемента?

Все это очень запутанно для меня, но я все равно должен это понять. Я искал об этом и обнаружил, что существует разница между указателем на массив и указателем на указатель, но я мало что понял.


person user4156958    schedule 03.12.2014    source источник
comment
многомерные массивы плоские. чтобы устранить путаницу, прочитайте о том, как они реализованы.   -  person Karoly Horvath    schedule 03.12.2014
comment
вы получите ответ из ответов на этот вопрос stackoverflow.com/questions/14100240/   -  person 999k    schedule 03.12.2014
comment
Разве вы не получите ошибку компиляции, несоответствие прототипа/функции? А почему вы спрашиваете про +0+0 вместо +0? Зачем вообще добавлять статический 0 в код?   -  person dhein    schedule 03.12.2014
comment
я не получаю никаких ошибок компиляции.   -  person user4156958    schedule 03.12.2014
comment
num на самом деле хранит адрес num[0][0], который является целым числом, поэтому u является указателем на целое число.   -  person rahul tyagi    schedule 03.12.2014
comment
какие! num — это указатель на массив, в котором будет храниться адрес num[0]   -  person user4156958    schedule 03.12.2014
comment
int fun(*p) { ... } не должен компилироваться. Какой компилятор (с какими флагами) вы используете?   -  person mafso    schedule 03.12.2014
comment
я использую блоки кода с gcc 4.7.1   -  person user4156958    schedule 04.12.2014
comment
У меня нет под рукой 4.7.1, но я совершенно уверен, что показанный код не скомпилируется с ним (без -std=[gnu|c][89|99]).   -  person mafso    schedule 04.12.2014
comment
пожалуйста, объясните мне, в чем разница между *p и (*p)[3].   -  person user4156958    schedule 04.12.2014
comment
С заданной функцией нет никакой разницы, обе они недействительны и не должны компилироваться. В общем, (*p)[3] есть *(*p + 3), и это все, что я могу сказать без объявления p.   -  person mafso    schedule 04.12.2014
comment
Это тот же учитель, что и в этом вопросе?   -  person glglgl    schedule 25.12.2014


Ответы (4)


Хитрость заключается в распаде указателя массива: когда вы упоминаете имя массива, он распадается на указатель на его первый элемент почти во всех контекстах. То есть num — это просто массив из трех массивов трех целых чисел (тип = int [3][3]).

Давайте проанализируем выражение *(*(num + 1) + 2).

Когда вы упоминаете num в выражении *(num + 1), оно распадается на указатель на его первый элемент, который представляет собой массив из трех целых чисел (тип = int (*)[3]). С этим указателем выполняется арифметика указателя, и размер того, на что указывает указатель, добавляется к значению указателя. В данном случае это размер массива из трех целых чисел (на многих машинах это 12 байт). После разыменования указателя у вас останется тип int [3].

Однако это разыменование касается только типа, потому что сразу после операции разыменования мы видим выражение *(/*expression of type int[3]*/ + 2), поэтому внутреннее выражение распадается обратно на указатель на первый элемент массива. Этот указатель содержит тот же адрес, что и указатель, полученный из num + 1, но имеет другой тип: int*. Следовательно, арифметика указателя для этого указателя продвигает указатель на два целых числа (8 байтов). Таким образом, выражение *(*(num + 1) + 2) дает целочисленный элемент со смещением 12 + 8 = 20 байт, что является шестым целым числом в массиве.


Что касается вашего вопроса о вызове fun(), этот вызов на самом деле не работает и работает только потому, что ваш учитель не включил аргументы в предварительное объявление fun(). Код

int fun(int* arg);

int main() {
    int num[3][3] = ...;
    ...
    fun(num);
}

вызвало бы ошибку времени компиляции из-за неправильного типа указателя. Код вашего учителя "работает", потому что указатель на первый массив в num совпадает с указателем на первый элемент первого массива в num, т.е. е. его код эквивалентен

int fun(int* arg);

int main() {
    int num[3][3] = ...;
    ...
    //both calls are equivalent
    fun(num[0]);
    fun(&num[0][0]);
}

который будет компилироваться без ошибок.

person cmaster - reinstate monica    schedule 04.12.2014

В этом примере показана матрица, указатели на первые целые числа массивов и указатель на указатель.

#include<stdio.h>
int fun(int (*p)[3]);  /* p is pointer to array of 3 ints */
int main()
{
    /* matrix */
    int num[3][3]={{23,32,478},{55,0,56},{25,13, 80}};
    /* three pointers to first integer of array */
    int *pnum[3] = {num[0], num[1], num[2]};
    /* pointer to pointer */
    int **ppnum = pnum;
    printf("%d\n", *(*(num+1)+2));
    fun(num);
    printf("%d\n", *(*(num+1)+2));
    pnum[1][2] = 2;
    printf("%d\n", *(*(num+1)+2));
    ppnum[1][2] = 3;
    printf("%d\n", *(*(num+1)+2));
    return 0;
}
int fun(int (*p)[3])
{
    p[1][2]=1;
    return 0;
}
person rcgldr    schedule 03.12.2014

На самом деле вам не нужны никакие указатели, чтобы напечатать что-либо здесь.

Ваш int num[3][3] на самом деле представляет собой массив из трех элементов, каждый из которых представляет собой массив из трех целых чисел. Таким образом, num[0][0] = 23, num[1][1] = 0 и так далее. Таким образом, вы можете сказать printf("%d", num[0][0]), чтобы напечатать первый элемент массива.

person Gophyr    schedule 03.12.2014
comment
я знаю, что могу это сделать, но я хочу знать это с помощью указателя, и мой вопрос в том, почему * (p + 0) используется для инициализации 0, почему бы и нет * (* (p + 0) + 0)? Поскольку num - это указатель на множество - person user4156958; 03.12.2014
comment
Если вы должны использовать указатели, просто используйте p. p уже известно, что это указатель в определении функций, поэтому вы можете просто сказать p = 0;, что вам больше ничего не нужно. Попытайся. - person Gophyr; 03.12.2014

Указатель на переменную:

Указатель — это переменная, в которой хранится адрес (переменной). Все это знают.


Указатель на массив:

Массив — это переменная, которая имеет начальную точку (адрес) группы одинаковых объектов.

А указатель — это переменная, в которой хранится начальная точка (адрес) массива.

Например:

int iArray[3];

iArray — это переменная, которая имеет адресное значение из трех целых чисел, а память выделяется статически. И приведенный ниже синтаксис представлен на типичных языках программирования.

// iArray[0] = *(iArray+0);
// iArray[1] = *(iArray+1);
// iArray[2] = *(iArray+2);

В приведенном выше примере iArray — это переменная, через которую мы можем получить доступ к трем целочисленным переменным, используя любой из упомянутых выше синтаксисов.

*(Массив+0); // Здесь iArray+0 — адрес первого объекта. и * означает разыменование *(iArray+1); // Здесь iArray+1 — адрес второго объекта. и * для разыменования

Так просто, что тут путать.


Следующие строки предназначены для вашего понимания

int iArray1[3];
int iArray2[3][3];

int *ipArray = 0;

ipArray = iArray1;        // correct
ipArray = iArray2[0];     // correct
ipArray = iArray2[2];     // correct
int **ippArray = iArray2; // wrong

Согласно приведенной выше последней строке, компилятор не воспримет это как допустимое назначение. Так что **p не используется.

Арифметика указателя не может быть применена к двойным массивам из-за способа выделения памяти.

person Neo    schedule 03.12.2014
comment
Вы вообще не говорили об указателе на массив в своем ответе. - person newacct; 04.12.2014