Преговор на ООП. Generic Programming. Шаблони в C++.

Преди същинските лекции по СДП да могат да започнат, нека се подсигурим, че когато после си говорим с разни термини, няма да има нищо прекалено изненадващо. В курса по ООП се изучават голям брой термини и концепции от програмирането, които няма откъде другаде да се научат. Така че, ако си преписал като пич на изпита по ООП, сега ще имаш шанс да се поправиш. Освен пред Бог. Бог вече те мрази. Завинаги.

Обектно ориентирано програмиране

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

Обекти

За постигане на целите си ООП програмите използват обекти1, които да са самостоятелни, но да могат да взаимодействат с други обекти.
Какво е обект? Всичко може да е обект. Формално това е съвкупност от данни с някакви дефинирани за тях методи. По този начин могат да се моделират учудващо много реални феномени. Данните са като променливи, но по-различно, а методите са като функции, но по-различно.
Обектите да бъдат самостоятелни означава всичката програмна логика, необходима за работата с един обект да се съдържа в самия него, а да си взаимодействат с други обекти би трябвало да е очевидно какво е. Един обект трябва да може да "вижда" другите обекти в програмата - да може да извика някой метод на друг обект. Представи си купчина дъски. Искаш да закачиш една дъска за друга. Трябват ти чук и пирон. Или най-малкото лепило. Купчината дъски не спазва обектно ориентираната парадигма. А сега си представи си конструктор. За да свържеш две блокчета трябва просто да ги закачиш едно за друго, защото всяко от тях е направено така, че да не изисква лепило, за да се закача за блокчета като него.

Например, можем да си направим обект, който да моделира химикалка. Той има променлива (число), което характеризира колко мастило има останало вътре. Има и метод пиши, който приема една променлива от тип буквен низ. Този метод проверява дали е останало достатъчно мастило в химикалката и ако това е така - изписва думата, след което намаля мастилото с някакво количество. Ако ли не - изписва думата само донякъде, или въобще нищо не прави.2

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

Принципи на ООП

Енкапсулация

Всеки обект си има свои собствени променливи. Писалка А може да има само 10% мастило, но това не пречи на писалка Б да има 90%.

Наследяване

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

Полиморфизъм

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

Има още много, много идеи, залегнали в ООП, но тези са най-силно застъпени в C++ ООП. Тези трите са един вид класически, макар и по никакъв начин не достатъчни в днешни дни.

Други парадигми

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

Представи си конструктор, лего. Имаш блокчета, с размер 2*2, 2*3, 2*8, 1*2, 1*3, 1*8. Използвай ги, за да ми построиш къща. Най-вероятно първо ще формираш квадратна рамка за основа с някакви размери. Да кажем 18*18. Ще сложиш няколко такива неща едно върху друго и ще се получат стени После от блокчета ще си направиш една голяма плоскост за таван и върху него с намаляващи по размер квадратни рамки ще сложиш покрив. Това е Обектно ориентирано програмиране.
А сега си представи, че ти дам една машина за леене на пластмаса и ти кажа да ми построиш къща. Можеш да си излееш пластмаса в каквито си искаш форми, само че накрая трябва да стане къща. Това е процедурно програмиране.
Кой от двата метода е по-бърз? По-лесен? По-вероятен да завърши с половин килограм непотребна, усукана в невъзможни форми пластмаса? Ако се счупи някоя част, в кой вариант ще е по-лесно да се смени?
И строителите са стигнали до същия извод, затова не си строим къщите от едно гигантско монолитно парче камък, а от няколко хиляди малки, подобни тухли. Едни и същи тухли могат да се използват както за едноетажна, така и за пететажна къща. Програмирай умно.

Generic Programming

Generic означава общ, базов. Под Generic Programming се има предвид да можем да си направим нещо, което после да може да се преизползва в различни ситуации. Този стил на програмиране е доста популярен в C++ средите, защото там все още си мислят, че това е някаква голяма и много сложна работа. То на C++ си Е много сложно де, но както и да е.

За пример, да кажем че имаме програма, която да обхожда и отпечатва квадратна матрица ред по ред. Приема аргумент (int ** matrix). Т.е. за матрицата от цели числа:

1 2 3
4 5 6
7 8 9

Програмата ще изведе:
1 2 3 4 5 6 7 8 9

Само че ако след 2 седмици ни трябва да си отпечатаме по този начин матрица от дробни числа, или низове? Майната му, матрица от химикалки? С тролчета отгоре? Трябва ли да се пише наново функцията, която печаташе целите числа, но този път с нещо друго? Ами ако след 2 години дойде младата програмистка Джордан Капри и реши че иска да печата матрица от нитратни краставици? И тя ще трябва да пише същата функция. Загуба на време, няма смисъл едно нещо да се пише хиляда пъти.
В различните езици този проблем се решава с вариращи нива на ефективност и елегантност.
В Python и Ruby, например, това въобще не е проблем. Една и съща функция ще си печата масива като пич, каквото и да има в него. Езикът сам разбира дали печата поеми на Шекспир или данъчна декларация. Естествено, тези езици са малко по-бавни, защото вършат повече работи зад кулисите. Така е с почти всички езици от високо ниво.

В C++ работата се решава с шаблони.
Май никъде другаде работата не се решава с шаблони. Така де, не защото е лоша, безмислено усложнена и непрактична идея. Не, просто никой друг, освен създателите на C++ не се е сетил да използва шаблони.

Шаблони

Шаблон, на английски template се използва когато искаме някоя функция или метод да бъде generic. Когато в него се използва някакъв клас без изрично да е упоменато какъв е той. Например, в горната програма просто обхождаме елементите на масива по някакъв начин и пращаме всеки от тях на cout. Оттам нататък не ни интересува какво става - може да се отпечатат, може да не се отпечатат. Това става като в програмата си работим нормално с някакъв масив, но не упоменаваме изрично какъв е той. Казваме, че е от някакъв измислен тип T3 и работим с него. Дали T = int, T = char или T = Himikalka ще се разбере по време на компилация. И навсякъде където работим с масива пишем само T, а не int, char или нещо такова. Общо, Generic.
Ето как става схемата:

template <class T>
void PrintSpecial( T ** matrix, int numrows, int rowsize)
{
    /* Няма проверка дали матрицата е празна. Съди ме ако искаш. */
    for(int x = 0; x < numrows; ++x)
    {
        for(int y = 0; y < rowsize; ++y)
        {
            cout<<matrix[x][y]<<" ";
        }
    }
    cout<<endl;
    return;
}

Сега вече можем да си го ползваме за каквото ни душа иска. Например за ето тези глупости:
int main()
{
    int masiv[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};
    PrintSpecial(masiv, 3, 3);        //Изкарва 1 2 3 4 5 6 7 8 9
    char masiv2[3][3] = {{'a','b','c'}, {'d','e','f'}, {'g','h','i'}};
    PrintSpecial(masiv2, 3, 3);        //Изкарва a b c d e f g h i
    return 0;
}

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

Относно избора на език

Искам да отбележа нещо важно. Само защото структури от данни се учи на C++ / Java и някои от структурите (например шаблони) са ограничени за определен език, това не означава, че всичко в този предмет важи само и единствено за C++ / Java.
По СДП ще се учат списъци, опашки, стекове, графи, дървета, разни асоциативни масиви и други неща, които ги има в абсолютно всички езици.
Според някои от най-добрите програмисти въобще, шаблоните в C++ са глупава и неприложима на практика идея. Според много хора самият C++ е глупав и неизползваем език, по-малко мощен от Java, и не толкова бърз, колкото чисто C. Аз също мисля така.
Всеки има право на мнение и ако ти по някаква причина не харесваш C++, това въобще не е проблем. Научи шаблоните(защото все пак са важна концепция в програмирането) и след това пиши всичко останало на любимия си език, няма да изпуснеш нищо важно.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-NonCommercial-ShareAlike 3.0 License