N-Dimensional Arrays: The N-Dimensional array is basically an array of arrays. As 1-D arrays are identified as a single index, 2-D arrays are identified using two indices, similarly, N-Dimensional arrays are identified using N indices. A multi-dimensional array is declared as follows:
int NDA[S1][S2][S3]……..[SN];
Explanation:
- Here, NDA is the name of the N-Dimensional array. It can be any valid identifier name.
- In the above syntax, S1, S2, S3……SN denotes the max sizes of the N dimensions.
- The lower bounds are assumed to be zeroes for all the dimensions.
- The above array is declared as an integer array. It can be any valid data type other than an integer as well.
Address Calculation of N-Dimensional Arrays:
Assumptions:
- The N-Dimensional array NDA with the maximum sizes of N dimensions are S1, S2, S3, ………, SN.
- The element whose address needs to be calculated has indices l1, l2, l3, …………..lN respectively.
- It is possible that the array indices do not have the lower bound as zero. For example, consider the following array T: T[-5…5][2……9][14…54][-9…-2].
Explanation:
- In the above array T, the lower bounds of indices are not zeroes.
- So that the sizes of the indices of the array are different now and can be calculated by using the formula:
UpperBound – LowerBound +1
- So, here the S1 = 5 – (-5) + 1 = 11. Similarly, S2 = 8, S3 = 41 and S4 = 8.
For address calculation, the lower bounds are t1, t2, t3…….tN. There exist two ways to store the array elements:
- Row Major
- Column Major
The Column Major formula:
Address of NDA[I1][I2]. . . [IN] = BAd + W*[((…ENSN-1+ EN-1 )SN-2 +… E3 )S2+ E2 )S1 +E1]
- BAd represents the base address of the array.
- W is Storage Size of one element stored in the array (in byte).
- Also, Ei is given by Ei = li – ti, where li and ti are the calculated indexes (indices of array element which needs to be determined) and lower bounds respectively.
The Row Major formula:
Address of NDA[I1][I2]. . . .[lN] = BAd + W*[((E1S2 + E2 )S3 +E3 )S4 ….. + EN-1 )SN + EN]
The simplest way to learn the formulas:
For row-major: If width = 5, the interior sequence is E1S2 + E2S3 + E3S4 + E4S5 + E5 and if width = 3, the interior sequence is E1S2 + E2S3 + E3. Figure out the pattern in the order and follow four basic steps for the formula of any width:
- Write the interior sequence.
- Put the closing bracket after each E except the first term. So for width = 5, it becomes
E1S2 + E2)S3 + E3)S4 + E4)S5 + E5).
- Put all the Opening brackets initially.
((((E1S2 + E2)S3 + E3)S4 + E4)S5 + E5).
- Incorporate the base address and width in the formula.
The approach is similar for the Column Major but the pattern of the interior sequence is reverse of the row-major pattern.
For column-major: If width =5, the interior sequence is E5S4 + E4S3 + E3S2+ E2S1 + E1.
Example: Let’s take a multi-dimensional array A[10][20][30][40] with the base address 1200. The task is to find the address of element A[1][3][5][6].
Here, BAd = 1200 and width = 4.
S1 = 10, S2 = 20, S3 = 30, S4 = 40
Since the lower bounds are not given, so lower bounds are assumed to be zero.
E1 = 1 – 0 = 1;
E2 = 3 – 0 = 3;
E3 = 5 – 0 = 5;
E4 = 6 – 0 = 6.
Any of the techniques (Row-major or column-major) can be used for calculating the answer (unless specified).
By applying the formula for row-major, directly write the formula as:
A[1][3][5][6] = 1200 + 4(((1 × 20 + 3)30 +5)40 + 6)
=1200 +4((23 × 30 +5)40 +6)
=1200 + 4(695 × 40 + 6)
=1200 + (4 × 27806)
=112424.
Last Updated :
20 Sep, 2022
Like Article
Save Article
Если у вас есть указатель вида
T *p;
то 1) для арифметики указателей используется значение sizeof( T )
, и 2) разыменование указателя дает lvalue
, на который указывает указатель.
Например, если имеется следующий фрагмент кода
int a[] = { 1, 2 };
int *p = &a[0];
то значение указателя после выполнения выражения ++p
будет равно
значение указателя перед инкрементом плюс значение sizeof( int )
В этом случае в результате указатель будет указывать на элемент a[1]
.
Если имеется объявление следующего массива
char array[] = "String";
то оно эквивалентно следующему объявлению массива
char array[7] = "String";
Если ввести typedef объявление
typedef char T[7];
то объявление вышеприведенного массива можно записать также как
T array = "String";
Поэтому чтобы объявить указатель на этот объект, следует записать
T *p = &array;
И, как показано выше, для арифметики указателей для указателя p
используется выражение sizeof( T )
, которое равно sizeof( char[7] )
.
Разыменовывая этот указатель, вы получите lvalue
объекта типа char[7]
Это легко проверить, выполнив предложение
printf( "%zun", sizeof( *p ) );
результатом которого будет значение 7
, то есть получена длина массива типа char[7]
.
Однако когда в качестве объекта, полученного после разыменования указателя, имеется массив, то он в выражения за редким исключением преобразуется к указателю на свой первый элемент.
Из стандарта C (6.3.2.1 Lvalues, arrays, and function designators)
3 Except when it is the operand of the sizeof operator or the unary &
operator, or is a string literal used to initialize an array, an
expression that has type ‘‘array of type’’ is converted to an
expression with type ‘‘pointer to type’’ that points to the initial
element of the array object and is not an lvalue. If the array object
has register storage class, the behavior is undefined.
Поэтому для вышеприведенного примера если выражение *p
, не используется в качестве операнда оператора sizeof
или оператора &
, то происходит неявное преобразование полученного массива в указатель на свой первый элемент. Поэтому, например, выражение **p
даст объект типа char
со значением 'S'
.
0 / 0 / 0 Регистрация: 20.05.2013 Сообщений: 36 |
|
1 |
|
Найти адреса элементов массива27.05.2013, 20:32. Показов 6562. Ответов 4
\
0 |
Игорь с++ 481 / 473 / 63 Регистрация: 26.01.2011 Сообщений: 2,033 |
||||
27.05.2013, 21:03 |
2 |
|||
qweeqweqwe, а в чём проблема то собственно ?
0 |
qweeqweqwe 0 / 0 / 0 Регистрация: 20.05.2013 Сообщений: 36 |
||||
27.05.2013, 21:25 [ТС] |
3 |
|||
qweeqweqwe, а в чём проблема то собственно ? Да я текст удалил, потому что вроде БЫ как сделал
Но адреса выводит непонятно, и не знаю правильно или нет
0 |
4981 / 3088 / 456 Регистрация: 10.11.2010 Сообщений: 11,165 Записей в блоге: 10 |
|
27.05.2013, 21:50 |
4 |
Адреса элементов выводит правильно, вот только цикл в 27-й строке мне кажется излишним…
1 |
0 / 0 / 0 Регистрация: 20.05.2013 Сообщений: 36 |
|
27.05.2013, 22:28 [ТС] |
5 |
Адреса элементов выводит правильно, вот только цикл в 27-й строке мне кажется излишним… Ваша правда. Спасибо, не заметил
0 |
Указатели, массивы и строки
Последнее обновление: 05.01.2023
В языке Си массивы и указатели тесно связаны. С помощью указателей мы также легко можем манипулировать элементами массива, как и с помощью индексов.
Имя массива без индексов в Си является адресом его первого элемента. Соответственно через операцию разыменования мы можем получить значение по этому адресу:
#include <stdio.h> int main(void) { int array[] = {1, 2, 3, 4, 5}; printf("array[0] = %d", *array); // array[0] = 1 return 0; }
Мы можем пробежаться по всем элементом массива, прибавляя к адресу определенное число:
#include <stdio.h> int main(void) { int array[5] = {1, 2, 3, 4, 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] = {1, 2, 3, 4, 5}; array++; // так сделать нельзя int b = 8; array = &b; // так тоже сделать нельзя
Использование указателя для работы с массивом
Имя массива всегда хранит адрес самого первого элемента, соответственно его можно присвоить другому указателю и затем через указатель обращаться к элеиментам массива:
#include <stdio.h> int main(void) { int array[5] = {1, 2, 3, 4, 5}; int *ptr = array; // указатель ptr хранит адрес первого элемента массива array printf("value: %d n", *ptr); // 1 return 0; }
Прибавляя (или вычитая) определенное число от адреса указателя, можно переходить по элементам массива. Например, перейдем к третьему элементу:
#include <stdio.h> int main(void) { int array[5] = {1, 2, 3, 4, 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 <stdio.h> int main(void) { int array[5] = {1, 2, 3, 4, 5}; int *ptr = array; // указатель ptr хранит адрес первого элемента массива array ptr = ptr + 2; // переходим к третьему элементу *ptr = 8; // меняем значение элемента, на который указывает указатель printf("array[2]: %d n", array[2]); // array[2] : 8 return 0; }
Стоит отметить, что указатель также может использовать индексы, как и массивы:
#include <stdio.h> int main(void) { int array[5] = {1, 2, 3, 4, 5}; int *ptr = array; // указатель ptr хранит адрес первого элемента массива array int value = ptr[2]; // используем индексы - получаем 3-й элемент (элемент с индексом 2) printf("value: %d n", value); // value: 3 return 0; }
Строки и указатели
Ранее мы рассмотрели, что строка по сути является набором символов, окончанием которого служит нулевой символ ”. И фактически строку можно представить в виде массива:
char hello[] = "Hello METANIT.COM!";
Но в языке Си также для представления строк можно использовать указатели на тип char:
#include <stdio.h> int main(void) { char *hello = "Hello METANIT.COM!"; // указатель на char - фактически строка printf("%s", hello); return 0; }
Оба определения строки – с помощью массива и указателя будут в равнозначны здесь будут равнозначны.
Перебор массива с помощью указателей
С помощью указателей легко перебрать массив:
int array[5] = {1, 2, 3, 4, 5}; for(int *ptr=array; ptr<=&array[4]; ptr++) { printf("address=%p t value=%d n", (void*)ptr, *ptr); }
Так как указатель хранит адрес, то мы можем продолжать цикл, пока адрес в указателе не станет равным адресу последнего элемента (ptr<=&array[4]
).
Аналогичным образом можно перебрать и многомерный массив:
#include <stdio.h> int main(void) { int array[3][4] = { {1, 2, 3, 4} , {5, 6, 7, 8}, {9, 10, 11, 12}}; 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 <= final; ptr++, i++) { printf("%d t", *ptr); // если остаток от целочисленного деления равен 0, // переходим на новую строку if(i%m==0) { printf("n"); } } 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
Массивы и указатели
Адресная арифметика
Рассмотрим программу:
#include <stdio.h> #define N 5 int main () { int arrI[N], i; for (i=0; i<N; i++) printf("%pn", &arrI[i]); }
Создается массив arrI, далее в цикле for
выводятся значения адресов ячеек памяти каждого элемента массива. Результат выполнения программы будет выглядеть примерно так:
0x7ffffbff4050 0x7ffffbff4054 0x7ffffbff4058 0x7ffffbff405c 0x7ffffbff4060
Обратите внимание на то, что значение адреса каждого последующего элемента массива больше значения адреса предыдущего элемента на 4 единицы. В вашей системе эта разница может составлять 2 единицы. Такой результат вполне очевиден, если вспомнить, сколько байтов отводится на одно данное типа int, и что элементы массива сохраняются в памяти друг за другом.
Теперь объявим указатель на целый тип и присвоим ему адрес первого элемента массива:
int *pI; pI = &arrI[0];
Цикл for
изменим таким образом:
for (i=0; i<N; i++) printf("%pn", pI + i);
Здесь к значению pI, которое является адресом ячейки памяти, прибавляется сначала 0, затем 1, 2, 3 и 4. Можно было бы предположить, что прибавление к pI единицы в результате дает адрес следующего байта за тем, на который указывает pI. А прибавление двойки вернет адрес байта, через один от исходного. Однако подобное предположение не верно.
Вспомним, что тип указателя сообщает, на сколько байт простирается значение по адресу, на который он указывает. Таким образом, хотя pI указывает только на один байт (первый), но “знает”, что его “собственность” простирается на все четыре (или два). Когда мы прибавляем к указателю единицу, то получаем указатель на следующее значение, но никак не на следующий байт. А следующее значение начинается только через 4 байта (в данном случае). Поэтому результат выполнения приведенного цикла с указателем правильно отобразит адреса элементов массива.
Прибавляя к указателям (или вычитая из них) целые значения, мы имеем дело с так называемой адресной арифметикой.
Напишите программу, в которой объявлен массив вещественных чисел из десяти элементов. Присвойте указателю адрес четвертого элемента, затем, используя цикл, выведите на экран адреса 4, 5 и 6-ого элементов массива.
Имя массива – это указатель на адрес его первого элемента
Да, это именно так, данный факт следует принять как аксиому. Вы можете убедиться в этом выполнив такое выражение:
printf("%p = %pn", arrI, &arrI[0]);
Отсюда следует, что имя массива – это ничто иное, как указатель. (Хотя это немного особенный указатель, о чем будет упомянуто ниже.) Поэтому выражения pI = &arrI[N]
и pI = arrI
дают одинаковый результат: присваивают указателю pI адрес первого элемента массива.
Раз имя массива — это указатель, ничего не мешает получать адреса элементов вот так:
for (i=0; i<N; i++) printf("%pn", arrI + i);
Соответственно значения элементов массива можно получить так:
for (i=0; i<N; i++) printf("%dn", *(arrI + i));
Примечание. Если массив был объявлен как автоматическая переменная (т.е. не глобальная и не статическая) и при этом не был инициализирован (не присваивались значения), то в нем будет содержаться “мусор” (случайные числа).
Получается, что запись вида arrI[3] является сокращенным (более удобным) вариантом выражения *(arr+3).
Взаимозаменяемость имени массива и указателя
Если имя массива является указателем, то почему бы не использовать обычный указатель в нотации обращения к элементам массива также, как при обращении через имя массива:
int arrI[N], i; int *pI; pI = arrI; for (i=0; i<N; i++) printf("%dn", 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 <stdio.h> int main () { char str[20], *ps = str, n=0; printf("Enter word: "); scanf("%s", str); while(*ps++ != '') n++; printf("%dn", n); }
Курс с решением части задач:
pdf-версия, android-приложение