Передача параметрів
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 )