Указатели как формальные параметры функции

Указатели

Объявление указателей и работа с адресами

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

При объяв­лении переменной-указателя последовательно указываются тип данных, адреса которых будет содержать переменная, звездочка и имя переменной-указателя, например,

int * iptr;

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

Получение адресов переменных программы обеспечивает специальная операция &.

Предположим, что в программе объявлена переменная i типа int:

int i;

Тогда ранее объявленной переменной-указателю iptr можно присвоить адрес переменной i:

iptr = &i;

После выполнения этого оператора переменная iptr будет хранить адрес переменной i.

Обратиться к значению переменной i теперь можно не только по ее имени, но и по ее адресу. Для доступа к значению переменной по ее адресу применяется специальная унарная опе­рация *. Её операндом является обозначение адреса некоторой величины, а результатом – расположенное по данному адресу значение.

Таким образом, следующие два оператора эквивалентны (iptr – всё та же переменная-указатель, y – переменная типа int):

y = i; // присваивание переменной y значения переменной i

y = *iptr; // присваивание переменной y значения, находящегося по адресу,

// который хранится в iptr, т.е. значения переменной i

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

*iptr = 10; // запись числового значения 10 по адресу, хранящемуся в iptr

После выполнения этого оператора значение переменной i, на которую указывает указатель iptr, станет равным 10.

Указатель – это не просто адрес, а адрес величины определенного типа. Так, рассматриваемый нами указатель iptr – адрес величины типа int. Можно объявить указатели на значения и других типов, например:

unsigned short* uptr; // указатель на короткое целое число без знака

char* ср; // указатель на символ

Каждое из этих объявлений выделяет память для переменной-указателя, но каждый из этих указателей пока ни на что не указывает. До тех пор, пока указателю не будет присвоен како­й-либо осмысленный адрес, его нельзя использовать в программе.

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

Тип, адресуемый указателем, определяет количество байтов памяти, рассматриваемых при выполнении операции обращения по адресу.

Адресная арифметика

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

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

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

ptr+n

где ptr – указате­ль, а n – целое число, представляет собой значение ptr, увеличенное на величину n*sizeof(*ptr).

Для указателей, как и для обычных переменных, определены операции ++ и +=, сочетающие в себе увеличение и присваивание. Так,

ptr++;

для указателя ptr означает увеличение его значения на sizeof(*ptr), а, скажем,

ptr+=2;

– на 2*sizeof(*ptr).

От указателя можно отнять целое число. При вычитании из указателя ptr целого положительного числа указатель сдвигается назад на соответствующее количество переменных того типа, на который он указывает (в байтах – на величину n*sizeof(*ptr), где n – вычитаемое число). Для указателей определены также операции -- и -=.

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

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

Сравнение указателей с числовыми значениями как сравнение данных разных типов не определено. Чтобы сравнить значение указателя с числовым адресным значением, необходимо явно преобразовать число в указатель на данные требуемого типа. Например, выражение

xptr > 0x12FF00

будет ошибочным, а выражение

xptr > (int*)0x12FF00

правильным.

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

Приоритет и порядок выполнения операций & и * тот же, что у других унарных операций (!, ~, -). То есть для

int i, *ip=&i;

*ip+1 – это (*ip)+1, а не *(ip+1).

Указатели как формальные параметры функции

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

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

void swap1 (int* x, int* y)

{

int z=*y;

*y = *x;

*x = z;

}

 

int main ()

{

int a=0, b=1;

swap1 (&a, &b);

. . .

return 0;

}

Понятно, что вызов функции swap1 приведет к обмену значениями переменных a и b.