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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Функциите ни предоставят възможността да използваме даден код няколко пъти. С решаването на все повече и повече задачи ще установите, че използването на вече съществуващи функции спестява много време и усилия. Нещо повече - всеки път, когато пишем програма на C++, използваме главната функция main().

Деклариране и дефиниция на функции

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

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

Деклариране

Нека разгледаме следния пример за деклариране на функция, намираща лицето на квадрат по зададена страна num.

Следващият ред е еквивалентен на горния, като това ще коментираме по-надолу.

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

Декларирането на една функция се осъществява след using namespace std; и преди главната функция main(). В примера тук са показани декларациите на функциите getSquare(), multiply() и printHeader().

Задължителните елементи при декларирането на функция:

  • Тип на връщаната стойност. В случая типът е double, което заявява, че функцията getSquare ще върне резултат, който е от тип double. Връщаната стойност може да бъде както int, double, char и т.н., така и void. Ако типът е void, то това означава, че функцията не връща резултат, а само изпълнява дадена операция.
  • Име на функцията. Името на финкцията е определено от нас, като не забравяме, че трябва да описва операцията, която е изпълнявана от кода в тялото й. В примера името е getSquare, което ни указва, че задачата на тази функция е да изчисли лицето на квадрат.
  • Списък с параметри. Декларира се между скобите ( и ), които изписваме след името на функцията. Тук изброяваме поредицата от параметри, които тя ще използва, като се подразбира, че могат да бъдат от различен тип (int, double, char и т.н.). Интересното тук е, че може (дори обикновено така се прави) да се запише само типът на използваните параметри (не се идентифицират). Това е допустимо само при декларирането на функция. Може да присъства само един параметър, няколко такива или да е празен списък. Ако няма параметри, то ще запишем единствено скобите (). В конкретния пример декларираме параметъра double num.
  • Накрая поставяме точка и запетая ;.
Задължително се поставя знакът "точка и запетая" ; в края на декларирането на дадена фукция.

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

Дефиниция

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

Дефинициите на функции се правят след главната функция main(). (За да се придобие по-ясна представа, след следващата подтема Извикване на функции ще реализираме цял програмен код с всички елементи на една функция.)

Задължителните елементи при дефинирането на функция:

  • Тип на връщаната стойност. Типът трябва да бъде съобразен с типа на връцаната стойност при декларацията на функцията, т.е. той трябва да е същият. Ако при декларацията е записано, че функцията ще връща целочислена стойност int, то и при дефинициата ще бъде записано int.
  • Име на функция. Същото като при декларацията на функцията.
  • Списък с параметри. Декларира се между скобите ( и ). Отново се съобразяваме със записаното при декларацията на функцията. Ако има повече от един параметър, то ги записваме в същия ред, както при деларацията. Например ако сме били декларирали function(double, int, double, char), то при дефиницията ще подредим параметрите по типове данни съответно double, int, double, char. Освен това тук задължително трябва да се идентифицират въпросните параметри, за разлика от декларацията, където можеше да се пропусне идетификция. Следователно списъкът с параметри ще изглежда (double var1, int var2, double var3, char var4) (примерна идентификация на параметрите).
  • Имплементацията (тялото). Записва се в областта, отделена от скобите { и }. Тази област се създава след списъка с параметри (вж. последния дотук програмен фрагмент) Ако функцията връща стойност, то в края на имплементацията ще допабим оператор return, а след него това, което ще връщаме, и знак ; За връщащи и невръщащи стойности функции ще говорим по-нататък.
  • Тук не се поставя точка и запетая ;, както при декларирането.

Видове променливи

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

Параметри на функции

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

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

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

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

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

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

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

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

Обобщение

Дотук подробно разгледахме елементите на една функция в езика C++, както и разпределието им в кода на прграмата. Оказва се, методиката за работа с функции, която разгледахме, е стандартна, но не е единствената. Ще разгледаме два различни подхода, като единия го разгледахме подробно. Нека напишем програма printSentence(), използваща функция за извеждане на изречението I am learning functions.. Използваме познатия алгоритъм:

  • Декларираме функцията printSentence() преди функцията main.
  • Дефинираме функцията printSentence() след функцията main.
  • Извикваме функцията printSentence() в главната main() (може и в друга функция, при други условия).
// Първи подход
#include <iostream>    
using namespace std;
// Декларираме
void printSentence();  
int main() {
    // Извикваме
    printSentence();   
    return 0;
}
// Дефинираме
void printSentence() {              
    cout << "I am learning functions.";
}

Можете да тествате примера онлайн: https://repl.it/@Denis77/printSentence1 .

Какъв е другият подход? Нека видим следната програма, решаваща същата задача.

// Втори подход
#include <iostream>    
using namespace std;
// Декрарираме и
// дефинираме
// едновременно
void printSentence() {                     
    cout << "I am learning functions.";    
}                                        
int main() {
    // Извикваме
    printSentence();
    return 0;
}

Можете да тествате примера онлайн: https://repl.it/@Denis77/printSentence2 .

Оказва се, че резултатът от двете програми е еднакъв. Разликата е само в кода - във втория код декларирането и дефиницията на функцията са слeти в едно (комбинирани), като това се случва преди да се декларира главната функция main(). Факт е, че вторият подход изисква по-малко писане на код, но в практиката често се използва първия подход, тъй като дава по-добра четимост на програмата, както и поради други причини.

В настоящата книга се използва сподхода отделените декларация и дефиниция. Това е и препоръчителният подход.

Прототип на функция

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

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

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

Част от касовата бележка Текст
Горна част 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/1374#0.

Функции с параметри

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

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

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

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

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

При декларирането на параметри можем да използваме различни типове променливи, като трябва да внимаваме всеки един параметър да има тип ( и име при дефинициата). Важно е да отбележим, че при последващото извикване на функцията, трябва да подаваме стойности за параметрите по реда, в който са декларирани самите те. Ако имаме подадени параметри в реда int и след това char, при извикването му не можем да подадем първо стойност за char и след това за 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/1374#1.

Прави ни впечатление това, че промеливата от main(), която предаваме като параметър на printSign(), е с име n, а във функцията printSign() се казва a. Друго нещо е, че можехме във въпросната функция да използваме параметър, който носи същото име като промеливата в main(), т.е. n.

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

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

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

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

Вход Изход Вход Изход
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/1374#2.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Оператор return

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

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

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

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

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

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

В програмирането не може да има два пъти оператор return един след друг, защото изпълнението на първия няма да позволи да се изпълни вторият.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Задачата може да се реши, като се използва функцията pow() от библиотеката cmath. Ако тя се използва, препоръчително e идентификаторът pow на параметъра да бъде променен, за да не се получи объркване в програмата. След като сме направили нужните изчисления, ни остава да разпечатаме резултата в главната функция main() на програмата.

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

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

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

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

Структури

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

Променливите name, age, sex и grade са елементи на структурата student. Декларацията на student се прави извън и преди главната функция main.Тялото на структурата (там, където декларираме елемените й) се намира между скобите { и }, като след затварящата скоба } задължително се поставя точка и запетая ;. А как се ползва в програмата?

Това, което правим в main(), е деклариране на променлива pupil от структурен тип данни student, който пък, от своя страна, беше деклариран преди main(). Въвежданите след това стойности всъщност са елементите на pupil. До тях достигаме, като изпишем името на декларираната от нас променлива от структурен тип данни pupil, след това поставим точка . и накрая изпишем желания от нас елемент (например name). Така за да въведем името на дадения ученик, използваме pupil.name.

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

Структури и функции

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

А дефиницията:

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

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

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

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

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

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

В този пример сигнатурата на функцията е неговото име (print), както и нейният параметър (char symbol).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

В езика за програмиране C++ не съществува понятието локална функция!

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

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

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

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

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

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

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

  • findStudent
  • loadReport
  • sine

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

  • function1
  • doSomething
  • handleStuff
  • simpleFunction
  • dirtyHack

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Упражнения

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

int min = getMin(getMin(num1, num2), num3);

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

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

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

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

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

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

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

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

#include<iostream>
using namespace std;
int main() {
    string str1, str2;
    cin >> str1;
    cin >> str2;
    str1 += str2;
    cout << str1;
  return 0;
}

Можете да тествате примера онлайн: https://repl.it/@Denis77/workWithTypeString .

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

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

Тествайте решението си тук: https://judge.softuni.bg/Contests/Practice/Index/1374#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/1374#10.

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

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

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

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

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

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

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

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

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/1374#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/1374#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
fifteen
three-hundred and fifty
Вход Изход Вход Изход
4
311
418
509
-9945
three-hundred and eleven
four-hundred and eighteen
five-hundred and nine
too small
3
500
123
9
five-hundred
one-hundred and twenty three
nine

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

Можем първо да отпечатаме стотиците като текст - (числото / 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/1374#13.

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

Да се напише функция еncrypt(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. След това ще "залепим" всички тези променливи като при задача "Повтаряне на низ". Така създаваме крайната променливата result от тип string и я извеждаме. Трябва да се завърти цикъл n пъти, като на всяка итерация към променливата result ще прибавяме криптирания символ.

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

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

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

results matching ""

    No results matching ""