You are on page 1of 24

Programmēšana I — 10.

Lietotāju funkcijas, 2. daļa

Andrejs Klačkovs, Mg.sc.admin.


Temata saturs
• Norādes (* un &)
• Argumentu nodošanas veidi
• Parametru nodošana pēc adreses
• Masīvi kā funkcijas argumenti
• Modifikators const
• Rekursīvas funkcijas
• Neefektīvas rekursijas piemērs
• Patstāvīgais darbs

2
&, *
Referencing un dereferencing, būs 3. blokā

Referencing = datu adreses iegūšana (&)


Dereferencing = vērtības iegūšana pēc adreses (*)

*Adrese (kas tur ir?)


Adrese Dati
& Dati (kur tas ir?)

3
Argumentu nodošanas veidi — 1.
1. pēc vērtības (by value): funkcija veido nododamo
vērtību kopijas, tātad mainoties formāla parametra
vērtībai funkcijas iekšienē, faktiskā parametra vērtība
nemainās.
2. pēc adreses (by address): faktiski funkcija tiek nodota
mainīgā-argumenta adrese atmiņā, tātad mainoties
formāla parametra vērtībai funkcijas iekšienē, mainās arī
faktiskā parametra vērtība:
• pēc references (by reference);
• pēc norādes (by pointer).
4
Argumentu nodošanas veidi — 2.
Nododot parametrus pēc references vai pēc norādes,
funkcijai ir tieša piekļuve argumentu vērtībām, un ir
iespēja atgriezt no funkcijas vairāk nekā vienu vērtību.
pēc adreses
pēc vērtības
pēc references pēc norādes
void test(int fa) void test(int& fa) void test(int* fa)
{ fa = 15; } { fa = 15; } { *fa = 15; }
int main() { int main() { int main() {
int a = 10; int a = 10; int a = 10;
test(a); test(a); test(&a);
cout << a; // 10 cout << a; // 15 cout << a; // 15
} } }
5
Argumentu nodošana, piemērs
void ByVal(int v) { v += 10; cout << "Within ByVal() : " << v; }
void ByRef(int &v) { v += 15; cout << "Within ByRef() : " << v; }
void ByPtr(int *v) { *v += 20; cout << "Within ByPtr() : " << *v; }
int main() {
int X = 10;
cout << "At startup, within main() : " << X;
cout << "\n\n*Passing by value (copy)\n"; ByVal(X);
cout << "\nAfter ByVal(), within main() : " << X;
cout << "\n*Passing by reference\n"; ByRef(X);
cout << "\nAfter ByRef(), within main() : " << X;
cout << "\n*Passing by pointer\n"; ByPtr(&X);
cout << "\nAfter ByPtr(), within main() : " << X;
6
}
Argumentu nodošana, rezultāts
At startup, within main() : 10

*Passing by value (copy)


Within ByVal() : 20
After calling ByVal(), within main() : 10
*Passing by reference
Within ByRef() : 25
After ByRef(), within main() : 25
*Passing by pointer
Within ByPtr() : 45
After ByPtr(), within main() : 45
7
Starpība starp & un * parametros
#include <iostream>
void f1(int value) { value = 1; }
void f2(int &value) { value = 1; }
void f3(int *value) { *value = 2; }
int main() {
int x = 0;
f1(x); std::cout << x << std::endl; // 0
f2(x); std::cout << x << std::endl; // 1
f3(&x); std::cout << x << std::endl; // 2
} 8
Starpība starp & un *

pēc references (&) pēc norādes (*)


?
by reference by pointer
Var būt NULL? Nē Jā
Var mainīt? Nē Jā
Var izmantot +? Nē Jā

Kas vēl?

9
Kur atstarpe? Kā ir pareizi?
void f1 (int& x) { … }

void f2 (int &x) { … }

void f3 (int & x) { … }

void f4 (int* x) { … }

void f5 (int *x) { … }

void f6 (int * x) { … }
10
Pēc vērtības vai pēc norādes?
#include <iostream>
using namespace std;
int sqr1(int i) { i = i*i; return i; }
void sqr2(int *i) { *i = (*i)*(*i); }
int main() {
int a; cout << "Enter number >"; cin >> a;
cout << "sqr1(): " << sqr1(a) << " a=" << a << endl;
sqr2(&a); cout << "sqr2(): a=" << a << endl;
}
11
Pēc vērtības vai pēc references?
#include <iostream>
#include <string>
using namespace std;
void string_test1(string s) { s = "Changed"; }
void string_test2(string &s) { s = "Changed"; }
int main() {
string str = "My String";
string_test1(str); cout << str << endl; // My String
string_test2(str); cout << str << endl; // Changed
} 12
Masīvi kā funkcijas argumenti — 1.
Funkcijā masīvs vienmēr tiek nodots pēc adreses, tātad
funkcija vienmēr strādā ar masīva oriģinālu,
nevis ar tā kopiju!
Masīvi var būt ļoti lieli, un masīva dublēšana katrai
funkcijai kura to izmanto, var aizņemt daudz laika
un vietas atmiņā.
Tiek nodota norāde uz masīva pirmo elementu un
masīva izmērs.
Lai nodotu daudzdimensiju masīvu, jānodod visi tā izmēri.

13
Masīvi kā funkcijas argumenti — 2.
#include <iostream>
double getAverage(int arr[], int size) {
int i, sum = 0;
for (i = 0; i < size; ++i) { sum += arr[i]; } // ir vajadzīgas?
return double(sum) / size;
}
int main() {
int MyValues[5] = { 1000, 1, 300, 157, 53 };
double avg = getAverage(MyValues, 5);
std::cout << "Average value is: " << avg << std::endl;
}
14
Modifikators const
#include <iostream>
using namespace std;
void constChange_test(const int b) {
b = 15; // kompilēšanas kļūda
}
int main() {
int a = 10; constChange_test(a);
cout << a;
}
15
Rekursīvas funkcijas – 1.
Rekursīva funkcija izsauc pati sevi – tā ir tieša rekursija.
Netieša rekursija: 2 vai vairākas funkcijas izsauc viena otru:
A() izsauc B(), B() savukārt izsauc A().
Ja funkcija izsauc sevi, stekā tiek izveidotas tās parametru
vērtību kopijas un pēc tam vadība tiek nodota pirmajam
funkcijas ķermeņa operatoram. Izsaucot funkciju atkārtoti,
process atkārtojas.
Piemēri – skaitļa faktoriāla rēķināšana, skaitļa kāpināšana
pakāpē, ātra masīva kārtošana (Hoara algoritms), darbs ar
datu struktūrām (piem., ar bināriem kokiem).
16
Rekursīvas funkcijas – 2.
Katrā rekursīvā funkcijā jābūt rekursijas pabeigšanas
nosacījumam, savādāk rekursija būs bezgalīga, kas atvedīs
pie programmas avārijas pabeigšanas.
Programma, kura izmanto rekursiju ar lielu ieliktu
izsaukumu skaitu, var iztērēt visu operatīvo atmiņu, kas
var izraisīt fatālo kļūdu.
Rekursijas “+”: programmas teksta saīsināšana.
Rekursijas “–”: grūtāk atkļūdot, laika un atmiņas patēriņš
funkcijas izsaukumiem un parametru kopiju nodošanai
tajā, steka pārpildīšanas iespēja.
http://cplusplus.com/articles/D2N36Up4/ 17
Rekursijas piemērs – faktoriāls
#include <iostream>
unsigned long long Factorial(unsigned char n)
{
if (n == 0) return 1; else return n*Factorial(n - 1);
}
int main()
{
std::cout << Factorial(20) << std::endl;
// 2432902008176640000
} 18
Neefektīvas rekursijas piemērs – 1.
int fibonacci(int n) {
if (n <= 1) return 1;
else return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
for (int i = 0; i < 45; i++)
std::cout << fibonacci(i) << " ";
}
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 6765
10946 17711 28657 46368 75025 121393 196418 317811 514229 832040
1346269 2178309 3524578 5702887 9227465 14930352 24157817 39088169
63245986 102334155 165580141 267914296 433494437 701408733
1134903170 1836311903 -1323752223 19
Neefektīvas rekursijas piemērs – 2.
Ja ir 45 skaitļi:
Dators: AMD FX-8350 (8 kodoli, 4.30GHz), 8Gb RAM
Laiks: 9 minūtes. 9!
6

5 4

4 3 3 2

3 2 2 1 2 1

2 1 20
Neefektīvas rekursijas piemērs – 3.
Operāciju skaits
1.6E+9
1.4E+9
1.2E+9
1.0E+9
8.0E+8
6.0E+8
4.0E+8
2.0E+8
0.0E+0
0 5 10 15 20 25 30 35 40 45

21
Neefektīvas rekursijas piemērs – 4.
Operāciju skaits
1.6E+9
1.4E+9
1.2E+9
1.0E+9
8.0E+8
6.0E+8
4.0E+8
2.0E+8
0.0E+0
40 41 42 43 44 45

22
Patstāvīgais darbs
Izstrādāt trīs efektīvākus algoritmus Fibonači skaitļu
aprēķināšanai.

Palīgjautājumi:
1. Kā var optimizēt mūsu rekursiju?
2. Kā var uzdevumu risināt ar ciklu palīdzību?
3. Kā var uzreiz atrast 50. skaitli, nerēķinot iepriekšējos?

23
Paldies par uzmanību!
Lietotāju funkcijas, 1. daļa

2021. / 11. aklackovs@edu.riga.lv

You might also like