Análisis paso a paso: como la programación orientada a objetos en lenguaje C

Este es el 009 original del hermano Dao.

I. Introducción

En el desarrollo integrado, el lenguaje C / C ++ es el más popular. Antes de la versión C ++ 11, su sintaxis es relativamente similar, excepto que C ++ proporciona un método de programación orientado a objetos .

Aunque el lenguaje C ++ se desarrolló a partir del lenguaje C, el C ++ actual ya no es una extensión del lenguaje C del año. A partir de la versión 2011, se parece más a un lenguaje completamente nuevo.

Así que nunca pensé, ¿por qué querías extender C ++ en primer lugar? ¿Cuáles son las deficiencias del lenguaje C que llevaron a la aparición de C ++?

C ++ ha resuelto estos problemas muy bien, pero con la expansión gradual de los estándares del lenguaje, la dificultad de aprender el lenguaje C ++ ha aumentado gradualmente. No desarrollaron varios proyectos, les da vergüenza decir que aprendieron C ++, esos valores de izquierda y derecha, plantillas, parámetros de plantilla, argumentos de plantilla, concepto variable , pila, etc., que no es el uso de dos o tres años será competente Dominado.

Sin embargo, el lenguaje C también tiene muchas ventajas:

De hecho, la última ventaja es la más importante: cuanta más gente la use, más fuerte será la vitalidad. Al igual que la sociedad actual, no se trata de la supervivencia del superior, sino de la supervivencia del más apto.

En este artículo, hablaremos sobre cómo usar ideas orientadas a objetos para programar en el lenguaje C. Tal vez no pueda, pero se recomienda encarecidamente que mire el proyecto con, porque tuve que abandonar cuando me hicieron esta pregunta dos veces.

En segundo lugar, ¿qué es la programación orientada a objetos?

Existe una fórmula de este tipo: programa = estructura de datos + algoritmo .

La programación orientada a procesos se usa generalmente en lenguaje C, que consiste en analizar los pasos necesarios para resolver el problema y luego usar la función para llamar a estos pasos paso a paso y procesar la estructura de datos en la función (ejecutar el algoritmo), es decir, la estructura de datos y el algoritmo están separados .

El lenguaje C ++ encapsula datos y algoritmos para formar un todo. Ya sea operando sus propiedades o llamando a su comportamiento, se ejecuta a través de un objeto. Ésta es la idea de la programación orientada a objetos.

Si se utiliza el lenguaje C para simular un método de programación de este tipo, es necesario resolver tres problemas:

  1. Encapsulación de datos
  2. heredar
  3. Polimorfismo

La primera pregunta: empaque

La encapsulación describe la organización de los datos, que consiste en organizar todos los atributos (datos) que pertenecen a un objeto juntos. El tipo de estructura en el lenguaje C lo soporta de manera inherente.

La segunda pregunta: herencia

La herencia describe la relación entre los objetos. Al heredar la clase principal, la subclase automáticamente posee las propiedades y comportamientos (es decir, métodos) en la clase principal. Este problema no es un problema siempre que comprenda el modelo de memoria del lenguaje C. Siempre que coloque una variable de la estructura principal en la posición de la primera variable miembro en la estructura de la subclase, el objeto de la subclase hereda las propiedades del padre clase.

Otro punto para agregar: ¡Para aprender cualquier idioma, debes entender el modelo de memoria!

El tercer problema: polimorfismo

Tomado literalmente, polimorfismo significa "estados múltiples" y describe un comportamiento dinámico . En C ++, el polimorfismo ocurre solo cuando se llaman funciones virtuales a través de referencias de clase base o punteros, es decir, el polimorfismo ocurre durante el tiempo de ejecución, y C ++ implementa internamente el polimorfismo mediante una tabla virtual. Luego, en lenguaje C, también podemos seguir esta idea para lograrlo.

Si un lenguaje solo admite clases y no admite polimorfismo, solo se puede decir que está basado en objetos, no orientado a objetos.

Dado que no hay ningún problema con la idea, implementemos uno simplemente.

En tercer lugar, primero implemente una clase principal para resolver el problema de encapsulación.

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;
}

tener una prueba:

#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;
}

Puede entenderse simplemente como: hay un espacio en el segmento de código para almacenar las funciones que pueden manejar el objeto Animal; hay un espacio en la pila para almacenar el objeto a.

Contraste con C ++: en el método C ++, el primer parámetro de este puntero está implícito. Al llamar a un método de un objeto, el compilador pasará automáticamente la dirección del objeto a este puntero.

Por lo tanto, simulamos la función en Animal.h, definimos el puntero que se muestra y le pasamos activamente la dirección del objeto al llamarlo, de esta manera la función puede procesar cualquier objeto Animal.

Cuarto, implemente una subclase para resolver el problema de la herencia.

Perro.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

Perro.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;
}

tener una prueba:

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;
}

Hay un espacio en el segmento de código para almacenar funciones que pueden manejar objetos Dog; hay un espacio en la pila para almacenar d objetos. Dado que el primer parámetro en la estructura Perro es el objeto Animal, desde el punto de vista del modelo de memoria, la subclase contiene los atributos definidos en la clase padre.

La parte inicial del modelo de memoria de Dog incluye automáticamente los miembros de Animal, lo que significa que Dog hereda los atributos de Animal.

5. Usa funciones virtuales para resolver problemas polimórficos

En C ++, si se define una función virtual en una clase principal, el compilador abrirá un espacio en esta memoria para colocar una tabla virtual. Cada elemento de esta tabla es un puntero de función, y luego en la memoria de la clase principal Put un puntero de tabla virtual en el modelo para apuntar a la tabla virtual anterior.

La descripción anterior no es muy precisa, depende principalmente de los métodos de procesamiento de varios compiladores, pero la mayoría de los procesadores C ++ hacen esto, podemos pensar en ello como tal.

Después de que la subclase hereda la clase principal, abrirá un espacio en la memoria para colocar la propia tabla virtual de la subclase, y luego dejará que el puntero de la tabla virtual heredada apunte a la propia tabla virtual de la subclase.

Dado que C ++ hace esto, usamos C para simular manualmente este comportamiento: crear tablas virtuales y punteros de tablas virtuales.

1. Animal.h es la clase principal Animal, agregue una tabla virtual y un puntero de tabla virtual

#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);
}

> Una tabla de función virtual animal_vtbl se define en el espacio de la pila. Cada elemento de esta tabla es un puntero de función. Por ejemplo, el puntero de función say apunta a la función _Animal_Say () en el segmento de código. > El primer miembro vptr del objeto a es un puntero a esta tabla de funciones virtuales animal_vtbl.

3. Dog.h permanece sin cambios

4. Defina la propia tabla virtual de la subclase en 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. Pruébelo

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

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

El modelo de memoria es el siguiente:

En el objeto d, el puntero de tabla virtual vptr heredado de la clase principal, la tabla virtual apuntada es dog_vtbl.

Al ejecutar Animal_Say (pa) , aunque el tipo de parámetro es un puntero a la clase padre Animal , el pa real pasado es un objeto que apunta a la subclase Dog , y el puntero de tabla virtual vptr en este objeto apunta a sí mismo en la subclase The tabla virtual definida dog_vtbl, el puntero de función say en esta tabla virtual apunta a la función virtual redefinida _Dog_Say en la subclase , por lo que this-> vptr-> say (this) finalmente llama a la función _Dog_Say.

Básicamente, la idea de desarrollo orientado a objetos en C es la anterior.
Este código es muy simple, solo tócalo tú mismo. Si quieres ser perezoso, deja un mensaje en segundo plano y te lo enviaré.

Seis, el uso de ideas orientadas a objetos de C en el proyecto.

1. Kernel de Linux

Eche un vistazo a varias estructuras sobre enchufes:

struct sock {
    ...
}

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

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

Sock puede considerarse como una clase padre Los primeros miembros de inet_sock y udp_sock son todos tipos de calcetines, lo que equivale a heredar todos los atributos de sock del modelo de memoria.

2. biblioteca glib

Tome la función de procesamiento de cadenas más simple como ejemplo:

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

El primer parámetro de la función API es un puntero a un objeto GString, que apunta al objeto de cadena que debe procesarse.

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

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

3. Otros artículos

También hay algunos proyectos, aunque desde el punto de vista de los parámetros de la función, parece que no están orientados a objetos, pero desde el punto de vista del diseño de la estructura de datos, también son ideas orientadas a objetos, como como:

Biblioteca de código abierto del protocolo Modbus libmodbus se
utiliza para el protocolo de comunicación inalámbrica de automatización del hogar ZWave
Hace mucho tiempo Plataforma de desarrollo de teléfonos móviles Qualcomm BREW


[Declaración original]

OF: Columbia Road (número público: El IOT de las cosas de la ciudad )

sé casi: Columbia Road

estación B: Share Columbia Road

Denver: Columbia Road share

CSDN: Columbia Road Share

Si cree que el artículo es bueno, reenvíelo y compártalo con sus amigos.


Voy a resumir y compartir la experiencia de combate real de los proyectos de desarrollo integrado por más de diez años , creo que usted no será decepcionado!

Reimpresión: bienvenido a reimprimir, pero sin el consentimiento del autor, esta declaración debe conservarse y el enlace original debe incluirse en el artículo.

Lectura recomendada

[1] Resulta que el principio de depuración subyacente de gdb es tan simple.
[2] La tecnología de doble búfer en el modo de productor y consumidor.
[3] El lenguaje de scripting LUA en profundidad le permite comprender completamente el principio de depuración

Supongo que te gusta

Origin blog.csdn.net/u012296253/article/details/111450689
Recomendado
Clasificación