c++ شرح - تعدد الأشكال في ج++




معنى علم (7)

بقدر ما أعلم:

يوفر C ++ ثلاثة أنواع مختلفة من تعدد الأشكال.

  • وظائف افتراضية
  • اسم الوظيفة الزائد
  • زيادة الحمولة من جانب المشغل

بالإضافة إلى الأنواع الثلاثة المذكورة أعلاه من تعدد الأشكال ، هناك أنواع أخرى من تعدد الأشكال:

  • وقت التشغيل
  • وقت الترجمة
  • تعدد الأشكال المخصص
  • بارامترايفي الشكل

وأنا أعلم أن تعدد الأشكال وقت التشغيل يمكن أن يتحقق عن طريق وظائف افتراضية ويمكن أن يتحقق تعدد الأشكال الثابت بواسطة وظائف القالب

لكن للاثنين الآخرين

  • تعدد الأشكال المخصص
  • تعدد الأشكال البارامترية يقول الموقع ،

تعدد الأشكال المخصص:

إذا كان نطاق الأنواع الفعلية التي يمكن استخدامها محدودًا ويجب أن يتم تحديد المجموعات بشكل فردي قبل الاستخدام ، فإن هذا يسمى تعدد أشكال ad-hoc.

تعدد الأشكال البارامترية:

إذا تمت كتابة جميع التعليمات البرمجية دون ذكر أي نوع معين ، وبالتالي يمكن استخدامها بشفافية مع أي عدد من الأنواع الجديدة ، يطلق عليها تعدد الأشكال parametric.

أنا بالكاد أفهمها :(

يمكن لأي شخص أن يفسر لهم كلاهما إذا كان ذلك ممكنا مع مثال؟ آمل أن تكون الإجابات على هذه الأسئلة مفيدة للعديد من المعاصرين الجدد من كلياتهم.


Answers

إذا كان أي شخص يقول CUT لهؤلاء الناس

The Surgeon
The Hair Stylist
The Actor

ماذا سيحدث؟

The Surgeon would begin to make an incision.
The Hair Stylist would begin to cut someone's hair.
The Actor would abruptly stop acting out of the current scene, awaiting directorial guidance.

So above representation shows What is polymorphism (same name, different behavior) in OOP.

If you are going for an interview and interviewer asks you tell/show a live example for polymorphism in the same room we are sitting at, say-

Answer - Door / Windows

Wondering How?

Through Door / Window - a person can come, air can come, light can come, rain can come, etc.

ie One form different behavior(Polymorphism).

To understand it better and in a simple manner I used above example.. If you need reference for code follow above answers.


قد لا يكون هذا مفيدًا ، لكنني قمت بذلك لتقديم أصدقائي للبرمجة من خلال إعطاء وظائف محددة ، مثل START ، و END للوظيفة الرئيسية ، لذا لم تكن شاقة للغاية (استخدموا ملف main.cpp فقط). أنه يحتوي على طبقات متعددة الأشكال والبنى ، والقوالب ، وناقلات ، صفائف ، توجيهات preproccessor ، الصداقة ، والمشغلين والمؤشرات (كل ما يجب أن تعرفه قبل محاولة تعدد الأشكال):

ملاحظة: لم يتم الانتهاء من ذلك ، ولكن يمكنك الحصول على هذه الفكرة

MAIN.CPP

#include "main.h"
#define ON_ERROR_CLEAR_SCREEN false
START
    Library MyLibrary;
    Book MyBook("My Book", "Me");
    MyBook.Summarize();
    MyBook += "Hello World";
    MyBook += "HI";
    MyBook.EditAuthor("Joe");
    MyBook.EditName("Hello Book");
    MyBook.Summarize();
    FixedBookCollection<FairyTale> FBooks("Fairytale Books");
    FairyTale MyTale("Tale", "Joe");
    FBooks += MyTale;
    BookCollection E("E");
    MyLibrary += E;
    MyLibrary += FBooks;
    MyLibrary.Summarize();
    MyLibrary -= FBooks;
    MyLibrary.Summarize();
    FixedSizeBookCollection<5> Collection("My Fixed Size Collection");
    /* Extension Work */ Book* Duplicate = MyLibrary.DuplicateBook(&MyBook);
    /* Extension Work */ Duplicate->Summarize();
END

main.h

#include <iostream>
#include <sstream>
#include <vector>
#include <string>
#include <type_traits>
#include <array>
#ifndef __cplusplus
#error Not C++
#endif
#define START int main(void)try{
#define END GET_ENTER_EXIT return(0);}catch(const std::exception& e){if(ON_ERROR_CLEAR_SCREEN){system("cls");}std::cerr << "Error: " << e.what() << std::endl; GET_ENTER_EXIT return (1);}
#define GET_ENTER_EXIT std::cout << "Press enter to exit" << std::endl; getchar();
class Book;
class Library;
typedef std::vector<const Book*> Books;
bool sContains(const std::string s, const char c){
    return (s.find(c) != std::string::npos);
}
bool approve(std::string s){
    return (!sContains(s, '#') && !sContains(s, '%') && !sContains(s, '~'));
}
template <class C> bool isBook(){
    return (typeid(C) == typeid(Book) || std::is_base_of<Book, C>());
}
template<class ClassToDuplicate> class DuplicatableClass{ 
public:
    ClassToDuplicate* Duplicate(ClassToDuplicate ToDuplicate){
        return new ClassToDuplicate(ToDuplicate);
    }
};
class Book : private DuplicatableClass<Book>{
friend class Library;
friend struct BookCollection;
public:
    Book(const char* Name, const char* Author) : name_(Name), author_(Author){}
    void operator+=(const char* Page){
        pages_.push_back(Page);
    }
    void EditAuthor(const char* AuthorName){
        if(approve(AuthorName)){
            author_ = AuthorName;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The author of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    void EditName(const char* Name){
        if(approve(Name)){
            name_ = Name;
        }
        else{
            std::ostringstream errorMessage;
            errorMessage << "The name of the book " << name_ << " could not be changed as it was not approved";
            throw std::exception(errorMessage.str().c_str());
        }
    }
    virtual void Summarize(){
        std::cout << "Book called " << name_ << "; written by " << author_ << ". Contains "
            << pages_.size() << ((pages_.size() == 1) ? " page:" : ((pages_.size() > 0) ? " pages:" : " pages")) << std::endl;
        if(pages_.size() > 0){
            ListPages(std::cout);
        }
    }
private:
    std::vector<const char*> pages_;
    const char* name_;
    const char* author_;
    void ListPages(std::ostream& output){
        for(int i = 0; i < pages_.size(); ++i){
            output << pages_[i] << std::endl;
        }
    }
};
class FairyTale : public Book{
public:
    FairyTale(const char* Name, const char* Author) : Book(Name, Author){}
};
struct BookCollection{
friend class Library;
    BookCollection(const char* Name) : name_(Name){}
    virtual void operator+=(const Book& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    virtual void operator-=(const Book& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    Books Collection;
};
template<class FixedType> struct FixedBookCollection : public BookCollection{
    FixedBookCollection(const char* Name) : BookCollection(Name){
        if(!isBook<FixedType>()){
            std::ostringstream errorMessage;
            errorMessage << "The type " << typeid(FixedType).name() << " cannot be initialized as a FixedBookCollection";
            throw std::exception(errorMessage.str().c_str());
            delete this;
        }
    }
    void operator+=(const FixedType& Book)try{
        Collection.push_back(&Book); 
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
    void operator-=(const FixedType& Book){
        for(int i = 0; i < Collection.size(); ++i){
            if(Collection[i] == &Book){
                Collection.erase(Collection.begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The Book " << Book.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
private:
    std::vector<const FixedType*> Collection;
};
template<size_t Size> struct FixedSizeBookCollection : private std::array<const Book*, Size>{
    FixedSizeBookCollection(const char* Name) : name_(Name){ if(Size < 1){ throw std::exception("A fixed size book collection cannot be smaller than 1"); currentPos = 0; } }
    void operator+=(const Book& Book)try{
        if(currentPos + 1 > Size){
            std::ostringstream errorMessage;
            errorMessage << "The FixedSizeBookCollection " << name_ << "'s size capacity has been overfilled";
            throw std::exception(errorMessage.str().c_str());
        }
        this->at(currentPos++) = &Book;
    }catch(const std::exception& e){
        std::ostringstream errorMessage;
        errorMessage << e.what() << " - on line (approx.) " << (__LINE__ -3);
        throw std::exception(errorMessage.str().c_str());
    }
private:
    const char* name_;
    int currentPos;
};
class Library : private std::vector<const BookCollection*>{
public:
    void operator+=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                std::ostringstream errorMessage;
                errorMessage << "The BookCollection " << Collection.name_ << " was already in the library, and therefore cannot be added";
                throw std::exception(errorMessage.str().c_str());
            }
        }
        push_back(&Collection);
    }
    void operator-=(const BookCollection& Collection){
        for(int i = 0; i < size(); ++i){
            if((*this)[i] == &Collection){
                erase(begin() + i);
                return;
            }
        }
        std::ostringstream errorMessage;
        errorMessage << "The BookCollection " << Collection.name_ << " was not found, and therefore cannot be erased";
        throw std::exception(errorMessage.str().c_str());
    }
    Book* DuplicateBook(Book* Book)const{
        return (Book->Duplicate(*Book));
    }
    void Summarize(){
        std::cout << "Library, containing " << size() << ((size() == 1) ? " book collection:" : ((size() > 0) ? " book collections:" : " book collections")) << std::endl;
        if(size() > 0){
            for(int i = 0; i < size(); ++i){
                std::cout << (*this)[i]->name_ << std::endl;
            }
        }
    }
};

هنا مثال أساسي باستخدام الطبقات متعدد الأشكال

#include <iostream>

class Animal{
public:
   Animal(const char* Name) : name_(Name){/* Add any method you would like to perform here*/
    virtual void Speak(){
        std::cout << "I am an animal called " << name_ << std::endl;
    }
    const char* name_;
};

class Dog : public Animal{
public:
    Dog(const char* Name) : Animal(Name) {/*...*/}
    void Speak(){
        std::cout << "I am a dog called " << name_ << std::endl;
    }
};

int main(void){
    Animal Bob("Bob");
    Dog Steve("Steve");
    Bob.Speak();
    Steve.Speak();
    //return (0);
}

ويعني تعدد الأشكال العديد من الأشكال على هذا النحو يتم استخدامه لمشغل للتعامل بشكل مختلف في حالات مختلفة. يستخدم تعدد الأشكال لتنفيذ الميراث. على سبيل المثال ، حددنا رسم fn () لشكل صف ، ثم يمكن تنفيذ الرسم fn لرسم الدائرة والمربع والمثلث والأشكال الأخرى. (وهي كائنات من شكل الطبقة)


في C ++ ، التمييز المهم هو وقت التشغيل مقابل الربط وقت التجميع. لا تساعد ميزة parametric المخصصة في الحقيقة ، كما سأوضح لاحقًا.

|----------------------+--------------|
| Form                 | Resolved at  |
|----------------------+--------------|
| function overloading | compile-time |
| operator overloading | compile-time |
| templates            | compile-time |
| virtual methods      | run-time     |
|----------------------+--------------|

ملاحظة - قد يتم حل تعدد الأشكال وقت التشغيل في وقت التحويل البرمجي ، ولكن هذا هو التحسين فقط. إن الحاجة إلى دعم دقة وقت التشغيل بكفاءة ، والتداول مقابل قضايا أخرى ، هي جزء مما أدى إلى أن الوظائف الافتراضية هي ما هي عليه. وهذا هو المفتاح الحقيقي لجميع أشكال تعدد الأشكال في C ++ - وينشأ كل من مجموعات مختلفة من المقايضات في سياق مختلف.

التحميل الزائد للوظيفة والحمولة الزائدة على المشغل هي نفس الشيء بكل الطرق المهمة. لا تؤثر الأسماء والبنية المستخدمة في استخدامها على تعدد الأشكال.

تتيح لك القوالب تحديد الكثير من الأحمال الزائدة في الوظيفة دفعة واحدة.

هناك مجموعة أخرى من الأسماء لنفس فكرة وقت الاستبصار ...

|---------------+--------------|
| early binding | compile-time |
| late binding  | run-time     |
|---------------+--------------|

ترتبط هذه الأسماء بشكل أكبر بـ OOP ، لذلك من الغريب أن نقول أن القالب أو وظيفة أخرى غير الأعضاء تستخدم الربط المبكر.

لفهم العلاقة بين الوظائف الظاهرية والوظيفة الزائدة بشكل أفضل ، من المفيد أيضًا فهم الفرق بين "الإرسال المفرد" و "الإرسال المتعدد". يمكن فهم الفكرة على أنها تقدم ...

  • أولا ، هناك وظائف monomorphic. يتم تعريف تنفيذ الدالة بشكل فريد من خلال اسم الدالة. لا شيء من المعلمات هو خاص.
  • ثم هناك إرسال واحد. تعتبر إحدى المعلمات خاصة ، وتستخدم (مع الاسم) لتحديد أي تطبيق يتم استخدامه. في OOP ، نميل إلى التفكير في هذه المعلمة على أنها "الكائن" ، ثم سردها قبل اسم الدالة وما إلى ذلك.
  • ثم ، هناك إرسال متعددة. تساهم أي / جميع المعلمات في تحديد التطبيق الذي سيتم استخدامه. لذلك ، مرة أخرى ، لا تحتاج أي معلمة أن تكون خاصة.

من الواضح أن هناك أكثر من OOP من ذريعة ترشيح معلمة واحدة خاصة ، ولكن هذا جزء واحد منها. ويرتبط إلى ما قلته حول المبادلات - الإرسال المنفرد من السهل القيام به بكفاءة (يسمى التنفيذ المعتاد "الجداول الافتراضية"). الإرسال المتعدد أكثر صعوبة ، ليس فقط من حيث الكفاءة ، ولكن أيضًا من أجل تجميع منفصل. إذا كنت فضوليًا ، فقد تبحث عن "مشكلة التعبير".

كما أنه من الغريب بعض الشيء استخدام مصطلح "الربط المبكر" للوظائف غير الأعضاء ، فمن الغريب بعض الشيء استخدام المصطلحات "إرسال مفرد" و "إرسال متعدد" حيث يتم حل تعدد الأشكال في وقت التحويل البرمجي. عادة ، يعتبر C ++ عدم إرسال متعددة ، والتي تعتبر نوع معين من دقة وقت التشغيل. ومع ذلك ، يمكن أن ينظر إلى زيادة الحمولة الوظيفية على أنها عملية إرسال متعددة تتم في وقت التحويل البرمجي.

العودة إلى parametric مقابل تعدد الأشكال الخاص ، هذه الشروط هي أكثر شعبية في البرمجة الوظيفية ، وأنها لا تعمل تماما في C ++. بالرغم من ذلك...

يعني تعدد الأشكال البارامتيري أن لديك أنواع كمعلمات ، ويتم استخدام نفس الشفرة بالضبط بغض النظر عن النوع الذي تستخدمه لهذه المعلمات.

تعدد الأشكال الخاص هو مخصص بمعنى أنك توفر رمز مختلف اعتمادا على أنواع معينة.

الحمولة الزائدة والوظائف الافتراضية كلاهما أمثلة لتعدد الأشكال المخصص.

مرة أخرى ، هناك بعض المرادفات ...

|------------+---------------|
| parametric | unconstrained |
| ad-hoc     | constrained   |
|------------+---------------|

ما عدا هذه ليست مرادفات تماما ، على الرغم من أنهم يعاملون عادة كما لو كانوا ، وهذا هو المكان الذي من المرجح أن تنشأ الارتباك في C ++.

السبب وراء علاج هذه المرادفات هو أنه من خلال تقييد تعدد الأشكال إلى فئات معينة من الأنواع ، يصبح من الممكن استخدام العمليات الخاصة بتلك الفئات من الأنواع. يمكن تفسير كلمة "فئات" هنا في مفهوم OOP ، ولكن في الحقيقة تشير فقط إلى مجموعات (تسمى عادةً) من الأنواع التي تشترك في عمليات معينة.

عادة ما يتم أخذ تعدد الأشكال البارامتري (على الأقل افتراضيا) ليشير إلى تعدد أشكال غير مقيد. نظرًا لاستخدام نفس الرمز بغض النظر عن معلمات النوع ، فإن العمليات الوحيدة الداعمة هي تلك التي تعمل لجميع الأنواع. من خلال ترك مجموعة من الأنواع غير المقيدة ، فإنك تحد بشدة من مجموعة العمليات التي يمكنك تطبيقها على هذه الأنواع.

على سبيل المثال هاسكل ، يمكنك ...

myfunc1 :: Bool -> a -> a -> a
myfunc1 c x y = if c then x else y

a هنا هو نوع متعدد الأشكال غير المقيد. يمكن أن يكون أي شيء ، لذلك ليس هناك الكثير يمكننا القيام به مع قيم من هذا النوع.

myfunc2 :: Num a => a -> a
myfunc2 x = x + 3

هنا ، يكون a مقيد ليكون عضوًا في فئة Num - وهي أنواع تتصرف مثل الأرقام. يتيح لك هذا القيد القيام بأشياء رقمية مع تلك القيم ، مثل إضافتها. حتى الرقم 3 هو متعدد الأشكال - من نوع الاستدلال من الأرقام التي تعني 3 من النوع a .

أفكر في هذا كما تعدد الأشكال parametric مقيد. هناك تطبيق واحد فقط ، ولكن لا يمكن تطبيقه إلا في حالات مقيدة. والجانب المخصص هو اختيار + و 3 لاستخدامه. كل "مثيل" من Num له تطبيق مميز خاص به. حتى في هاسكل "parametric" و "unconstrained" ليست مترادفات حقاً - لا تلومني ، ليس خطأي!

في C ++ ، كل من التحميل الزائد والوظائف الافتراضية هي تعدد أشكال مخصص. تعريف تعدد الأشكال المخصص لا يهم ما إذا كان يتم تحديد التنفيذ في وقت التشغيل أو وقت التحويل البرمجي.

C ++ تصبح قريبة جداً من تعدد الأشكال البارامترية مع القوالب إذا كان لكل معلمة قالب اكتب typename . هناك معلمات الكتابة ، وهناك تطبيق واحد بغض النظر عن الأنواع التي يتم استخدامها. ومع ذلك ، فإن قاعدة "فشل الاستبدال ليس خطأ" تعني أن القيود الضمنية تنشأ كنتيجة لاستخدام العمليات داخل القالب. تتضمن المضاعفات الإضافية تخصيص القالب لتقديم نماذج بديلة - تطبيقات مختلفة (مخصصة).

لذلك بطريقة C ++ لها تعدد أشكال بارامترفي ، لكنها مقيدة ضمنيًا ويمكن أن يتم تجاوزها بواسطة بدائل مخصصة - أي أن هذا التصنيف لا يعمل حقًا لـ C ++.


أما بالنسبة لتعدد الأشكال المخصص ، فهذا يعني زيادة الحمولة الوظيفية أو التحميل الزائد على المشغل. تحقق من هنا:

http://en.wikipedia.org/wiki/Ad-hoc_polymorphism

بالنسبة إلى تعدد الأشكال البارامتري ، يمكن أيضًا حساب وظائف القالب لأنها لا تأخذ بالضرورة محددات لأنواع FIXED. على سبيل المثال ، يمكن لوظيفة واحدة فرز مصفوفة من الأعداد الصحيحة ويمكنها أيضًا فرز مصفوفة من السلاسل ، إلخ.

http://en.wikipedia.org/wiki/Parametric_polymorphism


انها

#include <stdio.h>
int main(void){
     int x = 10;

     while( x-- > 0 ){ // x goes to 0

       printf("%d ", x);
     }

     return 0;
}

مجرد الفضاء يجعل الأشياء تبدو مضحكة ، -- decrements و > يقارن.





c++ polymorphism c++-faq