В прошлом году я решил изучить язык программирования 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 .