c言語はどのようにオブジェクト向けのプログラミングを実現するか


一、紹介
C言語はプロセス向けの言語であり、C++はオブジェクト向けであり、この特性はすでに人の心に深く浸透している.しかし,組み込みベースや大規模なオペレーティングシステムなどはCで開発されている.このような大規模なソフトウェアをプロセス向けに開発するだけでは、通じないに違いない.
したがって,C言語はオブジェクト指向の思考で開発することも可能である.例えば現在のSTM 32のHALライブラリはこの傾向に向かっている.
二、C言語実現パッケージ
ほとんどのC言語を用いて開発を行っているエンジニアは,より高度なプログラミング言語に触れる前に,C言語はプロセス向けであると考えている.確かに、いくつかの小規模なアプリケーションでは、C言語は一般的にプロセス向けプログラミングとして使用されています.例:シングルチップマシンアプリケーション開発.
しかし,C言語を用いて大規模なソフトウェアを開発する場合には,オブジェクト向けの考え方でソフトウェアフレームワーク全体を考慮し設計しなければならない.たとえば、組み込みLinuxオペレーティングシステムです.
組み込みLinuxオペレーティングシステムはC言語を主な記述言語として使用しているが,中の設計の大部分はオブジェクト向けのプログラミング思想を用いている.多くのシングルチップエンジニアや組み込みLinuxは初心者を駆動し、入門が困難だと感じている.多くの原因は、シングルチップマシンのようなプロセス向けの思考モデルにとどまっているからだ.
プログラミング言語はただのツールであり、プログラミング思想こそこのツールをうまく使う鍵である.C言語はツールにすぎず、オブジェクト向けはプログラミング思想であり、C言語の使い方を指導するために使用されています.
次に,C言語を用いたオブジェクト向けプログラムの開発を試み,C言語を用いてオブジェクト向けの基本的な特性を実現することを求めた.
まず、パッケージについてお話しします.
パッケージは抽象的な事物の属性と属性の操作関数をパッケージ化し、外部のモジュールはこの抽象的な事物が対外的に提供する関数インタフェースを通じて、その属性にアクセスするしかない.C++またはその他の高度な言語では、パッケージは通常「クラス」と呼ばれます.C言語は一般に構造体を用いて物事をカプセル化する.
 
次に、2つのコードを見てみましょう.この2つのコードは、主に座標クラスオブジェクトとその座標属性を宣言し、定義し、座標属性の操作関数も提供します.
 
ヘッダファイルh
#ifndef __COORDINATE_H_
#define __COORDINATE_H_

//       ,     x,y
typedef struct coordinate{
    short int x;
    short int y;
}COORDINATE_T,*P_COORDINATE_T;

extern P_COORDINATE_T coordinate_create(short int x,short int y);
extern void coordinate_destroy(P_COORDINATE_T p_coordinate);
extern void coordinate_moveby(P_COORDINATE_T p_coordinate,short int dx,short int dy);
extern short int coordinate_get_x(P_COORDINATE_T p_coordinate);
extern short int coordinate_get_y(P_COORDINATE_T p_coordinate);
extern void coordinate_test_function(void);
#endif // !__COORDINATE_H_

ソースファイルc

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "inc/coordinate.h"

//    coordinate  
P_COORDINATE_T coordinate_create(short int x,short int y)
{
    if((x < 0) || (y < 0)){
        printf("coordinate creat error! x or y can not be less than zero 
"); return NULL; } P_COORDINATE_T p_coordiante = NULL; p_coordiante = (P_COORDINATE_T)malloc(sizeof(COORDINATE_T)); if(NULL != p_coordiante){ p_coordiante->x = x; p_coordiante->y = y; } else printf("coordinate malloc error!
"); return p_coordiante; } // coordinate void coordinate_destroy(P_COORDINATE_T p_coordiante) { if(NULL != p_coordiante){ free(p_coordiante); p_coordiante = NULL; } } // coordinate void coordinate_moveby(P_COORDINATE_T p_coordiante,short int dx,short int dy) { if(NULL != p_coordiante){ p_coordiante->x += dx; p_coordiante->y += dy; } } // coordinate x short int coordinate_get_x(P_COORDINATE_T p_coordiante) { return (NULL != p_coordiante) ? p_coordiante->x : -1; } // coordinate y short int coordinate_get_y(P_COORDINATE_T p_coordiante) { return (NULL != p_coordiante) ? p_coordiante->y : -1; }

コードは比較的簡単で、ヘッダファイルcoordinate.hでは,構造体によってcoordinateクラスがカプセル化され,座標属性xとyが2つ含まれている.
coordinate_create関数は主にP_を作成するために使用されます.COORDINATE_Tタイプのオブジェクトにメモリ領域を割り当て、メモリ割り当てに成功した後、2つの座標属性の初期値を設定し、最後に申請に成功したオブジェクトポインタを返します.
coordinate_destroyは主にオブジェクトの前に申請したメモリ領域を解放し、オブジェクトポインタをNULLにリセットします.
他の操作関数は、主にクラスオブジェクトの属性を操作します.例えば、xとyの属性値を取得し、座標の属性値をリセットします.
以下はテスト関数で、メイン関数で呼び出すと、クラスcoordinateが外部に提供するインタフェースをテストできます.
void coordinate_test_function(void)
{
    P_COORDINATE_T p_coordiante_1 = NULL;
    P_COORDINATE_T p_coordiante_2 = NULL;

    p_coordiante_1 = (P_COORDINATE_T)coordinate_create(100,200);
    p_coordiante_2 = (P_COORDINATE_T)coordinate_create(10,20);

    if((NULL == p_coordiante_1) || (NULL == p_coordiante_2)){
        printf("p_coordiante_1 or p_coordiante_2 create error! 
"); return; } printf("p_coordiante_1 x = %d, y = %d
",coordinate_get_x(p_coordiante_1), coordinate_get_y(p_coordiante_1)); printf("p_coordiante_2 x = %d, y = %d
",coordinate_get_x(p_coordiante_2), coordinate_get_y(p_coordiante_2)); coordinate_moveby(p_coordiante_1,50,50); coordinate_moveby(p_coordiante_2,50,50); printf("after moveby p_coordiante_1 x = %d, y = %d
",coordinate_get_x(p_coordiante_1), coordinate_get_y(p_coordiante_1)); printf("after moveby p_coordiante_2 x = %d, y = %d
",coordinate_get_x(p_coordiante_2), coordinate_get_y(p_coordiante_2)); coordinate_destroy(p_coordiante_1); coordinate_destroy(p_coordiante_2); }

テストコードは比較的簡単で、主に2つのP_を作成しました.COORDINATE_Tタイプのオブジェクトは、座標の初期値を印刷し、外部に提供された関数で座標値を変更して印刷し、最後に作成したオブジェクトを破棄します.テスト関数の実行後、結果は次のとおりです.

p_coordiante_1 x = 100, y = 200 
p_coordiante_2 x = 10, y = 20 
after moveby p_coordiante_1 x = 150, y = 250 
after moveby p_coordiante_2 x = 60, y = 70

上記のコードから,構造体を用いることでデータをうまくカプセル化でき,指定した操作関数により構造内のデータにアクセスする必要があることが分かる.
各操作関数の最初のパラメータは、オブジェクト自体のポインタであり、このポインタを使用して特定のオブジェクトのプロパティにアクセスします.これは、C言語にはC++言語のようなthisポインタが存在しないため、関数内でオブジェクトインスタンスの他のメンバーにアクセスできるように、関数パラメータを明示的に渡すしかありません.
オブジェクト属性の各種操作関数については,関数ポインタを用いて構造体内に入れてカプセル化することもできる.しかし,理解を容易にするために,本稿ではこの方法を採用していない.
ソースのダウンロードアドレス:
https://github.com/embediot/my_program_test
三、C言語の継承を実現する
前の文章は主にC言語のオブジェクト向けプログラミング–パッケージの簡単な概念と実現について述べた.
本稿では,C言語を用いてオブジェクト向けプログラミングの重要な特性である継承をどのように実現するかについて議論する.
継承とは、既存のクラス(一般的に親クラスまたはベースクラスと呼ばれる)に基づいて、サブクラスまたは派生クラスと呼ばれる新しいクラスを再宣言または作成することです.サブクラスまたは派生クラスは、親のデータと関数にアクセスし、サブクラスに独自の属性とデータを追加します.C言語では、クラスの単一継承は構造体ネストによって実現できますが(多重継承はしばらく考慮しません)、構造体ネスト時に親オブジェクトが構造体メンバーの最初の位置に置かれる必要があることに注意してください.
次に、既存のcoordinateクラスに基づいて親クラスとしてrectangle派生クラスを再定義します.前の文章コードに基づいて,親coordinateを修正し,操作関数を関数ポインタで構造体内にカプセル化し,オブジェクトのカプセル化度合いをさらに向上させた.変更された親coordinateコードは、次のようになります.
ヘッダファイルh 
#ifndef __COORDINATE_H_
#define __COORDINATE_H_

//       ,     x,y,        
typedef struct coordinate {
    short int x;
    short int y;
    void (*moveby)(struct coordinate *p_coordinate,short int dx,short int dy);
    short int (*get_x)(struct coordinate *p_coordinate);
    short int (*get_y)(struct coordinate *p_coordinate);
}COORDINATE_T,*P_COORDINATE_T;

extern void coordinate_init(P_COORDINATE_T p_coordinate,short int x,short int y);
extern void coordinate_uninit(P_COORDINATE_T p_coordinate);

#endif // !__COORDINATE_H_

ヘッダファイルhでは、座標属性xとyを提供し、属性の操作関数ポインタを提供する位置クラスを宣言します.ヘッダファイル外部提供coordinate_initとcoordinate_uninitの2つの関数は、オブジェクトを初期化し、初期化を解除するために使用されます.
ソースファイルc 
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "inc/coordinate.h"

//  coordinate    
static void coordinate_moveby(struct coordinate *p_coordiante,short int dx,short int dy)
{
    if(NULL != p_coordiante){
        p_coordiante->x += dx;
        p_coordiante->y += dy;
    }
}

//  coordinate    x
static short int coordinate_get_x(struct coordinate *p_coordiante)
{
    return (NULL != p_coordiante) ? p_coordiante->x : -1;
}

//  coordinate    y
static short int coordinate_get_y(struct coordinate *p_coordiante)
{
    return (NULL != p_coordiante) ? p_coordiante->y : -1;
}

//    coordinate  
void coordinate_init(P_COORDINATE_T p_coordinate,short int x,short int y)
{
    if((x < 0) || (y < 0) || (NULL == p_coordinate)){
        printf("coordinate create error! x or y can not be less than zero 
"); return; } p_coordinate->x = x; p_coordinate->y = y; p_coordinate->moveby = coordinate_moveby; p_coordinate->get_x = coordinate_get_x; p_coordinate->get_y = coordinate_get_y; } // coordinate void coordinate_uninit(P_COORDINATE_T p_coordinate) { if(NULL != p_coordinate){ p_coordinate->x = -1; p_coordinate->y = -1; p_coordinate->moveby = NULL; p_coordinate->get_x = NULL; p_coordinate->get_y = NULL; } }

ソースファイルでcでは,属性の操作関数はstaticで宣言され,このソースファイルでのみ関数を呼び出すことができ,外部呼び出しは許されない.関数coordinate_Initでは,主に属性付与を行い,操作関数ポインタを登録し,後で直接関数ポインタで操作関数を呼び出すことができる.関数coordinate_uninitでは,主に各属性の付与値をクリアする.
これで、親coordinate全体の修正が完了し、親は属性と属性の操作関数を構造体内にカプセル化し、そのカプセル化の程度は比較的高く、外部は親の属性操作関数を直接呼び出すことができず、関数ポインタで呼び出さなければならない.
次に、親coordinateに基づいて、次のようにサブクラスrectangle、サブクラスのヘッダファイルでの宣言を再宣言します.
ヘッダファイルh 
#ifndef __RECTANGLE_H_
#define __RECTANGLE_H_
#include "coordinate.h"    //       

//    rectangle ,  coordinate 
typedef struct rectangle {
    COORDINATE_T coordinate; //  ,      
    unsigned short width;
    unsigned short height;
}RECTANGLE_T,*P_RECTANGLE_T;

extern P_RECTANGLE_T rectangle_create(short int x,short int y,unsigned short width,unsigned short height);
extern void rectangle_destroy(P_RECTANGLE_T p_rectangle);
extern void rectangle_test_function(void);

#endif // !__RECTANGLE_H_

ソースファイルc 
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "inc/rectangle.h"

//    rectangle   
P_RECTANGLE_T rectangle_create(short int x,short int y,unsigned short width,unsigned short height)
{
    P_RECTANGLE_T p_rectangle = NULL;

    p_rectangle = (P_RECTANGLE_T)malloc(sizeof(RECTANGLE_T));

    if(NULL != p_rectangle){       
        p_rectangle->width = width;
        p_rectangle->height = height;

        coordinate_init(&(p_rectangle->coordinate),x,y);
    }
    else printf("rectangle create error! 
"); return p_rectangle; } // rectangle void rectangle_destroy(P_RECTANGLE_T p_rectangle) { coordinate_uninit(&(p_rectangle->coordinate)); if(NULL != p_rectangle){ free(p_rectangle); p_rectangle = NULL; } }

ヘッダファイルでhでは、includeに親coordinateのインタフェースが含まれており、rectangleクラスを宣言するための新しい構造体が作成されています.この構造体は、親coordinateを最初のメンバーの位置に置くと同時に、自分の2つの属性、幅widthと高さheightを追加します.
        rectangle_create関数はP_を作成するために使用されますRECTANGLE_Tタイプのオブジェクトで、メモリ領域を割り当てます.割り当てに成功したら、呼び出しの親coordinate_Init関数は、親クラスの各種属性を初期化し、同時に自身の属性widthとheightを初期化し、最後に作成に成功したオブジェクトポインタを返します.
        rectangle_destroyは、親オブジェクト属性の初期化解除に使用され、オブジェクト属性にデフォルト値を再割り当てし、以前に申請したメモリ領域を解放し、rectangleオブジェクトを破棄します.
最初のファイルhとソースファイルrectangle.cは、矩形rectangleがwidthおよびheight属性のほかに座標xおよびy属性を含むため、サブクラスrectangleが親クラスcoordinateに基づいて宣言および構築されていることを示す.
親を構造体メンバーの最初の位置に置くのは、構造体メモリの連続性のため、強制タイプ変換を安全に行うことができるからです.たとえば、関数に入力するパラメータがCOORDINATEである場合Tタイプですが、強制タイプ変換でRECTANGLE_に転送できます.T型のパラメータ、具体的な使い方、以下のテスト関数を見ることができます.

void rectangle_test_function(void)
{
    P_RECTANGLE_T p_rectangle_1 = NULL;
    P_RECTANGLE_T p_rectangle_2 = NULL;

    //     P_RECTANGLE_T       
    p_rectangle_1 = (P_RECTANGLE_T)rectangle_create(0,0,150,150);
    p_rectangle_2 = (P_RECTANGLE_T)rectangle_create(200,200,500,500);

    if((NULL != p_rectangle_1) && (NULL != p_rectangle_2)){

        //            ,                  
        printf("p_rectangle_1,x = %d,y = %d,width = %d,height = %d 
",\ p_rectangle_1->coordinate.get_x(&(p_rectangle_1->coordinate)), \ p_rectangle_1->coordinate.get_y(&(p_rectangle_1->coordinate)), \ p_rectangle_1->width,p_rectangle_1->height); printf("p_rectangle_2,x = %d,y = %d,width = %d,height = %d
", \ p_rectangle_2->coordinate.get_x(&(p_rectangle_2->coordinate)), \ p_rectangle_2->coordinate.get_y(&(p_rectangle_2->coordinate)), \ p_rectangle_2->width,p_rectangle_2->height); // , ,1、 。2、 p_rectangle_1->coordinate.moveby((P_COORDINATE_T)p_rectangle_1, 50, 50); p_rectangle_2->coordinate.moveby(&(p_rectangle_2->coordinate), 50, 50); // printf("after moveby, p_rectangle_1,x = %d,y = %d,width = %d,height = %d
",\ p_rectangle_1->coordinate.get_x(&(p_rectangle_1->coordinate)), \ p_rectangle_1->coordinate.get_y(&(p_rectangle_1->coordinate)), \ p_rectangle_1->width,p_rectangle_1->height); printf("after moveby, p_rectangle_2,x = %d,y = %d,width = %d,height = %d
", \ p_rectangle_2->coordinate.get_x(&(p_rectangle_2->coordinate)), \ p_rectangle_2->coordinate.get_y(&(p_rectangle_2->coordinate)), \ p_rectangle_2->width,p_rectangle_2->height); } // rectangle_destroy(p_rectangle_1); rectangle_destroy(p_rectangle_2); }

次の図に示すように、関数の実行効果をテストします.

p_rectangle_1,x = 0,y = 0,width = 150,height = 150
p_rectangle_2,x = 200,y = 200,width = 500,height = 500
after moveby, p_rectangle_1,x = 50,y = 50,width = 150,height = 150
after moveby, p_rectangle_2,x = 250,y = 250,width = 500,height = 500

上記のコードのテストにより、以下の点をまとめることができます.
1、外部関数は、親の各メンバーを子クラスで直接使用できますが、子クラス構造体の最初のメンバーのみでアクセスできます.
2、親は子構造体の最初の位置に配置され、構造体メモリの連続性のため、強制タイプ変換によって直接アクセスできます.
3、C言語構造体の特性により、子クラスに親と同じ名前の関数が存在しても、親クラスの関数はクラスの関数に上書きされたり書き換えられたりしないため、子クラスと親クラスの間に関数の再ロードは存在しない.
 
ソースのダウンロードアドレス:
https://github.com/embediot/my_program_test
 
見終わりましたか.見終わったら早くほめないの?
説明:本文はある微信のネットユーザーから来ました.