ステップバイステップ分析:C言語でのオブジェクト指向プログラミング

これはダオ兄弟のオリジナルの009です

I.はじめに

組み込み開発では、C / C ++言語が最も一般的です。C++ 11バージョン以前は、C ++がオブジェクト指向プログラミングメソッドを提供することを除いて、構文は比較的似ています

C ++言語はC言語から開発されましたが、今日のC ++は、今年のC言語の拡張ではなくなりました。2011バージョン以降、まったく新しい言語のようになっています。

だから私は考えもしませんでした、なぜあなたはそもそもC ++を拡張したかったのですか?C ++の出現につながったC言語の欠点は何ですか?

C ++はこれらの問題を非常にうまく解決しましたが、言語標準が徐々に拡張されるにつれて、C ++言語の学習の難しさが徐々に増しています。いくつかのプロジェクトを開発していない、彼らがC ++を学んだと言うのは恥ずかしいです、それらの 左の値と右の値、テンプレート、テンプレートパラメータ、テンプレート引数変数 コンセプトパイルなど、2、3年の使用ではありませんが熟練しますマスター。

ただし、C言語には多くの利点もあります。

実際、最後の利点が最も重要です。より多くの人がそれを使用するほど、活力が強くなります。今日の社会と同じように、それは上司の生存ではなく、適者生存です。

この記事では、オブジェクト指向のアイデアを使用してC言語でプログラミングする方法について説明します。できないかもしれませんが、この質問を2回聞いたときに辞めなければならなかったので、でプロジェクトを確認することを強くお勧めします

第二に、オブジェクト指向プログラミングとは何ですか

そのような式があります:プログラム=データ構造+アルゴリズム

プロセス指向プログラミングは、一般的にC言語で使用されます。これは、問題を解決するために必要なステップを分析し、関数を使用してこれらのステップをステップバイステップ呼び出し、関数内のデータ構造を処理します(アルゴリズムを実行します)。つまり、データ構造とアルゴリズムは分離されています。

C ++言語は、データとアルゴリズムをカプセル化して全体を形成します。プロパティを操作する場合でも、動作を呼び出す場合でも、オブジェクトを介して実行されます。これは、オブジェクト指向プログラミングのアイデアです。

このようなプログラミング方法をシミュレートするためにC言語を使用する場合、次の3つの問題を解決する必要があります。

  1. データのカプセル化
  2. 継承
  3. ポリモーフィズム

最初の質問:パッケージング

カプセル化は、オブジェクトに属するすべての属性(データ)を一緒に編成することであるデータの編成を記述します。C言語の構造タイプは本質的にこれをサポートします。

2番目の質問:継承

継承は、オブジェクト間の関係を記述します。親クラスを継承することにより、サブクラスは、親クラスのプロパティと動作(つまり、メソッド)を自動的に所有します。この問題は、C言語のメモリモデルを理解している限り問題ではありません。親構造変数をサブクラス構造の最初のメンバー変数の位置に配置する限り、サブクラスオブジェクトは親のプロパティを継承します。クラス。

追加するもう1つのポイント:言語を学ぶには、メモリモデルを理解する必要があります。

3番目の問題:ポリモーフィズム

文字通り、ポリモーフィズムは「複数の状態」を意味し、動的な動作を表しますC ++では、ポリモーフィズムは、仮想関数が基本クラスの参照またはポインターを介して呼び出された場合にのみ発生します。つまり、ポリモーフィズムは実行時に発生し、C ++は仮想テーブルを介して内部的にポリモーフィズムを実装します。次に、C言語では、このアイデアに従って達成することもできます。

言語がクラスのみをサポートし、ポリモーフィズムをサポートしない場合、それはオブジェクト指向ではなく、オブジェクトベースであるとしか言えません。

アイデアに問題はないので、簡単に実装してみましょう。

第三に、最初に親クラスを実装してカプセル化の問題を解決します

Animal.h

#ifndef _ANIMAL_H_
#define _ANIMAL_H_

// 定义父类结构
typedef struct {
    int age;
    int weight;
} Animal;

// 构造函数声明
void Animal_Ctor(Animal *this, int age, int weight);

// 获取父类属性声明
int Animal_GetAge(Animal *this);
int Animal_GetWeight(Animal *this);

#endif

Animal.c

#include "Animal.h"

// 父类构造函数实现
void Animal_Ctor(Animal *this, int age, int weight)
{
    this->age = age;
    this->weight = weight;
}

int Animal_GetAge(Animal *this)
{
    return this->age;
}

int Animal_GetWeight(Animal *this)
{
    return this->weight;
}

テストがあります:

#include <stdio.h>
#include "Animal.h"
#include "Dog.h"

int main()
{
    // 在栈上创建一个对象
    Animal a;  
    // 构造对象
    Animal_Ctor(&a, 1, 3); 
    printf("age = %d, weight = %d \n", 
            Animal_GetAge(&a),
            Animal_GetWeight(&a));
    return 0;
}

簡単に言うと、コードセグメントには、Animalオブジェクトを処理できる関数を格納するためのスペースがあり、スタックには、オブジェクトを格納するためのスペースがあります。

C ++との対比:C ++メソッドでは、このポインターが暗黙的に示される最初のパラメーター。オブジェクトのメソッドを呼び出すと、コンパイラはオブジェクトのアドレスをこのポインタに自動的に渡します。

したがって、Animal.hで関数をシミュレートし、表示されるthisポインターを定義し、オブジェクトを呼び出すときにオブジェクトのアドレスをアクティブに渡します。このようにして、関数は任意のAnimalオブジェクトを処理できます。

第四に、継承の問題を解決するためにサブクラスを実装します

Dog.h

#ifndef _DOG_H_
#define _DOG_H_

#include "Animal.h"

// 定义子类结构
typedef struct {
    Animal parent; // 第一个位置放置父类结构
    int legs;      // 添加子类自己的属性
}Dog;

// 子类构造函数声明
void Dog_Ctor(Dog *this, int age, int weight, int legs);

// 子类属性声明
int Dog_GetAge(Dog *this);
int Dog_GetWeight(Dog *this);
int Dog_GetLegs(Dog *this);

#endif

Dog.c

#include "Dog.h"

// 子类构造函数实现
void Dog_Ctor(Dog *this, int age, int weight, int legs)
{
    // 首先调用父类构造函数,来初始化从父类继承的数据
    Animal_Ctor(&this->parent, age, weight);
    // 然后初始化子类自己的数据
    this->legs = legs;
}

int Dog_GetAge(Dog *this)
{
    // age属性是继承而来,转发给父类中的获取属性函数
    return Animal_GetAge(&this->parent);
}

int Dog_GetWeight(Dog *this)
{
    return Animal_GetWeight(&this->parent);
}

int Dog_GetLegs(Dog *this)
{
    // 子类自己的属性,直接返回
    return this->legs;
}

テストがあります:

int main()
{
    Dog d;
    Dog_Ctor(&d, 1, 3, 4);
    printf("age = %d, weight = %d, legs = %d \n", 
            Dog_GetAge(&d),
            Dog_GetWeight(&d),
            Dog_GetLegs(&d));
    return 0;
}

コードセグメントには、Dogオブジェクトを処理できる関数を格納するためのスペースがあります。スタックには、d個のオブジェクトを格納するためのスペースがあります。Dog構造の最初のパラメーターはAnimalオブジェクトであるため、メモリモデルの観点からは、サブクラスには親クラスで定義された属性が含まれます。

Dogのメモリモデルの最初の部分には、Animalのメンバーが自動的に含まれます。つまり、DogはAnimalの属性を継承します。

5.仮想関数を使用して多形問題を解決します

C ++では、仮想関数が親クラスで定義されている場合、コンパイラはこのメモリに仮想テーブルを配置するためのスペースを開きます。このテーブルの各項目は関数ポインタであり、次に親クラスのメモリに配置されます。上記の仮想テーブルを指すモデル内の仮想テーブルポインター。

上記の説明はあまり正確ではなく、主にさまざまなコンパイラの処理方法に依存しますが、ほとんどのC ++プロセッサはこれを行うため、そのように考えることができます。

サブクラスが親クラスを継承すると、メモリ内にスペースを開いてサブクラス自体の仮想テーブルを配置し、継承された仮想テーブルポインターがサブクラス自体の仮想テーブルを指すようにします。

C ++がこれを行うため、Cを使用して、この動作を手動でシミュレートします。仮想テーブルと仮想テーブルポインターを作成します。

1. Animal.hは親クラスAnimalであり、仮想テーブルと仮想テーブルポインターを追加します

#ifndef _ANIMAL_H_
#define _ANIMAL_H_

struct AnimalVTable;  // 父类虚表的前置声明

// 父类结构
typedef struct {
    struct AnimalVTable *vptr; // 虚表指针
    int age;
    int weight;
} Animal;

// 父类中的虚表
struct AnimalVTable{
    void (*say)(Animal *this); // 虚函数指针
};

// 父类中实现的虚函数
void Animal_Say(Animal *this);

#endif

2. Animal.c

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

// 父类中虚函数的具体实现
static void _Animal_Say(Animal *this)
{
    // 因为父类Animal是一个抽象的东西,不应该被实例化。
    // 父类中的这个虚函数不应该被调用,也就是说子类必须实现这个虚函数。
    // 类似于C++中的纯虚函数。
    assert(0); 
}

// 父类构造函数
void Animal_Ctor(Animal *this, int age, int weight)
{
    // 首先定义一个虚表
    static struct AnimalVTable animal_vtbl = {_Animal_Say};
    // 让虚表指针指向上面这个虚表
    this->vptr = &animal_vtbl;
    this->age = age;
    this->weight = weight;
}

// 测试多态:传入的参数类型是父类指针
void Animal_Say(Animal *this)
{
    // 如果this实际指向一个子类Dog对象,那么this->vptr这个虚表指针指向子类自己的虚表,
    // 因此,this->vptr->say将会调用子类虚表中的函数。
    this->vptr->say(this);
}

>仮想関数テーブルanimal_vtblは、スタックスペースで定義されています。このテーブルの各項目は、関数ポインターです。たとえば、関数ポインターは、コードセグメント内の関数_Animal_Say()を指します。>オブジェクトaの最初のメンバーvptrは、この仮想関数テーブルanimal_vtblへのポインターです。

3.Dog.hは変更されません

4.Dog.cでサブクラス自体の仮想テーブルを定義します

#include "Dog.h"

// 子类中虚函数的具体实现
static void _Dog_Say(Dog *this)
{
    printf("dag say \n");
}

// 子类构造函数
void Dog_Ctor(Dog *this, int age, int weight, int legs)
{
    // 首先调用父类构造函数。
    Animal_Ctor(&this->parent, age, weight);
    // 定义子类自己的虚函数表
    static struct AnimalVTable dog_vtbl = {_Dog_Say};
    // 把从父类中继承得到的虚表指针指向子类自己的虚表
    this->parent.vptr = &dog_vtbl;
    // 初始化子类自己的属性
    this->legs = legs;
}

5.テストします

int main()
{
    // 在栈中创建一个子类Dog对象
    Dog d;  
    Dog_Ctor(&d, 1, 3, 4);

    // 把子类对象赋值给父类指针
    Animal *pa = &d;
    
    // 传递父类指针,将会调用子类中实现的虚函数。
    Animal_Say(pa);
}

メモリモデルは次のとおりです。

親クラスから継承された仮想テーブルポインタvptrであるオブジェクトdでは、ポイントされた仮想テーブルはdog_vtblです。

Animal_Say(pa)を実行すると、パラメータータイプは親クラスAnimalへのポインターですが、渡される実際のpaはサブクラスDogを指すオブジェクトであり、このオブジェクトの仮想テーブルポインターvptrサブクラスTheのそれ自体を指します。定義された仮想テーブルdog_vtbl、この仮想テーブルの関数ポインタsayは、サブクラスの再定義された仮想関数_Dog_Sayを指しているため、this-> vptr-> say(this)は最終的に関数_Dog_Sayを呼び出します。

基本的に、Cでのオブジェクト指向開発のアイデアは上記のとおりです。
このコードは非常に単純です。自分でタップするだけです。怠惰になりたい場合は、バックグラウンドでメッセージを残してください。それをお送りします。

6、プロジェクトでのCオブジェクト指向のアイデアの使用

1.Linuxカーネル

ソケットに関するいくつかの構造を見てください。

struct sock {
    ...
}

struct inet_sock {
    struct sock sk;
    ...
};

struct udp_sock {
    struct sock sk;
    ...
};

Sockは親クラスと見なすことができます。inet_sockとudp_sockの最初のメンバーはすべてsockタイプであり、これはメモリモデルからsockのすべての属性を継承することと同じです。

2.glibライブラリ

例として、最も単純な文字列処理関数を取り上げます。

GString * g_string_truncate(GString * string、
gint len)GString * g_string_append(GString * string、gchar * val)
GString * g_string_prepend(GString * string、gchar * val)

API関数の最初のパラメーターは、処理する必要のある文字列オブジェクトを指すGStringオブジェクトへのポインターです。

GString *s1, *s2;
s1 = g_string_new("Hello");
s2 = g_string_new("Hello");

g_string_append(s1," World!");
g_string_append(s2," World!");

3.その他のアイテム

関数パラメータの観点からはオブジェクト指向ではないように見えますが、データ構造の設計の観点からはオブジェクト指向のアイデアでもあるプロジェクトもあります。なので:

Modbusプロトコルオープンソースライブラリlibmodbusは
ホームオートメーションワイヤレス通信プロトコルに使用されますZWave
ずっと前にQualcomm携帯電話開発プラットフォームBREW


【原文】

OF:コロンビアロード(公開番号:町のもののIOT

はほとんど知っています:コロンビアロード

駅B:シェアコロンビアロード

デンバー:コロンビアロードシェア

CSDN:コロンビアロードシェア

記事が良いと思う場合は、転送して友達と共有してください。組み込み開発における10年以上のプロジェクトの実際の戦闘経験


要約して共有します。あなたが失望することはないと信じています!

転載:転載へようこそが、著者の同意なしに、この声明を保持し、元のリンクを記事に記載する必要があります。

推奨読書

[1] gdbの基本的なデバッグ原理は非常に単純であることがわかりました
[2]プロデューサーモードとコンシューマーモードのダブルバッファリングテクノロジー。
[3]詳細なLUAスクリプト言語により、デバッグ原理を完全に理解できます。

おすすめ

転載: blog.csdn.net/u012296253/article/details/111450689