3. Визначення функції //Greatest Common Divider // заголовок int gcd (int m, int n) //тіло функції { while (m != n) if ( m>n ) m=m-n; else n=n-m; // m == n //результат return m; }
4. Виклик функції int a, b, c; cin>>a>>b; c = gcd (a, b); // 1. Ініціалізація параметрів // int m=a; int n=b; //2. Виконання тіла фукції //3. Обчислення результату (c=m;) Звичайно виклик функції полягає в припиненні нормального ходу виконання об'єктного коду, що містить виклик. Відбувається ініціалізація і починає виконуватись об'єктний код функції. Після повернення результату виконання основного коду відновлюється
5. Способи виконання функцій Закрита функція Викликається командою переходу з поверненням Відкрита (вбудована) функція Підставляється в кожне місце виклику В обох випадках параметри і результат передаються в один і той же спосіб
6. Вбудовані функції Об'єктний код закритої функції присутній в програмі один раз. Відповідні команди керування приводять до її активації, виконання коду і наступного повернення в код виклику. Це накладні витрати виклику . Їх можна зменшити, якщо розміщувати код функції безпосередньо в місці виклику. Накладними витратами в цьому разі стане дублювання коду в кожному місці виклику. Компілятор сам приймає рішення, як реалізувати функцію ( inline лише порада). inline double max( double x, double y) { return (x>y)?x:y; }
7. Приблизна схема викликів z= max (5, 6); z= max(max(1,2),3); double x = 5; double y = 6; z = (x>y)?x:y; double x = 1; double y = 2; double x1 = (x>y)?x:y ; double y1 = 3; z = (x1>y1)?x1:y1 ;
8. Макровизначення Макровизначення на перший погляд дуже схожі на вбудовані функції, вони теж підставляються в місця виклику, але відрізняються від вбудованих функцій способом передачі параметрів текстовою підстановкою. Приклад макровизначення # define MAX(x,y) ((x)>(y))?(x):(y) Приклади макровикликів MAX(1,2); MAX(a+ b , c-d );
9. Приклади макропідстановок double a = MAX(1,2); //((1)>(2))?(1):(2) cout<<a<<endl; //2 double b=2; a= MAX(++a,b); // (( ++ a )>(b))?( ++ a ):(b) or // (3>2)?4:2) == 4 cout<<a<<endl; // 4 // The next expression is not correct // because of priorities cout<<MAX(++a,b)<<endl; //means // cout<< (5>2)?6:4 <<endl or // (cout<<(5>2))?6:(4<<endl ) ERROR
10. Виклик функції inline double max( double x, double y) { return (x>y)?x:y; } double a = max (1,2); // double x=1; double y=2; // ініціалізація параметрів // return (1>2)?1:2 == 2 виконання тіла і повернення cout<<a<<endl; //2 double b=2; a= max (++a,b); // double x=++a, double y=b; (x==3, y==2) // return (3>2)?3:2 == 3
12. Передача параметрів Розглянемо оголошення функції void f ( T x); де f ― ім'я функції, T ― тип параметру, x ― ім'я параметру Розглянемо виклик цієї функції f(e) ; де e ― вираз підходящого типу (фактичний параметр) Виконання функції починається визначенням локальної змінної для параметру x з її одночасною ініціалізацією T x = e ;
13. Передача параметрів значенням Ініціалізація T x = e ; говорить про те, що перед виконанням функції обчислюється значення фактичного параметру е ( r-value) Кажуть, що параметри передаються значенням Залежно від типу T ці значення можуть бути значеннями базових типів, структур, адресами (у випадку указників) відсилками (у випадку псевдонімів)
14. Сторонній ефект Якщо передаються значення базових типів або структур, то виконання функції не впливає на наступні значення фактичних параметрів Якщо формальний параметр є указником, то можлива зміна значення елементу пам'яті, на який цей указник встановлено Якщо формальний параметр псевдонім, то через нього функція одержує прямий доступ до елементу пам'яті, до якого псевдонім відсилає Якби не було стороннього ефекту, то який сенс мали б функції виду void f (T x) ?
15. Приклад 1. swap void swap( double x, double y) { // значення базового типу double double z = x; x=y; y=z; cout<<x<<‘,’<<y<<endl; //2,1 } double a=1, b=2; swap( a,b); // double x=a, y=b; cout<<a<<‘,‘<<b<<endl; // 1,2
16. Приклад 2 . p swap void p swap( double * x, double * y) { double z = * x; * x= * y; * y=z; cout<<*x<<‘,’<<*y<<endl; //2,1 } double a=1, b=2; p swap( & a, & b); // за коректність відповідає користувач // double *x= &a, *y= &b; cout<<a<<‘,‘<<b<<endl; // 2,1
17. Приклад 3 . r swap ( тільки С++) void r swap( double & x, double & y) { double z = x; x=y; y=z; cout<<x<<‘,’<<y<<endl; // 2,1 } double a=1, b=2; r swap( a,b); // за коректність відповідає розробник // double & x=a, & y=b; cout<<a<<‘,‘<<b<<endl; // 2, 1
18. Безпечність виклику Безпечні swap( a,b); r swap( a,b); Небезпечний p swap( &a, &b); // p swap( 0, &b); ??? void pswap(double *x, double *y) { assert((x!=0)&&(y!=0)); double z = *x; *x=*y; *y=z; cout<<*x<<','<<*y<<endl; }
19. Масиви як параметри Масиви ― головна причина використання параметрів-указників Використання параметром масиву нічим не відрізняється від параметра-указника T *a T a[]
20. Задача додому Що б це значило? Організуйте виклик функції ptrswap та графічно зобразіть роботу з пам'яттю void ptrswap( int *&u, int *&v ) { int *w = v; v = u; u = w; }
21. Приклад 4. Не має значення, як визначити параметр в кожній з трьох сигнатур void init ( char * ch); void show ( char ch[]); void swapar ( char []);
22. Приклад 4 (1). const int n=26; int main () { char ch[n]; init (ch); show(ch); swapar(ch); show(ch); return 0; }
23. Приклад 4 ( 2 ) . void init( char * ch) { for (int i= 'a'; i<'a'+ n ; i++) // не дуже добре ch[i-'a'] = i; } Ніхто не заборонить навіть таке for (int i= 'a'; i<'a'+ 100000 ; i++) ch[i-'a'] = i; Результат передбачити неможливо, швидше всього access violation
24. Приклад 4 ( 3 ) . Допустимий навіть зовсім безглуздий виклик char ch; init( & ch) ; Теж access violation
25. Приклад 4 ’ (1). Краще рішення передати розмір масиву додатковим параметром void init ( char * ch, int n ); void show ( char ch[], int n ); void swapar ( char [], int );
26. Приклад 4 ’ (2). int main () { const int n=26; char ch[n]; init (ch, n ); show (ch, n ); swapar (ch, n ); show (ch, n ); return 0; }
27. Приклад 4 ’(2) . void init ( char * ch , size_t n ) { for ( int i= 'a'; i<'a'+ n ; i++) //тепер в порядку ch [ i-'a' ] = i; }
28. Приклад 4 ’(3) . void show( char ch[n], size_t n) { cout<<"start string:"<<endl; for ( int i=0; i< n ; i++) cout<<ch[i]<<' '; cout<<endl; }
29. Приклад 4 ’(4) . void swapar( char ch[], size_t n) { for ( int i = 0; i< n /2; i++) pswap(ch +i ,ch+n-1-i); };
30. Приклад 4 ’( 5 ) . Результат роботи s tart string : a b c d e f g h i j k l m n o p q r s t u v w x y z s tart string : z y x w v u t s r q p o n m l k j i h g f e d c b a
31. Висновок Передаючи в функцію параметром масив (або указник як масив) , передбачаємо розмір масиву додатковим параметром void f (T *array, size_t size); або краще void f (T [] array, size_t size);
32. “ Загорнуті” масиви // Визначення “загорнутого” вектора struct WrappedVector { static const int n; // статичне поле одне на всіх double * x; }; // сигнатура добутку (дуже погана) double prod ( WrappedVector , WrappedVector );
33. Параметри сталі відсилки // сигнатура добутку (краще) // структури не копіюються double prod ( WrappedVector & , WrappedVector & ); // сигнатура добутку (ще краще) // структури не копіюються, // доступ для зміни фактичного // параметру закрито double prod ( const WrappedVector & , const WrappedVector & );
34. Скалярний добуток // визначення статичного поля структури const int WrappedVector::n = 100; // визначення скалярного добутку double prod ( const WrappedVector& a, const WrappedVector& b) { double s = 0; for ( int i=0; i<a.n; i++) s+=a.x[i]*b.x[i]; return s; }
35. Створення вектору ― конструктор // Відсилка ― повний доступ void construct ( WrappedVector & ); void construct (WrappedVector & a) { a.x = new double [a.n]; for ( int i=0; i<a.n; i++) a.x[i] = rand(); return ; }
36. Видалення об'єкту ― д еструктор void destroy (Wrapped Vector & a ) { delete [] a.x; a.x = 0; return ; } // Як примусити кожного прибирати за собою?
38. Типи параметрів (перший підсумок) const T T T& const T& Копіювання, незмінний формальний параметр , немає впливу на фактичний параметр Копіювання, змінний формальний параметр, немає впливу на фактичний параметр Відсилка без копіювання, повний доступ до фактичного параметру (C++) Стала відсилка без копіювання, фактичний параметр незмінний (C++)
39. Порівняння типів параметрів Найнадійніший параметр-результат T& Параметри-значення Точка зору користувача: параметри T , const T і const T& не відрізняються з точки зору використання Точка зору розробника: const T надійніший, бо не створює непорозумінь; const T& найефективніший, оскільки не вимагає витрат на копіювання
40. Сталий параметр //Greatest Common Divider int gcd ( const int m 0 , const int n 0 ) { int m = m0, n = n0; while (m != n) if ( m>n ) m=m-n; else n=n-m; // m == n // Початкові значення не втрачено assert((m0%m==0) && (n0%n==0) ); return m; }
41. Параметри-указники T* const T* T* const const T*const Повний але непрямий доступ до фактичного параметру; передача масивів ( T[]) (в стилі чистого С) Маловживаний тип параметру; доступ лише для читання: сталий фактичний параметр ( vs const T&) Незмінний формальний параметр - указник , немає впливу на фактичний параметр-адресу ( this в C++) Незмінний указник на сталий фактичний параметр В усіх випадках 5-8 сам указник передається значенням
42. Головна проблема параметра-указника Як відрізнити масив від скалярного значення? Вихід: жорстка дисципліна програмування, додатковий параметр типу size_t з відповідним коментарем void f ( double * px, size_t size_of_px );
43. Типова помилка в параметрі-указнику // Думалось, що ця функція виділятиме пам’ять для // указника t void allocate ( char *t, size_t n, char c) { t = new char [n]; t[n-1]='\0'; for (int i=0;i<n-1;i++) t[i]=c; cout<<“This is inside: “<<t<<endl; return ; }
44. Результат // Виклик функції allocate char a[]=“This was before”; cout<<a<<endl; // This was before allocate (a, 10 , ‘a’); // This is inside: aaaaaaaaaa cout<<a<<endl; // This was before // Вихід: передати указник відсилкою void construct ( WrappedVector & );
45. Параметри-указники T** T*& . . . . . . . . . . . . . . . . . const T* const * const Указник-вихідний параметр (чистий С) Те ж саме, але в стилі С++ А ще різні варіації на тему const
46. Указник другого рівня // Тепер правильно void allocate ( char ** t size_t n, char c) { * t = new char [n]; (*t) [n-1]='\0'; for ( int i=0;i<n-1;i++) (*t) [i]=c; cout<< *t <<endl; return ; }
47. Виклик указником другого рівня // Виклик функції allocate char * a=“This was before”; cout<<a<<endl; // This was before allocate ( & a, 10 , ‘a’); // This is inside: aaaaaaaaaa cout<<a<<endl; // aaaaaaaaaa
48. Псевдонім указника void allocate ( char * & t, size_t n, char c) { t = new char [n]; t[n-1]='\0'; for ( int i=0;i<n-1;i++) t[i]=c; cout<<t<<endl; return ; }
49. Виклик відсилкою до указника // Виклик функції allocate char * a=“This was before”; cout<<a<<endl; // This was before allocate (a, 10 , ‘a’); // This is inside: aaaaaaaaaa cout<<a<<endl; // aaaaaaaaaa
51. Вершина дерева Параметр aTree відіграє роль параметру-результату void createTree (Tree ** aTree, int node, Tree *left, Tree *right) { *aTree = new Tree; (*aTree) -> node = node; (*aTree) -> left = left; (*aTree) -> right = right; return ; }
53. Приклад використання int main () { Tree * t, * t1, *t2 ; createTree(&t1,1,0,0); createTree(&t2,2,0,0); createTree(&t,3,t1,t2); destroyTree (&t); // Проблема: значення t1 і t2 return 0; }
55. Указники vs. відсилки Замінимо void createTree (Tree **aTree, int node, Tree *left, Tree *right); на void createTree (Tree * & aTree, int node, Tree *left, Tree *right); але не void createTree (Tree * & aTree, int node, Tree &left, Tree &right ); бо виклик createTree(t1,1,0,0); стане незаконним
56. Зведення типів при передачі параметрів void show ( doubl e x) { cout<<x<<endl; } // OK int k=1; show(k); //1 long int lk=2; show(lk); //2 float x=3; show(x); //3
57. Неявне зведення відсилок неможливе void show ( double & x) { cout<<x<<endl; } // ERRORS int k=1; show(k); long int lk=2; show(lk); float x=3; show(x);
58. Явне зведення відсилок хибне void show ( double & x) { cout<<x<<endl; } // Type conversion int k=1; show(( double &) k); //-9.25596e+061 long int lk=2; show(( double &) lk); //2.122e-314 float x=3; show(( double &) x); //4.77656e-314 Не намагайтеся допомогти компілятору: не вийде!
59. Неявне зведення указників неможливе теж void show ( double * x) { cout<<x<<endl; } // ERRORS int k=1; show(&k); long int lk=2; show(&lk); float x=3; show(&x);
63. Точка площини struct Point { double _x; double _y; }; const Point plus (Point u, Point v) { Point res; res._x = u._x+v._x; res._y = u._y+v._y; return res; }
64. Дерево Як і в попередньому прикладі результат повністю копіюється при виході з функції Tree createTree ( int node, Tree * left, Tree * right) { Tree aTree; aTree.node = node; aTree.left = left; aTree.right = right; return aTree ; }
65. Результати-відсилки Дають повний доступ до частин агрегатів даних, але не забувайте про правильну передачу параметру double & x (Point u) { return u._x; } Point v; v._x = 10; v._y = 20; cout<<x(v) <<endl; //10 x(v)= 125; cout<< x(v)<<endl; //10 , а не 125 ?!
66. Результати і параметри-відсилки Тепер повний доступ до параметру double & x (Point & u) { return u._x; } Point v; v._x = 10; v._y = 20; cout<< x(v) <<endl; //10 x(v)= 125; cout<< x(v) <<endl; // 125 , а не 10?!
67. Сталі відсилки Результати не копіюються, але й не доступні для змін const double & x ( const Point & u) { return u._x; } Point v; v._x = 10; v._y = 20; cout<< x(v) <<endl; //10 // x(v)= 125; not possible now
68. Результати-указники Tree* createTree ( int node, Tree *left, Tree *right) { Tree * aTree = new Tree; aTree->node = node; aTree->left = left; aTree->right = right; return aTree; } Тепер можлива суперпозиція t = createTree (3, createTree (1,0,0), createTree (2,0,0));
69. Висновки Результат, створений у тілі функції , передаємо значенням Доступ до частини або всього агрегату даних, переданого фактичним параметром, забезпечуємо результатом відсилкою Створені у функції агрегати даних передаються указником
71. Неефективність рекурсії int BadFib ( int n ) { // Так не варто рахувати!!! if (n==0) return 0; if (n==1) return 1; return BadFib(n-1)+BadFib(n-2); }
72. Ефективна рекурсія void fib ( int &f1, int &f2, int n) { // n зменшується на 1, f1 і f2 зсуваються вправо int f; if (n>=2) { f=f2; f2+=f1; f1=f; fib(f1, f2, n-1); } }