- •1.1. Понятие указателя
- •Int* ptrInt;
- •1.2. Синтаксис описания указателя
- •1.3. Адрес переменной и значение переменной по адресу
- •Int main()
- •1.4. Адресная арифметика
- •1.5. Обращение к элементам массива
- •1.6. Приведение типов при работе с указателями
- •1.7. Работа с динамическими переменными
- •2.2. Передача параметров по адресу
- •Void swap(int* a, int* b)
- •2.3. Передача больших объемов данных
- •2.4. Инициализация указателей
- •2.5. Применение ссылок при работе с функциями
- •Void swap(int& a, int& b)
- •Int main()
- •Int Carrage(int* cr);
1.5. Обращение к элементам массива
Последний важный момент рассмотренного кода в том, как преобразуется обращение к элементу массива. Имя массива — это указатель на его начало. Индекс, переданный в квадратных скобках, — смещение относительно начала массива (именно поэтому первый элемент массива имеет номер 0).
Конструкция arr[i] будет преобразована компилятором к *(arr + i). К начальному адресу массива будет прибавлено число с учетом размерности типа данных. А затем будет взято значение по вычисленному адресу.
1.6. Приведение типов при работе с указателями
При работе с указателями не следует забывать о типах значений, на которые они указывают.
char c[10];
char* pc = c;
int* pi = pc;
Компилятор языка C даст предупреждение, а компилятор C++ эту операцию считает не безопасной, поэтому выдает ошибку: указатели указывают на разные типы данных. Устранить неоднозначность может только программист, выполнив приведение типов:
int* pi = (int*)pc;
для компилятора с++ чаще встречается другой форма записи с аналогичным действием:
int* pi = reinterpret_cast<int*>(pc);
Выполняя явное приведение типов, программист будет сам отвечать за дальнейшую корректность работы приведенных указателей.
1.7. Работа с динамическими переменными
До настоящего момента в рассмотренных примерах мы рассматривали указатели, которые указывали на уже существующие переменные. Но очень часто указатели используются для работы с динамическими переменными – переменными, которые могут создаваться и уничтожаться в процессе работы программы.
В этом случае все необходимые операции по созданию и удалению таких переменных должен выполнять программист. Он несет полную ответственность за корректную работу таких указателей.
Рассмотрим следующий пример:
int *pV;
*pV = 10;
С точки зрения компилятора все корректно: pV – это указатель на ячейку памяти, предназначенную для хранения значения типа int, 10 – это целое.
Но мы объявили не инициализированный указатель, это значит, что адрес, хранящийся в указателе pV, может указывать на случайную ячейку памяти (в том числе и на ту, которая может быть занята другой переменной).
Поэтому попытка записать по хранимому адресу значение может вызвать ошибку (если эта память уже кем-то занята), а может не вызвать (если ячейка еще свободна). Но даже если ошибки не будет, нужно помнить, что эта память считается свободной и может быть занята любой другой переменной, это означает, что вы можете потерять свое значение.
Для того чтобы указанная операция была выполнена корректна, необходимо выполнить дополнительную операцию:
pV = new int;
*pV = 10;
Оператор new выделит память, необходимую для размещения значения типа int, и вернет адрес этой ячейки памяти.
Важно помнить, что память под переменные, выделенные оператором new, не освобождается автоматически. Если программист только выделяет память и не освобождает ее после завершения использования, то имеет место «утечка памяти», она опасна в случае долго работающих программ или программ, работающих с большими объемами данных. Поэтому память, выделенная при помощи new, должна быть освобождена при помощи delete:
delete pV;
1. 8. Основные выводы:
int x; //объявление переменной целого типа
int* p; //объявление указателя на переменную целого типа
p = &x; //присвоить p адрес переменной x
x = *p; //присвоить x значение, которое находится по адресу, сохраненному в p
2. Применение указателей при работе с функциями
2.1. Передача параметров по значению
Функция имеет возможность вернуть результат работы в своем имени. Например:
int summ(double a, double b)
{
return a + b;
}
Мы можем попытаться вернуть больше одного значения с помощью формальных параметров. Рассмотрим пример программы с функцией, которая меняет местами значения двух переменных.
#include <iostream>
using std::cout;
using std::endl;
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int x = 10, y = 20;
int* p;
cout<<"value x = "<< x<<" value y = "<< y<<endl<<endl;
cout<<"call swap(x, y);"<<endl<<endl;
swap(x, y);
cout<<"value x = "<< x<<" value y = "<< y<<endl<<endl;
return 0;
}
Если выполнить функцию, передав в нее x и y, окажется, что никакого обмена не произошло.
При вызове этой функции формальные параметры a и b получат копии значений фактических параметров x и у (с которыми функция была вызвана).
