Указатели
Лекция №24. Указатели и массивы
План:
1. Указатели;
2. Применение void;
3. Массивы.
Указатели в Си++ используются для связи переменных с машинными адресами. Понятие указателя тесно сплетено с обработкой массивов и строк. Массивы в Си++ могут рассматриваться как специальная форма указателя, связанная с непрерывной областью памяти для хранения индексируемой последовательности значений.
Если v – переменная, то &v – это адрес или место в памяти, где хранится её значение. Оператор определения адреса & (address operator) является унарным и имеет такой же приоритет и порядок выполнения справа налево, как и остальные унарные операторы. В программе можно объявить переменные-указатели и затем использовать их для получения адресов в качестве значений.
Объявление
Int* p;
говорит, что переменная p будет иметь тип «указатель на целое». Допустимая область значений для любого указателя всегда включает специальный адрес 0, а также набор положительных целых, которые интерпретируются как машинные адреса. Вот некоторые примеры присваивания значений указателю p:
p = &i; //адрес объекта i
p = 0; //специальное значение
p = static_cast<int*>(1507); //абсолютный адрес
в первом примере подразумевается, что p «ссылается на i», или «указывает на i», или «содержит адрес i». Компилятор решает, какой адрес присвоить переменной i. Этот адрес может отличаться от машины к машине и даже может быть разным при разных запусках программы на одной и той же машине. Второй пример – присваивание указателю p специального значения 0. Это значение обычно используется для индикации возникновения особых условий. Например, указатель со значением ноль возвращается оператором new, если исчерпана свободная память. Значение 0 также используется для обозначение того, что достигнут конец динамической структуры данных, такой как дерево или список. В третьем примере приведение необходимо, чтобы избежать ошибки типов, и используется фактический адрес памяти.
Оператор разыменования (dereferencing) или непрямого обращения * является унарным. Если p – указатель, то *p – значение переменной, на которую указывает р. прямое значение р – это адрес памяти, тогда как *р – это непрямое значение р, то есть значение, находящееся по адресу, который хранится в указателе р. в некотором смысле, * - это оператор обратный &. Вот фрагмент, показывающий их взаимосвязь:
Int i = 5, j;
Int* p = &i; //указатель инициализирован адресом переменной i
Cout << *p << “ = i, хранится по адресу “ << p << endl;
J = p; // недопустимо, указатель не преобразуется в целое
J = *p + 1; // допустимо
P = &j; // р указывает на j
Адреса переменных можно использовать в качестве аргументов функции, реализуя таким образом вызов по ссылке (call-by-reference). В результате значения переменных могут изменяться в вызывающем окружении. Указатели используются в списке парметров для определения адресов переменных, значения которых могут изменяться. При обращении к функции адреса переменных должны передаваться как аргументы.
Пример:
Void order ( int*, int*);
Int main()
{
int i = 7, j = 3;
cout << i << ‘\ t’ << j << endl; // напечатается 7 3
order (&i, &j);
cout << i << ‘\ t’ << j << endl; // напечатается 7 3
}
void order (int* p, int* q)
{
int temp;
if (*p > *q) {
temp = *p;
*p = *q;
*q = temp;
}
}
Большая часть этой программы выполняется функцией order (). Обратите внимание, что в качестве аргументов передаются адреса i и j. Эти адреса передаются по значению, и следовательно, на могут быть изменены в вызывающем окружении. Тем не менее, значения i и j (а не их адреса) могут изменяться в вызывающем окружении. Разберём функцию order():
- void order (int* p, int* q)
оба параметра р и q – указатели на целое. Приведённая конструкция реализует вызов по ссылке на основе указателей. Параметра должны передаваться адреса. В main() функция была вызвана как order (&i, &j);
- if (*p > *q) {
temp = *p;
*p = *q;
*q = temp;
}
здесь сравниваются и, в случае неправильного порядка, меняются местами в вызывающем окружении произвольные значения, хранящиеся по адресам, на которые указывают p и q.
Можно определить правила использования аргументов-указателей для выполнения обращения по ссылке:
1) объявить параметры-указатели в заголовке функции:
2) использовать разыменованный указатель в теле функции;
3) передать адреса в качестве аргумента при вызове функции.
Унарные операторы new и delete служат для управления свободной памятью (free store). Свободная память – это предоставляемая системой область памяти для объектов, время жизни которых напрямую управляется программистом. Программист создаёт объект с помощью ключевого слова new, а уничтожает его, используя delete. Это важно при работе с динамическими структурами данных.
В С++ оператор new принимает следующие формы:
New имя_типа;
New имя_типа инициализатор;
New имя_типа [выражение];
В каждом случае имеется по крайней мере два эффекта. Во-первых, выделяется надлежащий объём свободной памяти для хранения указанного типа. Во-вторых, возвращается базовый адрес объекта (в качестве значения оператора new). Когда память недоступна, оператор new либо возвращает значение 0 либо активизирует соответствующее исключение.
Пример:
int *p, *q;
p = new int (5); // выделение памяти и инициализация
q = new int [10]; // получаем массив от q[0] до q[9] причём q = &q[0]
В этом фрагменте указателю на целое p присваивается адрес ячейки памяти, полученный при размещении целого объекта. Место в памяти, на которое указывает p, инициализируется значением 5. Такой способ обычно не используется для простых типов вроде int, так как гораздо удобнее и естественнее разместить целую переменную глобально.
Оператор delete уничтожает объект, созданный с помощью new, отдавая тем самым распределённую память для повторного использования. Оператор delete модет принимать следующие формы:
Delete выражение;
Delete [] выражение;
Первая форма используется, если соответствующее выражение new размещало не массив. Во второй форме присутствуют пустые квадратные скобки, показывающие, что изначально размещался массив объектов. Оператор delete не возвращает значения, поэтому можно сказать, что возвращаемый им тип – void.