C++ objektově orientovaná nadstavba programovacího jazyka C (1. část) Josef Dobeš Katedra radioelektroniky (13137), blok B2, místnost 722 dobes@fel.cvut.cz 5. května 2014 České vysoké učení technické v Praze, Fakulta elektrotechnická 1
1 Základní idea C++ koncepce třídy (class) V jazyce C jsou odděleny datové a řídící struktury, vztah mezi daty a funkcemi je následující: proces A proces B datové struktury proces C Oproti tomu v C++ data a operace s daty (funkce) tvoří jednu koherentní jednotku (class): proces A proces B proces C datové struktury 2
Třída (class) má podobnou syntaxi jako struktura (struct) známá ze standardů jazyka C (dokonce v C++ lze vytvořit třídu i pomocí kĺıčového slova struct pak jsou implicitně data typu public; pokud použijeme kĺıčové slovo class, jsou všechna data implicitně typu private), obsahuje navíc však i procedury, které pracují s daty třídy. Z hlediska povoleného přístupu se elementy třídy děĺı na tři skupiny: public: data a funkce dostupné z kterýchkoliv jiných tříd private: privátní data a kódy dostupné pouze z dané třídy protected: data dostupná dceřinými třídami (třídy mohou mohou vytvářet hierarchický strom, což je mimořádně účinný nástroj v projektech velkých týmů) Níže uvedená třída pro práci s datem obsahuje privátní data month, day a year, veřejné funkce setdate a showdate a explicitní konstruktor date (který se vždy jmenuje jako třída): /* "date.h" */ class date private : int month; // prvek třídy: data int day; // prvek třídy: data int year; // prvek třídy: data public : date(int = 4, int = 30, int = 2011); // konstruktor void setdate(int, int, int ); // člen třídy: funkce void showdate(); // člen třídy: funkce ; 3
2 Konstruktory Pro konstruktory platí následující zásady: Konstruktorem je jakákoliv funkce, která má stejné jméno jako třída. Konstruktor může být přetížen (overloaded, viz následující příklad). Konstruktor nemá návratovou hodnotu (tj. není ani void). Pokud není žádný konstruktor definován, kompilátor vytvoří implicitní konstruktor. Default konstruktor je takový, který nepotřebuje žádné parametry, pokud je volán, což může nastat z následujících důvodů: Nejsou deklarovány žádné parametry. Všem parametrům byla udělena default hodnota, viz date.h. V souboru date.cpp je definován konstruktor a obě funkce třídy. Funkce setw zajistí zápis proměnné int do dvou cifer a funkce setfill zajistí, že bude zapsána nula i na prvním místě; endl zajišt uje přechod na další řádek. Povšimněme si předepsaného oddělovače ::. (Povšimněme si dále, že pro některé překladače je neustále nutné psát std::, což lze vyřešit deklarací using namespace std viz následující příklad.) 4
3 Destruktory Opakem konstruktoru je destruktor; pro destruktory platí následující zásady: Jméno destruktoru je znak tilde následovaný jménem třídy. Pro jednu třídu může být pouze jeden destruktor. Destruktor nebere žádné parametry a nemá žádnou návratovou hodnotu. Destruktor je volán, když je objekt třídy likvidován. Pokud není destruktor explicitně definován, default destruktor je vytvořen překladačem. Typickou činností destruktoru je uvolňování dynamicky alokované paměti funkcemi třídy. 5
/* "date.cpp" */ #include <iostream> // pro std::cout (standardní výstup C++) a std::endl #include <iomanip> // pro funkce std::setfill a std::setw #include "date.h" date::date(int mm, int dd, int yyyy) month = mm; day = dd; year = yyyy; void date::setdate(int mm, int dd, int yyyy) month = mm; day = dd; year = yyyy; return ; // zajímavý způsob "vrácení prázdné hodnoty" void date::showdate() std::cout << "The date is "; std::cout << std::setfill('0') << std::setw(2) << month << '/' << std::setw(2) << day << '/' << std::setw(2) << year % 100; // výběr pouze posledních dvou číslic std::cout << std::endl; return ; 6
Typický hlavní program, který používá výše uvedenou třídu vypadá následovně (pro C++ programy je standardem určeno, že funkce main musí být typu int tato návratová hodnota je totiž použita k dalšímu řízení systému): #include "date.h" int main() // GNU GCC vyžaduje návratovou hodnotu int pro programy v C++ date a, b, c(5, 1, 2011); // utvoření tří objektů, poslední inicializován b.setdate(12, 25, 2002); // přiřazení hodnot datovým členům objektu b a.showdate( ); // zobrazení hodnot datových členů objektu a b.showdate( ); // zobrazení hodnot datových členů objektu b c.showdate( ); // zobrazení hodnot datových členů objektu c return 0; a výsledek tohoto hlavního programu je podle očekávání (Angličané a Američané vždy používají pořadí měsíc/den/rok): The date is 04/30/11 The date is 12/25/02 The date is 05/01/11 7
4 Friend funkce Pomocí tříd lze přetěžovat (tj. redefinovat) operátory, což značně usnadňuje psaní programů. Následující třída bude sloužit k přesnému sčítání zlomků pomocí odděleného ukládání čitatele a jmenovatele a vykonavatelem této matematické operace bude redefinovaný operátor +: class fraction // přetížení operátoru, abychom mohli sčítat zlomky pomocí znaménka "+" friend fraction operator +(fraction f1, fraction f2); public : fraction(int n, int d = 1); // první konstruktor // přiřadí čitateli n, jmenovateli d // pokud druhý argument není použit, použije se implicitní hodnota 1 fraction(); // druhý konstruktor, přiřadí čitateli 0 a jmenovateli 1 void get(); // načte zlomek z klávesnice void show(); // zobrazí zlomek na obrazovce long double evaluate(); // vyčíslí hodnotu zlomku v pohyblivé čárce private : int numerator; // horní část zlomku, čitatel int denominator; // spodní část zlomku, jmenovatel ; Deklarací friend (kĺıčové slovo friend) lze umožnit vně definované funkci přistupovat k prvkům třídy a lze tak redefinovat operátory (používá se k tomu speciální kĺıčové slovo operator). Povšimněme si rovněž deklarace více konstruktorů v této třídě. Překladač rozpozná, který má použít podle toho, s jakým počtem parametrů je konstruktor volán. 8
5 Přetížení operátoru #include <iostream> #include "fraction.h" fraction operator +(fraction f1, fraction f2) // přetíží operator "+" pro sčítání zlomků fraction r; // pro návratovou hodnotu f1 + f2 // výpočet čitatele: r.numerator = (f1.numerator * f2.denominator) + (f2.numerator * f1.denominator); // výpočet jmenovatele: r.denominator = f1.denominator * f2.denominator; return r; // návrat výsledku fraction::fraction(int n, int d) // definice prvního konstruktoru, který umož ňuje zadání čitatele // i jmenovatele s implicitní hodnotou 1 numerator = n; denominator = d; fraction::fraction() // druhý konstruktor; pokud nejsou argumenty ur čeny, dosadí implicitně 0/1 numerator = 0; denominator = 1; 9
void fraction::get() // získá zlomek ze standardního vstupu ve form ě "čitatel/jmenovatel" char div_sign; // použito k načtení znaku '/' ze vstupu std::cin >> numerator >> div_sign >> denominator; void fraction::show() // zobrazí zlomek ve formě "čitatel/jmenovatel" std::cout << numerator << '/' << denominator; long double fraction::evaluate() // spočítá a vrátí desetinnou hodnotu zlomku long double n = numerator; // převede čitatele do pohyblivé čárky long double d = denominator; // převede jmenovatele do pohyblivé čárky return n / d; // vrátí representaci v pohyblivé čárce Vidíme, že tělo funkce operator + je definováno běžným způsobem neobvyklé je pouze její záhlaví. Rovněž oba konstruktory i další tři funkce třídy jsou definovány obvyklým způsobem. Závěrem ještě podotkněme, že i iostream funkce vstupu a výstupu jsou v jazyce C++ vytvořeny redefinicí operátorů. 10
#include <iostream> #include <iomanip> // aby se mohl nastavit vyžádaný počet desetinných míst #include "fraction.h" using namespace std; // aby se nemuselo neustále používat std:: (všude!) int main() // čtyři konstruktory fraction f1(3, 2), f2(4), f3, f4; // f4 bude použit pro uložení součtu // zobrazení vytvořených zlomků cout << "The fraction f1 is "; f1.show(); cout << endl << "The fraction f2 is "; f2.show(); cout << endl << "The fraction f3 is "; f3.show(); // zadání vlastního zlomku: cout << endl << "Enter a fraction of your own: "; f3.get(); cout << "You entered "; f3.show(); // sečtení zlomků pomocí přetíženého operátoru f4 = f1 + f3; // zobrazení zlomku a výsledku cout << endl << "The sum of "; f1.show(); cout << " and "; f3.show(); cout << " is "; f4.show(); // výpočet a zobrazení hodnoty zlomku v pohyblivé čárce cout << endl << "The value of this fraction is " << setprecision(20) << f4.evaluate() << endl; return 0; 11
Výstup programu potvrzuje správnou funkci sčítání zlomků jakož i to, že redefinované funkce vstupu a výstupu v C++ jsou schopny pracovat s proměnnými long double (funkce setprecision zajistila výpis na dvacet platných cifer). The fraction f1 is 3/2 The fraction f2 is 4/1 The fraction f3 is 0/1 Enter a fraction of your own: 2/3 You entered 2/3 The sum of 3/2 and 2/3 is 13/6 The value of this fraction is 2.1666666666666666667 12
Obsah 1 Základní idea C++ koncepce třídy (class) 2 2 Konstruktory 4 3 Destruktory 5 4 Friend funkce 8 5 Přetížení operátoru 9 13