Массивы и указатели


В предыдущем руководстве по указателям вы узнали, что указатель на данный тип данных может хранить адрес любой переменной этого конкретного типа данных. Например, в следующем коде переменная-указатель pcхранит адрес символьной переменной c.

char c = 'A';
char *pc = &c;

Здесь cскалярная переменная, которая может хранить только одно значение. Однако вы уже знакомы с массивами, которые могут содержать несколько значений одного и того же типа данных в непрерывно выделенном блоке памяти. Итак, вы можете задаться вопросом, а могут ли у нас тоже быть указатели на массивы? Действительно, можем.

Давайте начнем с примера кода и посмотрим на его вывод. Мы обсудим его поведение позже.

char vowels[] = {'A', 'E', 'I', 'O', 'U'};
char *pvowels = vowels;
int i;

// Print the addresses
for (i = 0; i < 5; i++) {
    printf("&vowels[%d]: %p, pvowels + %d: %p, vowels + %d: %p\n", i, &vowels[i], i, pvowels + i, i, vowels + i);
}

// Print the values
for (i = 0; i < 5; i++) {
    printf("vowels[%d]: %c, *(pvowels + %d): %c, *(vowels + %d): %c\n", i, vowels[i], i, *(pvowels + i), i, *(vowels + i));
}

Типичный вывод вышеуказанного кода показан ниже.

& гласные [0]: 0x7ffee146da17, pvowels + 0: 0x7ffee146da17, гласные + 0: 0x7ffee146da17

& гласные [1]: 0x7ffee146da18, pvowels + 1: 0x7ffee146da18, гласные + 1: 0x7ffee146da18

& гласные [2]: 0x7ffee146da19, pvowels + 2: 0x7ffee146da19, гласные + 2: 0x7ffee146da19

& гласные [3]: 0x7ffee146da1a, pvowels + 3: 0x7ffee146da1a, гласные + 3: 0x7ffee146da1a

& гласные [4]: ​​0x7ffee146da1b, pvowels + 4: 0x7ffee146da1b, гласные + 4: 0x7ffee146da1b

гласные [0]: A, * (pvowels + 0): A, * (гласные + 0): A

гласные [1]: E, * (pvowels + 1): E, * (гласные + 1): E

гласные [2]: I, * (pvowels + 2): I, * (гласные + 2): I

гласные [3]: O, * (pvowels + 3): O, * (гласные + 3): O

гласные [4]: ​​U, * (pvowels + 4): U, * (гласные + 4): U

Как вы правильно догадались, выдает расположение в памяти i- го элемента массива . Более того, поскольку это символьный массив, каждый элемент занимает один байт, так что последовательные адреса памяти разделяются одним байтом. Мы также создали указатель, и присвоили ему адрес массива . допустимая операция; хотя в целом это не всегда может иметь смысл (подробнее рассматривается в Арифметике указателей ). В частности, вывод, показанный выше, указывает на то, что и являются эквивалентными. Не стесняйтесь изменять типы данных переменных массива и указателя, чтобы проверить это.&vowels[i]vowelspvowelsvowelspvowels + i&vowels[i]pvowels + i

Если вы внимательно посмотрите на предыдущем коде, вы увидите , что мы также использовали другой , по- видимому удивительное обозначения: . Более того, и возвращает одно и то же - адрес i- го элемента массива . С другой стороны, и как вернуть я й элемент массива . Почему это так?vowels + ipvowels + ivowels + ivowels*(pvowels + i)*(vowels + i)vowels

Это связано с тем, что имя самого массива является (постоянным) указателем на первый элемент массива. Другими словами, все обозначения vowels, и указывают на одно и то же место.&vowels[0]vowels + 0

Распределение динамической памяти для массивов

К настоящему времени мы знаем, что можем перемещаться по массиву с помощью указателей. Более того, мы также знаем, что можем динамически выделять (непрерывную) память, используя указатели на блоки. Эти два аспекта можно объединить для динамического выделения памяти для массива. Это показано в следующем коде.

// Allocate memory to store five characters
int n = 5;
char *pvowels = (char *) malloc(n * sizeof(char));
int i;

pvowels[0] = 'A';
pvowels[1] = 'E';
*(pvowels + 2) = 'I';
pvowels[3] = 'O';
*(pvowels + 4) = 'U';

for (i = 0; i < n; i++) {
    printf("%c ", pvowels[i]);
}

printf("\n");

free(pvowels);

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

Так когда это полезно? Помните, что при объявлении массива необходимо заранее знать количество элементов, которые он будет содержать. Поэтому в некоторых сценариях может случиться так, что пространство, выделенное для массива, будет меньше или больше желаемого. Однако, используя динамическое распределение памяти, можно выделить столько памяти, сколько требуется программе. Более того, неиспользуемая память может быть освобождена, как только она больше не требуется, путем вызова функции. С другой стороны, с динамическим распределением памяти нужно ответственно обращаться к везде, где это необходимо. В противном случае произойдут утечки памяти.free()free()

Мы завершаем это руководство рассмотрением динамического распределения памяти для двумерного массива. Аналогичным образом это можно обобщить на n -мерности. В отличие от одномерных массивов, где мы использовали указатель, в этом случае нам требуется указатель на указатель, как показано ниже.

int nrows = 2;
int ncols = 5;
int i, j;

// Allocate memory for nrows pointers
char **pvowels = (char **) malloc(nrows * sizeof(char *));

// For each row, allocate memory for ncols elements
pvowels[0] = (char *) malloc(ncols * sizeof(char));
pvowels[1] = (char *) malloc(ncols * sizeof(char));

pvowels[0][0] = 'A';
pvowels[0][1] = 'E';
pvowels[0][2] = 'I';
pvowels[0][3] = 'O';
pvowels[0][4] = 'U';

pvowels[1][0] = 'a';
pvowels[1][1] = 'e';
pvowels[1][2] = 'i';
pvowels[1][3] = 'o';
pvowels[1][4] = 'u';

for (i = 0; i < nrows; i++) {
    for(j = 0; j < ncols; j++) {
        printf("%c ", pvowels[i][j]);
    }

    printf("\n");
}

// Free individual rows
free(pvowels[0]);
free(pvowels[1]);

// Free the top-level pointer
free(pvowels);

Упражнение

Ниже показаны первые семь рядов треугольника Паскаля . Обратите внимание, что строка i содержит i элементов. Следовательно, для хранения чисел из первых трех строк потребуется 1 + 2 + 3 = 6 слотов памяти.

1

1 1

1 2 1

1 3 3 1

1 4 6 4 1

1 5 10 10 5 1

1 6 15 20 15 6 1

Завершите приведенный ниже скелетный код, чтобы сохранить числа из первых трех строк треугольника Паскаля в двумерном «массиве» с использованием динамического распределения памяти. Обратите внимание, что для хранения этих шести чисел необходимо выделить ровно шесть слотов памяти. Никакой дополнительной памяти не выделяется. В конце вашей программы освободите все блоки памяти, используемые в этой программе.