polymorphism c
Rola polimorfizmu w C ++ z przykładami.
Polimorfizm jest jednym z czterech filarów programowania obiektowego. Polimorfizm oznacza posiadanie wielu form. Można to zdefiniować jako technikę, dzięki której obiekt może przybierać różne formy w zależności od sytuacji.
Mówiąc językiem programowania, możemy powiedzieć, że obiekt może zachowywać się inaczej w różnych warunkach.
W tym samouczku dowiemy się szczegółowo o typach polimorfizmu, sposobach implementacji polimorfizmu oraz o różnych innych koncepcjach polimorfizmu.
=> Sprawdź tutaj, aby zobaczyć samouczki szkoleniowe od A do Z języka C ++ tutaj.
Na przykład, kobieta może przyjąć wiele ról w różnych sytuacjach. Dla dziecka jest matką, gospodynią domową, pracownicą w biurze itp. Kobieta przyjmuje więc różne role i wykazuje różne zachowania w różnych warunkach. To jest prawdziwy przykład polimorfizmu.
Podobnie w świecie programowania możemy mieć operator „+”, czyli binarny operator dodawania, który zachowuje się inaczej, gdy zmieniają się operandy. Na przykład, gdy oba operandy są numeryczne, wykonuje dodawanie.
Z drugiej strony, gdy operandy są łańcuchami, działa jako operator konkatenacji. Zatem polimorfizm, w skrócie, oznacza byt, który przybiera wiele form lub zachowuje się inaczej w różnych warunkach.
Czego się nauczysz:
- Rodzaje polimorfizmu
- Compile Time Polymorphism vs. Polimorfizm w czasie wykonywania
- Polimorfizm czasu kompilacji
- Przeciążanie funkcji
- Przeciążanie operatorów
- Wniosek
- rekomendowane lektury
Rodzaje polimorfizmu
Polimorfizm dzieli się na dwa typy.
- Polimorfizm czasu kompilacji
- Polimorfizm w czasie wykonywania
Schemat przedstawiający to pokazano poniżej:
Jak pokazano na powyższym diagramie, polimorfizm dzieli się na polimorfizm w czasie kompilacji i polimorfizm w czasie wykonywania. Polimorfizm czasu kompilacji jest dalej podzielony na przeciążanie operatorów i przeciążanie funkcji. Polimorfizm w czasie wykonywania jest dalej implementowany przy użyciu funkcji wirtualnych.
Polimorfizm czasu kompilacji jest również znany jako wczesne wiązanie lub polimorfizm statyczny. W tym typie polimorfizmu metoda obiektu jest wywoływana w czasie kompilacji. W przypadku polimorfizmu w czasie wykonywania metoda obiektu jest wywoływana w czasie wykonywania.
Polimorfizm w czasie wykonywania jest również znany jako dynamiczne lub późne wiązanie lub dynamiczny polimorfizm. W kolejnych tematach przyjrzymy się szczegółowej implementacji każdej z tych technik.
Compile Time Polymorphism vs. Polimorfizm w czasie wykonywania
Zobaczmy poniżej główne różnice między czasem kompilacji a polimorfizmem w czasie wykonywania.
Polimorfizm czasu kompilacji | Polimorfizm w czasie wykonywania |
---|---|
Znany również jako statyczny polimorfizm lub wczesne wiązanie | Znany również jako dynamiczny polimorfizm lub późne / dynamiczne wiązanie |
Objects jest wywoływana w czasie kompilacji | Metoda obiektu jest wywoływana w czasie wykonywania |
Zwykle implementowane przy użyciu przeciążania operatorów i funkcji | Realizowane przy użyciu funkcji wirtualnych i przesłaniania metod |
Przeciążanie metod to polimorfizm w czasie kompilacji, w którym więcej niż jedna metoda może mieć tę samą nazwę, ale różne listy parametrów i typy. | Nadpisywanie metody to polimorfizm w czasie wykonywania, w którym więcej niż jedna metoda ma tę samą nazwę i ten sam prototyp |
Ponieważ metody są znane w czasie kompilacji, wykonanie jest szybsze | Wykonywanie jest wolniejsze, ponieważ metoda jest znana w czasie wykonywania |
Zapewnij mniejszą elastyczność wdrażania rozwiązań, ponieważ wszystko musi być znane w czasie kompilacji | Znacznie bardziej elastyczne we wdrażaniu złożonych rozwiązań, ponieważ metody są określane w czasie wykonywania |
Polimorfizm czasu kompilacji
Polimorfizm w czasie kompilacji to technika, w której metoda obiektu jest wywoływana w czasie kompilacji.
Ten typ polimorfizmu jest realizowany na dwa sposoby.
- Przeciążanie funkcji
- Przeciążanie operatorów
Szczegółowo omówimy każdą technikę.
Przeciążanie funkcji
Mówi się, że funkcja jest przeciążona, gdy mamy więcej niż jedną funkcję o tej samej nazwie, ale z różnymi typami parametrów lub inną liczbą argumentów.
W ten sposób funkcja może zostać przeciążona na podstawie typów parametrów, kolejności parametrów i liczby parametrów.
Należy zauważyć, że dwie funkcje o tej samej nazwie i tej samej liście parametrów, ale o różnych typach zwracanych wartości nie są przeciążoną funkcją i spowodują błąd kompilacji, jeśli zostaną użyte w programie.
Podobnie, gdy parametry funkcji różnią się tylko wskaźnikiem i jeśli typ tablicy jest równoważny, nie należy go używać do przeciążania.
Inne typy, takie jak statyczne i niestatyczne, stałe i zmienne itp. Lub deklaracje parametrów, które różnią się obecnością lub brakiem wartości domyślnych, również nie powinny być używane do przeciążania, ponieważ są równoważne z punktu widzenia implementacji.
Na przykład,następujące prototypy funkcji są funkcjami przeciążonymi.
Add(int,int); Add(int,float); Add(float,int); Add(int,int,int);
W powyższych prototypach widzimy, że przeciążamy funkcję Dodaj na podstawie typu parametrów, kolejności lub kolejności parametrów, liczby parametrów itp.
Weźmy pełny przykład programowania, aby lepiej zrozumieć przeciążanie funkcji.
#include #include using namespace std; class Summation { public: int Add(int num1,int num2) { return num1+num2; } int Add(int num1,int num2, int num3) { return num1+num2+num3; } string Add(string s1,string s2){ return s1+s2; } }; int main(void) { Summation obj; cout< Wynik:
35
191
19
Witaj świecie
W powyższym programie mamy klasę Summation, która definiuje trzy przeciążone funkcje o nazwie Add, które pobierają dwa argumenty liczb całkowitych, trzy argumenty całkowite i dwa argumenty łańcuchowe.
W funkcji głównej wykonujemy cztery wywołania funkcji, które zapewniają różne parametry. Pierwsze dwa wywołania funkcji są proste. W trzecim wywołaniu funkcji Add jako argumenty podajemy dwie wartości zmiennoprzecinkowe.
W tym przypadku dopasowywana funkcja to int Add (int, int), ponieważ wewnętrznie zmiennoprzecinkowa jest konwertowana na double, a następnie dopasowywana do funkcji z parametrami int. Gdybyśmy określili double zamiast float, mielibyśmy inną przeciążoną funkcję z parametrami double jako parametrami.
Ostatnie wywołanie funkcji używa wartości ciągu jako parametrów. W tym przypadku operator Add (+) działa jak operator konkatenacji i łączy dwie wartości ciągów w celu utworzenia pojedynczego ciągu.
Korzyści z przeciążania funkcji
Główną zaletą przeciążania funkcji jest promowanie możliwości ponownego wykorzystania kodu. Możemy mieć jak najwięcej funkcji o tej samej nazwie, o ile są one przeciążone w oparciu o typ argumentu, sekwencję argumentów i liczbę argumentów.
W ten sposób łatwiej jest mieć różne funkcje o tej samej nazwie, które reprezentują zachowanie tej samej operacji w różnych warunkach.
Gdyby nie było przeciążenia funkcji, musielibyśmy napisać zbyt wiele różnych rodzajów funkcji o różnych nazwach, przez co kod byłby nieczytelny i trudny do dostosowania.
Przeciążanie operatorów
Przeciążanie operatorów to technika, za pomocą której nadajemy inne znaczenie istniejącym operatorom w C ++. Innymi słowy, przeciążamy operatory, aby nadać specjalne znaczenie typom danych zdefiniowanym przez użytkownika jako obiektom.
Większość operatorów w C ++ jest przeciążona lub ma specjalne znaczenie, dzięki czemu mogą pracować na typach danych zdefiniowanych przez użytkownika. Zwróć uwagę, że podczas przeciążania podstawowe działanie operatorów nie ulega zmianie. Przeciążanie po prostu nadaje operatorowi dodatkowe znaczenie, utrzymując ich podstawową semantyczną tę samą.
Chociaż większość operatorów może być przeciążona w C ++, istnieje kilka operatorów, których nie można przeciążać.
Te operatory są wymienione w poniższej tabeli.
Operatorzy Operator rozpoznawania zakresu (: :) Rozmiar selektor elementu (.) selektor wskaźnika elementu członkowskiego (*) operator trójskładnikowy (? :)
Funkcje, których używamy do przeciążania operatorów, nazywamy „ Funkcje operatora ”.
Funkcje operatora są podobne do zwykłych funkcji, ale z pewną różnicą. Różnica polega na tym, że nazwa funkcji operatora zaczyna się od słowa kluczowego „ operator ”, Po którym następuje symbol operatora, który ma zostać przeciążony.
Funkcja operatora jest wtedy wywoływana, gdy w programie używany jest odpowiedni operator. Te funkcje operatora mogą być funkcjami składowymi lub metodami globalnymi, a nawet funkcją zaprzyjaźnioną.
Ogólna składnia funkcji operatora to:
return_type classname::operator op(parameter list) { //function body }
Tutaj „operator op” to funkcja operatora, w której operator jest słowem kluczowym, a op jest operatorem, który ma zostać przeciążony. Return_type to typ wartości, która ma zostać zwrócona.
Zobaczmy kilka przykładów programowania, aby zademonstrować przeciążenie operatora za pomocą funkcji operatora.
Przykład 1:Przeciążenie operatora jednoargumentowego przy użyciu funkcji operatora składowego.
#include using namespace std; class Distance { public: int feet; // Constructor to initialize the object's value Distance(int feet) { this->feet = feet; } //operator function to overload ++ operator to perform increment on Distance obj void operator++() { feet++; } void print(){ cout << '
Incremented Feet value: ' << feet; } }; int main() { Distance d1(9); // Use (++) unary operator ++d1; d1.print(); return 0; }
Wynik:
Zwiększona wartość stopy: 10
W tym miejscu przeładowaliśmy jednoargumentowy operator inkrementacji za pomocą funkcji operator ++. W funkcji głównej używamy operatora this ++ do inkrementacji obiektu klasy Distance.
Przykład 2:Przeciążenie operatora binarnego przy użyciu funkcji operatora elementu członkowskiego.
#include using namespace std; class Complex { int real, imag; public: Complex(int r = 0, int i =0) {real = r; imag = i;} //Operator function to overload binary + to add two complex numbers Complex operator + (Complex const &obj) { Complex c3; c3.real = real + obj.real; c3.imag = imag + obj.imag; return c3; } void print() { cout << real << ' + i' << imag << endl; } }; int main() { Complex c1(2, 5), c2(3, 7); cout<<'c1 = '; c1.print(); cout<<'c2 = '; c2.print(); cout<<'c3 = c1+c2 = '; Complex c3 = c1 + c2; // calls overloaded + operator c3.print(); }
Wynik:
c1 = 2 + i5
c2 = 3 + i7
c3 = c1 + c2 = 5 + i12
Tutaj użyliśmy klasycznego przykładu dodawania dwóch liczb zespolonych przy użyciu przeciążenia operatora. Definiujemy klasę reprezentującą liczby zespolone oraz funkcję operatora do przeciążenia + operator, w którym dodajemy części rzeczywiste i urojone liczb zespolonych.
W funkcji głównej deklarujemy dwa złożone obiekty i dodajemy je za pomocą operatora przeciążonego +, aby uzyskać pożądany wynik.
W poniższym przykładzie użyjemy funkcji znajomego, aby dodać dwie liczby zespolone, aby zobaczyć różnicę w implementacji.
#include using namespace std; class Complex { int real, imag; public: Complex(int r = 0, int i =0) {real = r; imag = i;} //friend function to overload binary + to add two complex numbers friend Complex operator +(Complex const &, Complex const &); void print() { cout << real << ' + i' << imag << endl; } }; Complex operator + (Complex const &c1, Complex const &c2) { Complex c3; c3.real = c1.real + c2.real; c3.imag = c1.imag + c2.imag; return c3; } int main() { Complex c1(2, 5), c2(3, 7); cout<<'c1 = '; c1.print(); cout<<'c2 = '; c2.print(); cout<<'c3 = c1+c2 = '; Complex c3 = c1 + c2; // calls overloaded + operator c3.print(); }
Wynik:
c1 = 2 + i5
c2 = 3 + i7
c3 = c1 + c2 = 5 + i12
Widzimy, że wynik programu jest taki sam. Jedyną różnicą w implementacji jest użycie funkcji zaprzyjaźnionej do przeciążenia operatora + zamiast funkcji składowej w poprzedniej implementacji.
Kiedy funkcja zaprzyjaźniona jest używana jako operator binarny, musimy jawnie określić oba operandy funkcji. Podobnie, gdy operator jednoargumentowy jest przeciążony funkcją zaprzyjaźnioną, musimy dostarczyć do funkcji pojedynczy operand.
Oprócz funkcji operatorskich możemy również napisać plik operator konwersji który jest używany do konwersji z jednego typu na inny. Te przeciążone operatory konwersji powinny być funkcją składową klasy.
Przykład 3:Przeciążanie operatorów za pomocą operatora konwersji.
#include using namespace std; class DecFraction { int numerator, denom; public: DecFraction(int num, int denm) { numerator = num; denom = denm; } // conversion operator: converts fraction to float value and returns it operator float() const { return float(numerator) / float(denom); } }; int main() { DecFraction df(3, 5); //object of class float res_val = df; //calls conversion operator cout << 'The resultant value of given fraction (3,5)= '< Wynik:
Wynikowa wartość podanego ułamka (3,5) = 0,6
W tym programie użyliśmy operatora konwersji, aby zamienić podany ułamek na wartość zmiennoprzecinkową. Po zakończeniu konwersji operator konwersji zwraca wynikową wartość do dzwoniącego.
W funkcji głównej, gdy przypiszemy obiekt df do zmiennej res_val, następuje konwersja i wynik jest przechowywany w res_val.
Możemy również wywołać konstruktor z jednym argumentem. Kiedy możemy wywołać konstruktor z klasy przy użyciu pojedynczego argumentu, nazywa się to „ konwersja budowniczy ”. Konstruktor konwersji może służyć do niejawnej konwersji do konstruowanej klasy.
#include using namespace std; class Point { private: int x,y; public: Point(int i=0,int j=0) {x = i;y=j;} void print() { cout<<' x = '< Wynik:
Punkt skonstruowany przy użyciu zwykłego konstruktora
x = 20 y = 30
Punkt skonstruowany przy użyciu konstruktora konwersji
x = 10 y = 0
Tutaj mamy klasę Point, która definiuje konstruktor z domyślnymi wartościami. W funkcji main konstruujemy obiekt pt o współrzędnych x i y. Następnie po prostu przypisujemy pt wartość 10. W tym miejscu wywoływany jest konstruktor konwersji i x ma przypisaną wartość 10, podczas gdy y otrzymuje domyślną wartość 0.
Reguły przeciążania operatorów
Wykonując przeładowanie operatorów musimy zwrócić uwagę na poniższe zasady.
- W C ++ jesteśmy w stanie przeciążać tylko istniejące operatory. Nowo dodanych operatorów nie można przeciążać.
- Kiedy operatory są przeciążone, musimy upewnić się, że co najmniej jeden z operandów jest typu zdefiniowanego przez użytkownika.
- Aby przeciążać niektóre operatory, możemy również skorzystać z funkcji zaprzyjaźnionej.
- Kiedy przeciążamy operatory jednoargumentowe za pomocą funkcji składowej, nie przyjmuje ona żadnych jawnych argumentów. Pobiera jeden jawny argument, gdy jednoargumentowy operator jest przeciążony przy użyciu funkcji znajomego.
- Podobnie, gdy operatory binarne są przeciążane za pomocą funkcji składowej, musimy podać jeden jawny argument do funkcji. Gdy operatory binarne są przeciążane za pomocą funkcji zaprzyjaźnionej, funkcja przyjmuje dwa argumenty.
- Istnieją dwa operatory w C ++, które są już przeciążone. To są „=” i „&”. Dlatego, aby skopiować obiekt tej samej klasy, nie musimy przeciążać operatora = i możemy go użyć bezpośrednio.
Zalety przeciążenia operatorów
Przeciążenie operatorów w C ++ pozwala nam rozszerzyć funkcjonalność operatorów o typy zdefiniowane przez użytkownika, w tym obiekty klas, oprócz typów wbudowanych.
Rozszerzając funkcjonalność operatora na typy zdefiniowane przez użytkownika, nie musimy pisać złożonego kodu do wykonywania różnych operacji na typach zdefiniowanych przez użytkownika, ale możemy to zrobić w jednej operacji, tak jak na typach wbudowanych.
Wniosek
Polimorfizm czasu kompilacji zapewnia możliwość przeciążania głównie w celu rozszerzenia funkcjonalności kodu w zakresie przeciążania funkcji i operatorów.
bąbelkowy kod sortowania c ++
Poprzez przeciążenie funkcji możemy napisać więcej niż jedną funkcję o tej samej nazwie, ale o różnych parametrach i typach. Dzięki temu kod jest prosty i czytelny. Przeciążając operatorów, możemy rozszerzyć funkcjonalność operatorów, dzięki czemu możemy wykonywać podstawowe operacje również na typach zdefiniowanych przez użytkownika.
W naszym nadchodzącym samouczku dowiemy się więcej o polimorfizmie środowiska uruchomieniowego w C ++.
=> Przeczytaj serię szkoleń Easy C ++.
rekomendowane lektury
- Polimorfizm środowiska uruchomieniowego w C ++
- Funkcje znajomego w C ++
- Rekursja w C ++
- Samouczek dotyczący głównych funkcji języka Python z praktycznymi przykładami
- Pełne omówienie C ++
- Samouczek QTP nr 21 - Jak uczynić testy QTP modułowymi i wielokrotnego użytku za pomocą akcji i bibliotek funkcji
- Samouczek potoków w systemie Unix: Potoki w programowaniu w systemie Unix
- Funkcje biblioteczne w C ++