C言語はオブジェクト指向の原則を実装しています〜

言語開発の歴史について何人の人が学んだかはわかりませんが、初期のC言語の文法機能は実際には比較的単純です。アプリケーションの要件とシナリオが変化するにつれて、C言語の文法機能は絶えずアップグレードおよび変化しています。

私たちの教科書にはそのような結論があります:C言語はプロセス指向言語です、C ++はオブジェクト指向プログラミング言語ですが、オブジェクト指向の概念はC言語段階にあり、いくつかのオペレーティングシステムなどの多くの場所に適用されますカーネル、通信プロトコルなど。

OOP(Object Oriented Programming)とも呼ばれるオブジェクト指向プログラミングは、特定の言語やツールではありません。これは単なる設計方法と設計アイデアです。それが示す3つの最も基本的な特性は、カプセル化、継承、および多形

オブジェクト指向を実現するためにC言語を使用する理由

テキストを読む前に、一部の読者は確かにそのような質問をします:私たちはC ++オブジェクト指向言語を持っています、なぜオブジェクト指向を達成するためにC言語を使用する必要があるのですか?

オブジェクト指向ではない言語であるC言語も、オブジェクト指向のアイデアを使用してプログラムを作成できます。オブジェクト指向のC ++言語を使用してオブジェクト指向のプログラミングを実装する方が簡単ですが、C言語の効率は他のオブジェクト指向のプログラミング言語に匹敵しません。

もちろん、オブジェクト指向の開発を実装するためにC言語を使用することは理解するの比較的難しいため、ほとんどの人はC言語を学習しましたが、Linuxカーネルのソースコードを理解できません。

したがって、この質問は、特定のCプログラミングの経験を持つ読者が理解できる限り、実際には非常に簡単に理解できます。プロセス指向のC言語およびオブジェクト指向のC ++言語と比較すると、コード操作の効率とコードの量は大きく異なります。パフォーマンスが低く、リソースが少ないMCUでC言語のオブジェクト指向プログラミングを使用することは特に重要です。

認定済み

C言語を使用してオブジェクト指向を実現するには、最初にいくつかの基本的な知識が必要です。例:(C言語の場合)構造、関数、ポインター、関数ポインターなど、(C ++の場合)基本クラス、派生、多態性、継承など。

まず、これらの基本的な知識を理解するだけでなく、ある程度のプログラミング経験を積むことです。前述の「オブジェクト指向は設計方法と設計アイデア」であるため、文字通りの理解だけでは、そのような設計アイデアはありません。ありえない。

したがって、特に実際のプロジェクトでは、初心者がC言語を使用してオブジェクト指向を実装することはお勧めしません。使用する前に、基本的なスキルを十分に練習することをお勧めします。

C言語を使用してオブジェクト指向を実装する方法は多数あります。最も基本的なカプセル化、継承、および多態性について以下に説明します。

パッケージ

カプセル化とは、データと関数をクラスにパックすることです。実際、ほとんどのC言語プログラマーはそれに近づいています。

C標準ライブラリのfopen()、fclose()、fread()、fwrite()などの関数の操作オブジェクトはFILEです。データの内容はFI​​LE、データの読み取りおよび書き込み操作はfread()、fwrite()、fopen()はコンストラクターに類似しており、fclose()はデストラクターです。

これはよく理解されているようですので、基本的なパッケージ機能を実装しましょう。

#ifndef SHAPE_H
#define SHAPE_H


#include <stdint.h>


// Shape 的属性
typedef struct {
    int16_t x; 
    int16_t y; 
} Shape;


// Shape 的操作函数,接口函数
void Shape_ctor(Shape * const me, int16_t x, int16_t y);
void Shape_moveBy(Shape * const me, int16_t dx, int16_t dy);
int16_t Shape_getX(Shape const * const me);
int16_t Shape_getY(Shape const * const me);


#endif /* SHAPE_H */

これはShapeクラスの宣言であり、非常にシンプルで理解しやすいものです。通常、宣言はヘッダーファイル「Shape.h」に配置されます。Shapeクラスの定義を見てください。もちろん、「Shape.c」にあります。

#include "shape.h"


// 构造函数
void Shape_ctor(Shape * const me, int16_t x, int16_t y)
{
    me->x = x;
    me->y = y;
}


void Shape_moveBy(Shape * const me, int16_t dx, int16_t dy) 
{
    me->x += dx;
    me->y += dy;
}


// 获取属性值函数
int16_t Shape_getX(Shape const * const me) 
{
    return me->x;
}
int16_t Shape_getY(Shape const * const me) 
{
    return me->y;
}

main.cをもう一度見てください

#include "shape.h"  /* Shape class interface */
#include <stdio.h>  /* for printf() */


int main() 
{
    Shape s1, s2; /* multiple instances of Shape */


    Shape_ctor(&s1, 0, 1);
    Shape_ctor(&s2, -1, 2);


    printf("Shape s1(x=%d,y=%d)\n", Shape_getX(&s1), Shape_getY(&s1));
    printf("Shape s2(x=%d,y=%d)\n", Shape_getX(&s2), Shape_getY(&s2));


    Shape_moveBy(&s1, 2, -4);
    Shape_moveBy(&s2, 1, -2);


    printf("Shape s1(x=%d,y=%d)\n", Shape_getX(&s1), Shape_getY(&s1));
    printf("Shape s2(x=%d,y=%d)\n", Shape_getX(&s2), Shape_getY(&s2));


    return 0;
}

コンパイル後、実行結果を確認します。

Shape s1(x=0,y=1)
Shape s2(x=-1,y=2)
Shape s1(x=2,y=-3)
Shape s2(x=0,y=0)

全体の例は非常に単純で理解しやすいです。将来、コードを書くときは、標準ライブラリのファイルIO操作についてもっと考える必要があります。そうすれば、オブジェクト指向のプログラミングの考え方を意識的に育むこともできます。

継承

継承とは、既存のクラスに基づいて新しいクラスを定義することです。これにより、コードを再利用し、コードをより適切に整理できます。C言語では、基本クラスが継承されたクラスの最初のデータメンバーの位置に配置されている限り、単一の継承を実装することも非常に簡単です。

たとえば、Rectangleクラスを作成します。必要なのは、Shapeクラスの既存のプロパティと操作を継承し、Shapeとは異なるプロパティと操作をRectangleに追加することだけです。

以下は、Rectangleの宣言と定義です。

#ifndef RECT_H
#define RECT_H


#include "shape.h" // 基类接口


// 矩形的属性
typedef struct {
    Shape super; // 继承 Shape


    // 自己的属性
    uint16_t width;
    uint16_t height;
} Rectangle;


// 构造函数
void Rectangle_ctor(Rectangle * const me, int16_t x, int16_t y,
                    uint16_t width, uint16_t height);


#endif /* RECT_H */
#include "rect.h"


// 构造函数
void Rectangle_ctor(Rectangle * const me, int16_t x, int16_t y,
                    uint16_t width, uint16_t height)
{
    /* first call superclass’ ctor */
    Shape_ctor(&me->super, x, y);


    /* next, you initialize the attributes added by this subclass... */
    me->width = width;
    me->height = height;
}

Rectangleの継承関係とメモリレイアウトを見てみましょう。

このメモリレイアウトにより、Rectangleオブジェクトへのポインタを、Shapeオブジェクトへのポインタを渡すことを期待する関数に安全に渡すことができます。つまり、関数パラメータは「Shape *」であり、「Rectangle」を渡すことができます。 * "そしてそれは非常に安全です。このようにして、基本クラスのすべてのプロパティとメソッドを継承されたクラスに継承できます。

#include "rect.h"  
#include <stdio.h> 


int main() 
{
    Rectangle r1, r2;


    // 实例化对象
    Rectangle_ctor(&r1, 0, 2, 10, 15);
    Rectangle_ctor(&r2, -1, 3, 5, 8);


    printf("Rect r1(x=%d,y=%d,width=%d,height=%d)\n",
           Shape_getX(&r1.super), Shape_getY(&r1.super),
           r1.width, r1.height);
    printf("Rect r2(x=%d,y=%d,width=%d,height=%d)\n",
           Shape_getX(&r2.super), Shape_getY(&r2.super),
           r2.width, r2.height);


    // 注意,这里有两种方式,一是强转类型,二是直接使用成员地址
    Shape_moveBy((Shape *)&r1, -2, 3);
    Shape_moveBy(&r2.super, 2, -1);


    printf("Rect r1(x=%d,y=%d,width=%d,height=%d)\n",
           Shape_getX(&r1.super), Shape_getY(&r1.super),
           r1.width, r1.height);
    printf("Rect r2(x=%d,y=%d,width=%d,height=%d)\n",
           Shape_getX(&r2.super), Shape_getY(&r2.super),
           r2.width, r2.height);


    return 0;
}

出力結果:

Rect r1(x=0,y=2,width=10,height=15)
Rect r2(x=-1,y=3,width=5,height=8)
Rect r1(x=-2,y=5,width=10,height=15)
Rect r2(x=1,y=2,width=5,height=8)

多形性

C ++言語で多態性を実現するには、仮想関数を使用します。C言語では、多形性も実現できます。

ここで、もう一度円を追加する必要があります。Shapeで関数を展開するには、area()関数とdraw()関数を追加する必要があります。しかし、Shapeは抽象クラスと同等であり、それ自体を描画する方法は言うまでもなく、独自の領域を計算する方法もわかりません。また、面積の計算方法や長方形や円の幾何学的画像も異なります。

Shapeクラスを再宣言しましょう:

#ifndef SHAPE_H
#define SHAPE_H


#include <stdint.h>


struct ShapeVtbl;
// Shape 的属性
typedef struct {
    struct ShapeVtbl const *vptr;
    int16_t x; 
    int16_t y; 
} Shape;


// Shape 的虚表
struct ShapeVtbl {
    uint32_t (*area)(Shape const * const me);
    void (*draw)(Shape const * const me);
};


// Shape 的操作函数,接口函数
void Shape_ctor(Shape * const me, int16_t x, int16_t y);
void Shape_moveBy(Shape * const me, int16_t dx, int16_t dy);
int16_t Shape_getX(Shape const * const me);
int16_t Shape_getY(Shape const * const me);


static inline uint32_t Shape_area(Shape const * const me) 
{
    return (*me->vptr->area)(me);
}


static inline void Shape_draw(Shape const * const me)
{
    (*me->vptr->draw)(me);
}




Shape const *largestShape(Shape const *shapes[], uint32_t nShapes);
void drawAllShapes(Shape const *shapes[], uint32_t nShapes);


#endif /* SHAPE_H */

仮想関数を追加した後、クラス図を見てください。

5.1仮想テーブルと仮想ポインタ

仮想テーブルは、このクラスのすべての仮想関数の関数ポインターのコレクションです。

仮想ポインタは、仮想テーブルへのポインタです。この仮想ポインタはすべてのオブジェクトインスタンスに存在する必要があり、すべてのサブクラスに継承されます。

これらは、「C ++オブジェクトモデルの内部」の最初の章で紹介されています。

5.2コンストラクターでvptrを設定します

各オブジェクトインスタンスで、vptrはそのvtblを指すように初期化する必要があります。初期化するのに最適な場所は、クラスコンストラクターです。実際、コンストラクターでは、C ++コンパイラーが暗黙的に初期化されたvptrを作成します。C言語では、vptrを明示的に初期化する必要があります。

Shapeのコンストラクターでこのvptrを初期化する方法は次のとおりです。

#include "shape.h"
#include <assert.h>


// Shape 的虚函数
static uint32_t Shape_area_(Shape const * const me);
static void Shape_draw_(Shape const * const me);


// 构造函数
void Shape_ctor(Shape * const me, int16_t x, int16_t y) 
{
    // Shape 类的虚表
    static struct ShapeVtbl const vtbl = 
    { 
       &Shape_area_,
       &Shape_draw_
    };
    me->vptr = &vtbl; 
    me->x = x;
    me->y = y;
}




void Shape_moveBy(Shape * const me, int16_t dx, int16_t dy)
{
    me->x += dx;
    me->y += dy;
}




int16_t Shape_getX(Shape const * const me) 
{
    return me->x;
}
int16_t Shape_getY(Shape const * const me) 
{
    return me->y;
}


// Shape 类的虚函数实现
static uint32_t Shape_area_(Shape const * const me) 
{
    assert(0); // 类似纯虚函数
    return 0U; // 避免警告
}


static void Shape_draw_(Shape const * const me) 
{
    assert(0); // 纯虚函数不能被调用
}




Shape const *largestShape(Shape const *shapes[], uint32_t nShapes) 
{
    Shape const *s = (Shape *)0;
    uint32_t max = 0U;
    uint32_t i;
    for (i = 0U; i < nShapes; ++i) 
    {
        uint32_t area = Shape_area(shapes[i]);// 虚函数调用
        if (area > max) 
        {
            max = area;
            s = shapes[i];
        }
    }
    return s;
}




void drawAllShapes(Shape const *shapes[], uint32_t nShapes) 
{
    uint32_t i;
    for (i = 0U; i < nShapes; ++i) 
    {
        Shape_draw(shapes[i]); // 虚函数调用
    }
}

5.3vtblの継承とvptrのオーバーロード

上記のように、基本クラスにはvptrが含まれており、サブクラスはそれを自動的に継承します。ただし、vptrは、サブクラスの仮想テーブルによって再割り当てする必要があります。また、これはサブクラスのコンストラクターでも発生する必要があります。以下はRectangleのコンストラクターです。

#include "rect.h"  
#include <stdio.h> 


// Rectangle 虚函数
static uint32_t Rectangle_area_(Shape const * const me);
static void Rectangle_draw_(Shape const * const me);


// 构造函数
void Rectangle_ctor(Rectangle * const me, int16_t x, int16_t y,
                    uint16_t width, uint16_t height)
{
    static struct ShapeVtbl const vtbl = 
    {
        &Rectangle_area_,
        &Rectangle_draw_
    };
    Shape_ctor(&me->super, x, y); // 调用基类的构造函数
    me->super.vptr = &vtbl;           // 重载 vptr
    me->width = width;
    me->height = height;
}


// Rectangle's 虚函数实现
static uint32_t Rectangle_area_(Shape const * const me) 
{
    Rectangle const * const me_ = (Rectangle const *)me; //显示的转换
    return (uint32_t)me_->width * (uint32_t)me_->height;
}


static void Rectangle_draw_(Shape const * const me) 
{
    Rectangle const * const me_ = (Rectangle const *)me; //显示的转换
    printf("Rectangle_draw_(x=%d,y=%d,width=%d,height=%d)\n",
           Shape_getX(me), Shape_getY(me), me_->width, me_->height);
}

5.4仮想関数呼び出し

以前の仮想テーブル(仮想テーブル)と仮想ポインター(仮想ポインター)の基本的な実装では、次のコードで仮想呼び出し(レイトバインディング)を実装できます。

uint32_t Shape_area(Shape const * const me)
{
    return (*me->vptr->area)(me);
}

この関数は.cファイルに配置できますが、各仮想呼び出しに追加の呼び出しオーバーヘッドがあるという欠点があります。この欠点を回避するために、コンパイラがインライン関数(C99)をサポートしている場合。次のように、定義をヘッダーファイルに入れることができます。

static inline uint32_t Shape_area(Shape const * const me) 
{
    return (*me->vptr->area)(me);
}

古いコンパイラ(C89)の場合は、次のようなマクロ関数を使用して実装できます。

#define Shape_area(me_) ((*(me_)->vptr->area)((me_)))

例の呼び出しメカニズムを見てください。

5.5 main.c

#include "rect.h"  
#include "circle.h" 
#include <stdio.h> 


int main() 
{
    Rectangle r1, r2; 
    Circle    c1, c2; 
    Shape const *shapes[] = 
    { 
        &c1.super,
        &r2.super,
        &c2.super,
        &r1.super
    };
    Shape const *s;


    // 实例化矩形对象
    Rectangle_ctor(&r1, 0, 2, 10, 15);
    Rectangle_ctor(&r2, -1, 3, 5, 8);


    // 实例化圆形对象
    Circle_ctor(&c1, 1, -2, 12);
    Circle_ctor(&c2, 1, -3, 6);


    s = largestShape(shapes, sizeof(shapes)/sizeof(shapes[0]));
    printf("largetsShape s(x=%d,y=%d)\n", Shape_getX(s), Shape_getY(s));


    drawAllShapes(shapes, sizeof(shapes)/sizeof(shapes[0]));


    return 0;
}

出力結果:

largetsShape s(x=1,y=-2)
Circle_draw_(x=1,y=-2,rad=12)
Rectangle_draw_(x=-1,y=3,width=5,height=8)
Circle_draw_(x=1,y=-3,rad=6)
Rectangle_draw_(x=0,y=2,width=10,height=15)

総括する

繰り返しますが、オブジェクト指向のプログラミングは、特定のプログラミング言語に限定されない方法です。C言語を使用してカプセル化と単一継承を実装することは、理解と実装が比較的簡単ですが、多態性はもう少し複雑です。多態性を広く使用する場合は、C ++言語に切り替えることをお勧めします。結局のところ、このレベルの複雑さはこの言語によってカプセル化されます。はい、あなたはそれを単に使う必要があります。しかし、それはC言語が多形性の特徴を実現できないという意味ではありません。

出典:https://blog.csdn.net/onlyshi/article/details/81672279


1.仮想円卓会議パート1-組み込みシステム情報セキュリティ

2. Huaweiの5Gの秘密はトルコ人の手にありましたか?

3.最新の半導体ランキングが発表され、Nvidiaは50%の成長を達成しました!

4. [MCU]「柔軟でリソースを節約する」IAPアップグレードプログラム

5.RISC-Vがホットスポットになるのはなぜですか?

6.開発者に適したHongmengOSの紹介〜

免責事項:この記事はオンラインで複製されており、著作権は元の作者に帰属します。著作権の問題が発生した場合は、お問い合わせください。ご提供いただいた著作権証明書に基づいて著作権を確認し、作者の報酬を支払うか、コンテンツを削除します。

おすすめ

転載: blog.csdn.net/DP29syM41zyGndVF/article/details/110507699