C言語 -- オブジェクト指向プログラミングのポリモーフィズム

シリーズ記事ディレクトリ

C 言語 – オブジェクト指向プログラミングのカプセル化

C言語 – オブジェクト指向プログラミングの継承


序文

オブジェクト指向プログラミングを実現するC言語の記事第3回 前2回の記事では、オブジェクト指向のカプセル化と継承のC言語での実装について説明しましたが、ポリモーフィズムの理解と実装の方法について、C++での説明は最小限に抑えました。


1. ポリモーフィズムとは?

ポリモーフィズムとは、さまざまなタイプのオブジェクトに応じて同じインターフェイスを呼び出して、さまざまな機能を実現する機能を指します。C++ にはオーバーロードと呼ばれる関数があり、次の関数のように同様の効果があります。

例を参照してください:

/*函数重载*/
int add(int a,int b)
{
    
    
	return (a+b);
}

int add(int a,int b,int c)
{
    
    
	return (a+b+c);
}

int main(int argc, char** argv)
{
    
    
	int i,h,k;
	add(i,j);
	add(i,j,k);
}

次の関数は、コンパイラが同じ名前で異なるパラメーターが渡された関数を調べることで、異なる関数を呼び出すことができるというものです。C では、同じ名前の関数はエラーを報告しますが、C++ ではそうではありません (単に異なるパラメータ)。これはオーバーロードと呼ばれます. オーバーロードはポリモーフィズムに似ています. 同じ名前のインターフェイスを呼び出して、さまざまな受信パラメーターに応じてさまざまな機能を実現できます.

では、C言語でポリモーフィズムを実装する方法と、Cコンパイラには関数のオーバーロードの機能がありません。

–>答えは、関数ポインターと関数テーブルを使用して、さまざまな関数を呼び出して、初期化中にオブジェクトを作成することです.アドレス)、アクセスできるようにします.

グラフィック:

二、ポリモーフィズムを実現するC言語

1. ポリモーフィズムの基本的な実現原理

C言語はポリモーフィズムを実装しており、ポインタのメモリ割り当てと強制的な型変換を理解すれば、C言語におけるポリモーフィズムの原理を知ることができます。

図: 次のデモでは、実行時にオブジェクトが作成されます。コアは、関数テーブルにバインドされた関数が実際には多態性関数を呼び出さないため、さまざまな関数を実現できます。

呼び出された多形関数は、実際に関数ポインタを介してバインドされた関数を呼び出し、多形を実現します。関数ポインターを介してオブジェクト関数にアクセスできるだけでなく、多相関数を直接呼び出してさまざまなオブジェクトを渡し関数ポインターを間接的に呼び出してオブジェクトにアクセスすることもできます。

順序、つまりメモリの問題は、最初にバインドしてから実行する必要があることに注意してください。
ここに画像の説明を挿入

基本的な実装がわかったので、次のコードを見てみましょう。

2. 簡単なデモ

この簡単なデモは、前回のC 言語 - オブジェクト指向プログラミングの継承に関する記事の続きで、モンスターを倒してアップグレードする例を引き続き使用していますが、小さな変更があります。通常、ペットは通常攻撃を持ち、進化レベル 1 オブジェクトの通常攻撃アクションは進化レベル 2 オブジェクトとは異なるため、同じ通常攻撃関数を呼び出して、異なる進化レベルのオブジェクトに応じて異なるアクションを実現する方法.

これは、進化レベル 1 と進化レベル 2 が継承関係であるなどの制約条件であり、理解するのに便利です。もちろん、ポリモーフィズムは継承関係である必要はありません. 食べるという動作など、同じ機能を実行する異なるタイプのオブジェクトでもかまいません. 人間オブジェクトと猫オブジェクトは同じアクション関数を呼び出します, そして実装は異なる動作です. .

継承関係ではないポリモーフィズムを実装する方法についても、上記のメモリ割り当てと関数ポインタを理解することは非常に簡単です。

struct Base_skill_vtabl;
struct Child_skill_vtabl;

/*小米属性*/
typedef struct 
{
    
    
    char name[10];
    int Heal_value;
	/*函数表指针,C++如果使用的virtual关键词,虚化了称为虚指针,指向函数行为表*/
    struct Base_skill_vtabl const *vptr;
}Base_Pets;

/*函数行为表,C++如果使用的virtual关键词,称为虚表*/
struct Base_skill_vtabl
{
    
    
    int (*illet_skill)(Base_Pets * const Me);
    int (*ordinary_skill)(void * const Me);
};

/*大米属性*/

typedef struct 
{
    
    
	/*继承基类属性*/
    Base_Pets *base_pet;
	/*自己的属性*/
	int Magic_value;
    struct Child_skill_vtabl const *vptr;
}Child_Pets;

struct Child_skill_vtabl
{
    
    
    int (*rice_skill)(Child_Pets* const Me);
    int (*ordinary_skill)(void * const Me);
};

/*普通函数*/
int Rich_skill(Child_Pets* const Me);
int Illet_skill(Base_Pets * const Me);
int Child_Ordinary_skill(void * const Me);
int Base_Ordinary_skill(void * const Me);

/*多态函数*/
int Ordinary_skill(void * const Me);

//构建基类
Base_Pets * Ctor_base_Pets(char *name)
{
    
      
    Base_Pets *ptr;

    struct Base_skill_vtabl *table;
    /*分配空间*/
    ptr=(Base_Pets *)malloc(sizeof(Base_Pets));

    table=(struct Base_skill_vtabl *)malloc(sizeof(struct Base_skill_vtabl));    
    //清零
    memset(ptr,0,sizeof(struct Base_skill_vtabl));
    
    strcpy(ptr->name,name);

    ptr->vptr=table;

    table->illet_skill=&Illet_skill;
    table->ordinary_skill=&Base_Ordinary_skill;

    printf("create Base_Pets class\n");
    return ptr;

}
//删除对象/析构对象
void Del_Base_Pets(Base_Pets * const Me)
{
    
    

    printf("Destroy Base_Pets\n");
    if(Me!=NULL) 
    {
    
    
        free((void *)Me->vptr);
        free(Me);
    }
}

//继承基类对象
//构建基类
Child_Pets *Ctor_Child_Pets(char *name,int magic_value)
{
    
      
 
    Child_Pets *ptr;

    struct Child_skill_vtabl *table;
    /*分配空间*/
    ptr=(Child_Pets *)malloc(sizeof(Child_Pets));
    table=(struct Child_skill_vtabl *)malloc(sizeof(struct Child_skill_vtabl)); 

    //清零
    memset(ptr,0,sizeof(struct Child_skill_vtabl));
    
    ptr->base_pet=Ctor_base_Pets(name);
    ptr->Magic_value=10;

    ptr->vptr=table;

    table->rice_skill=&Rich_skill;
    table->ordinary_skill=&Child_Ordinary_skill;

    printf("create Child_Pets class\n");   
    return ptr;
}

//删除对象/析构对象
void Del_Child_Pets(Child_Pets * const Me)
{
    
    

    printf("Destroy Child_Pets\n");

    if(Me!=NULL) 
    {
    
    
        Del_Base_Pets(Me->base_pet);
        free((void *)Me->vptr);
        free(Me);
    }
}

int Rich_skill(Child_Pets* const Me)
{
    
    
    printf("use the rich skill\n");
    return 1;
}

int Illet_skill(Base_Pets * const Me)
{
    
    
    printf("use the illet skill\n");
    return 1;
}

/*调用接口函数,也就调用函数指针*/
inline int Ordinary_skill(void * const Me)
{
    
    
    Child_Pets * const _Me=(Child_Pets *)Me;
    /*这里仅仅是实现调用的操作*/
    return (_Me->vptr->ordinary_skill(Me));
}

/*这里是精髓,通过强制指针转换来达到访问不同对象的目的*/
int Base_Ordinary_skill(void * const Me)
{
    
    
    /*指针显示转换,就可以传入同一对象的指针,访问不同对象的内容*/
    Base_Pets const * const _Me=(Base_Pets * const)Me;
    printf("Base_Pets use Ordinary_skill,without magic\r\n");
    return 0;
}
/*这里是精髓,通过强制指针转换来达到访问不同对象的目的*/
int Child_Ordinary_skill(void * const Me)
{
    
    
    /*指针显示转换,就可以访问不同对象的内容*/
    Child_Pets const * const _Me=(Child_Pets * const)Me;
    
    printf("Child_Pets use Ordinary_skill,magic val is %d\r\n",_Me->Magic_value);
    
    return 0;
}

int main()
{
    
    
    //创建对象指针
    Child_Pets *test;

    //创建对象
    test=Ctor_Child_Pets("child",10);

    /*普通调用*/
    printf("samlpe function \r\n");
    test->base_pet->vptr->illet_skill(test->base_pet);
    test->vptr->rice_skill(test);
    test->vptr->ordinary_skill(test);
    printf("polymorphic function \r\n");
    
    /*多态调用*/
    Ordinary_skill(test);//传入子类指针
    Ordinary_skill(test->base_pet);//传入父类指针

    Del_Child_Pets(test);

    system("pause");

}

運用実績

create Base_Pets class
create Child_Pets class
samlpe function
use the illet skill
use the rich skill
Child_Pets use Ordinary_skill,magic val is 10
polymorphic function
Child_Pets use Ordinary_skill,magic val is 10
Base_Pets use Ordinary_skill,without magic
Destroy Child_Pets
Destroy Base_Pets
Press any key to continue . . .

3. 簡単な分析

ポリモーフィズムを実装する前に、メモリを割り当て、実行された関数をバインドして、関数ポインタを介して関数にアクセスできるようにする必要があります。メモリを割り当てた後、ポリモーフィズムを実装することは、必須の型変換をうまく利用し、ポインターの型を変換して別のオブジェクトを実行することです。インターフェイス関数を介して、関数ポインタが指す関数を実行します。

3. C++ 知識の追加導入

追加の紹介として、ポリモーフィズムを実現するために C 言語を学習する場合と比較して、 C++仮想ポインターと仮想関数の違いを大まかに理解するために、2 つの図のみを使用します。

C++ を学習していない場合は、画像を見るだけで十分です. C 言語と比較して、C++ のオブジェクト指向コンテンツで最も重要なことは、一部の実装が簡素化され、一部のメモリ解放と一部の構文が追加されて、オブジェクト指向の実現。オブジェクト指向の本質から出発すると、どちらも同じであり、オブジェクト指向の本質は思考です。

次に、最初にコードを見てみましょう: 引き続き上記の例を使用します

1. 仮想ポインタと仮想関数

下の表から、実際には C に似ていることがわかります.ただし、コンパイラは、関数のバインディングやポインターの呼び出しなど、多くのことを行ってくれます.バインディングと作成を表示する必要はありません.コンパイラは私たちを助けます.ばっちり成功。C++には実際に関数ポインタ(仮想ポインタ、型はポインタに似た仮想関数テーブル)があり、オブジェクト作成時に仮想ポインタが割り当てられます(ポリモーフィズムの場合)、C++もポリモーフィックです関数ポインターを介して (証拠は、C++ がオブジェクトを作成するときに、メモリが実際にポインター サイズのメモリに割り当てられることです)。ここで、C++ では実際に仮想ポインターと仮想関数の概念が導入されていることがわかります.C でオブジェクト指向を実装することもできますが、それはより面倒です.
ここに画像の説明を挿入

2.C++ コード

class illet
{
    
    
  public:

  illet(){
    
    }
  illet(string name)
  {
    
    
    this->name=name;
    this->heal_val=0;
    cout<<"create Base_Pets class"<<endl;  
  }

  ~illet()
  {
    
    
    cout<<"Destroy Base_Pets"<<endl;  
  }

  virtual int Ordinary_skill()
  {
    
    
    cout<<"Base_Pets use Ordinary_skill,without magic"<<endl; 
    return 0;
  }  

  protected:

  string name;int heal_val;
};

class rich:public illet
{
    
    
  public:
  
  rich(string name,int m_val)
  {
    
    
    this->name=name;
    this->magic_val=m_val;
    cout<<"create Child_Pets class"<<endl;
  }

  ~rich()
  {
    
    
    cout<<"Destroy Child_Pets"<<endl;
  }

  virtual int Ordinary_skill()
  {
    
    
    cout<<"Child_Pets use Ordinary_skill,magic val is "<<this->magic_val<<endl; 
    return 0;
  }    

  protected:
  int magic_val;  
};

void test()
{
    
    
  rich r_t("child",10);
  illet i_t("base");

  r_t.Ordinary_skill();
  i_t.Ordinary_skill();

}

int main()
{
    
    
  test();
  system("pause");
}

実行結果:基本的にCと同じ

create Child_Pets class
create Base_Pets class
Child_Pets use Ordinary_skill,magic val is 10
Base_Pets use Ordinary_skill,without magic
Destroy Base_Pets
Destroy Child_Pets
Destroy Base_Pets
Press any key to continue . . .

Take a look at the above code. 次の図で C++ がどのようにポリモーフィズムを実装しているかを分析してみましょう. 実際、これは C に似ています.

3. 純粋仮想機能

ここで純仮想関数を見てみましょう. いわゆる純仮想関数とは, オブジェクトを作成する際に仮想関数テーブルを作成しないことを意味します. 継承の際に基底クラスは多態的な関数を書き換える必要があり, 関数はpointer は (暗黙的に) 基本クラスの仮想関数テーブルを指します。このように、仮想関数は実際にはメモリを割り当てない宣言であるため、呼び出すことができないことが理解できます。

コードは以下のように表示されます:


class illet
{
    
    
  public:

  illet(){
    
    }
  illet(string name)
  {
    
    
    this->name=name;
    this->heal_val=0;
    cout<<"create Base_Pets class"<<endl;  
  }

  ~illet()
  {
    
    
    cout<<"Destroy Base_Pets"<<endl;  
  }
	/*创建纯虚函数*/
  virtual int Ordinary_skill()=0;

  protected:

  string name;int heal_val;
};

class rich:public illet
{
    
    
  public:
  
  rich(string name,int m_val)
  {
    
    
    this->name=name;
    this->magic_val=m_val;
    cout<<"create Child_Pets class"<<endl;
  }

  ~rich()
  {
    
    
    cout<<"Destroy Child_Pets"<<endl;
  }

  virtual int Ordinary_skill()
  {
    
    
    cout<<"Child_Pets use Ordinary_skill,magic val is "<<this->magic_val<<endl; 
    return 0;
  }    

  protected:
  int magic_val;  
};

void test()
{
    
    
  rich r_t("child",10);
 // illet i_t("base");不允许调用,编译器报错

  r_t.Ordinary_skill();
//  i_t.Ordinary_skill();

}

int main()
{
    
    
  test();
  system("pause");
}

おすすめ

転載: blog.csdn.net/weixin_46185705/article/details/123756383