Глава 10. Функции

В настоящата глава ще се запознаем с финкции и ще научим какво представляват те, както и кои са базовите концепции при работа с тях. Ще научим защо е добра практика да ги използваме, как да ги декларираме и извикваме. Ще се запознаем с параметри и връщана стойност на функция, както и как да използваме тази връщана стойност. Накрая на главата, ще разгледаме утвърдените практики при използване на финкциите.

Какво е "функция"?

До момента установихме, че при писане на код на програма, която решава дадена задача, ни улеснява това, че разделяме задачата на части. Всяка част отговаря за дадено действие и по този начин не само ни е по-лесно да решим задачата, но и значително се подобрява както четимостта на кода, така и проследяването за грешки.

Всяко едно парче код, което изпълнява дадена функционалност и което сме отделили логически, може да изземе функционалността на финкцията. Точно това представляват финкциите – парчета код, които са именувани от нас по определен начин и които могат да бъдат извикани толкова пъти, колкото имаме нужда.

Един функция може да бъде извикан толкова пъти, колкото ние преценим, че ни е нужно за решаване на даден проблем. Това ни спестява повторението на един и същи код няколко пъти, както и намалява възможността да пропуснем грешка при евентуална корекция на въпросния код.

Прости финкции

Простите финкции отговарят за изпълнението на дадено действие, което спомага за решаване на определен проблем. Такива действия могат да бъдат разпечатване на даден низ на конзолата, извършване на някаква проверка, изпълнение на цикъл и други.

Нека разгледаме следния пример за прост функция:

Този функция има задачата да отпечата заглавие, което представлява поредица от символа -. Поради тази причина името му е PrintHeader. Кръглите скоби ( и ) винаги следват името, независимо как сме именували финкцията. По-късно ще разгледаме как трябва да именуваме финкциите, с които работим, а за момента ще отбележим само, че е важно името му да описва действието, което той извършва.

Тялото на финкцията съдържа програмния код, който се намира между къдравите скоби { и }. Тези скоби винаги следват декларацията му и между тях поставяме кода, който решава проблема, описан от името на финкцията.

Защо да използваме финкции?

До тук установихме, че финкциите спомагат за разделянето на обемна задача на по-малки части, което води до по-лесно решаване на въпросното задание. Това прави програмата ни не само по-добре структурирана и лесно четима, но и по-разбираема.

Чрез финкциите избягваме повторението на програмен код. Повтарящият се код е лоша практика, тъй като силно затруднява поддръжката на програмата и води до грешки. Ако дадена част от кода ни присъства в програмата няколко пъти и се наложи да променим нещо, то промените трябва да бъдат направени във всяко едно повторение на въпросния код. Вероятността да пропуснем място, на което трябва да нанесем корекция, е много голяма, което би довело до некоректно поведение на програмата. Това е причината, поради която е добра практика, ако използваме даден фрагмент код повече от веднъж в програмата си, да го дефинираме като отделен функция.

финкциите ни предоставят възможността да използваме даден код няколко пъти. С решаването на все повече и повече задачи ще установите, че използването на вече съществуващи финкции спестява много време и усилия.

Деклариране на финкции

В езика C++ декларираме финкциите в рамките на даден клас, т.е. между отварящата { и затваряща } скоби на класа. Декларирането представлява регистрирането на финкцията в програмата, за да бъде разпознаван в останалата част от нея. Най-добре познатият ни пример за функция е финкцията Main(…), който използваме във всяка една програма, която пишем.

Със следващия пример ще разгледаме задължителните елементи в декларацията на един функция.

  • Тип на връщаната стойност. В случая типа е double, което означава, че функцияът от примера ще върне резултат, който е от тип double. Връщаната стойност може да бъде както int, double, string и т.н., така и void. Ако типът е void, то това означава, че функцияът не връща резултат, а само изпълнява дадена операция.
  • Име на финкцията. Името на финкцията е определено от нас, като не забравяме, че трябва да описва функцията, която е изпълнявана от кода в тялото му. В примера името е GetSquare, което ни указва, че задачата на този функция е да изчисли лицето на квадрат.
  • Списък с параметри. Декларира се между скобите ( и ), които изписваме след името му. Тук изброяваме поредицата от параметри, които финкцията ще използва. Може да присъства само един параметър, няколко такива или да е празен списък. Ако няма параметри, то ще запишем само скобите (). В конкретния пример декларираме параметъра double num.
  • Декларация static в описанието на финкцията. За момента може да приемем, че static се пише винаги, когато се декларира функция, а по-късно, когато се запознаем с обектно-ориентираното програмиране (ООП), ще разберем разликата между статични финкции (споделени за целия клас) и финкции на обект, които работят върху данните на конкретна инстанция на класа (обект).

При деклариране на финкции е важно да спазваме последователността на основните му елементи - първо тип на връщаната стойност, след това име на финкцията и накрая списък от параметри, ограден с кръгли скоби ().

След като сме декларирали финкцията, следва неговата имплементация (тяло). В тялото на финкцията описваме алгоритъма, по който той решава даден проблем, т.е. тялото съдържа кода (програмен блок), който реализира логиката на финкцията. В показания пример изчисляваме лицето на квадрат, а именно num * num.

Когато декларираме дадена променлива в тялото на един функция, я наричаме локална променлива за финкцията. Областта, в която съществува и може да бъде използвана тази променлива, започва от реда, на който сме я декларирали и стига до затварящата къдрава скоба } на тялото на финкцията. Тази област се нарича област на видимост на променливата (variable scope).

Извикване на финкции

Извикването на функция представлява стартирането на изпълнението на кода, който се намира в тялото на финкцията. Това става като изпишем името му, последвано от кръглите скоби () и знака ; за край на реда. Ако функцияът ни изисква входни данни, то те се подават в скобите (), като последователността на фактическите параметри трябва да съвпада с последователността на подадените при декларирането на финкцията. Ето един пример:

Даден функция може да бъде извикан от няколко места в нашата програма. Единият начин е да бъде извикан от главния функция.

функция може да бъде извикан и от тялото на друг функция, който не е главния функция на програмата ни.

Съществува вариант функцияът да бъде извикан от собственото си тяло. Това се нарича рекурсия и можете да намерите повече информация за нея в Wikipedia или да потърсите сами в Интернет.

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

Пример: празна касова бележка

Да се напише функция, който печата празна касова бележка. функцияът трябва да извиква други три финкцията: един за принтиране на заглавието, един за основната част на бележката и един за долната част.

Част от касовата бележка Текст
Горна част CASH RECEIPT
------------------------------
Средна част Charged to____________________
Received by___________________
Долна част ------------------------------
(c) SoftUni

Примерен вход и изход

Вход Изход
(няма) CASH RECEIPT
------------------------------
Charged to____________________
Received by___________________
------------------------------
(c) SoftUni

Насоки и подсказки

Първата ни стъпка е да създадем void функция за принтиране на заглавната част от касовата бележка (header). Нека му дадем смислено име, което описва кратко и ясно задачата му, например PrintReceiptHeader. В тялото му ще напишем кода от примера по-долу:

Съвсем аналогично ще създадем още два финкцията за разпечатване на средната част на бележката (тяло) PrintReceiptBody и за разпечатване на долната част на бележката (footer) PrintReceiptFooter.

След това ще създадем и още един функция, който ще извиква трите финкцията, които написахме до момента един след друг:

Накрая ще извикаме финкцията PrintReceipt от тялото на главния Main функция за нашата програма:

Тестване в Judge системата

Програмата с общо пет финкцията, които се извикват един от друг, е готова и можем да я изпълним и тестваме, след което да я пратим за проверка в judge системата: https://judge.softuni.bg/Contests/Practice/Index/594#0.

финкции с параметри

Много често в практиката, за да бъде решен даден проблем, функцияът, с чиято помощ постигаме това, се нуждае от допълнителна информация, която зависи от задачата му. Именно тази информация представляват параметрите на финкцията и неговото поведение зависи от тях.

Използване на параметри в финкциите

Както отбелязахме по-горе, параметрите освен нула на брой, могат също така да са един или няколко. При декларацията им ги разделяме със запетая. Те могат да бъдат от всеки един тип (int, string и т.н.), а по-долу е показан пример как точно ще бъдат използвани от финкцията.

Декларираме финкцията и списъка му с параметри, след което пишем кода, който той ще изпълнява.

След това извикваме финкцията и му подаваме конкретни стойности:

При декларирането на параметри можем да използваме различни типове променливи, като трябва да внимаване всеки един параметър да има тип и име. Важно е да отбележим, че при последващото извикване на финкцията, трябва да подаваме стойности за параметрите по реда, в който са декларирани самите те. Ако имаме подадени параметри в реда int и след това string, при извикването му не можем да подадем първо стойност за string и след това за int. Единствено можем да разменяме местата на подадените параметри, ако изрично изпишем преди това името на параметъра, както ще забележим малко по-нататък в един от примерите. Това като цяло не е добра практика!

Нека разгледаме примера за декларация на функция, който има няколко параметъра от различен тип.

Пример: знак на цяло число

Да се създаде функция, който печата знака на цяло число n.

Примерен вход и изход

Вход Изход
2 The number 2 is positive.
-5 The number -5 is negative.
0 The number 0 is zero.

Насоки и подсказки

Първата ни стъпка е създаването на функция и даването му на описателно име, например PrintSign. Този функция ще има само един параметър от тип int.

Следващата ни стъпка е имплементирането на логиката, по която програмата ни ще проверява какъв точно е знакът на числото. От примерите виждаме, че има три случая - числото е по-голямо от нула, равно на нула или по-малко от нула, което означава, че ще направим три проверки в тялото на финкцията.

Следващата ни стъпка е да прочетем входното число и да извикаме новия функция от тялото на Main финкцията.

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#1.

Незадължителни параметри

Езикът C++ поддържа използването на незадължителни параметри. Те позволяват пропускането на параметри при извикването на финкцията. Декларирането им става чрез осигуряване на стойност по подразбиране в описанието на съответния параметър.

Следващият пример онагледява употребата на незадължителните параметри:

Показаният функция PrintNumbers може да бъде извикан по няколко начина:

Пример: принтиране на триъгълник

Да се създаде функция, който принтира триъгълник, както е показано в примерите.

Примерен вход и изход

Вход Изход Вход Изход
3 1
1 2
1 2 3
1 2
1
4 1
1 2
1 2 3
1 2 3 4
1 2 3
1 2
1

Насоки и подсказки

Преди да създадем функция за принтиране на един ред с дадени начало и край, прочитаме входното число от конзолата. След това избираме смислено име за финкцията, което описва целта му, например PrintLine, и го имплементираме.

От задачите за рисуване на конзолата си спомняме, че е добра практика да разделяме фигурата на няколко части. За наше улеснение ще разделим триъгълника на три части - горна, средна линия и долна.

Следващата ни стъпка е с цикъл да разпечатаме горната половина от триъгълника:

След това разпечатваме средната линия:

Накрая разпечатваме долната част от триъгълника, като този път стъпката на цикъла намалява.

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#2.

Пример: рисуване на запълнен квадрат

Да се нарисува на конзолата запълнен квадрат със страна n, както е показно в примерите.

Примерен вход и изход

Вход Изход Вход Изход
4 --------
-\/\/\/-
-\/\/\/-
--------
5 ----------
-\/\/\/\/-
-\/\/\/\/-
-\/\/\/\/-
----------

Насоки и подсказки

Първата ни стъпка е да прочетем входа от конзолата. След това трябва да създадем функция, който ще принтира първия и последен ред, тъй като те са еднакви. Нека не забравяме, че трябва да му дадем описателно име и да му зададем като параметър дължината на страната. Ще използваме конструктора new string.

Следващата ни стъпка е да създадем функция, който ще рисува на конзолата средните редове. Отново задаваме описателно име, например PrintMiddleRow.

Накрая извикваме създадените финкции в главния функция Main() на програмата, за да нарисуваме целия квадрат:

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#3.

Връщане на резултат от функция

До момента разгледахме финкции, които извършват дадено действие, например отпечатване на даден текст, число или фигура на конзолата. Освен този тип финкции, съществуват и такива, които могат да връщат някакъв резултат. Именно тези финкции ще разгледаме в следващите редове.

Типове на връщаната от финкцията стойност

До сега разглеждахме примери, в които при декларация на финкции използвахме ключовата дума void, която указва, че функцияът не връща резултат, а изпълнява определено действие.

Ако заместим void с тип на променлива, то това ще укаже на програмата, че финкцията трябва да върне някаква стойност от указания тип. Тази върната стойност може да бъде от всякакъв тип – int, string, double и т.н.

За да върне един функция резултат е нужно да внимаваме да напишем очаквания тип на резултата при декларацията на финкцията на мястото на void.

Важно е да отбележим, че резултатът, който се връща от финкцията, може да е от тип, съвместим с типа на връщаната стойност на финкцията. Например, ако декларираният тип на връщаната стойност е double, то можем да върнем резултат от тип int.

Оператор return

За да получим резултат от финкцията, на помощ идва операторът return. Той трябва да бъде използван в тялото на финкцията и указва на програмата да спре изпълнението му и да върне на извиквача на финкцията определена стойност, която се определя от израза след въпросния оператор return.

В примера по-долу имаме функция, който чете две имена от конзолата, съединява ги и ги връща като резултат. Връщаната стойност е от тип string:

Операторът return може да бъде използван и във void финкции. Тогава самият функция ще спре изпълнението си, без да връща никаква стойност, а след него не трябва да има израз, който да бъде върнат. В този случай употребата на return е единствено за излизане от финкцията.

Има случаи, в които return може да бъде извикван от няколко места в финкцията, но само ако има определени входни условия.

В примера по-долу имаме функция, който сравнява две числа и връща резултат съответно -1, 0 или 1 според това дали първият аргумент е по-малък, равен или по-голям от втория аргумент, подаден на функцията. функцияът използва ключо-вата дума return на три различни места, за да върне три различни стойности според логиката на сравненията на числата:

Кодът след return е недостъпен

След return оператора, в текущия блок, не трябва да има други редове код, тъй като тогава Visual Studio ще покаже предупреждение, съобщавайки ни, че е засякъл код, който не може да бъде достъпен:

В програмирането не може да има два пъти оператор return един след друг, защото изпълнението на първия няма да позволи да се изпълни вторият. Понякога програмистите се шегуват с фразата “пиши return; return; и да си ходим”, за да обяснят, че логиката на програмата е объркана.

Употреба на връщаната от финкцията стойност

След като даден функция е изпълнен и върне стойност, то тази стойност може да се използва по няколко начина.

Първият е да присвоим резултата като стойност на променлива от съвместим тип:

Вторият е резултатът да бъде използван в израз:

Третият е да подадем резултата от работата на финкцията към друг функция:

Пример: пресмятане на лицето на триъгълник

Да се напише функция, който изчислява лицето на триъгълник по дадени основа и височина и връща стойността му.

Примерен вход и изход

Вход Изход
3
4
6

Насоки и подсказки

Първата ни стъпка е да прочетем входа. След това създаваме функция, но този път внимаваме при декларацията да подадем коректния тип данни, които искаме финкцията да върне, а именно double.

Следващата ни стъпка е да извикаме новия функция от нашия Main() функция и да запишем върнатата стойност в подходяща променлива.

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#4.

Пример: степен на число

Да се напише функция, който изчислява и връща резултата от повдигането на число на дадена степен.

Примерен вход и изход

Вход Изход Вход Изход
2
8
256 3
4
81

Насоки и подсказки

Първата ни стъпка отново ще е да прочетем входните данни от конзолата. Следващата стъпка е да създадем функция, който ще приема два параметъра (числото и степента) и ще връща като резултат число от тип double.

След като сме направили нужните изчисления, ни остава да разпечатаме резултата в главния функция Main() на програмата.

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#5.

финкции, връщащи няколко стойности

В практиката се срещат случаи, в които се нуждаем даден функция да върне повече от един елемент като резултат. За да е възможен подобен сценарий във Visual Studio и C++ (от C++ 7 нататък) е интегриран стойностният тип ValueTuple, както и литерал от тип ValueTuple. Накратко типът ValueTuple представлява съвкупност от две стойности, позволяващи временното съхранение на множество стойности. Стойностите биват съхранявани в променливи (полета - какво са полета, ще разгледаме на по-късен етап) от съответните типове. Въпреки, че типът Tuple съществува и преди C++ 7, той не е добре поддържан от езика в предишните му версии и е неефективен. Затова в предходните версии на езика C++ елементите в един Tuple са представяни като Item1, Item2 и т.н. и имената на техните променливи (променливите, в които се съхраняват) е било невъзможно да бъдат променяни. В C++ 7 е въведена поддръжка на типа (ValueTuple), което позволява задаване на смислови имена на елементите в един ValueTuple.

Деклариране на ValueTuple

Нека разгледаме примерна декларация на променлива от тип ValueTuple:

var personInfo = (name: "Steeve", age: 27, "Bulgaria");

За улеснение при декларирането използваме ключовата дума var, а в скобите изброяваме имената на желаните стойности, следвани от самите стойности. Нека погледнем и в дебъг режим какво се съдържа в променливата personInfo:

Виждаме, че се състои от няколко полета с имена и стойности, описани при инициализацията на променливата. Забелязваме, че последната променлива е именувана Item3. Това е така, защото по време на инициализацията не сме уточнили име за променливата, в която се пази стойността "Bulgaria". В такъв случай именуването е по подразбиране, т.е. променливите са именувани с Item1, Item2, Item3 и т.н.

функция, връщащ няколко стойности

Следният функция приема за параметри две целочислени числа (x и y) и връща две стойности - резултата от целочислено деление на двете числа и остатъка от делението им:

static (int result, int reminder) Divide(int x, int y)
{
    int result = x / y;
    int reminder = x % y;

    return (result, reminder);
}

Този функция връща резултат от тип ValueTuple, съдържащ две променливи (полета) от тип int, съответно именувани result и reminder. Извикването на финкцията се осъществява по следния начин:

var division = Divide(1, 3);

За да достъпим резултатите, върнати от финкцията, прилагаме точковата нотация към променливата division:

Варианти на финкции

В много езици за програмиране един и същ функция може да е деклариран в няколко варианта с еднакво име и различни параметри. Това е известно с термина “method overloading”. Сега нека разгледаме как се пишат тези overloaded methods.

Сигнатура на финкцията

В програмирането начинът, по който се идентифицира един функция, е чрез двойката елементи от декларацията му – име на финкцията и списък от неговите параметри. Тези два елемента определят неговата спецификация, т. нар. сигнатура на финкцията.

В този пример сигнатурата на финкцията е неговото име (Print), както и неговият параметър (string text).

Ако в програмата ни има финкции с еднакви имена, но с различни сигнатури, то казваме, че имаме варианти на финкции (method overloading).

Варианти на финкции

Както споменахме, ако използваме едно и също име за няколко финкцията с различни сигнатури, то това означава, че имаме варианти на функция. Кодът по-долу показва как три различни финкцията могат да са с едно и също име, но да имат различни сигнатури и да изпълняват различни действия.

Сигнатура и тип на връщаната стойност

Важно е да отбележим, че връщаният тип като резултат на финкцията не е част от сигнатурата му. Ако връщаната стойност беше част от сигнатурата на финкцията, то няма как компилаторът да знае кой функция точно да извика.

Нека разгледаме следния пример - имаме два финкцията с различен тип на връщаната стойност. Въпреки това Visual Studio ни показва, че има грешка, защото сигнатурите и на двата са еднакви. Съответно при опит за извикване на функция с име Print(…), компилаторът не би могъл да прецени кой от двата финкцията да изпълни.

Пример: по-голямата от две стойности

Като входни данни са дадени две стойности от един и същ тип. Стойностите могат да са от тип int, char или string. Да се създаде функция GetMax(), който връща като резултат по-голямата от двете стойности.

Примерен вход и изход

Вход Изход Вход Изход Вход Изход
int
2
16
16 char
a
z
z string
Ivan
Todor
Todor

Насоки и подсказки

За да създадем този функция, първо трябва да създадем три други финкцията с едно и също име и различни сигнатури. Първо създаваме функция, който ще сравнява цели числа.

Следвайки логиката от предходния функция, създаваме такъв със същото име, който обаче ще сравнява символи.

Следващият функция, който трябва да създадем, ще сравнява низове. Тук логиката ще е малко по-различна, тъй като стойностите от тип string не позволяват да бъдат сравнявани чрез операторите < и >. Ще използваме финкцията CompareTo(…), който връща числова стойност: по-голяма от 0 (сравняваният обект е по-голям), по-малка от 0 (сравняваният обект е по-малък) и 0 (при два еднакви обекта).

Последната стъпка е да прочетем входните данни, да използваме подходящи променливи и да извикаме финкцията GetMax() от тялото на финкцията Main().

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#6.

Вложени финкции (локални функции)

Нека разгледаме следния пример:

Какво е локална функция?

Виждаме, че в този код, в главния функция Main() има друг деклариран функция Result(). Такъв вложен функция се нарича локална функция и е нововъведение в C++ 7. Локалните функции могат да се декларират във всеки един друг функция. Когато C++ компилаторът компилира такива функции, те биват превърнати в private финкции. Тъй като разликата между public и private финкции се изучава на по-късен етап, за момента ще отбележим, че private финкциите могат да се използват само в класа, в който са декларирани. Програмите, които пишем на това ниво, използват само един клас, затова и приемаме, че можем да използваме вложените финкции без каквито и да било притеснения.

Защо да използваме локални функции?

С времето и практиката ще открием, че когато пишем код, често се нуждаем от финкции, които бихме използвали само един път, или пък нужният ни функция става твърде дълъг. По-нагоре споменахме, че когато един функция съдържа в себе си прекалено много редове код, то той става труден за поддръжка и четене. В тези случаи на помощ идват локалните функции - те предоставят възможност в даден функция да се декларира друг функция, който ще бъде използван например само един път. Това спомага кода ни да е по-добре подреден и по-лесно четим, което от своя страна спомага за по-бърза корекция на евентуална грешка в кода и намалява възможността за грешки при промени в програмната логика.

Деклариране на локални функции

Нека отново разгледаме примера от по-горе.

В този пример, функцияът Result() е локална функция, тъй като е вложен в финкцията Main(), т.е. Result() е локален за Main(). Това означава, че функцияът Result() може да бъде използван само в финкцията Main(), тъй като е деклариран в него. Единствената разлика между вложените финкции и обикновените финкции е, че вложените финкции не могат да бъдат static. Тъй като дефиницията за static функция се разглежда на по-късен етап, за момента ще приемем, че при декларирането на една локална функция, изписваме единствено типa на връщаната стойност, името на финкцията и списъка му с параметри. В конкретния разглеждан случай, това е double Result(double a, double b).

Локалните функции имат достъп до променливи, които се използват в съдържащия ги функция. Следващият пример демонстрира как се случва това.

Тази особеност на вложените финкции ги прави много удобни помощници при решаването на дадена задача. Те спестяват време и код, които иначе бихме вложили, за да предаваме на вложените финкции параметри и променливи, които се използват в финкциите, в които са вложени.

Именуване на финкции. Добри практики при работа с финкции

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

Именуване на финкции

Когато именуваме даден функция е препоръчително да използваме смислени имена. Тъй като всеки функция отговаря за някаква част от нашия проблем, то при именуването му трябва да вземем предвид действието, което той извършва, т.е. добра практика е името да описва неговата цел.

Задължително е името да започва с главна буква и трябва да е съставено от глагол или от двойка: глагол + съществително име. Форматирането на името става, спазвайки Upper Case Camel конвенцията, т.е. всяка дума, включително първата, започва с главна буква. Кръглите скоби ( и ) винаги следват името му.

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

Няколко примера за коректно именуване на финкции:

  • FindStudent
  • LoadReport
  • Sine

Няколко примера за лошо именуване на финкции:

  • Method1
  • DoSomething
  • HandleStuff
  • SampleMethod
  • DirtyHack

Ако не можем да измислим подходящо име, то най-вероятно функцияът решава повече от една задача или няма ясно дефинирана цел и тогава трябва да помислим как да го разделим на няколко отделни финкцията.

Именуване на параметрите на финкциите

При именуването на параметрите на финкцията важат почти същите правила, както и при самите финкции. Разликите тук са, че за имената на параметрите е добре да използваме съществително име или двойка от прилагателно и съществително име, както и че при именуване на параметрите се спазва lowerCamelCase конвенцията, т.е. всички думи без първата започват с главна буква. Трябва да отбележим, че е добра практика името на параметъра да указва каква е мерната единица, която се използва при работа с него.

Няколко примера за коректно именуване на параметри на финкции:

  • firstName
  • report
  • speedKmH
  • usersList
  • fontSizeInPixels
  • font

Няколко примера за некоректно именуване на параметри:

  • p
  • p1
  • p2
  • populate
  • LastName
  • last_name

Добри практики при работа с финкции

Нека отново припомним, че един функция трябва да изпълнява само една точно определена задача. Ако това не може да бъде постигнато, тогава трябва да помислим как да разделим финкцията на няколко отделни такива. Както казахме, името на финкцията трябва точно и ясно да описва неговата цел. Друга добра практика в програмирането е да избягваме финкции, по-дълги от екрана ни (приблизително). Ако все пак кода стане много обемен, то е препоръчително финкцията да се раздели на няколко по-кратки, както в примера по-долу.

Структура и форматиране на кода

При писането на финкции трябва да внимаваме да спазваме коректна индентация (отместване по-навътре на блокове от кода).

Пример за правилно форматиран C++ код:

Пример за некоректно форматиран C++ код:

Когато заглавният ред на финкцията е твърде дълъг, се препоръчва той да се раздели на няколко реда, като всеки ред след първия се отмества с две табулации надясно (за по-добра четимост):

Друга добра практика при писане на код е да оставяме празен ред между финкциите, след циклите и условните конструкции. Също така, опитвайте да избягвате да пишете дълги редове и сложни изрази. С времето ще установите, че това подобрява четимостта на кода и спестява време.

Препоръчваме винаги да се използват къдрави скоби за тялото на проверки и цикли. Скобите не само подобряват четимостта, но и намалят възможността да бъде допусната грешка и програмата ни да се държи некоректно.

Какво научихме от тази глава?

В тази глава се запознахме с базовите концепции при работа с финкции:

  • Научихме, че целта на финкциите е да разделят големи програми с много редове код на по-малки и кратки задачи.
  • Запознахме се със структурата на финкциите, как да ги декларираме и извикваме по тяхното име.
  • Разгледахме примери за финкции с параметри и как да ги използваме в нашата програма.
  • Научихме какво представляват сигнатурата и връщаната стойност на финкцията, както и каква е функцията на оператора return в финкциите.
  • Запознахме се с добрите практики при работа с финкции, как да именуваме финкциите и техните параметри, как да форматираме кода и други.

Упражнения

За да затвърдим работата с финкции, ще решим няколко задачи. В тях се изисква да напишете функция с определена функционалност и след това да го извикате като му подадете данни, прочетени от конзолата, точно както е показано в примерния вход и изход.

Задача: "Hello, Име!"

Да се напише функция, който получава като параметър име и принтира на конзолата "Hello, \!".

Примерен вход и изход

Вход Изход
Peter Hello, Peter!

Насоки и подсказки

Дефинирайте функция PrintName(string name) и го имплементирайте, след което в главната програма прочетете от конзолата име на човек и извикайте финкцията като му подадете прочетеното име.

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#7.

Задача: по-малко число

Да се създаде функция GetMin(int a, int b), който връща по-малкото от две числа. Да се напише програма, която чете като входни данни от конзолата три числа и печата най-малкото от тях. Да се използва финкцията GetMin(…), който е вече създаден.

Примерен вход и изход

Вход Изход Вход Изход
1
2
3
1 -100
-101
-102
-102

Насоки и подсказки

Дефинирайте функция GetMin(int a, int b) и го имплементирайте, след което го извикайте от главната програма както е показано по-долу. За да намерите минимума на три числа, намерете първо минимума на първите две от тях и след това минимума на резултата и третото число:

var min = GetMin(GetMin(num1, num2), num3);

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#8.

Задача: повтаряне на низ

Да се напише функция RepeatString(str, count), който получава като параметри променлива от тип string и цяло число n и връща низа, повторен n пъти. След това резултатът да се отпечата на конзолата.

Примерен вход и изход

Вход Изход Вход Изход
str
2
strstr roki
6
rokirokirokirokirokiroki

Насоки и подсказки

Допишете финкцията по-долу като добавите съединяването входния низ към резултата в цикъла:

Имайте предвид, че в езика C++ съединяването на низове в цикъл води до лоша производителност и не се препоръчва. Потърсете и пробвайте по-ефективни решения тук: https://stackoverflow.com/questions/411752.

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#9.

Задача: n-та цифра

Да се напише функция FindNthDigit(number, index), който получава число и индекс N като параметри и печата N-тата цифра на числото (като се брои от дясно на ляво, започвайки от 1). След това, резултатът да се отпечата на конзолата.

Примерен вход и изход

Вход Изход Вход Изход Вход Изход
83746
2
4 93847837
6
8 2435
4
2

Насоки и подсказки

За да изпълним алгоритъма, ще използваме while цикъл, докато дадено число не стане 0. На всяка итерация на while цикъла ще проверяваме дали настоящият индекс на цифрата не отговаря на индекса, който търсим. Ако отговаря, ще върнем като резултат цифрата на индекса (number % 10). Ако не отговаря, ще премахнем последната цифра на числото (number = number / 10). Трябва да следим коя цифра проверяваме по индекс (от дясно на ляво, започвайки от 1). Когато намерим цифрата, ще върнем индекса.

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#10.

Задача: число към бройна система

Да се напише функция IntegerToBase(number, toBase), който получава като параметри цяло число и основа на бройна система и връща входното число, конвертирано към посочената бройна система. След това, резултатът да се отпечата на конзолата. Входното число винаги ще е в бройна система 10, а параметърът за основа ще е между 2 и 10.

Примерен вход и изход

Вход Изход Вход Изход Вход Изход
3
2
11 4
4
10 9
7
12

Насоки и подсказки

За да решим задачата, ще декларираме стрингова променлива, в която ще пазим резултата. След това трябва да изпълним следните изчисления, нужни за конвертиране на числото.

  • Изчисляваме остатъка от числото, разделено на основата.
  • Вмъкваме остатъка от числото в началото на низа, представящ резултата.
  • Разделяме числото на основата.
  • Повтаряме алгоритъма, докато входното число не стане 0.

Допишете липсващата логика в финкцията по-долу:

static string IntegerToBase(int number, int toBase) {
    string result = "";
    while (number != 0) {
        // Implement the missing conversion logic
    }
    return result;
}

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#11.

Задача: известия

Да се напише програма, която прочита цяло число n и на следващите редове въвежда n съобщения (като за всяко съобщение се прочитат по няколко реда). Всяко съобщение започва с messageType: success, warning или error:

  • Когато messageType е success да се четат operation + message (всяко на отделен ред).
  • Когато messageType е warning да се чете само message.
  • Когато messageType е error да се четат operation + message + errorCode (всяко на отделен ред).

На конзолата да се отпечата всяко прочетено съобщение, форматирано в зависимост от неговия messageType. Като след заглавния ред за всяко съобщение да се отпечатат толкова на брой символа =, колкото е дълъг съответният заглавен ред и да се сложи по един празен ред след всяко съобщение (за по-детайлно разбиране погледнете примерите).

Задачата да се реши с дефиниране на четири финкцията: ShowSuccessMessage(), ShowWarningMessage(), ShowErrorMessage() и ReadAndProcessMessage(), като само последният функция да се извиква от главния Main() функция:

Примерен вход и изход

Вход Изход
4
error
credit card purchase
Invalid customer address
500
warning
Email not confirmed
success
user registration
User registered successfully
warning
Customer has not email assigned
Error: Failed to execute credit card purchase.
==============================================
Reason: Invalid customer address.
Error code: 500.

Warning: Email not confirmed.
=============================

Successfully executed user registration.
========================================
User registered successfully.

Warning: Customer has not email assigned.
=========================================

Насоки и подсказки

Дефинирайте и имплементирайте посочените четири финкцията.

В ReadAndProcessMessage() прочетете типа съобщение от конзолата и според прочетения тип прочетете останалите данни (още един два или три реда). След това извикайте съответния функция за печатане на съответния тип съобщение.

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#12.

Задача: числа към думи

Да се напише функция Letterize(number), който прочита цяло число и го разпечатва с думи на английски език според условията по-долу:

  • Да се отпечатат с думи стотиците, десетиците и единиците (и евентуални минус) според правилата на английския език.
  • Ако числото е по-голямо от 999, трябва да се принтира "too large".
  • Ако числото е по-малко от -999, трябва да се принтира "too small".
  • Ако числото е отрицателно, трябва да се принтира "minus" преди него.
  • Ако числото не е съставено от три цифри, не трябва да се принтира.

Примерен вход и изход

Вход Изход Вход Изход
3
999
-420
1020
nine-hundred and ninety nine
minus four-hundred and twenty
too large
2
15
350
three-hundred and fifty
Вход Изход Вход Изход
4
311
418
509
-9945
three-hundred and eleven
four-hundred and eighteen
five-hundred and nine
too small
2
500
123
five-hundred
one-hundred and twenty three

Насоки и подсказки

Можем първо да отпечатаме стотиците като текст - (числото / 100) % 10, след тях десетиците - (числото / 10) % 10 и накрая единиците - (числото % 10).

Първият специален случай е когато числото е точно закръглено на 100 (напр. 100, 200, 300 и т.н.). В този случай отпечатваме "one-hundred", "two-hundred", "three-hundred" и т.н.

Вторият специален случай е когато числото, формирано от последните две цифри на входното число, е по-малко от 10 (напр. 101, 305, 609 и т.н.). В този случай отпечатваме "one-hundred and one", "three-hundred and five", "six-hundred and nine" и т.н.

Третият специален случай е когато числото, формирано от последните две цифри на входното число, е по-голямо от 10 и по-малко от 20 (напр. 111, 814, 919 и т.н.). В този случай отпечатваме "one-hundred and eleven", "eight-hundred and fourteen", "nine-hundred and nineteen" и т.н.

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#13.

Задача: криптиране на низ

Да се напише функция Encrypt(char letter), който криптира дадена буква по следния начин:

  • Вземат се първата и последна цифра от ASCII кода на буквата и се залепят една за друга в низ, който ще представя резултата.
  • Към началото на стойността на низа, който представя резултата, се залепя символа, който отговаря на следното условие:
    • ASCII кода на буквата + последната цифра от ASCII кода на буквата.
  • След това към края на стойността на низа, който представя резултата, се залепя символа, който отговаря на следното условие:
    • ASCII кода на буквата - първата цифра от ASCII кода на буквата.
  • функцияът трябва да върне като резултат криптирания низ.

Пример:

  • j → p16i
    • ASCII кодът на j e 106 → Първа цифра - 1, последна цифра - 6.
    • Залепяме първата и последната цифра → 16.
    • Към началото на стойността на низа, който представя резултата, залепяме символа, който се получава от сбора на ASCII кода + последната цифра → 106 + 6 → 112 → p.
    • Към края на стойността на низа, който представя резултата, залепяме символа, който се получава от разликата на ASCII кода - първата цифра → 106 - 1 → 105 → i.

Използвайки финкцията, описан по-горе, да се напише програма, която чете поредица от символи, криптира ги и отпечатва резултата на един ред.

Приемаме, че входните данни винаги ще бъдат валидни. Главният функция трябва да прочита входните данни, подадени от потребителя – цяло число n, следвани от по един символ на всеки от следващите n реда.

Да се криптират символите и да се добавят към криптирания низ. Накрая като резултат трябва да се отпечата криптиран низ от символи като в следващия пример.

Пример:

  • S, o, f, t, U, n, i → V83Kp11nh12ez16sZ85Mn10mn15h

Примерен вход и изход

Вход Изход
7
S
o
f
t
U
n
i
V83Kp11nh12ez16sZ85Mn10mn15h
Вход Изход
7
B
i
r
a
H
a
x
H66<n15hv14qh97XJ72Ah97xx10w

Насоки и подсказки

На променливата от тип string, в която ще се пази стойността на резултата, ще присвоим първоначална стойност string.Empty. Трябва да се завърти цикъл n пъти, като на всяка итерация към променливата, в която пазим стойността на резултата, ще прибавяме криптирания символ.

За да намерим първата и последната цифри от ASCII кода, ще използваме алгоритъма, който използвахме за решаване на задача "Число към бройна система".

Тестване в Judge системата

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/594#14.

results matching ""

    No results matching ""