Генеричко програмирање
У рачунарству, генеричко програмирање је техника која дозвољава да једна променљива може да чува различите типове података (такозвана вишеобличност или полиморфизам) све док су задовољени одређени услови као што су подкласа и правилна декларација. Дакле, дозвољава нам стварање функција и класа које не зависе од типа. Пример: STL вектор, листа, стек итд. На пример, ако се жели направити листа користећи генеричност, могућа декларација би била List <Т>, где Т представља врсту података. Када се начини примерак може се направити List<Integer> или List<Animal>. Према листи се затим поступа као према листи оног типа података који је наведен. Од објектно оријентисаних програмских језика, програмски језици C++, D, BETA, Eiffel, Ada i neke verzije Jave (1.5 и новије) подржавају генеричке типове података. VB.NET i C# су почели да подржавају генеричке типове од верзије .NET 2.0. Шаблони – основа за генеричко програмирање: шаблон је у ствари формула или рецепт за стварање класе или функције. Постоје функцијски шаблони и шаблони класе.
Под појмом генеричког програмирања подразумева се израда програмског кода који се у непромењеном облику може применити на различите типове података.
У C језику се за генеричко програмирање користе предпроцесорске макро наредбе, нпр:
#define Kvadrat(x) x*x
Ово је макро наредба којом се део кода који је у заградама означен са x замењује са x*x. Макро наредбе нису функције. Оне се реализују у току компајлирања лексичком заменом текста.
Пример:
Макро наредба: | Извршава се као: |
---|---|
инт и;
Квадрат(и) * 3 |
инт и;
и*и*3 |
флоат x;
x*Квадрат(6.7) |
флоат x;
x*6.7*6.7 |
флоат x;
x=Квадрат(x+6.7) |
флоат x;
x=x+6.7*x+6.7 //грешка |
У прва два примера показано је како се иста макро наредба користи за типове int и float. Грешка у трећем примеру, се може избећи тако да се „аргументи“ макро наредбе напишу у заградама тј.
#define Kvadrat(x) (x)*(x)
Проблем код макроа је да је код макроа непрегледан и тешко је уочити compile – грешке, будући да компајлер само код макроа налепи уместо позива и могуће су семантичке грешке које је врло тешко открити
То је један од разлога због којег у C++-у имамо генеричке (темплате) функције (функцијске шаблоне). C++ је строго типизирани језик, тј при дефиницији функције морамо навести типове параметара. C++ допушта коришћење преоптерећених (overloaded) функција. То су функције које имају једнака имена (и припадају истом досегу – namespaceu) али различиту листу параметара. Преоптерећивање функције има неколико недостатака: уколико желимо нешто променити у коду функције морамо то учинити на пуно места, па се повећава и могућност грешке, не можемо предвидети на којим ће све типовима корисник хтети да позове функцију.
У C++ се исти ефекат као у C-у са #define може постићи коришћењем дефинисањем inline функција:
inline int Kvadrat(int x) {
return x*x;
}
inline float Kvadrat(float x) {
return x*x;
}
inline double Kvadrat(double x) {
return x*x;
}
Препорука: боље је да се користе inline функције него макро наредбе, јер се тиме осигурава боља контрола типова и избегава могућност појаве претходно описане грешке. Приметимо да сва три типа дефиниције функције Квадрат имају исти облик:
T Kvadrat(T x) {
return x*x;
}
где Т може бити int, float или double. Ова дефиниција има генерички облик. У C++ језику се може вршити генеричко дефинисање функција, на начин да се испред дефиниције или декларације функције, помоћу кључне речи template, означи да Т представља „генерички тип“.
template <class T> T Kvadrat (T x) {
return x*x;
}
или
template <typename T> T Kvadrat(T x) {
return x*x;
}
Разлика у ова два записа је у употреби кључне речи class или typename, али значење је потпуно исто. Извршење позива функције се мора обавити са стварним типовима:
int x,y;
y=Kvadrat<int>(x);
Погодно је да се класе и функције могу писати генерички, параметризовано типовима података. Такве генеричке класе и функције називају се у језику C++ шаблонима (темплатес). Из шаблона се генеришу стварне класе, односно функције, за конкретне типове. Генерички механизам[2] је у потпуности статички -субституција параметара је у време превођења. Функцијски шаблон се не користи док компајлер не наиђе на позив генеричке функције. Тек тада се ствара и преводи нова варијанта функције у зависности о типу на којем је функција позвана. Тај процес стварања нове конкретне варијанте функције из функцијског шаблона назива се инстанцијација. Типови се код функцијских шаблона препознају аутоматски. Због тога се и дефиниција и имплементација таквих функција пише у .х датотеку. Компајлер тек у тренутку позива функције може одредити начин како се преводи генеричка функција, стога су генеричке функције по употреби еквивалентне макро наредбама. Да би се могло извршити компајлирање програма у којем се користи генеричка функција, компајлеру мора бити на располагању потпуна дефиниција функције, а не само декларација функције као што је случај код употребе регуларних функција.
Генерички тип Т се може користити унутар функције за ознаку типа локалне променљиве:
template <class T> T Kvadrat(T x){
T result=x*x;
return result;
}
int main() {
int i=5,k;
float a=10.9,b;
k=Kvadrat<int>(i);
b=Kvadrat<float>(a);
cout <<k<<endl;
cout<<b<<endl;
return 0;
}
25
118.81
Не мора се увек, при позиву функције, специфицирати генерички тип ако се из типова аргумената недвосмислено може закључити који се параметарски тип користи.
int i=5,k;
float a=10.9,b;
k=Kvadrat(i);
b=Kvadrat(a);
cout <<k<<endl;
cout<<b<<endl;
Препорука је ипак, да се увек експлицитно специфира параметарски тип. Могу се дефинисати генеричке функције са више генеричких типова. Пример:
template <class T, class U> t GetMin (T a, U b){
return (a<b?a:b);
}
дефинише функцију са два аргумената којима се придружују генерички типови Т и У. Могуће је извршити позив функције на следећи начин: или још једноставније
i=GetMin (j,l);
Иако су ј и л различитог типа, компајлер сам врши конверзије интегралних типова.
Претпоставимо да радимо са неким скупом објеката које желимо да некако организујемо. У том случају користе се класе које се означавају као колекције (колекција=збирка, скупљање). Оне дефинишу објекте који се користе за организацију других објеката одговарајућег типа у колекције на одговарајући начин. Могуће је у колекцију додавати објекте различитог типа, али онда имамо проблем утврђивања типа објекта приликом његовог коришћења. Ако покушамо да неки објекат из ове колекције употребимо као објекат неке друге класе, наилазимо на проблем. Нпр. нека смо омогућили да колекцији могу да се додају објекти класа Tacka, Duz и String. Ако, примера ради, покушамо да употребимо неки објекат класе Duz или String као објекат класе Tacka, програм неће бити преведен. Да би се избегли овакви проблеми, треба дефинисати колекцију тако да се онемогући случајно додавање објекта погрешног типа. Један начин за решење овог проблема је дефинисати засебне колекције које могу да садрже само објекте једног типа. Али онда се јавља други проблем – имаћемо за сваки тип објекта по једну класу која дефинише одговарајућу колекцију. Други, ефикаснији начин, су управо генерички типови. Они омогућују да се дефинише само једна класа којом је колекција представљена. Сви објекти у колекцији су истог типа, али сада постоји могућност да тај тип може бити произвољан. На овај начин, имамо једну колекцију која може да се понаша и као колекција тачака, и као колекција дужи и као колекција стрингова итд. Генерички тип је класни или интерфејсни тип који има један или више параметара. Дефиниција конкретне класе или интерфејса из генеричког типа врши се додавањем конкретног аргумента за сваки од параметара генеричког типа.
public class LinkedList<T>{
…
}
Параметар Т се употребљава у дефиницији метода и поља где постоји зависност од типа овог аргумента. Појаве овог параметра у дефиницији генеричког типа се означавају као променљиве типа, јер ће бити замењене конкретном вредношћу тог типа, на исти начин као што параметри метода бивају замењени аргументум који се проследи методу. Креирање класе из датог генеричког типа врши се навођењем одговарајућег аргумента за параметер унутар <>. Сва појављивања параметра Т у дефиницији генеричког типа биће замењена датим аргументум. Генерички тип на тај начин дефинише скуп типова, добијених за различите вредности параметра типа Т. Као аргумент за параметер Т може се проследити само класни или интерфејсни тип. Примитивни типови не могу да се користе. Уместо њих користимо одговарајуће класе које их енкапсулирају. Генерички тип LinkedList<T> који чува double вредности:
LinkedList<Double> temp= new LinkedList<Double>();
temp.addItem(10.8)
Пошто је тип параметра Double компајлер аутоматски умеће конверзију вредности 10.8 из double у Double. Можемо да дефинишемо генерички тип који дефинише скуп класа које обухватају пар објеката произвољног типа. У том случају дефинишемо генерички тип са два параметра:
public class Par <TipKljuca, TipVrednosti> {
public Par(TipKljuca kljuc, TipVrednosti vr) {
this.kljuc = kljuc;
this.vr = vr;
}
…
private TipKljuca kljuc;
private TipVrednosti vr;
}
Употреба:
Par <String,String>unos =new Par<String,String>(“Milan”, “4445555”);
Област важења параметарског типа је цела генеричка класа, искључујући статичке чланице класе. Статичка поља не могу бити дефинисана променљивом типа, нити статички методи могу да имају параметре или повратне вредности које су параметарског типа. Уколико генеричка класа садржи неко статичко поље, сваки тип произведен из датог генеричког типа, имаће своју копију статичког поља. Нека генеричка класа LinkedList<> садржи статичко поље count за бројање креираних објеката. Свака класа добијена из генеричке за конкретан аргумент типа имаће своју копију овог поља. На тај начин,
count у класи LinkedList<String> броји креиране објекте ове класе
count у класи LinkedList<Point> броји креиране објекте ове класе итд.
Генерички метод
уредиМожемо да дефинишемо метод са својим независним скупом од једног или више параметара. Такав метод назива се параметарски метод или генерички метод. Генерички метод може да постоји и унутар обичне класе.
public static <T> void ispisiSve(LinkedList lista){
for(T objekat: lista)
System.out.println(objekat);
}
<Т> испред кога се наводи public или static кључна реч представља параметарски тип (или листу типова) за генерички метод. Овде имамо само један параметарски тип за метод ispisiSve, али их може бити више. Листа параметарских типова се увек појављује између угластих заграда и наводи се иза квалификатора приступа, а пре повратне вредности метода. Аргумент типа ће бити одређен на основу параметра типа који је прослеђен при позиву метода.
Низови и параметризовани типови
уредиНизови елемената типа добијеног од генеричког типа нису допуштени!
Колекције
уредиJava system колекција је скуп генеричких типова које користимо за креирање колекцијских класа. Колекцијска класа је класа која организује скуп објеката датог типа на одређен начин (нпр. ланчане листе, стек,….). Највећи број ових класа које чине систем колекција дефинисане су у пакету java.util. Размотрићемо следеће генеричке типове:
- Iterator<T> интерфејсни тип - декларише методе за итерирање кроз један по један елемент колекције
- ArrayList<T> тип - има структуру динамичког низа, број објеката које можемо сачувати се аутоматски повећава по потреби.
- LinkedList<T> тип - двоструко повезана листа, омогућено је итерирање у оба смера
Класу која дефинише колекцију објеката често називамо контејнерском класом. Постоје три основна типа колекција који организују објекте на различите начине:
- скупови
- низови
- каталози
Скуп
уредиСкуп(Set) је најједноставнија колекција у којој објекти нису на неки специјалан начин уређени I објекти се једноставно додају скупу, без икакве контроле где иду. Можемо итерирати кроз све објекте скупа, испитати да ли је неки објекат члан скупа. Скуп не може да садржи дупликате. Објекат можемо и обрисати из скупа, али само ако имамо референце на њега у скупу.
Низ
уредиГлавна карактеристика низа је да су објекти смештени на линеаран начин, у произвољном, фиксном редоследу где имамо почетак и крај. Колекције у општем случају имају способност да се прошире да би се сместио потребан број објеката.Примери:
- Низови
- ArrayList, Vector
- LinkedList
- Редови
- Stack(LIFO)
- Queue(FIFO)
Пошто је низ линеаран, могуће је додати нови објекат на почетак или крај или уметнути нови објекат иза дате позиције. Добијање објекта из низа се може извршити на висе начина:
- Може се селектовати први или последњи
- Може се добити објекат са дате позиције
- Може се претраживати низ унапред или уназад да би се испитало да ли се неки објекат налази у низу и слично
Итератор
уредиИтератор је објекат који се користи за итерирање кроз колекцију, тј. приступ једном по једном објекту колекције. Употреба итератора омогуће употребу облика фор петље карактеристичног за претраживање колекција. Било који објекат који представља скуп или низ може да креира објекат типа Iterator<>. Објекат типа Iterator<> садржи референце на све објекте колекције у неком редоследу и њима се може приступити помоћу метода интерфејса Итератор<>:
- T next() - враћа објекат типа Т и поставља Iterator<T> објекат да при следећем позиву овог метода реферише на следећи објекат колекције. Ако нема објекта који ће бити враћен, избацује се изузетак типа NoSuchElementException
- void remove() - брише последњи објекат враћен позивом метода next(). Ако next() није био позван или ако се позове remove() два пута након позива next(), биће избачен изузетак типа IllegalStateException
Пример:
Класа примерак;
while(iterator.hasNext())
primerak=iterator.next();
Овде се подразумева да је итератор типа Iterator<Klasa> и да чува референце на објекат добијен из колекције. Један објекат који имплементира интерфејс Iterator<> је за једну итерацију кроз колекцију. Уколико је касније потребно поново проћи кроз све елементе колекције , неопходно је креирати нови објекат.
Лист итератори
уредиУ пакету java.util дефинисан је интерфејс ListIterator<>. Декларише методе који се користе за кретање кроз колекцију напред и назад, тако да се до објекта може доћи висе него једном. ListIterator<> интерфејс наслеђује Iterator<> интерфејс.
Списак метода:
- T next() - као код Iterator<>
- Boolean hasNext() - као код Iterator<>
- int nextIndex() - враћа индекс објекта који ће бити враћен позивом метода next() као тип int, или враћа број елемената у листи ако је *ListIterator<> објекат на крају листе
- T previous() – враћа претходни објекат по редоследу у самој листи. Користимо га за враћање кроз листу.
- Boolean hasPrevious() – враћа труе ако претходни позив previous() враћа објекат.
- int previousIndex() - враћа индекс објекта који ће бити враћен следећим позивом метода previous() или -1 ако је ListIterator<> објекат на почетку листе
- void remove() - брише последњи објекат добијен позивом метода next() или previous(). Ако next() или previous() нису били позвани, избацује се изузетак типа IllegalStateException, а ако операција брисања није подржана за дату колекцију, избацује се изузетак типа UnsupportedOperationException
- void add(T obj) – додаје аргумент непосредно пре објекта који би био враћен следећем позивом метода next() или иза објекта који би био враћен следећим позивом метода previous(). Позив next() након додавања враћа додати елемент, а за previous() се ништа не мења. Ако објекат не може да се дода избавује се изузетак типа UnsupportedOperationException уколико постоји неки други разлог због којег додавање не може да се изврши.
- void set(T obj) – мења последњи објекат добијен позивом next() или previous(). I овај метод може да избаци изузетке типа UnsupportedOperationException, ClassCastException и IllegalArgumentException.
ArrayList
уредиArrayList<T> дефинише колекцију са елементима типа Т. Објекат ArrayList<T> ради као и низ осим што додатно расте аутоматски, када је потребан већи капацитет. Као и низови, и ArrayList садрже референце на објекте, не сам објекте. То је правило за све колекције. ArrayList се, за разлику од низа, карактерише величином (size). Капацитет је максималан број објеката које ArrayList може да садржи. Капацитет је променљив, јер се аутоматски повећава када се дода нови објекат у већ пун вецтор. Методом ensureCapacity(int) поставља се минимални капацитет.
Пример:
v.ensureCapacity(150); /*ako je kapacitet objekta v manji od 150, kapacitet se uvećava na 150. AKo je 150 ili vise neće biti promenjen ovom naredbom.*/
Конструктори
уреди- ArrayList<>() – подразумевани конструктор креира празан ArrayList<> подразумеваног капацитета 10. Капацитет се повећава за 50% уколико се дода елемент у већ пун ArrayList.
- ArrayList<String> a= new ArrayList<String>();
- ArrayList<String> a= new ArrayList<String>(100); - експлицитно се задаје капацитет
Методе
уреди- size() – враћа број елемената који су смештени у ArrayList
- add(T) - додаје се објекат иза текућег последњег објекта у ArrayList
- add(int,T) – смешта се објекат на позицију задату првим аргументум, остали се померају за једно место у десно.
- set(int,T) – постављамо објекат у ArrayList на позицију задату првим аргументум, брише се објекат који је претходно био на тој позицији.
- addAll(листа) – додају се сви објекти друге колекције у ArrayList
- addAll(int,листа)
- get(int) – враћа елемент на задатој позицији
Чињеница да се капацитет ArrayList повећава за 50% сваки пут када се АрраyЛист попуни утиче на то да се меморија беспотребно заузима. Међутим, ово може да се превазиђе употребом метода trimToSIze(). Овај метод мења капацитет тако да одговара тренутној величини.
names.trimToSize(); /*postavlja kapacitet na trenutnu veličinu. Ako je veličina u trenutku izvršavanja metoda 30 i kapacitet se postavlja na 30. Naravno i dalje se mogu dodavati novi objekti u ArrayList.*/
Можемо добити и ListIterator референцу из вектора позивањем метода listIterator().
ListIterator<String> it = names.listIterator(); /* sada je moguće kretati se u oba smera kroz ArrayList upotrebom metoda iz klase ListIterator. */
ListIterator <String>it = names.listIterator(2); /* ovim se dobija ListIterator objekat koji se odnosi samo na deo ArrayList, pri čemu je argument metoda listIterator() upravo prvi element date sekvence ArrayList. */
List <String>list = names.subList(2,5); /* dobija se podskup objekata iz ArrayList kao kolekcija tipa List<>. Argumetni metoda subList() su početak i kraj sekvence objekata koja čini podskup, pri čemu poslednji element nije uključen. */
Могуће су и комбинације:
ListIterator<String> listIter = names.subList(5,15).listIterator(2); /* subList() враћа подлисту објеката ArrayList од позиције 5 до позиције 14 закључно. Након тога се за дату листу позива метод listIterator() који враћа лист итератор над елементима листе почевши од елемента са индексом 2, па до краја. То одговара елементима полазног ArrayList са индексима од 7 до 14. Овим итератором се кроз дату секвенцу објеката полазног ArrayList може кретати у оба смера. */
Метод toArray() омогућава да се елементи вектора добију у облику обичног низа.
String[] data = names.toArray(new String[names.size()]); /* argument metoda toArray() mora biti niz elemenata istog tipa kao i elementi ArrayList ili nadtipa. Ako niz nije dovoljne veličine da primi sve elemente ArrayList, kreiraće se novi niz i referenca na taj niz će biti vraćena. Ovde metod toArray() vraća niz String[] koji sadrži sve elemente ArrayList names u odgovarajućem redosledu.*/
String[] people = {“Bill”, “Barbara”, “Brian”};
List<String> nameList = java.util.Arrays.asList(people);
Статички параметарски метод asList() дефинисан у класи java.util.Arrays конвертује низ елемената датог типа Т у List<T> колекцију. Аргумент метода је низ који се конвертује и враћа се референца типа List<T>. Ова референца се може проследити као аргумент конструктору класе ArrayList:
ArrayList<String> names = new ArrayList(nameList); /* Time se dobija ArrayList koji sadrži elemente datog niza. */
Брисање елемената:
- remove(int) – брисање референце на објекат на задатој позицији. Враћа се референца на уклоњени објекат, тако да остаје могућност да се сачува референца након што се уклони из ArrayList.
- remove(T) – брисање првог објекта са задатом референцом. Уколико је пронађен и уклоњен објекат, враћа се true, иначе false
- removeAll(names.subList(2,5)); - бришу се елементи са индексима од 2 до 4
- retailAll(names.subList(2,5)); - остају само елементи са индексима од 2 до 4
- removeAllElements() – уклања све елементе из ArrayList
Да ли је ArrayList празан може се утврдити методом isEmpty(). Уколико је величина 0, метод враћа true, иначе false. Уколико ArrayList садржи само null елементе, то не значи да је празан тј да је величина 0 или да ће метод isEmpty() вратити true. Да би се испразнио елементи тј референце на објекте морају бити уклоњене, а не само постављене на null.
Претраживање ArrayList:
- int indexOf(T) – враћа позицију датог објекта у ArrayList
- int indexOf(T,int) – враћа прву позицију датог објекта у ArrayList, почевши од позиције задате као други аргумент
За сортирање елемената колекције најбоље је имплементирати интерфејс Comparable<>. Он декларише само један метод compareTo() – враћа -1, 0 или 1 ако је објекат мањи, једнак или већи од аргумента. Ако је Comparable<> интерфејс имплементиран за тип објекта смештен у колекцији, можемо само објекат колекције предати као аргумент методу Collections.sort() интерфејса Collections<>.
Повезана листа (LinkedList)
уредиКонстуктори
уреди- без аргумената
- са аргументом Collection<> који креира LinkedList<> објекат који садржи објекте колекције која се прослеђује као аргумент
Методе
уреди- add(),addAll() – као код класе Vector<>
- addFirst() – додаје објекат на почетак листе
- addLast() – додаје објекат на крај листе
- get(int) – као код класе Вецтор<>
- getFirst(),getLast()
- remove(),removeFirst(),removeLast()
- set()
- size() – враћа број елемената листе
Меотдом iterator() добија се Iterator<> објекат над листом. Методом listIterator() објекат ListIterator<>, као и код класе Vector<>
Каталози
уредиКаталог (HashMap<K,V>) такође називамо и речником. Заједно се чувају и кључ и објекат. Сваки објекат је јединствено одређен својим кључем. Сви кључеви, из тог разлога, морају да буду различити. Издвајање објеката из каталога врши се помоћу одговарајућег кључа, јер кључ одређује где се у каталогу налази објекат. Све класе за рад са каталозима имплементирају генерички интерфејс Map<K,V>. Разматраћемо генеричку класу HashMap<K,V>. Имплементација каталога помоћу генеричке класе HashMap<> подразумева да су парови кључ/објекат смештени у низ. Индекс у низу добија се на основу кључа за шта се користи метод hashCode().Овај метод наслеђује се из класе Object и он производи подразумевани хешкод, осим ако није предефинисан у некој од изведених класа. Стога, било који објекат може да буде коришћен као кључ. На основу њега се методом hashCode() генерише хешкод којим се одређује индекс пара са датим кључем у низу.
Хеширање
уредиАко желимо да као кључеве користимо објекте које сами дефинишемо, треба да предефинишемо метод equals() из класе Object. Да би се обезбедила потребна функционалност, нова верзија треба да врати true када два различита објекта садрже исте вредности. Такође, могуће је предефинисати метод hashCode() тако да расподела буде прилично униформна на скупу могућих вредности за кључеве. Један начин је да се за сваки податак-чланицу класе генерише цео број нпр. постојећим методом hashCode() који се затим множи простим бројем (сваки члан различитим) и на крају се добијени резултати сумирају. Генерисање целог броја за податак-чланицу класе се обично врши позивањем метода hashCode(). Просте бројеве треба бирати тако да не буду превелики, како резултат не би био ван опсега за int. Кад год је податак-чланица класе објекат неке друге класе, а не примитивног типа, неопходно је имплементирати hashCode() метод за дату класу.
Констуктори за HashMap<>
уреди- HashMap() – подразумевани, креира каталог подразумеваног капацитета 16, а подразумевани лоад фактор је 0.75
- HashMap(int capacity) – креира каталог датог капацитета, са подразумеваним лоад фактором
- HashMap(int capacity, float loadFactor) – креира каталог са датим капацитетом и лоад фактором
- четври констуктор креира каталог на основу постојећег каталога
Капацитет каталога је број парова кључ/објекат који могу да се чувају. Аутоматски се повећава, ако је неопходно. Пожељно је да се за капацитет каталога задаје прост број, како би се избегла колизија. Фактор пуњења (лоад фактор) се користи за одлучивање када капацитет хеш табеле треба да се повећа . Када величина табеле достигне вредност производа фактора пуњења и капацитета, капацитет се аутоматски повећава два пута уз додавање 1 да би се осигурало да је нови капацитет барем непаран, ако већ није прост.
Смештање, добијање и уклањање објеката каталога:
- V put(K key, V value) – смешта објекат value у каталог користећи кључ key.
- V remove(Object key) – уклања пар повезан са кључем ако постоји и враћа референцу на објекат. Уколико не постоји одговарајући пар са датим кључем, или је објекат придружен кључу null, враћа се null.
- V get(Object key) – враћа објекат са истим кључем као key. Објекат остаје у каталогу. Ако нема ниједног објекта са датим кључем, или је null смештено уместо објекта, враћа се null
Ако get() врати null, не знамо да ли објекат повезан са кључем не постоји или је објекат null. За ово служи метод containsKey() који као аргумент има дати кључ. Он враћа true ако је кључ смештен у каталогу.
Map<> интерфејс обезбеђује 3 начина за добијање колекционог прегледа садржаја каталога:
- keySet() – враћа Set објекат који реферише на кључеве каталога
- entrySet() – враћа Set<Map.Entry> објекат који реферише на парове кључ/објекат – сваки пар је објекат типа Map.Entry. Entry је генерички интерфејсни тип дефинисан унутар интерфејса Map<>.
- values() – враћа Collection објекат који реферише на објекте из каталога.
Генеричке класе
уредиНа исти начин, као што се дефинишу генеричке функције, могу се дефинисати I генеричке класе. Дефиниција чланова класе се проводи помоћу генеричког типа. На пример
template<class T> class array {
...
}
помоћу које се може реализовати низови произвољног типа. На пример декларацијом
array<int> myints(5);
се формира низ од 5 целобројних елемената, а са
array<string> mystrings(5);
се формира низ од 5 стрингова.
template <class T> class array{
T *m_pbuff;
int m_size;
public:
array(int N = 10):
m_size(N) {
m_pbuff=new T[N];
}
~array() {
delete []
m_pbuff;
}
int size() {
return m_size;
}
void set(int x, T value);
T get(int x);
T & operator [] (int i){
return m_pbuff[i];
}
const T & operator [] (int i) const {
return m_pbuff[i];
}
}
template <class T> void array<T>::set(int x, T value){
m_pbuff[x]=value;
}
template <class T> T array<T>::get(int x){
return m_pbuff[x];
}
Get() и set() функције смо могли дефинисати inline унутар дефиниције класе. Оне су дефинисане изван класе како би се показало да се тада увек испред дефиниције функције мора написати генерички префикс: template <class T>.
int main () {
array <int> myints(5);
array<float> myfloats(5);
myints.set (0, 100);
myfloats.set(3, 3.1416);
cout << "myints ima: " << myints.size() <<" elemenata"<< '\n';
cout << myints[0] << '\n';
cout << myfloats[3] << '\n';
return 0;
}
Резултат је:
myints има: 5 елемената
100
3.1416
Елементима низа се приступа помоћу set/get функција или помоћу индексне нотације.
Пример:
Класа pair служи као контењер два елемента који могу бити различитих типова. Формираћемо низ таквих парова помоћу генеричке класе array. У тај низ ћемо уписати и из њега исписати низ парова којима први елемент представља редни број (tip int), а други елемент је квадратни корен првога (tip float).
template<class T1, class T2> class pair{
T1 value1;
T2 value2;
public:
pair (T1 first=0, T2 second=0){
value1=first;
value2=second;
}
T1 GetFirst () {return value1;}
void SetFirst (T1 val) {
value1 = val;
}
T2 GetSecond () {
return value2;
}
void SetSecond (T2 val) {
value2 = val;
}
};
int main (){
// niz od 5 parova int,float
array <pair<int,float> > arr(5);
for(int i=0; i<arr.size(); i++){
arr[i].SetFirst(i);//prvi sadrži redni broj
arr[i].SetSecond(sqrt(i));//kvadratni koren prvog
}
for(int i=0; i<arr.size(); i++)
cout<<arr[i].GetFirst() <<':'
<<arr[i].GetSecond()<< endl;
cout << endl;
return 0;
}
Резултат је:
0:0
1:1
2:1.41421
3:1.73205
4:2
Важно је упозорити на облик декларације низа парова:
array <pair<int,float> > arr(5);
- Видимо да се конкретна реализација генеричког типа може извршити и помоћу других генеричких типова.
- У декларацији се појављују два знака > >. Између њих обвезно треба написати размак, јер ако би написаликомпајлер би пријавио грешку због тога јер се знак >> третира као оператор.
array <pair<int,float>> arr(5); // greška : >>
Да би се избегле овакове грешке, препоручује се коришћење typedef декларације облика:
typedef pair <int,float> if_pair;
array <if_pair> arr(5);
Специјализација шаблона
уредиСтандардна библиотека шаблона[4] је софтверкса библиотека програмског језика C++ која је делимично укључена у С++ стандардну библиотеку. Обезбеђује четири копмоненте: контејнере, итераторе, алгоритме и функторе. STL обезбеђује готов скуп основних класа за C++ као што су контејнери и асоцијативни низови, који се може користити уз било који уграђени тип и уз било који кориснички дефинисан тип који подржава неке основне операције (као што су копирање и додела). STL алгоритми су независни од контејнера, што значајно смањује комплексност библиотеке. STL остварује резултате кроз употребу шаблона. Овај приступ обезбеђује полиморфизам времена компајлирања који је често ефикаснији од традиционалног полиморфизма времена покретања. Модерни C++ преводиоци подешени су тако да минимализују било какву казнз апстракције насталу интезивном употребом STL-a. STL је настао као прва библиотека генеричких алгоритама и структура података за C++, са четири идеје на уму: генеричко програмирање, апстракција без губитка ефикасности, фон Нојманова архитектура и вредносна семантика.
Понекад се с једним шаблоном не могу обухватити сви случајеви реализације с различитим типовима. У том случају, за типове којима реализација одступа од шаблона, може дефинисати посебан случај реализације који називамо специјализација шаблона.
За класу коју дефинишемо шаблон:
template<class gen_tip> identifikator{
…
}
Специјализација за конкретан тип се записује у облику:
template<>class identifikator<konkretan_tip>{
…
}
Специјализација се сматра делом шаблона, стога њена декларација започиње са template<>. У њој се морају дефинисати сви чланови шаблона, али искључиво са конкретним типовима за које се врши специјализација шаблона. Подразумева се да морају бити дефинисани конструктор и деструктор, уколико су дефинисани у општем шаблону.
Шаблони са константним параметрима
уредиПараметри шаблона могу бити и интегралне константе (int,char,long, unsigned). На пример,
template <class T, int N> class array {
...
}
је шаблон за дефинирање класе array помоћу генеричког типа Т и интегралне константе N. Вредност интегралне константе се мора специфицирати при декларацији објекта. На пример,
array <float,5> myfloats;
декларише се myfloat као низ реалних бројева којем се додатна својства задају с константом N=5. У следећем примеру користићемо овај константни параметар за постављање величине низа.
template <class T, int N>
class array{
T m_buff[N]; // niz od N elemenata
int m_size;
public:
array() : m_size(N) {}
int size() {
return m_size;
}
T & operator [] (int i) {
return m_buff[i];
}
const T & operator [] (int i) const{
return m_buff[i];
}
};
int main () {
array <int,5> myints;
array<float,5> myfloats;
myints[0]= 100;
myfloats[3]= 3.1416; 13
cout << "myints ima: "<<myints.size()
<<" elemenata"<< '\n';
cout << myints[0] << '\n';
cout << myfloats[3] << '\n';
return 0;
}
Добије се испис:
100
3.1416
Предодређени и функцијски параметри шаблона
уредиУ шаблонима се могу користити предодређени параметри. На пример, шаблоном
template <class T=int, int N=10> class array {
…
}
се омогућују декларације облика:
array<> a_int_10; // niz od 10 int
array<float> a_float_10; // niz od 10 float
array<char, 100> a_char_100; // niz od 100 char
Напомена: предодређени параметри се не смеју користити у функцијским шаблонима Параметри шаблона могу бити функције:
template <int Tfunc(int)>
class X {
...
y = Tfunc(x); !!!
...
}
Морамо пазити да узимамо што мање претпоставки на типове који су параметри шаблона. Препоручује се: параметри генеричких функција су const референце (да бисмо избегли захтев да се објекти морају копирати, да постоји и да је добро дефинисан copy конструктор), за упоређивање користимо само оператор < (не и оператор >, оператор >=, итд)
template <class T>
T min(const T &a, const T &b){
return a < b ? a : b;
}
Поређење шаблона и макро наредби
уредиШаблоне можемо сматрати интегралним макро наредбама. Примена шаблона има неколико предности у односу на макро наредбе:
- Лакше их је схватити јер шаблони изгледају као регуларне класе(или функције)
- У развоју шаблона лакше се преводи тестирање с различитим типовима
- Компајлер осигурава знатно већу контролу грешака него је то случај с макро наредбама
- Функцијски шаблони имају дефинисан досег, јер се могу дефинисати као део класе (friend) или namespace
- Функцијски шаблони могу бити рекурзивни
- Функцијски шаблони се могу преоптеретити
- При комплајлирању неке датотеке, компајлер мора располагати с потпуном имплементацијом шаблона. Стога је уобичајено да се спецификација и имплементација функција записује у истој „.х“ датотеци. Макро карактер шаблона се посебно очитава код класа које имају статичке чланове. У том случају се за сваку реализацију шаблона иницира посебно статичка променљива (код регуларних се класа за све објекте једне класе генерише само једна статичка променљива)
Дефиниција и употреба класе tvector<class T>
уредиГенеричка класа tvector представља подскуп стандардне класе vector. Намењено јој је манипулисање с хомогеним колекцијама елемената произвољног типа. Извршичемо спецификацију и имплемента класе tvector темељем знања која смо стекли разматрајући генеричку класу array и класу Tstring.
Објекти tvector класе имају следеће карактеристике:
- capacity() – капацитет вектор је број ћелија које се аутоматски алоцирају за спремање елемената вектора
- size() – величина вектора је број елемената које стварно садржи вектор
- resize(n) – ако је потребан већи капацитет вектора, он се може добити позивом функције resize(n)
- operator [] – поједином елементу вектора се приступа помоћу индексног оператора, или се користе две функције:
- push_back(el) додаје елемент на индексу иза последње уписаног елемента
- pop_back(el) брише последњи елемент из вектора
При позиву push_back() funkcije vodi se računa o kapacitetu vektora i vrši se automatsko alociranje potrebne memorije (memorija se udvostručuje ako potrebna veličina vektora premašuje trenutni kapacitet). Namena ove dve funkcije je da se vektor može koristiti kao dinamička kolekcija elemenata (kontejner).
Референце
уреди- ^ „Generičke funkcije i klase – template (predlošci)” (PDF). Архивирано из оригинала (PDF) 20. 12. 2016. г. Приступљено 11. 12. 2016.
- ^ Generički mehanizam
- ^ „Generički klasni tipovi” (PDF). Архивирано из оригинала (PDF) 20. 12. 2016. г. Приступљено 11. 12. 2016.
- ^ Standardna biblioteka šablona