Введение
Арифметика указателей - это мощная особенность языка C, которая позволяет манипулировать адресами памяти, прибавляя или вычитая значения. Одна из наиболее распространенных применений арифметики указателей - это обход массивов. Используя указатели, мы можем эффективно перемещаться по массивам как в прямом, так и в обратном направлении.
В этом практическом занятии (LabEx) вы научитесь создавать и инициализировать массивы, настраивать указатели для доступа к элементам массива и использовать арифметику указателей для обхода массивов. Эта техника является фундаментальной в программировании на языке C и составляет основу для многих продвинутых операций по манипуляции данными.
Создание базовой программы на языке C
Начнем с создания нового файла на языке C в редакторе VSCode. Этот файл будет содержать нашу основную программу для обхода массивов с использованием указателей.
В WebIDE найдите панель "Explorer" слева и перейдите в каталог
~/project.Щелкните правой кнопкой мыши по папке
projectи выберите "New File". Назовите файлmain.c.Скопируйте следующую базовую структуру программы на языке C в файл:
#include <stdio.h>
int main() {
printf("Array Traversal using Pointers\n");
return 0;
}
Сохраните файл, нажав Ctrl+S или выбрав "File > Save" из меню.
Скомпилируем и запустим эту программу, чтобы убедиться, что все настроено правильно. Откройте терминал в WebIDE, выбрав "Terminal > New Terminal" из меню, и выполните следующие команды:
cd ~/project
gcc main.c -o main
./main
Вы должны увидеть следующий вывод:
Array Traversal using Pointers
Это подтверждает, что ваша среда разработки на языке C работает корректно. В следующих шагах мы модифицируем эту программу для работы с массивами и указателями.
Объявление и инициализация массивов и указателей
На этом этапе мы научимся объявлять массив и указатель, которые являются основными компонентами для обхода массивов с использованием указателей.
Понимание массивов и указателей
Массив в языке C представляет собой набор элементов одного и того же типа, хранящихся в последовательных ячейках памяти. Например, целочисленный массив из 5 элементов зарезервирует в памяти место для 5 целых чисел, расположенных друг за другом.
Указатель - это переменная, которая хранит адрес памяти другой переменной. Мы можем использовать указатели для косвенного доступа к значению, хранящемуся по определенному адресу памяти.
Давайте модифицируем наш файл main.c, чтобы он содержал массив и указатель:
#include <stdio.h>
int main() {
printf("Array Traversal using Pointers\n\n");
// Declare and initialize an array of 5 integers
int arr[5] = {10, 20, 30, 40, 50};
// Declare a pointer of integer type
int *ptr;
// Print the array elements using array notation
printf("Array elements using array notation:\n");
for(int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
В этом коде:
- Мы объявили целочисленный массив
arrиз 5 элементов и инициализировали его значениями 10, 20, 30, 40 и 50. - Мы объявили целочисленный указатель
ptr, который будет использоваться позже для обращения к элементам массива. - Мы вывели элементы массива, используя традиционный синтаксис доступа к массиву.
Скомпилируйте и запустите программу, чтобы увидеть элементы массива:
gcc main.c -o main
./main
Вы должны увидеть следующий вывод:
Array Traversal using Pointers
Array elements using array notation:
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50
На следующем этапе мы свяжем указатель с массивом и будем обращаться к элементам массива с использованием указателя.
Связь указателей с массивами и прямой обход
На этом этапе мы установим связь между нашим указателем и массивом, а затем используем указатель для прямого обхода массива.
Связывание указателя с массивом
В языке C имя массива без индекса представляет собой адрес первого элемента массива. Это означает, что мы можем напрямую присвоить этот адрес переменной-указателю.
Давайте модифицируем наш файл main.c, чтобы связать указатель с массивом и обойти его:
#include <stdio.h>
int main() {
printf("Array Traversal using Pointers\n\n");
// Declare and initialize an array of 5 integers
int arr[5] = {10, 20, 30, 40, 50};
// Declare a pointer of integer type
int *ptr;
// Assign the address of the first element of the array to the pointer
ptr = arr; // This is equivalent to ptr = &arr[0]
// Print the array elements using array notation
printf("Array elements using array notation:\n");
for(int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
// Print the array elements using pointer notation
printf("\nArray elements using pointer notation (forward traversal):\n");
for(int i = 0; i < 5; i++) {
printf("*(ptr + %d) = %d\n", i, *(ptr + i));
}
return 0;
}
В этом обновленном коде:
- Мы присвоили адрес первого элемента массива
arrуказателюptr. - Мы добавили новый цикл, который обходит массив с использованием арифметики указателей.
- Выражение
*(ptr + i)обращается к значению по адресу памятиptr + i. Когдаiравно 0, это первый элемент массива; когдаiравно 1, это второй элемент и так далее.
Скомпилируйте и запустите программу, чтобы увидеть результаты:
gcc main.c -o main
./main
Вы должны увидеть следующий вывод:
Array Traversal using Pointers
Array elements using array notation:
arr[0] = 10
arr[1] = 20
arr[2] = 30
arr[3] = 40
arr[4] = 50
Array elements using pointer notation (forward traversal):
*(ptr + 0) = 10
*(ptr + 1) = 20
*(ptr + 2) = 30
*(ptr + 3) = 40
*(ptr + 4) = 50
Обратите внимание, что оба метода дают одинаковый вывод. Это показывает, что арифметика указателей может быть использована для доступа к элементам массива так же, как и традиционный индексирование массива.
Реализация инкремента указателя для обхода массива
На предыдущем этапе мы обращались к элементам массива с помощью выражения *(ptr + i). Хотя это работает отлично, язык C предоставляет более компактный способ обхода массива с использованием указателей: оператор инкремента (++).
Когда мы инкрементируем указатель, он перемещается в следующую ячейку памяти, исходя из размера типа данных, на который он указывает. Для целочисленного указателя инкрементирование перемещает его к следующему целому числу в памяти.
Давайте модифицируем наш файл main.c, чтобы использовать инкремент указателя для обхода массива:
#include <stdio.h>
int main() {
printf("Array Traversal using Pointers\n\n");
// Declare and initialize an array of 5 integers
int arr[5] = {10, 20, 30, 40, 50};
// Declare and initialize a pointer to the first element of the array
int *ptr = arr; // Same as ptr = &arr[0]
// Print the array elements using pointer incrementation
printf("Array elements using pointer incrementation:\n");
for(int i = 0; i < 5; i++) {
printf("*ptr = %d\n", *ptr);
ptr++; // Move the pointer to the next element
}
// Reset the pointer to the beginning of the array
ptr = arr;
// Print all elements in a single line using pointer incrementation
printf("\nAll elements in a single line: ");
for(int i = 0; i < 5; i++) {
printf("%d ", *ptr++); // Print and then increment
}
printf("\n");
return 0;
}
В этом обновленном коде:
- Мы инициализируем указатель
ptrсразу при объявлении. - Внутри первого цикла мы используем
*ptrдля доступа к текущему элементу, а затемptr++для перехода к следующему элементу. - После первого цикла мы сбрасываем
ptr, чтобы он снова указывал на начало массива. - Во втором цикле мы используем пост - инкрементный оператор
*ptr++, который сначала использует текущее значениеptr, а затем инкрементирует его.
Скомпилируйте и запустите программу, чтобы увидеть результаты:
gcc main.c -o main
./main
Вы должны увидеть следующий вывод:
Array Traversal using Pointers
Array elements using pointer incrementation:
*ptr = 10
*ptr = 20
*ptr = 30
*ptr = 40
*ptr = 50
All elements in a single line: 10 20 30 40 50
Это демонстрирует, как использовать инкремент указателя для обхода массива. Основная идея заключается в том, что ptr++ автоматически учитывает размер типа данных при переходе к следующему элементу.
Реализация обратного обхода с использованием декремента указателя
На предыдущих этапах мы осуществляли прямой обход массива. Теперь давайте научимся обходить массив в обратном порядке с использованием декремента указателя.
Для обратного обхода массива нам нужно:
- Инициализировать указатель так, чтобы он указывал на последний элемент массива.
- Декрементировать указатель, чтобы двигаться по массиву в обратном направлении.
Давайте модифицируем наш файл main.c для реализации обратного обхода:
#include <stdio.h>
int main() {
printf("Array Traversal using Pointers\n\n");
// Declare and initialize an array of 5 integers
int arr[5] = {10, 20, 30, 40, 50};
// Forward traversal using pointer increment
int *ptr = arr;
printf("Forward traversal using pointer increment:\n");
for(int i = 0; i < 5; i++) {
printf("%d ", *ptr);
ptr++;
}
printf("\n\n");
// Backward traversal using pointer decrement
// Point to the last element of the array
ptr = &arr[4]; // or ptr = arr + 4
printf("Backward traversal using pointer decrement:\n");
for(int i = 0; i < 5; i++) {
printf("%d ", *ptr);
ptr--; // Move the pointer to the previous element
}
printf("\n\n");
// Alternative approach: Start from the last element and decrement in the loop condition
printf("Alternative backward traversal approach:\n");
for(ptr = &arr[4]; ptr >= arr; ptr--) {
printf("%d ", *ptr);
}
printf("\n");
return 0;
}
В этом обновленном коде:
- Сначала мы выполняем прямой обход массива с использованием инкремента указателя.
- Для обратного обхода мы устанавливаем указатель на последний элемент массива с помощью
ptr = &arr[4]. - Внутри цикла мы выводим текущий элемент и затем декрементируем указатель с помощью
ptr--. - Мы также показываем альтернативный метод, где декремент указателя является частью инструкции обновления цикла
for.
Скомпилируйте и запустите программу, чтобы увидеть результаты:
gcc main.c -o main
./main
Вы должны увидеть следующий вывод:
Array Traversal using Pointers
Forward traversal using pointer increment:
10 20 30 40 50
Backward traversal using pointer decrement:
50 40 30 20 10
Alternative backward traversal approach:
50 40 30 20 10
Это демонстрирует, как обходить массив как в прямом, так и в обратном направлениях с использованием арифметики указателей. Возможность инкрементировать и декрементировать указатели позволяет легко перемещаться по массивам в любом направлении.
Резюме
В этом практическом занятии (лабораторной работе) вы узнали, как обходить массивы с использованием указателей в языке программирования C. Вот основные концепции, которые мы рассмотрели:
Основы массивов и указателей:
- Массивы в языке C хранят элементы в последовательных ячейках памяти.
- Указатели хранят адреса памяти и могут использоваться для доступа к этим ячейкам.
Связь между указателями и массивами:
- Имя массива (без индекса) представляет собой адрес первого элемента.
- Мы можем присвоить этот адрес указателю, чтобы установить связь с массивом.
Техники прямого обхода массива:
- Использование арифметики указателей:
*(ptr + i) - Использование инкремента указателя:
*ptrс последующимptr++ - Комбинирование разыменования и инкремента:
*ptr++
- Использование арифметики указателей:
Техники обратного обхода массива:
- Инициализация указателя на последний элемент:
ptr = &arr[size-1] - Использование декремента указателя:
ptr--для перемещения в обратном направлении - Условие цикла может проверять, достиг ли указатель начала массива.
- Инициализация указателя на последний элемент:
Арифметика указателей - это мощный инструмент в языке C, который позволяет эффективно манипулировать памятью и обеспечивает гибкость при работе с массивами и другими структурами данных. Эта техника является основой для более продвинутых концепций программирования, таких как динамическое выделение памяти, связанные списки и другие сложные структуры данных.



