Арифметика указателей в C

Привет, будущие программисты! Сегодня мы отправляемся в захватывающее путешествие по миру арифметики указателей в C. Не волнуйтесь, если вы новичок в программировании; я веду вас шаг за шагом, как я делал это для многих студентов на протяжении многих лет своего преподавания. Так что взяйте свой любимый напиток, удобно посадитесь, и давайте погружемся!

C - Pointer Arithmetics

Что такое указатели?

Перед тем как перейти к арифметике указателей, быстро пересмотрим, что такое указатели. В C указатель — это переменная, которая хранит адрес памяти другой переменной. Представьте себе, что это указателей, указывающий на то, где какие-то данные хранятся в памяти вашего компьютера.

Вот простой пример:

int x = 10;
int *ptr = &x;

В этом коде ptr — это указатель, который хранит адрес x. Оператор & даёт нам адрес x.

Теперь, когда мы обновили наши знания, давайте исследуем волшебный мир арифметики указателей!

Инкремент и декремент указателя

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

Посмотрим на пример:

int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr;  // ptr указывает на первый элемент массива arr

printf("%d\n", *ptr);  // Вывод: 10
ptr++;
printf("%d\n", *ptr);  // Вывод: 20
ptr--;
printf("%d\n", *ptr);  // Вывод: 10

В этом коде, когда мы инкрементируем ptr, он не просто добавляет 1 к адресу. Он на самом деле переходит к следующему целому числу в массиве. Аналогично, при декрементировании он переходит обратно к предыдущему целому числу.

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

Добавление и вычитание целого числа из указателя

Мы также можем добавлять или вычитать целые числа из/к указателю. Эта операция похожа на инкрементирование или декрементирование, но мы можем перемещаться на несколько шагов сразу.

Вот пример:

int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr;

printf("%d\n", *(ptr + 2));  // Вывод: 30
printf("%d\n", *(ptr + 4));  // Вывод: 50
printf("%d\n", *(ptr - 1));  // Неопределённое поведение! Будьте внимательны!

Когда мы добавляем 2 к ptr, мы не добавляем 2 к адресу памяти. Мы перемещаемся на 2 целых числа вперёд в массиве. Аналогично, ptr + 4 перемещает нас на 4 целых числа вперёд.

Но будьте осторожны! Если вы попытаетесь обратиться к памяти перед началом массива (например, ptr - 1, когда ptr находится в начале массива), или после его окончания, вы получите неопределённое поведение. Это как попытка прочитать предыдущую страницу книги, когда вы уже на первой странице — она не существует!

Вычитание указателей

Вот хитрость: мы можем вычесть один указатель из другого, чтобы выяснить, сколько элементов между ними. Это работает только, если оба указателя указывают на элементы в одном и том же массиве.

Посмотрим на пример:

int arr[] = {10, 20, 30, 40, 50};
int *ptr1 = arr;
int *ptr2 = &arr[3];

printf("%ld\n", ptr2 - ptr1);  // Вывод: 3
printf("%ld\n", ptr1 - ptr2);  // Вывод: -3

В этом коде ptr2 - ptr1 даёт нам 3, потому что между arr[0] и arr[3] находятся 3 элемента. Заметьте, что результат подписан — если мы вычтем больший указатель из меньшего, мы получим отрицательное число.

Сравнение указателей

Наконец, последнее, но не менее важное: мы можем сравнивать указатели с использованием относительных операторов (<, >, <=, >=, ==, !=). Это особенно полезно при работе с массивами.

Вот пример:

int arr[] = {10, 20, 30, 40, 50};
int *ptr1 = arr;
int *ptr2 = &arr[3];

if (ptr1 < ptr2) {
printf("ptr1 указывает на элемент, который идёт перед элементом, на который указывает ptr2\n");
}

if (ptr1 == &arr[0]) {
printf("ptr1 указывает на первый элемент массива\n");
}

В этом коде мы сравниваем положения элементов, на которые указывают ptr1 и ptr2. Помните, что при сравнении указателей мы на самом деле сравниваем адреса памяти.

Резюме операций с арифметикой указателей

Вот удобная таблица, подводящая итог операциям с арифметикой указателей, которые мы изучили:

Операция Описание Пример
Инкремент (++) Переходит к следующему элементу ptr++
Декремент (--) Переходит к предыдущему элементу ptr--
Добавление (+) Перемещается вперёд на n элементов ptr + n
Вычитание (-) Перемещается назад на n элементов ptr - n
Вычитание указателей Вычисляет элементы между указателями ptr2 - ptr1
Сравнение Сравнивает положения элементов ptr1 < ptr2

И вот и всё, друзья! Мы путешествовали по земле арифметики указателей в C. Помните, что с великой силой приходит великая ответственность. Арифметика указателей — это мощные инструменты, но они также могут привести к ошибкам, если использовать их небрежно. Всегда убедитесь, что вы не обращаетесь к памяти, которую не должны!

Как я всегда говорю своим студентам, лучший способ действительно понять эти концепции — это практика. Так что запустите свой компилятор C и начните экспериментировать с этими операциями. Счастливого кодирования, и да будут ваши указатели всегда указывать туда, куда вы собираетесь!

Credits: Image by storyset