В прошлом году я решил изучить язык программирования C из-за его статуса одного из основополагающих и примитивных языков в этой области. Я осознал его важность как строительного блока для других языков, таких как Python, C++ и Rust. Во время моего обучения я столкнулся с серьезной проблемой в понимании концепции указателей. В течение нескольких месяцев я изо всех сил пытался понять эту фундаментальную концепцию, которая оказалась препятствием для понимания других важных концепций C, таких как строки, функции и массивы, все из которых основывались на твердом понимании указателей. Эта статья предназначена для программистов, интересующихся тем, как эффективно управлять переменными и хранить их во время кодирования.

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

В качестве иллюстрации рассмотрим компоненты памяти компьютера: оперативную память (ОЗУ) и постоянную память (ПЗУ). Оперативная память используется для запуска приложений в системе и служит временным хранилищем. Однако, когда система выключается, данные в оперативной памяти стираются или уничтожаются. С другой стороны, ПЗУ, также называемое хранилищем, — это место, где мы постоянно храним данные в системе. Данные, хранящиеся в ПЗУ, остаются нетронутыми независимо от того, работает система или выключена. Как правило, размер ПЗУ часто больше, чем у ОЗУ.

Еще одной отличительной чертой ОЗУ и ПЗУ является простота извлечения и хранения данных. Чтобы проиллюстрировать это, давайте рассмотрим сценарий, в котором у вас есть библиотека, заполненная книгами, и стол в вашей комнате. Предположим, есть определенные книги, которые вы хотите прочитать. Оптимальный поиск и хранение данных предполагает размещение нужных книг на вашем столе рядом с вами, поскольку это экономит ваше время на поиск в вашей обширной библиотеке. В этой аналогии ОЗУ похоже на стол в вашей комнате, а ПЗУ представляет собой библиотеку. Как упоминалось ранее, библиотека (ПЗУ) обычно больше по размеру по сравнению с таблицей (ОЗУ).

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

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

Указатель — это просто переменная, в которой хранится адрес другой переменной.

В C примитивными типами данных являются в основном integers, float, boolean и characters. integer и floats обычно занимают 4 bytes памяти, тип данных boolean занимает 2 bytes памяти, а тип данных character занимает 1 byte памяти. Понимание этих размеров имеет решающее значение, поскольку управление памятью часто является динамическим процессом. Знание требований к размеру типов данных, которые вы собираетесь создать, может помочь предотвратить конфликты памяти и ошибки во время выполнения программы.

В памяти a byte примерно равно 8 bits. и каждый тип данных переменной имеет свой определенный размер. Бит представляет собой двоичный код из 0 и 1.

Например, я пытаюсь создать целочисленную переменную в C следующим образом:

int b = 2;

C — язык со статической типизацией, поэтому мы должны определить тип данных перед именем переменной. При компиляции программы переменная b создается и сохраняется в памяти, а не в хранилище, а это значит, что после выполнения программы переменная уничтожается и не существует. Позиция в памяти, где хранится переменная, называется address переменной.

Как видно из иллюстрации b занимает 4 байта памяти, потому что мы уже объявили, что переменная b является целым числом, а базовый адрес переменной b равен 1000,

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

Это происходит каждый раз, когда мы объявляем переменные и другие компоненты программы, и одной из концепций, используемых для управления сложностью управления памятью, являются указатели. Теперь давайте продолжим и обсудим указатели.

С помощью указателей мы можем ссылаться на переменную b, сохраняя ее адрес в переменной с именем pointer variable следующим образом:

int *ptr;

ptr = &b;

Итак, мы объявили переменную-указатель ptr с помощью оператора *. Переменная хранит адрес целочисленной переменной, а затем следующая строка кода сохраняет адрес переменной b в переменной-указателе ptr с помощью оператора адреса & в C.

Вот как этот процесс визуально отображается в памяти:

Чтобы провести аналогию, давайте рассмотрим чашку с водой, поставленную на кухне. В этом сценарии мы можем провести параллель с концепцией указателей. Сама чашка представляет собой переменную b, а вода внутри чашки представляет значение, которое она содержит, например, целое число 2. Точно так же, как чашка находится на кухне, адрес памяти, связанный с переменной b, указывает на ячейку памяти, где хранится значение 2.

Точно так же, когда нам нужно получить доступ или сослаться на переменную b и ее значение, мы можем просто указать ее адрес в памяти. Любой, кому нужно получить доступ к переменной b или найти ее, может также обратиться к адресу памяти и получить сохраненное значение, точно так же, как получить воду из чашки, указав на кухню. Указатели служат способом эффективной навигации и управления данными в памяти.

Мы можем визуализировать работу указателей в коде, используя следующий код:

#include <stdio.h>

int main()
{
    int b = 2;

    int *ptr;

    ptr = &b;

    printf("The value of variable b is %d\n", b);
    printf("The value the ptr pointer points to is %d\n", *ptr);
    printf("The address of variable b is %p\n", &b);
    printf("The address that the pointer ptr stores is %p\n", ptr);
    printf("The address of pointer ptr is %p\n", &ptr);
    return 0;
}

В коде, написанном выше, мы определили переменную b и присвоили ей значение 2. Затем мы идем дальше и объявляем указатель ptr для хранения адреса целочисленной переменной с помощью оператора * и присваиваем адрес переменной b указателю ptr.

Вывод:

The value of variable b is 2
The value the ptr pointer points to is 2
The address of variable b is 0x7ffdd1cdd06c
The address that the pointer ptr stores is 0x7ffdd1cdd06c
The address of pointer ptr is 0x7ffdd1cdd070

Наблюдение за выводом данного кода. Мы используем спецификатор формата %p для получения адреса переменной, и адреса представлены в шестнадцатеричном формате. Кроме того, важно понимать, что у самой переменной-указателя также есть адрес, а размер переменной-указателя в основном составляет 8 bytes памяти. если мы посмотрим на вывод кода, мы заметим, что адрес переменной b и адрес, хранящийся в указателе ptr, совпадают.

Применение указателей

Теперь мы тщательно обсудили концепцию указателей. Давайте продолжим и рассмотрим некоторые приложения этой важной концепции и рассмотрим ее актуальность в C.

1. Функции маневрирования

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

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

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

В качестве примера возьмем следующий код:

int add_number(int num) {
    num + 2;
    return num;
}

int main() {
    int num = 5;
    add_number(num);
    printf("The new value of num is %d.\n", num);
    return 0;
}

В следующем коде у нас есть две функции; функция add_number для увеличения числа на 2 и функция main, которая является точкой входа в программу. В функции main мы инициируем функцию add_number. Давайте посмотрим, как выглядит результат программы.

Вывод:

The new value of num is 5

В данном коде значение num в функции add_number не влияет на значение переменной num в функции main. Это связано с тем, что они имеют отдельные области действия и являются отдельными переменными. Когда программа входит в функцию add_number и добавляет 2 к локальной переменной num внутри этой функции, это не влияет на переменную num в функции main.

В результате, когда программа возвращается к функции main после выполнения функции add_number, значение переменной num в функции main остается неизменным, что приводит к выводу 5 вместо ожидаемого 7.

Это подчеркивает важность понимания области действия переменных и роли указателей в обеспечении связи и модификации переменных в различных функциях.

Теперь давайте решим проблему в предыдущем коде с помощью указателей, мы инициируем концепцию указателей в коде следующим образом:

#include <stdio.h>

void add_number(int *num) {
    *num += 2;
}

int main() {
    int num = 5;
    add_number(&num);
    printf("The new value of num is %d\n", num);
    return 0;
}

Код демонстрирует использование указателей для изменения переменной num в функции add_number. Функция add_number принимает указатель на целое число в качестве параметра и обновляет значение указанной переменной num, добавляя к ней 2. Измененное значение num теперь отражено в функции main.

Вывод:

The new value of num is 7.

Теперь мы видим, что наше измененное значение переменной num теперь отражено в функции main, потому что мы вызывали переменную по ссылке с помощью указателя, а не по значению.

2. Система контроля версий

Система контроля версий — это инструмент, используемый для отслеживания и управления изменениями в проекте. Он обычно используется в различных видах деятельности, которыми мы занимаемся ежедневно. Например, при создании документа или написании содержимого в Microsoft Word мы часто вносим изменения в процессе создания. Само приложение ведет учет изменений, внесенных в файл, что позволяет нам просматривать и возвращаться к предыдущим версиям в любой момент времени.

Это также относится к программированию и разработке программного обеспечения. Примеры системы контроля версий, используемой для разработки программного обеспечения, включают GitHub, Gitlab и Bitbucket.

Распределенная система управления версиями, такая как Git, использует концепцию указателей в своем рабочем процессе. Например, в GitHub при внесении изменений в файл на локальном компьютере пользователю необходимо добавить изменения в промежуточную область с помощью команды git add .. Эта команда подготавливает изменения к фиксации. После внесения изменений пользователь может затем переместить эту версию изменений файла в каталог .git с помощью следующей команды:

git commit -m "Commit message."

Эта команда создает новую версию файла, известную как фиксация, которая хранится в каталоге .git. Коммит действует как указатель на конкретные изменения, внесенные в файл в этот конкретный момент времени. Это позволяет пользователю отслеживать историю изменений, внесенных в проект, и управлять ею, облегчая совместную работу и контроль версий в распределенной среде. Историю изменений, внесенных в файл в каждый момент времени, можно просмотреть с помощью следующей команды:

git log

В Git есть указатель HEAD, указывающий на последнюю фиксацию и ветку проекта. Указатель HEAD также можно использовать для указания на предыдущее сообщение фиксации в журнале фиксации. Указатель HEAD очень полезен в том смысле, что его можно легко использовать для возврата к предыдущей версии и любой другой версии проекта, если текущая версия изменения содержит ошибку.

Используя указатель HEAD, Git предоставляет мощный механизм для управления изменениями и их отмены, обеспечивая эффективный контроль версий и исправление ошибок в проекте.

Предоставленное изображение отображает журнал фиксации проекта с открытым исходным кодом, размещенного на GitHub. Он демонстрирует организованную структуру изменений файла для каждой версии, сопровождаемую уникальным хэш-кодом, присвоенным каждому сообщению фиксации. Кроме того, на изображении показано положение указателя HEAD, указывающего на последнюю фиксацию в ветке Dolamu_branch проекта, что означает, что текущее состояние не находится в ветке master.

Так процесс выглядит визуально

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

чтобы узнать больше о системах контроля версий, вы можете прочитать эту статью от Knoldus inc.

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

Спасибо за чтение.

Дополнительные материалы на PlainEnglish.io.

Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Подпишитесь на нас в Twitter, LinkedIn, YouTube и Discord .