Передача параметрів


4.3. Передача параметрів значенням, особливості передачі параметрів у випадку указників, псевдонімів, псевдонімів сталих, псевдонімів указників, масивів, псевдонімів масивів; замовчувані значення параметрів; перетворення типів при передачі параметрів

 

Як ми побачили з попереднього розділу в С++ підтримується передача параметрів значенням. Власне це єдиний спосіб передачі параметрів, якщо не рахувати текстових підстановок в макровизначеннях. Передача параметрів значенням має багато переваг, головною з яких є її висока надійність. Підпрограма не має виходу за рамки відведеної їй пам’яті, а тому сфера її впливу добре локалізується. Разом з тим добре відомі проблеми, що виникають при передачі параметрів значенням, а саме проблеми зміни значень фактичних параметрів.

 

Ось приклад

void DoubleMyValue (short valueParam)

{

valueParam *= 2;

}

int main ()

{

short number = 10;

DoubleMyValue (number);

cout<<number<<’\n’;

}

 

В результаті виконання підпрограми значення формального параметру valueParam подвоїться, але це подвоєння не справить жодного впливу на фактичний параметр number. Складена в такий спосіб програма стає марною. Правда, в цьому конкретному випадку ситуацію легко поправити

 

 

p>Результат виконання програми:

 

Перед swap(): i: 10 j: 20

Після swap(): i: 10 j: 20

 

Перший вихід із проблемної ситуації пропонують указники.


Як і раніше параметри передаються значеннями, але значення ці тепер адреси.

 

Перед swap(): i: 10 j: 20

Після swap(): i: 20 j: 10

 

 

void rswap( int &x, int &y)

{

int z = x; x=y; y=z;

}

int main() {

int i = 10;

int j = 20;

cout<<"Перед swap():\ti: "<<i<<"\tj: "<<j<<endl;

rswap( i, j ) ;

cout<<"Після swap():\ti: "<<i<<"\tj: "<<j<<endl;

return 0;

}

 

 

 

 

void pswap( int *x, int *y)

{

int z = *x; *x=*y; *y=z;

cout<<"pswap: "<<&x<<" z:"<<&z<<endl;

}

void rswap( int &x, int &y)

{

int z = x; x=y; y=z;

cout<<"rswap: "<<&x<<" z:"<<&z<<endl;

}

void swap( int x, int y)

{

int z = x; x=y; y=z;

cout<<"rswap: "<<&x<<" z:"<<&z<<endl;

}

int main() {

int i = 10, &ri = i;

int j = 20;

cout<<"main: "<<hex<<&i<<" ri: "<<&ri<<endl;

cout<<"Перед swap():\ti: "<<i<<"\tj: "<<j<<endl;

swap( i, j ) ;

cout<<"Після swap():\ti: "<<i<<"\tj: "<<j<<endl;

pswap( &i, &j ) ;

cout<<"Після pswap():\ti: "<<i<<"\tj: "<<j<<endl;

rswap( i, j ) ;

cout<<"Після rswap():\ti: "<<i<<"\tj: "<<j<<endl;

return 0;

}

 

Оголошення int *&v;читається справа наліво: псевдонім указника

 

void ptrswap( int *&, int *& );

int main()

{

int i = 10;

int j = 20;

int *pi = &i;

int *pj = &j;

cout<<"Перед ptrswap():\tpi: "

<<*pi<<"\tpj: "<<*pj<<endl;

ptrswap( pi, pj ) ;

cout<<"Після ptrswap():\tpi: "

<<*pi<<"\tpj: "<<*pj<<endl;

return 0;

}

 

 

Перед ptrswap: pi: 10 pj: 20

Після ptrswap: pi: 20 pj: 10

 

 

У цьому прикладі 1000 разів копіюватиметься масив з 1000 чисел незалежно від того, у чому полягатиме обчислення функції calc, наприклад це могла б бути сума або середнє арифметичне.

 

 

 

Якщо ми замінимо сигнатуру, задавши параметр псевдонімом, уникнемо копіювання, але не будемо гарантовані від зміни параметру всередині функції

 

int calc( Huge &par);

 

 

void getArray( int[ 10 ] ) ;

і

void getPtr( int* );

 

Треба добре розуміти, що оскільки масив передається указником, він не копіюється. Порівняйте з попереднім прикладом, де передавався елемент масиву, що в свою чергу був визначений як структура.

 

 

void putValues( int[ ], int size );

int main()

{

int i, j[ 2] ;

putValues( &i, 1);

putValues( j , 2);

return 0;

}

 

void putValues( int (&arr)[10] ) ;

int main()

{

int i, j [ 2 ];

putValues(i);

// помилкака: аргумент не є масивом

putValues(j);

// помилка:

// аргумент не є масивом з 10 елементів типу int

return 0;

}

 

 

p>Додатковий параметр sz відповідає за розмірність. Перша перевірка з’ясовує, чи були указники попередньо встановлені. Те ж саме копіювання символьних масивів додаткового параметру розмірності не потребує, оскільки вичерпання масиву перевіряється нульовим кодом

 

void сopyString (char *source, char *dest)

{

while ( *source != ‘\0’ )

{

*dest = *source;

dest++;

source++;

}

*dest = ‘\0’;

}

 

або це ж саме компактним С-текстом

 

void CopyString (char *source, char *dest)

{

while (*dest++ = *source++);

}

 

Для функцій існує можливість визначати замовчувані значення аргументів. Замовчування визначаються або безпосередньо в реалізації функції, або при визначенні її прототипу. Останній спосіб видається більш доцільним.

 

void GenerateATone (short frequency = 440)

{

//Частота 440 відповідає ноті сі

};

 

Виклики можуть бути такими

 

GenerateATone (330);

GenerateATone ();

 

Для визначення замовчувань у багатомісних функціях існує одне обмеження: всі аргументи без початкових значень повинні передувати у списку аргументів усім аргументам з замовчуваними значеннями. Так прототип

 

void NormalDefaults (short x, short y=2, short z=3);

 

відповідає правилу, в той час як наступний — ні, оскільки перший аргумент має, а другий аргумент не має замовчування

 

void WillNotCompile (long time = 100L, short stack);

 

Використання замовчуваних значень аргументів при виклику функції ілюструється наступним прикладом.

 

#include <iostream.h>

void MyFunc( short param1,

short param2 = 0,

short param3 = 0 );

int main()

{

MyFunc( 1 );

MyFunc( 1, 2 );

MyFunc( 1, 2, 3 );

return 0;

}

void MyFunc( short param1,

short param2,

short param3 )

{

cout << "MyFunc( " << param1

<< ", " << param2

<< ", " << param3

<< " )\n";

}

MyFunc( 1, 0, 0 )

MyFunc( 1, 2, 0 )

MyFunc( 1, 2, 3 )