C言語オブジェクトへの継承、マルチステート、可変パラメータ、関数ポインタ

13453 ワード

最近、C言語でクラスのパッケージ、継承、多態を実現するには、手書きで書きましょう.初めてブログを書きますが、間違いがあれば、ご指導をお願いします.内容は:1.クラスの定義(プライベート変数)2.クラスのメソッド(関数ポインタ、構築/解析関数)3.クラスの継承(ベースクラス/派生クラス)4.マルチステート(可変パラメータ)
1.クラスの定義
C++ではクラスをclassで定義し、C言語ではstruct構造体でクラスを表すことができ、別の構造体変数をプライベートメンバー変数として使用し、関数ポインタをクラスとして使用する方法.
//           
struct PrivatePoint{
    int x;
    int y;
};

// Point 
typedef struct point{
    struct PrivatePoint priPoint;
}Point;

このように定義すればpoint->priPointのようになるかもしれませんxはこのようにプライベート変数xに直接アクセスする点で,C++のプライベート概念とは異なる.私の理解では、この「プライベート」とは、PrivatePointの定義を別のものにすることができるという意味です.hファイルでは、Pointクラス定義でstruct PrivatePointを宣言するだけです.このプライベート変数の内容は誰も知らない.
2.関数ポインタ
関数ポインタは、次の形式で宣言された関数を指すポインタ変数です.
      ( *      ) ([    ]);

構造体に関数ポインタ変数を定義し、クラスを表す方法.この方法は対応する.cファイルで実現します.ここではShapeクラスを例にとり,Triangleクラス(三角形)とCircleクラス(円形)を継承クラスとして後述する.
// CClassBase.r
#ifndef _CCLASSBASE_R_
#define _CCLASSBASE_R_

#include 
//   :  ,      
typedef struct{
    int size;   //      

    //     
    void* (*constructor)(const void *self, va_list *va);    
    //     
    void* (*destructor)(void *self);    
    //       
    double (*area)(const void *self);
}Shape;

#endif 

3.継承
上記shapeクラスはベースクラスとして虚クラスであるため,その構造/解析関数は実現されず,システムはshapeクラスオブジェクトをインスタンス化しない.Triangleクラス、Circleクラスは継承クラスとして使用され、システムが継承クラスのオブジェクトを作成すると、対応するコンストラクション関数が呼び出されます.
まず、継承クラスTriangle、Circleを定義します.
// CClassChildren.r
#ifndef _CLASSCHILDREN_R_
#define _CLASSCHILDREN_R_

#include "CClassBase.r"

//     ,      
typedef struct{
    const Shape shape;
    double sideLen;
}Triangle;

//    ,      
typedef struct{
    const Shape shape;
    double radius;
}Circle;

#endif

次に、ベースクラスを実装する方法.
// CClassChildren.c
#include "CClassBase.r"
#include "CClassChildren.r"
#include 

#define PI 3.1415926

//***************************** Triangle *******************************
static void* Triangle_constructor(const void* _self, va_list* arg_ptr)  
{                   // Triangle      
    Triangle* self = (Triangle*)_self;
    self->sideLen = va_arg(*arg_ptr, double);
    return self;
}

static void* Triangle_destructor(void* _self)
{                   // Triangle      
    return 0;
}

static double Triangle_area(const void* _self)
{                   // Triangle       
    Triangle* self = (Triangle*)_self;
    double len = self->sideLen;
    return sqrt(3.0) / 4 * len * len;
}

//     Triangle  
static const Shape _Triangle = { sizeof(Triangle), Triangle_constructor, Triangle_destructor, Triangle_area };
const void* triangle = &_Triangle;

//***************************** Circle ************************************
static void* Circle_constructor(const void* _self, va_list* arg_ptr)        
{               // Circle      
    Circle* self = (Circle*)_self;
    self->radius = va_arg(*arg_ptr, double);
    return self;
}

static void* Circle_destructor(void* _self)
{               // Circle      
    return 0;
}

static double Circle_area(const void* _self)
{               // Circle       
    Circle* self = (Circle*)_self;
    double r = self->radius;
    return PI * r * r;
}

//     Circle  
static const Shape _Circle = { sizeof(Circle), Circle_constructor, Circle_destructor, Circle_area };
const void* circle = &_Circle;

最後にhファイルに継承クラス変数を宣言します.上記のコード実装は、ユーザーを非表示にすることに相当します.
// CClassChildren.h
#ifndef _CCLASSCHILDREN_H_
#define _CCLASSCHILDREN_H_

extern const void* triangle;
extern const void* circle;

#endif

これらを見た後、霧が出てくるのではないでしょうか.ベースクラスと継承クラスが定義されているので、クラスのオブジェクトを作成してメソッドを呼び出す方法を考えましょう.C++がこれらのステップをどのように実現するかを考えてみましょう.まず、newクラスオブジェクト->クラスのコンストラクション関数を呼び出す->クラスを呼び出す方法で対応する機能を実現する->クラスのコンストラクション関数を呼び出し、クラスオブジェクトを破棄します.
// CClassNew.h
ifndef _CCLASSNEW_H_
#define _CCLASSNEW_H_

#include `
#include 
#include 
#include "CClassBase.r"

//         
void* new_shape(const void* _self, ...)
{
    const Shape* shape = (const Shape*)_self;
    void* p = calloc(1, shape->size);
    *(const Shape**)p = shape;

    if (shape->constructor) //         ,   
    {
        va_list va;
        va_start(va, _self);
        p = shape->constructor(p, &va);
        va_end(va);
    }
    return p;
}

//    
void delete_shape(void* _self)
{
    const Shape** shape = (const Shape**)_self;
    if (_self && shape && (*shape)->destructor)
        _self = (*shape)->destructor(_self);
    free(_self);
}

//       
double area(const void* _self)
{
    const Shape* const *p = (Shape**)_self;
    assert(_self && *p && (*p)->area);
    return (*p)->area(p);
}

#endif

ここのパラメータ_selfとは、伝達された異なる継承クラスを指し、このメソッドが呼び出されると、異なる継承クラスに基づいて対応するクラスのメソッドが呼び出され、マルチステートが実現される.
4.マルチステート
上のnew_shape、delete_shape,area関数は異なるクラスに基づいて異なるメソッドを呼び出すことで,マルチステートを実現する.プログラムでの具体的な呼び出しは次のとおりです.
// main.c
#include 
#include 
#include "CClassChildren.h"
#include "CClassNew.h"

int main()
{
    double a = 2.0, b = 1.0, c = 3.0;

    void * p = new_shape(triangle, a);
    printf("    %.1f          : %f

"
, a, area(p)); delete_shape(p); p = new_shape(circle, b); printf(" %.1f : %f

"
, b, area(p)); delete_shape(p); return 0; }

ここでは可変パラメータの使い方を補足します.可変パラメータといえば、int printf(const char*format,...)と定義されるprintfが最も一般的である.省略記号はパラメータの個数が不定であることを示す.これにより、可変パラメータを持つ関数の定義形式は次のようになります.
         (     , ...);

可変パラメータを使用すると、次のマクロが使用されます.ヘッダファイルは
 void va_start( va_list arg_ptr, prev_param );  //     
 type va_arg( va_list arg_ptr, type ); //        
 void va_end( va_list arg_ptr );           //     

ここのva_arg呼び出しは一度に次のパラメータを読み出し、カウントできません.
//   
void func(int param, ...)
{
    va_list arg_ptr;
    va_start(arg_ptr, param);
    int i = va_arg(arg_ptr, int); //        
    int j = va_arg(arg_ptr, int); //        
    va_end(arg_ptr);
    printf("%d %d", i, j);
}

参考:http://blog.csdn.net/foruok/article/details/18192167C言語における可変パラメータの使い方