A linguagem C implementa o princípio orientado a objetos ~

Não sei quantas pessoas aprenderam sobre a história do desenvolvimento da linguagem.As funções gramaticais da linguagem C primitiva são, na verdade, relativamente simples. Conforme os requisitos e cenários do aplicativo mudam, as funções gramaticais da linguagem C são constantemente atualizadas e mudadas.

Embora nosso livro tenha essa conclusão: a linguagem C é uma linguagem orientada a processos , C ++ é uma linguagem de programação orientada a objetos, mas o conceito de orientada a objetos está no estágio da linguagem C e é aplicado a muitos lugares, como alguns sistemas operacionais Kernel, protocolo de comunicação, etc.

A programação orientada a objetos, também conhecida como OOP (Object Oriented Programming), não é uma linguagem ou ferramenta específica. É apenas um método de design e filosofia de design . As três características mais básicas que exibe são encapsulamento, herança e Polimórfico .

Por que usar a linguagem C para realizar orientação a objetos

Antes de ler o texto, alguns leitores certamente farão essas perguntas: Temos uma linguagem C ++ orientada a objetos, por que deveríamos usar a linguagem C para obter orientação a objetos?

A linguagem C, uma linguagem não orientada a objetos, também pode usar idéias orientadas a objetos para escrever programas. É mais simples implementar a programação orientada a objetos usando a linguagem C ++ orientada a objetos , mas a eficiência da linguagem C é incomparável por outras linguagens de programação orientadas a objetos.

Obviamente, o uso da linguagem C para implementar o desenvolvimento orientado a objetos é relativamente difícil de entender , e é por isso que a maioria das pessoas aprendeu a linguagem C, mas não consegue entender o código-fonte do kernel do Linux.

Portanto, esta questão é realmente muito fácil de entender, desde que leitores com certa experiência em programação C devam ser capazes de entender: comparada à linguagem C orientada a processos e à linguagem C ++ orientada a objetos, a eficiência da operação do código e a quantidade de código são muito diferentes . É particularmente importante usar a programação orientada a objetos da linguagem C em MCUs com baixo desempenho e poucos recursos.

Qualificado

Para usar a linguagem C para obter orientação a objetos, primeiro você precisa ter alguns conhecimentos básicos. Por exemplo: (em linguagem C) estrutura, função, ponteiro e ponteiro de função, etc., (em C ++) classe base, derivação, polimorfismo, herança, etc.

Em primeiro lugar, não é apenas para entender esses conhecimentos básicos, mas para ter certa experiência de programação, porque o dito acima "orientado a objetos é um método de design e uma ideia de design", se for apenas um entendimento literal, não existe essa ideia de design. De jeito nenhum.

Portanto, não é recomendado para iniciantes usar a linguagem C para implementar a orientação a objetos, especialmente em projetos reais. Recomenda-se praticar bem as habilidades básicas antes de usá-lo.

Existem muitas maneiras de implementar a orientação a objetos usando a linguagem C, e o encapsulamento, a herança e o polimorfismo mais básicos são descritos abaixo.

Pacote

Encapsulamento é empacotar dados e funções em uma classe.Na verdade, a maioria dos programadores da linguagem C tem estado perto disso.

O objeto de operação de funções como fopen (), fclose (), fread (), fwrite () na biblioteca padrão C é FILE. O conteúdo dos dados é FILE, as operações de leitura e gravação de dados são fread (), fwrite (), fopen () é análogo ao construtor e fclose () é o destruidor.

Isso parece ser bem compreendido, então vamos implementar os recursos básicos de empacotamento.

#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 */

Esta é a declaração da classe Shape, muito simples e fácil de entender. Normalmente, a declaração é colocada no arquivo de cabeçalho "Shape.h". Dê uma olhada na definição da classe Shape, é claro que está em "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;
}

Olhe para main.c novamente

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

Após a compilação, observe o resultado da execução:

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

Todo o exemplo é muito simples e fácil de entender. Ao escrever código no futuro, você deve pensar mais sobre a operação de IO de arquivo da biblioteca padrão, para que também possa cultivar conscientemente o pensamento de programação orientada a objetos.

herdar

Herança é definir uma nova classe com base em uma classe existente , o que ajuda a reutilizar o código e organizar melhor o código. Na linguagem C, também é muito simples implementar herança única, desde que a classe base seja colocada na posição do primeiro membro de dados da classe herdada.

Por exemplo, agora vamos criar uma classe Rectangle, só precisamos herdar as propriedades e operações existentes da classe Shape e, em seguida, adicionar propriedades e operações que são diferentes de Shape para Rectangle.

A seguir está a declaração e definição de retângulo:

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

Vamos dar uma olhada na relação de herança e layout de memória de Rectangle:

Por causa desse layout de memória, você pode passar com segurança um ponteiro para um objeto Retângulo para uma função que espera passar um ponteiro para o objeto Forma, ou seja, um parâmetro de função é "Forma *", você pode passar "Retângulo * "e é muito seguro. Desta forma, todas as propriedades e métodos da classe base podem ser herdados pela classe herdada!

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

Resultado de saída:

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)

Polimorfismo

Alcançar polimorfismo na linguagem C ++ é usar funções virtuais. Na linguagem C, o polimorfismo também pode ser alcançado.

Agora, precisamos adicionar um círculo novamente e, para expandir a função em Forma, precisamos adicionar as funções area () e draw (). Mas Shape é equivalente a uma classe abstrata, não sei como calcular minha própria área, muito menos como desenhar a mim mesmo. Além disso, os métodos de cálculo de área e imagens geométricas de retângulos e círculos também são diferentes.

Vamos redeclarar a classe 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 */

Observe o diagrama de classe depois de adicionar funções virtuais:

5.1 Tabela virtual e ponteiro virtual

Tabela Virtual é uma coleção de ponteiros de função para todas as funções virtuais desta classe.

Virtual Pointer é um ponteiro para uma mesa virtual. Este ponteiro virtual deve existir em todas as instâncias do objeto e será herdado por todas as subclasses.

Eles são apresentados no primeiro capítulo de "Por dentro do modelo de objeto C ++".

5.2 Definir vptr no construtor

Em cada instância do objeto, vptr deve ser inicializado para apontar para seu vtbl. O melhor lugar para inicializar é no construtor da classe. Na verdade, no construtor, o compilador C ++ cria implicitamente um vptr inicializado. Na linguagem C, temos que inicializar o vptr explicitamente.

Aqui está como inicializar este vptr no construtor de Shape.

#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.3 Herdando vtbl e sobrecarregando vptr

Conforme mencionado acima, a classe base contém vptr e as subclasses o herdarão automaticamente. No entanto, vptr precisa ser reatribuído pela tabela virtual da subclasse. E, isso também deve acontecer no construtor da subclasse. O seguinte é o construtor de 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 Chamada de função virtual

Com a implementação básica das tabelas virtuais anteriores (Tabelas virtuais) e ponteiros virtuais (Ponteiros virtuais), as chamadas virtuais (ligação tardia) podem ser implementadas com o código a seguir.

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

Esta função pode ser colocada no arquivo .c, mas trará a desvantagem de que cada chamada virtual tem sobrecarga de chamada adicional. Para evitar esta lacuna, se o compilador suportar funções embutidas (C99). Podemos colocar a definição no arquivo de cabeçalho, semelhante ao seguinte:

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

Se for um compilador mais antigo (C89), podemos implementá-lo com uma função macro, semelhante à seguinte:

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

Dê uma olhada no mecanismo de chamada no exemplo:

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

Resultado de saída:

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)

Resumindo

Novamente, a programação orientada a objetos é um método, não limitado a uma determinada linguagem de programação. Usar a linguagem C para implementar o encapsulamento e a herança única é relativamente simples de entender e implementar, mas o polimorfismo é um pouco mais complicado. Se você planeja usar o polimorfismo amplamente, é recomendável mudar para a linguagem C ++. Afinal, esse nível de complexidade é encapsulado por esta linguagem. Sim, você só precisa usá-lo de forma simples. Mas isso não significa que a linguagem C não possa realizar a característica do polimorfismo.

Fonte: https://blog.csdn.net/onlyshi/article/details/81672279


1. Virtual Roundtable Part 1-Embedded System Information Security

2. O segredo do 5G da Huawei estava nas mãos de um turco? !

3. As últimas classificações de semicondutores são lançadas e a Nvidia atinge um crescimento de 50%!

4. [MCU] Um programa de atualização IAP "flexível e com economia de recursos"

5. Por que o RISC-V está se tornando um ponto quente?

6. Introdução ao Hongmeng OS adequado para desenvolvedores ~

Aviso: Este artigo é reproduzido online e os direitos autorais pertencem ao autor original. Se você estiver envolvido em questões de direitos autorais, entre em contato conosco, iremos confirmar os direitos autorais com base nos materiais de certificação de direitos autorais que você fornecer e pagar a remuneração do autor ou excluir o conteúdo.

Acho que você gosta

Origin blog.csdn.net/DP29syM41zyGndVF/article/details/110507699
Recomendado
Clasificación