Указатели и ссылки на Си

Ни один компьютер не сможет функционировать без встроенной «памяти». Речь идет как об ОЗУ, так и о жестких дисках. Даже в обычном калькуляторе есть память, которая позволяет складывать два числа и получать третье. Раньше люди экономили каждый байт оперативной памяти, которой было очень мало, до 256 мегабайт.

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

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

Указатель в языке C (си) – это переменная, которая указывает на адрес памяти. Через указатель можно записывать данные в память, редактировать их и освобождать место для новых значений. В языках программирования с динамической типизацией (например, python) нет проблем с памятью.

Говоря точнее, в python пользователь только создает объекты и по желанию удаляет их. Все остальное делает так называемый «сборщик мусора», который вместо того, чтобы запрашивать больше ОЗУ, переопределяет уже полученную. В си-подобных языках такой прием не работает, потому даже обычная зацикленная программа для вывода «helloworld» в консоль может перегрузить память.

Какую информацию хранят указатели

Указатели – это отдельный тип данных, который хранит адрес ячейки памяти в формате «0xFFFFFFFF». Из этого следует, что указатели – это не обычные переменные. Это стоит учесть, чтобы в будущем не присвоить указателю значение обыкновенной переменной.

Чем указатель лучше обычной переменной

Как и говорилось выше, обычная переменная сохраняется в памяти до завершения программы. Это не опасно для маленьких программ, но представьте, что Вы разрабатываете большой ААА-проект. Это будет очень детальная игра, где проработан каждый выстрел. Знаете, сколько памяти необходимо на это? Больше, чем кажется. А это лишь небольшая составная игры.

При таком раскладе, для минутной игры понадобилось бы 128 гигабайт оперативной памяти. Разве это так? Нет, 8 ГБ вполне хватает для любой игры. Причина заключается как раз в указателях. Разработчики заботятся о памяти и постоянно ее чистят. Они добавляют данные в память, а потом их заменяют, вместо того, чтобы выделять новую память для новых данных. В этом и помогают указатели. Еще одно преимущество указателей – передача адреса, а не значения.

Рассмотрим следующий код:

#include<iostream>

void print(int[]);

int main()
{
intchisla[] = {7, 34, 2, 14124, 22};
print(chisla);
return 0;
}

void print(int numbers[])
{
std::cout<< «First number: » << numbers[0] <<std::endl;
}

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

Как использовать указатели

Указатели на переменные

Ключевым идентификатором указателей является знак «*». Рассмотрим простой пример:

#include<iostream>
using namespace std;

int main()
{
int *x = new int;
int *y = new int(5);

*x = 10;
*y = *x + *y;

cout<< «y is » << *y<<endl;

delete x;
delete y;

return 0;
}

Рассмотрим код построчно:

int *x = newint;
int *y = newint(5);

В этой части кода мы создали указатели целочисленного типа, одному из которых выдали значение «5». В памяти выделилось место для этих переменных:

*x = 10;
*y = *x + *y;

Все переменные указываем только со звездочкой, иначе словим ошибку.

delete b;
delete a;

Данные не нужны, мы оставляем память, но удаляем значение. Если мы использовали статичные перемены, то память была бы занята до завершения программы.

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

Теперь попробуем зарезервировать место для связки данных — массива. Повторим код выше, но уже с указателями.

#include <iostream>

void print(int*);

int main()
{
intnums[] = {1, 2, 3, 4, 5};
print(nums);
return 0;
}

void print(int *numbers)
{
std::cout<< «First number: » << *numbers <<std::endl;
}

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

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

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

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

Многомерные массивы чем-то напоминают матрицы. В них данные выстраиваются нелинейно, а связкой. В памяти они определяются точно также. Ниже представлена схема распределения памяти для многомерного массива:

Указатели и ссылки на Си

Как видно в схеме, у нас имеется три блока данных: название массива, первая ступень массива, массивы второй степени (внутренние массивы). Для обращения в элементу x[0][1] нам понадобится следующая конструкция:

int *link = x[0]
link[0*4 + 1]

Символ после звездочки означает количество элементов в строке.

Отличия ссылок и указателей

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

Когда стоит использовать указатели, а когда функции

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

Причины использовать ссылки:

  • Не изменяет значения данных в памяти;
  • Работает быстрее указателей;
  • Никак не влияет на переменные;
  • Копирует данные из одной части памяти в другую.

Причины использовать указатели:

  • Позволяют создавать динамические массивы;
  • Можно изменять значения по адресу памяти;
  • Не засоряют память.

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

Освобождение памяти с помощью команды delete

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

Удаление обыкновенных указателей

Для начала выделяем память

int *a = new int;
int *b = new int;
float *c = newfloat;

А после освобождаем с помощью команды

delete c;
delete b;
delete a;

Удаление целого массива данных

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

delete [] *Имя_массива*;

Стоит отметить, что метод работает только с динамическими массивами. В противном случае можно словить ошибку.

Создание динамических массивов с помощью указателей

Как известно, для статичного массива нужно определить количество элементов, которое будет константой. Следственно, мы не можем сделать массив с запасом, не потеряв при этом много памяти «впустую». Решением в данной ситуации выступают динамические массивы. Ниже представлен код создания динамического массива:

#include<iostream>
usingnamespacestd;

int main()
{
intnum; // размер массива
cout<< «Enterintegervalue: «;
cin>>num; // получение от пользователя размера массива

int *p_darr = newint[num]; // Выделение памяти для массива
for (inti = 0; i<num; i++) {
// Заполнение массива и вывод значений его элементов
p_darr[i] = i;
cout<< «Value of » <<i<< » element is » <<p_darr[i] <<endl;
}
delete [] p_darr; // очистка памяти
return 0;
}

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

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

Частые ошибки и трудности при работе с указателями

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

Объясняется сложность исправления тем, что происходит работа напрямую с памятью. Компилятор не может отслеживать подобные махинации. Он получил команду изменить данные в определенном секторе памяти и выполнил. К чему это привело – компилятор не сообщит.

Пример ошибочного указателя:

int main(void)
{
int x, *p;

x = 10;
*p = x; /* ошибка, p не инициализирован */

return 0;
}

Мы обратились к указателю p, которому не присвоено значение. Если бы p была статичной переменной, то отобразилась бы соответствующая ошибка.

Или другой пример:

#include<stdio.h>

int main(void)
{
int x, *p;

x = 10;
p = x;

printf(«%d», *p);

return 0;
}

В данном коде переменная pхранит адрес памяти, а мы передаем ей числовое значение. Потому происходит конфликт типов данных. Что же происходит с значением внутри p? Оно принимает случайное значение. Более правильный вариант использовать операнд (&) перед х, то есть:

p = &x;

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

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

Заключение

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

Образовательный портал 3TY.RU
Adblock
detector