Как сделать указатель на массив c
В языке Си массивы и указатели тесно связаны. С помощью указателей мы также легко можем манипулировать элементами массива, как и с помощью индексов.
Имя массива без индексов в Си является адресом его первого элемента. Соответственно через операцию разыменования мы можем получить значение по этому адресу:
#include int main(void) < int array[] = ; printf("array[0] = %d", *array); // array[0] = 1 return 0; >
Мы можем пробежаться по всем элементом массива, прибавляя к адресу определенное число:
#include int main(void) < int array[5] = ; for(int i = 0; i < 5; i++) < void* address = array + i; // получаем адрес i-го элемента массива int value = *(array + i); // получаем значение i-го элемента массива printf("array[%d]: address=%p \t value=%d \n", i, address, value); >return 0; >
То есть, например, адрес второго элемента будет представлять выражение a+1 , а его значение — *(a+1) .
Со сложением и вычитанием здесь действуют те же правила, что и в операциях с указателями. Добавление единицы означает прибавление к адресу значения, которое равно размеру типа массива. Так, в данном случае массив представляет тип int , размер которого, как правило, составляет 4 байта, поэтому прибавление единицы к адресу означает увеличение адреса на 4. Прибавляя к адресу 2, мы увеличиваем значение адреса на 4 * 2 =8. И так далее.
В итоге в моем случае я получу следующий результат работы программы:
array[0]: address=0060FE98 value=1 array[1]: address=0060FE9C value=2 array[2]: address=0060FEA0 value=3 array[3]: address=0060FEA4 value=4 array[4]: address=0060FEA8 value=5
В то же время имя массива это не стандартный указатель, мы не можем изменить его адрес, например, так:
int array[5] = ; array++; // так сделать нельзя int b = 8; array = &b; // так тоже сделать нельзя
Использование указателя для работы с массивом
Имя массива всегда хранит адрес самого первого элемента, соответственно его можно присвоить другому указателю и затем через указатель обращаться к элеиментам массива:
#include int main(void) < int array[5] = ; int *ptr = array; // указатель ptr хранит адрес первого элемента массива array printf("value: %d \n", *ptr); // 1 return 0; >
Прибавляя (или вычитая) определенное число от адреса указателя, можно переходить по элементам массива. Например, перейдем к третьему элементу:
#include int main(void) < int array[5] = ; int *ptr = array; // указатель ptr хранит адрес первого элемента массива array ptr = ptr + 2; // перемезаем указатель на 2 элемента вперед printf("value: %d \n", *ptr); // value: 3 return 0; >
Здесь указатель ptr изначально указывает на первый элемент массива. Увеличив указатель на 2, мы пропустим 2 элемента в массиве и перейдем к элементу array[2] .
И как и другие данные, можно по указателю изменить значение элемента массива:
#include int main(void) < int array[5] = ; int *ptr = array; // указатель ptr хранит адрес первого элемента массива array ptr = ptr + 2; // переходим к третьему элементу *ptr = 8; // меняем значение элемента, на который указывает указатель printf("array[2]: %d \n", array[2]); // array[2] : 8 return 0; >
Стоит отметить, что указатель также может использовать индексы, как и массивы:
#include int main(void) < int array[5] = ; int *ptr = array; // указатель ptr хранит адрес первого элемента массива array int value = ptr[2]; // используем индексы - получаем 3-й элемент (элемент с индексом 2) printf("value: %d \n", value); // value: 3 return 0; >
Строки и указатели
Ранее мы рассмотрели, что строка по сути является набором символов, окончанием которого служит нулевой символ ‘\0’. И фактически строку можно представить в виде массива:
char hello[] = "Hello METANIT.COM!";
Но в языке Си также для представления строк можно использовать указатели на тип char :
#include int main(void) < char *hello = "Hello METANIT.COM!"; // указатель на char - фактически строка printf("%s", hello); return 0; >
Оба определения строки — с помощью массива и указателя будут в равнозначны здесь будут равнозначны.
Перебор массива с помощью указателей
С помощью указателей легко перебрать массив:
int array[5] = ; for(int *ptr=array; ptr
Так как указатель хранит адрес, то мы можем продолжать цикл, пока адрес в указателе не станет равным адресу последнего элемента ( ptr
Аналогичным образом можно перебрать и многомерный массив:
#include int main(void) < int array[3][4] = < , , >; int n = sizeof(array)/sizeof(array[0]); // число строк int m = sizeof(array[0])/sizeof(array[0][0]); // число столбцов int *final = array[0] + n * m - 1; // указатель на самый последний элемент for(int *ptr=array[0], i = 1; ptr > return 0; >
Так как в данном случае мы имеем дело с двухмерным массивом, то адресом первого элемента будет выражение array[0] . Соответственно указатель указывает на этот элемент. С каждой итерацией указатель увеличивается на единицу, пока его значение не станет равным адресу последнего элемента, который хранится в указателе final.
Мы также могли бы обойтись и без указателя на последний элемент, проверяя значение счетчика, пока оно не станет равно общему количеству элементов (m * n):
for(int *ptr = array[0], i = 0; i < m*n;) < printf("%d \t", *ptr++); if(++i%m==0) < printf("\n"); >>
Но в любом случае программа вывела бы следующий результат:
1 2 3 4 5 6 7 8 9 10 11 12
Указатель на массив C++
И имеется следующая конструкция, указывающая на массив из 20 элементов типа short:
short (*pas)[20] = &tell
Получившимся типом данных переменной pas является тип: short (*)[20] . Собственно вопрос, как создать переменную данного типа, и выделить ей память при помощи операции new?
Пример взят из книги: Стивен Прата — Язык программирования C++ (6 издание). Стр. 182.
Отслеживать
80.6k 9 9 золотых знаков 78 78 серебряных знаков 134 134 бронзовых знака
задан 26 окт 2016 в 15:46
133 1 1 золотой знак 2 2 серебряных знака 9 9 бронзовых знаков
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
Если у вас есть объявление массива вида
T a[N];
где T — это некоторый тип, а N — это число элементов в массиве, то указатель на первый элемент массива будет иметь тип T * . Например
T *p = a;
После этого определения указатель p указывает на первый элемент массива a .
Чтобы выделить динамически память для массива, аналогичного массиву, определенному выше, вы можете записать
T *p = new T[N];
Здесь элемент массива имеет тип T , а p как и выше показывает на первый элемент динамически выделенного безыменного массива..
Теперь представим, что T это алиас для типа short[20] , например
typedef short T[20];
Тогда ранее показанные объявления для указателя могут быть записаны как
T *p = new T[1];
Если снова вернуться к исходному типу short[20] , то получим
short( *p )[20] = &a;
short( *p )[20] = new short[1][20];
Последнее предложение означает, что выделяется массив из одного элемента (вы можете выделять массив из произвольного числа элементов в соответствии с вашей задачей), элементами которого в свою очередь являются массивы из 20 элементов типа short .
Имейте в виду, что когда используется так называемая арифметика указателей, то значение указателя меняется на значение кратное sizeof( T )
Поэтому если вы, например, объявите указатель как
short( *p )[20] = &a;
где T эквивалентно short[20] , то после применения, например, инкремента к этому указателю
указатель будет содержать адрес сразу же после последнего элемента массива a .
Ну, и напоследок пример работы с таким указателем.
#include #include #include #include #include const size_t N = 20; short ( * create_2D_array( size_t n ) )[N] < short ( *p )[N] = new short[n][N]; return p; >int main() < short a[N]; std::iota( std::begin( a ), std::end( a ), 1 ); for ( short x : a ) std::cout ( *p + N ), std::reverse_iterator( *p ), 1 ); for ( short x : *p ) std::cout
Вывод программы на консоль
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
Массивы и указатели
Создается массив arrI, далее в цикле for выводятся значения адресов ячеек памяти каждого элемента массива. Результат выполнения программы будет выглядеть примерно так:
0x7ffffbff4050 0x7ffffbff4054 0x7ffffbff4058 0x7ffffbff405c 0x7ffffbff4060
Обратите внимание на то, что значение адреса каждого последующего элемента массива больше значения адреса предыдущего элемента на 4 единицы. В вашей системе эта разница может составлять 2 единицы. Такой результат вполне очевиден, если вспомнить, сколько байтов отводится на одно данное типа int, и что элементы массива сохраняются в памяти друг за другом.
Теперь объявим указатель на целый тип и присвоим ему адрес первого элемента массива:
int *pI; pI = &arrI[0];
Цикл for изменим таким образом:
for (i=0; iN; i++) printf("%p\n", pI + i);
Здесь к значению pI, которое является адресом ячейки памяти, прибавляется сначала 0, затем 1, 2, 3 и 4. Можно было бы предположить, что прибавление к pI единицы в результате дает адрес следующего байта за тем, на который указывает pI. А прибавление двойки вернет адрес байта, через один от исходного. Однако подобное предположение не верно.
Вспомним, что тип указателя сообщает, на сколько байт простирается значение по адресу, на который он указывает. Таким образом, хотя pI указывает только на один байт (первый), но "знает", что его "собственность" простирается на все четыре (или два). Когда мы прибавляем к указателю единицу, то получаем указатель на следующее значение, но никак не на следующий байт. А следующее значение начинается только через 4 байта (в данном случае). Поэтому результат выполнения приведенного цикла с указателем правильно отобразит адреса элементов массива.
Прибавляя к указателям (или вычитая из них) целые значения, мы имеем дело с так называемой адресной арифметикой.
Напишите программу, в которой объявлен массив вещественных чисел из десяти элементов. Присвойте указателю адрес четвертого элемента, затем, используя цикл, выведите на экран адреса 4, 5 и 6-ого элементов массива.
Имя массива - это указатель на адрес его первого элемента
Да, это именно так, данный факт следует принять как аксиому. Вы можете убедиться в этом выполнив такое выражение:
printf("%p = %p\n", arrI, &arrI[0]);
Отсюда следует, что имя массива – это ничто иное, как указатель. (Хотя это немного особенный указатель, о чем будет упомянуто ниже.) Поэтому выражения pI = &arrI[N] и pI = arrI дают одинаковый результат: присваивают указателю pI адрес первого элемента массива.
Раз имя массива — это указатель, ничего не мешает получать адреса элементов вот так:
for (i=0; iN; i++) printf("%p\n", arrI + i);
Соответственно значения элементов массива можно получить так:
for (i=0; iN; i++) printf("%d\n", *(arrI + i));
Примечание. Если массив был объявлен как автоматическая переменная (т.е. не глобальная и не статическая) и при этом не был инициализирован (не присваивались значения), то в нем будет содержаться "мусор" (случайные числа).
Получается, что запись вида arrI[3] является сокращенным (более удобным) вариантом выражения *(arr+3).
Взаимозаменяемость имени массива и указателя
Если имя массива является указателем, то почему бы не использовать обычный указатель в нотации обращения к элементам массива также, как при обращении через имя массива:
int arrI[N], i; int *pI; pI = arrI; for (i=0; iN; i++) printf("%d\n", pI[i]);
Отсюда следуют выводы. Если arrI — массив, а pI — указатель на его первый элемент, то пары следующих выражений дают один и тот же результат:
- arrI[i] и pI[i] ;
- &arrI[i] и &pI[i] ;
- arrI+i и pI+i ;
- *(arrI+i) и *(pI+i) .
Что получается в результате выполнения данных пар выражений: адреса или значения элементов массива?
Указателю pI можно присвоить адрес любого из элементов массива. Например, так pI =&arrI[2] или так pI = arr+2 . В таком случае результат приведенных выше пар выражений совпадать не будет. Например, когда будет выполняться выражение arrI[i] , то будет возвращаться i-ый элемент массива. А вот выражение pI[i] уже вернет не i-ый элемент от начала массива, а i-ый элемент от того, адрес которого был присвоен pI. Например, если pI был присвоен адрес третьего элемента массива ( pI = arr+2 ), то выражение arrI[1] вернет значение второго элемента массива, а pI[1] — четвертого.
Присвойте указателю (pI) ссылку не на первый элемент массива (arrI). В одном и том же цикле выводите результат выражений arrI[i] и pI[i] , где на каждой итерации цикла i для обоих выражений имеет одинаковое значение. Объясните результат выполнения такой программы.
Имя массива — это указатель-константа
Несмотря на вышеописанную взаимозаменяемость имени массива определенного типа на указатель того же типа, между ними есть разница. Указатель может указывать на любой элемент массива, его значение можно изменять. Имя массива всегда указывает только на первый элемент массива, изменять его значение нельзя.
Это значит, что выражение pI = arrI допустимо, а arrI = pI нет. Имя массива является константой. При этом не надо путать имя массива (адрес) и значения элементов массива. Последние константами не являются. Действительно, ведь для всех переменных мы не можем менять их адрес в процессе выполнения программы, можем менять лишь их значения. В этом смысле имя массива — это обычная переменная, хотя и содержащая адрес.
Как следствие в программном коде выражения присваивания, инкрементирования и декрементирования допустимы для указателей, а для имени массива — запрещены.
Посмотрите на программу ниже. Что она делает? Почему? Проверьте ваши рассуждения опытным путем.
#include int main () { char str[20], *ps = str, n=0; printf("Enter word: "); scanf("%s", str); while(*ps++ != '\0') n++; printf("%d\n", n); }
Курс с решением части задач:
pdf-версия
Как сделать указатель на массив c
В C++ указатели и массивы тесно связаны. Обычно компилятор преобразует массив в указатели. С помощью указателей можно манипулировать элементами массива, как и с помощью индексов.
Имя массива по сути является адресом его первого элемента. Соответственно через операцию разыменования мы можем получить значение по этому адресу:
#include int main() < int nums[] ; std::cout
Так, в моем случае я получу следующий консольный вывод:
nums[0] address: 0x1f1ebffe60 nums[0] value: 1
Прибавляя к адресу первого элемента некоторое число, мы можем получить определенный элемент массива.
#include int main() < int nums[] ; int num2 = *(nums + 1); // второй элемент int num3 = *(nums + 2); // третий элемент std::cout int, размер которого, как правило, составляет 4 байта, поэтому прибавление единицы к адресу означает увеличение адреса на 4. Прибавляя к адресу 2, мы увеличиваем значение адреса на 4 * 2 = 8. И так далее.Например, в цикле пробежимся по всем элементам:
#include int main() < int nums[] ; for(unsigned i<>; i < std::size(nums); i++) < std::cout nums[0]: address=0xd95adffc30 value=1 nums[1]: address=0xd95adffc34 value=2 nums[2]: address=0xd95adffc38 value=3 nums[3]: address=0xd95adffc3c value=4 nums[4]: address=0xd95adffc40 value=5Но при этом имя массива это не стандартный указатель, и мы не можем изменить его адрес, например, так:
int nums[] ; nums++; // так сделать нельзя int b ; nums = &b; // так тоже сделать нельзяУказатели на массивы
Имя массива всегда хранит адрес самого первого элемента. И нередко для перемещения по элементам массива используются отдельные указатели:
int nums[] ; int *ptr ; int num3 = *(ptr+2); std::coutЗдесь указатель ptr изначально указывает на первый элемент массива. Увеличив указатель на 2, мы пропустим 2 элемента в массиве и перейдем к элементу nums[2] .
Можно сразу присвоить указателю адрес конкретного элемента массива:
int nums[] ; int *ptr ; // адрес третьего элемента std::cout #include int main() < const int n = 5; int nums[n]; for(int *ptr; ptr <=&nums[n-1]; ptr++) < std::cout << "address=" << ptr << "\tvalue brush:cpp;">#include int main() < int nums[3][4] < , , >; unsigned int n < sizeof(nums)/sizeof(nums[0]) >; // число строк unsigned int m < sizeof(nums[0])/sizeof(nums[0][0]) >; // число столбцов int *end ; // указатель на самый последний элемент 0 + 3 * 4 - 1 = 11 int *ptr ; // указатель на первый элемент for( unsigned i; ptr > >Поскольку в данном случае мы имеем дело с двухмерным массивом, то адресом первого элемента будет выражение a[0] . Соответственно указатель указывает на этот элемент. С каждой итерацией указатель увеличивается на единицу, пока его значение не станет равным адресу последнего элемента, который хранится в указателе end.
Мы также могли бы обойтись и без указателя на последний элемент, проверяя значение счетчика:
#include int main() < const unsigned n ; // число строк const unsigned m ; // число столбцов int nums[n][m] < , , >; const unsigned count ; // общее количество элементов int *ptr; // указатель на первый элемент первого массива for(unsigned i; i > >Но в обоих случаях программа вывела бы следующий результат:
1 2 3 4 5 6 7 8 9 10 11 12Указатель на строки и массивы символов
Поскольку массив символов может интерпретироваться как строка, то указатель на значения типа char тоже может интерпретироваться как строка:
#include int main() < char hello[] ; char *phello ; std::coutПри выводе на консоль значения указателя фактически будет выводиться строка.
Также можно применять операцию разыменовывания для получения отдельных символов, например, выведем первый символ:
std::coutЕсли же необходимо вывести на консоль адрес указателя, то его надо преобразовать к типу void*:
std::coutВ остальном работа с указателем на массив символов производится также, как и с указателями на массивы других типов.
Также поскольку указатель типа char тоже может интерпретироваться как строка, то теоретически мы можем написать следующим образом:
char *phello ;Однако следует учитывать, что строковые литералы в С++ рассматриваются как константы. Поэтому предыдущее определение указателя может при компиляции вызвать как минимум предупреждение, а попытка изменить элементы строки через указатель - к ошибке компиляции. Поэтому при определении указателя на строку, следует определять указатель как указатель на константу:
#include int main() < const char *phello ; // указатель на константу std::coutМассивы указателей
Также можно определять массивы указателей. В некотором смысле массив указателей будет похож на массив, который содержит другие массивы. Однако массив указателей имеет преимущества.
Например, возьмем обычный двухмерный символьный массив - массив, который хранит строки:
#include int main() < char langs[][20] < "C++", "Python", "JavaScript">; std::coutДля определения двухмерного массива мы должны указать как минимум размер вложенных массивов, который будет достаточным, чтобы вместить каждую строку. В данном случае размер каждого вложенного массива - 20 символов. Однако зачем для первой строки - "C++", которая содержит 4 символа (включая концевой нулевой байт) выделять аж 20 байтов? Это - ограничение подобных массивов. Массивы указателей же позволяют обойти подобное ограничение:
#include int main() < const char *langs[] < "C++", "Python", "JavaScript">; // перебор массива for(unsigned i<>; i < std::size(langs); i++) < std::cout >В данном случае элементами массива langs являются указатели: 3 указателя, каждый из которых занимает 4 или 8 байт в зависимости от архитекутуры (размер адреса). Каждый из этих указателей указывает на адрес в памяти, где расположены соответствующие строки: "C++", "Python", "JavaScript". Однако каждая из этих строк будет занимать именно то пространство, которое ей непосредственно необходимо. То есть строка "С++" будет занимать 4 байта. С одной стороны, мы здесь сталкиваемся с дополнительными издержками: дополнительно выделяется память для хранения адресов в указателях. С другой стороны, когда строки в массиве сильно различаются по длине , то мы можем получить общий выигрыш в количестве потребляемой памяти.