Промышленное производство
Промышленный Интернет вещей | Промышленные материалы | Техническое обслуживание и ремонт оборудования | Промышленное программирование |
home  MfgRobots >> Промышленное производство >  >> Industrial programming >> Язык C

Мастерство указателей и многомерных массивов в C

В языке C массив — это коллекция значений похожего типа, хранящихся в непрерывных областях памяти. Каждый элемент массива (одномерного или многомерного) идентифицируется одним или несколькими уникальными целочисленными индексами.

Указатель, с другой стороны, хранит адрес переменной. Адрес 0-го элемента в массиве — это указатель массива. Можно использовать «оператор разыменования» для доступа к значению, на которое ссылается указатель.

Можно объявить одномерный, двумерный или многомерный массив в C. Термин «размерность» относится к количеству индексов, необходимых для идентификации элемента в коллекции.

Указатели и одномерные массивы

В одномерном массиве каждый элемент идентифицируется одним целым числом:

int a[5] = {1, 2, 3, 4, 5};

Здесь число «1» находится на 0-м индексе, «2» на индексе 1, и так далее.

Переменная, хранящая адрес 0-го элемента, является его указателем —

int *x = &a[0];

Просто имя массива также указывает на адрес 0-го элемента. Так что можно также использовать это выражение —

int *x = a;

Пример

Поскольку значение указателя увеличивается на размер типа данных, x++ перемещает указатель к следующему элементу массива.

#include <stdio.h>
int main(){
    int arr[] = {1, 2, 3, 4, 5};
    int length = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
    int *ptr = arr;
    while (i < length){
        printf("arr[%d]: %d \n", i, *(ptr + i));
        i++;
    }
    return 0;
}

Вывод

При запуске этот код выдаст следующий вывод —

arr[0]: 1
arr[1]: 2
arr[2]: 3
arr[3]: 4
arr[4]: 5

Указатели и двумерные массивы

Если одномерный массив похож на список элементов, то двумерный массив похож на таблицу или матрицу.

Элементы в 2D массиве можно считать логически расположенными в строках и столбцах. Следовательно, положение любого элемента определяется двумя индексами — его номером строки и номером столбца. Индексы строк и столбцов начинаются с «0».

int arr[2][2];

Такой массив представляется как —

Col0Col1Col2
Row0arr[0][0]arr[0][1]arr[0][2]
Row1arr[1][0]arr[1][1]arr[1][2]
Row2arr[2][0]arr[2][1]arr[2][2]

Можно отметить, что табличное расположение — лишь логическое представление. Компилятор выделяет блок непрерывных байт. В C выделение памяти для массива происходит в row-major порядке, что означает, что элементы считываются в массив по строкам.

Здесь мы объявляем 2D массив с тремя строками и четырьмя столбцами (число в первых квадратных скобках всегда относится к количеству строк) как —

int arr[3][4] = {
    {1, 2, 3, 4},
    {5, 6, 7, 8},
    {9, 10, 11, 12}
};

Компилятор выделит память для вышеуказанного 2D массива в row-wise порядке. Предполагая, что первый элемент массива находится по адресу 1000, а размер типа int — 4 байта, элементы массива получат следующие выделенные адреса памяти —

Row 0Row 1Row 2
Value1 2 3 45 6 7 89 10 11 12
Address1000 1004 1008 10121016 1020 1024 10281032 1036 1040 1044

Мы присвоим адрес первого элемента массива num указателю ptr с использованием оператора адреса &.

int *ptr = &arr[0][0];

Пример 1

Если указатель увеличивается на 1, он переходит к следующему адресу. Все 12 элементов в массиве «3x4» можно получить в цикле следующим образом —

#include <stdio.h>
int main(){
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12},
    };
    // указатель ptr, указывающий на массив num
    int *ptr = &arr[0][0];
    int i, j, k = 0;
    // печать элементов массива num через указатель ptr
    for (i = 0; i < 3; i++){
        for (j = 0; j < 4; j++){
            printf("%d ", *(ptr + k));
            k++;
        }
        printf("\n");
    }
    return 0;
}

Вывод

При запуске этот код выдаст следующий вывод —

1 2 3 4
5 6 7 8
9 10 11 12

В общем, адрес любого элемента массива можно получить с помощью следующей формулы —

адрес элемента в i-й строке и j-м столбце = baseAddress + [(i * no_of_cols + j) * sizeof(array_type)]

В нашем массиве 3x4,

адрес arr[2][2] = 1000 + (2*4 + 2)*4 = 1044

Вы можете обратиться к вышеуказанной таблице, и она подтвердит, что адрес arr[2][2] равен 1044.

Пример 2

Используйте разыменование указателя для получения значения по адресу. Давайте используем эту формулу для обхода массива с помощью его указателя —

#include <stdio.h>
int main(){
    // 2d array
    int arr[3][4] = {
        {1, 2, 3, 4},
        {5, 6, 7, 8},
        {9, 10, 11, 12}
    };
    int ROWS = 3, COLS = 4;
    int i, j;
    // pointer
    int *ptr = &arr[0][0];
    // печать элемента массива через указатель ptr
    for (i = 0; i < ROWS; i++){
        for (j = 0; j < COLS; j++) {
            printf("%4d ", *(ptr + (i * COLS + j)));
        }
        printf("\n");
    }
    return 0;
}

Вывод

При запуске этот код выдаст следующий вывод —

1 2 3 4
5 6 7 8
9 10 11 12

Указатели и трёхмерные массивы

Трёхмерный массив — это массив двумерных массивов. Такой массив объявляется с тремя индексами —

int arr[x][y][j];

Этот массив можно рассматривать как «x» количество слоёв таблиц, каждая таблица имеющая «x» строк и «y» столбцов.

Пример 3D массива —

int arr[3][3][3] = {
    { {11, 12, 13}, {14, 15, 16}, {17, 18, 19} },
    { {21, 22, 23}, {24, 25, 26}, {27, 28, 29} },
    { {31, 32, 33}, {34, 35, 36}, {37, 38, 39} },
};

Указатель на 3D массив можно объявить как —

int *ptr = &arr[0][0][0];

Зная, что имя массива само является адресом 0-го элемента, мы можем записать указатель 3D массива как —

int *ptr = arr;

Каждый слой из «x» строк и «y» столбцов занимает —

x * y * sizeof(data_type)

Количество байт. Предполагая, что память, выделенная для 3D массива arr как объявлено выше, начинается с адреса 1000, второй слой (с i = 1) начинается на позиции 1000 + (3 * 3) * 4 = 1036 байт.

ptr = Base address of 3D array arr

Если JMAX — количество строк, а KMAX — количество столбцов, то адрес элемента в 0-й строке и 0-м столбце 1-го среза —

arr[1][0][0] = ptr + (1 * JMAX * KMAX)

Формула для получения значения элемента в j-й строке и k-м столбце i-го среза может быть задана как —

arr[i][j][k] = *(ptr + (i * JMAX * KMAX) + (j * KMAX + k))

Пример: Печать 3D массива с помощью разыменования указателя

Давайте используем эту формулу для печати 3D массива с помощью разыменования указателя —

#include <stdio.h>
int main(){
    int i, j, k;
    int arr[3][3][3] = {
        { {11, 12, 13}, {14, 15, 16}, {17, 18, 19} },
        { {21, 22, 23}, {24, 25, 26}, {27, 28, 29} },
        { {31, 32, 33}, {34, 35, 36}, {37, 38, 39} },
    };
    int JMAX = 3, KMAX = 3;
    int *ptr = arr; // &arr[0][0][0];
    for(i = 0; i < 3; i++){
        for(j = 0; j < 3; j++){
            for(k = 0; k < 3; k++){
                printf("%d ", *(ptr + (i * JMAX * KMAX) + (j * KMAX + k)));
            }
            printf("\n");
        }
        printf("\n");
    }
    return 0;
}

Вывод

При запуске этот код выдаст следующий вывод —

11 12 13 14 15 16 17 18 19
21 22 23 24 25 26 27 28 29
31 32 33 34 35 36 37 38 39

В общем, доступ к массиву через указатель довольно похож на доступ через подписанное представление. Главное различие в том, что подписанное объявление массива выделяет память статически, тогда как мы можем использовать указатели для динамического выделения памяти.

Для передачи многомерного массива в функцию нужно использовать указатели вместо подписок. Однако использование подписанного массива удобнее, чем указателей, которые могут быть сложны для новичков.

Язык C

  1. Пространства имён в C++: избегайте конфликтов имен
  2. Освоение ввода и вывода в C++: практическое руководство
  3. Строки в C: null-терминированные массивы символов, функции библиотеки string.h и лучшие практики
  4. Инкапсуляция в C#: модификаторы доступа и защита данных
  5. Понимание .NET Framework: архитектура, компоненты и принципы проектирования
  6. Небезопасный код в C#: Указатели и управление памятью
  7. Объединения в C (union): эффективное использование памяти для разных типов данных
  8. Цикл for в C++: синтаксис, принцип работы и практические примеры
  9. Публичное, защищённое и приватное наследование в C++: подробный разбор
  10. Сериализация и десериализация в C#: практический пример