logo
ответы

76. Передача данных через указатели

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

Решение этой проблемы: необходимо заставить функцию обрабатывать не копию аргумента, а само значение аргумента.

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

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

int Div (int N1, int N2, int *Ost) // int *Ost – параметр-указатель

{

*Ost = N1 % N2; // *Ost – разыменование параметра-указателя

return N1 / N2;

}

int main()

{

int I = 10, J = 3, R, O;

R = Div (I, J, &O); // &O – адрес аргумента O

cout << I << “ / ” << J << “ = ” << R << “. Остаток равен “ << O << endl;

return 0;

}

Функция Div находит результат и остаток от деления параметра N1 на N2. Результат деления возвращается через имя функции, а остаток через параметр Ost.

Для того чтобы обеспечить возвращение вычисленного внутри функции остатка, соответствующий параметр функции определен как указатель (int *Ost). При вызове функции в качестве аргумента для этого параметра был использован адрес переменной &O, а не само значение переменной O. При вычислении остатка внутри функции выполняется разыменование параметра Ost (*Ost - обращение по адресу, хранящемуся в параметре Ost), и вычисленный остаток записывается по этому адресу (то есть в переменную O). Таким образом, значение переменной O изменяется.

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

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

1. Соответствующий параметр в заголовке функции необходимо определить как указатель на тип данных аргумента.

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

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

Несколько проще обстоит дело с передачей массивов, так как переменные типа массив сами являются указателями на первый элемент массива. В связи с этим отпадает необходимость в выполнении пунктов 2 и 3 из перечисленных выше. Рассмотрим пример:

void ReadArr ( int *P, int n)

{

for (int I = 0; I < n; ++I)

cin >> P[I];

}

void WriteArr ( int Arr[], int n)

{

for (int I = 0; I < n; ++I)

cout << Arr[I] << “ “;

cout << endl;

}

int main()

{

const n = 10;

int A[n];

ReadArr (A, n);

WriteArr (A, n);

return 0;

}

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

Во второй функции параметр для передачи массива определен как массив из элементов типа int, но это одновременно и указатель на первый элемент массива.

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

Во второй функции при обращении к очередному элементу массива также не требуется разыменовывать параметр Arr – обращение к ним осуществляется естественным для массива способом – с помощью индексации элементов.

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

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

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

void StringProc(char * S, int L)

или

void StringProc(char S[], int L)

Этот способ передачи данных часто называют передачей данных по адресу.