[Practical combat] What exactly is C language object programming?

Preface

Before, Xiao Yao shared and wrote an article about face-to-face objects. In this article, 真的可以,用C语言实现面向对象编程OOPXiao Yao compiled a ZhengNL three-in-one article about face-to-face objects. The example is also very easy to understand. I hope it will be helpful to everyone. .

Although C language is not an object-oriented language, you can also use object-oriented ideas to design our programs.

C语言 + 面向对象的思想It is widely used in our embedded system, and the main advantage is that it can make our software more expandable, easier to read, and easier to maintain.

Because this piece of knowledge is also more important and belongs to general knowledge, I plan to share a few notes and study it with everyone.

Of course, C language is not an object-oriented language, and it will be difficult to fully implement some of the same object-oriented features as C++. Therefore, the content we share is also basic and practical.

Encapsulation and abstraction

封装性It is one of the three characteristics of object-oriented programming (encapsulation, inheritance, polymorphism), but it is also the most important feature. 封装+抽象The combination can provide a low-coupling module.

Data encapsulation is a mechanism that binds data and functions to manipulate data. Data abstraction is a mechanism that only exposes the interface to the user while hiding the specific implementation details.

In the C language, data encapsulation can be 结构体started, and data members and function pointer members for operating data can be placed in the structure. Of course, the structure can also contain only the data to be manipulated.

Let's take a simple example as a demonstration.

Design a software module, the objects to be operated in the module are 长方形, the interfaces that need to be provided externally are:

1. Create a rectangular object;

2. Set the length and width;

3. Obtain the rectangular area;

4. Print rectangular information (length, width, height);

5. Delete the rectangular object.

Let's complete the demo code together. First of all, let's think about it. What is the name of our interface? In fact, this is a rule to follow. Let's see how the object-oriented interface of RT-Thread is designed:

We also imitate this naming form to name several interfaces of our demo:

1、rect_create
2、rect_set
3、rect_getArea
4、rect_display
5、rect_delete

We create a rect.hheader file and declare here several interfaces we provide to the outside world. At this time, our header file can be designed as:

There is no problem with this. However, the data is not well hidden, and the things we provide for external use should be as simple as possible.

We can think about the file operation interface of C language, what kind of file operation interface the C language library provides us? Such as:

Swipe left and right to view all codes >>>

FILE *fopen(const char *pathname, const char *mode);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

We will create a file handle (descriptor), and then only need to manipulate the file handle, we don't need to care about FILEhow it is implemented.

What is a handle? Look at the explanation of Baidu Baike:

We can also create our object handle, and we only need to expose our object handle in the header file provided to the outside world, without exposing the specific implementation. The above header file rect.hcode can be modified to:

Here we used void*, 其为无类型指针,void *可以指向任何类型的数据. Then the specific structure to be operated can be implemented in .c:

Below we implement the above five functions in turn:

1. The rect_create function

Swipe left and right to view all codes >>>

/* 创建长方形对象 */
HandleRect rect_create(const char *object_name)
{
 printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);

 /* 给rect结构体变量分配内存 */
 pRect rect = (pRect)malloc(sizeof(Rect));
 if (NULL == rect)
 {
  //free(rect);
  //rect = NULL;
  abort();
 }
 /* 给rect->object_name字符串申请内存 */
 rect->object_name = (char*)malloc(strlen(object_name) + 1);
 if (NULL == rect->object_name)
 {
  //free(rect->object_name);
  //rect->object_name = NULL;
  abort();
 }

 /* 给结构体各成员进行初始化 */
 strncpy(rect->object_name, object_name, strlen(object_name) + 1);
 rect->length = 0;
 rect->width = 0;
 
 return ((HandleRect)rect);
}

rect object creation function: first allocate memory, then assign values ​​to each member of the rect structure, and finally return the rect object handle. The object_name member of rect is a string, so memory must be allocated separately.

2. The rect_set function

Swipe left and right to view all codes >>>

/* 设置长方形对象长、宽 */
void rect_set(HandleRect rect, int length, int width)
{
 printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);
 if (rect)
 {
  ((pRect)rect)->length = length;
  ((pRect)rect)->width = width;
 }
}

3. The rect_getArea function

Swipe left and right to view all codes >>>

/* 获取长方形对象面积 */
int rect_getArea(HandleRect rect)
{
 return ( ((pRect)rect)->length * ((pRect)rect)->width );
}

4. rect_display function

Swipe left and right to view all codes >>>

/* 打印显示长方形对象信息 */
void rect_display(HandleRect rect)
{
 printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);
 if (rect)
 {
  printf("object_name = %s\n", ((pRect)rect)->object_name);
  printf("length = %d\n", ((pRect)rect)->length);
  printf("width = %d\n", ((pRect)rect)->width);
  printf("area = %d\n", rect_getArea(rect));
 }
}

5. rect_delete function

Swipe left and right to view all codes >>>

void rect_delete(HandleRect rect)
{
 printf(">>>>>>>>>> %s: %s (line: %d) <<<<<<<<<<\n", __FILE__, __FUNCTION__, __LINE__);
 if (rect)
 {
  free(((pRect)rect)->object_name);
  free(rect);
  ((pRect)rect)->object_name = NULL;
  rect = NULL;
 }
}

rect object deletion function: mainly to release the memory requested by malloc in the creation function.

We can see the five main interface contains three types of objects: 创建对象函数、操作函数、删除对象函数. The operation functions here are the rect_set function, the rect_getArea function and the rect_display function. Of course, there can be more operation functions.

The characteristic of the operation function is that at least one handle representing the object needs to be passed in, the actual data structure conversion is performed inside the function, and then the corresponding operation is performed.

6. Test procedure:

Swipe left and right to view all codes >>>

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

int main(void)
{
 HandleRect rect = rect_create("rect_obj");  // 创建Rect对象句柄
 rect_set(rect, 20, 5);         // 设置     
 rect_display(rect);            // 打印显示 
 rect_delete(rect);             // 删除Rect对象句柄 
 
 return 0;
}

operation result:

In object-based programming, encapsulation is the most basic and most important content. Its object mainly includes two aspects: 属性and 方法.

In C language-based object programming, you can use handles to represent objects, that is, the members of the data structure pointed to by the handle represent the properties of the object, and the function that actually manipulates the handle represents the method of the object.

inherit 

To put it simply, inheritance is what the father has, and the child can inherit it.

When creating a class, we do not need to rewrite new data members and member functions, just specify that the newly created class inherits the members of an existing class.

This existing class is called the base class , and the newly created class is called the derived class .

Inheritance will be subdivided into many in C++, we don't think about that much, and only share the simpler and more practical ones.

In C language object programming, there are two ways to achieve inheritance:

The first is: the structure contains the structure to achieve inheritance.

The second is: the use of private pointers to achieve inheritance.

The following is still shared with examples:

Structure contains structure

Take the example of our previous note as an example to continue. The example from the previous article is:

If the object we want to manipulate becomes a cuboid, the cuboid can inherit the data members and functions of the rectangle, so that some of the previous code can be reused. See the code for specific operations:

1. Structure

2. Header file

3. Cuboid object creation and deletion functions

4. Operation function

5. Test and test results

It can be seen that the rectangular parallelepiped structure can inherit the data of the rectangular structure, and the related operations of the rectangular parallelepiped object can also inherit the related operations of the rectangular object. In this way, some codes from the previous article on the operation of rectangular objects can be reused, which improves the code reuse rate.

Use private pointers to achieve inheritance

Add a private pointer member inside the structure, this private member can achieve the role of extended attributes, for example, the above Rect structure is designed as:

typedef struct _Rect
{
 char *object_name;
 int length;
 int width;
 void* private; 
}Rect, *pRect;

This private pointer can be bound with other extended properties when creating the object. such as:

The data you want to expand is:

Object creation function with extended attributes:

Obviously, using private pointers is also a way to achieve inheritance.

However, for this example, using private pointers for inheritance seems a bit confusing, because the attributes of a rectangle are roughly only length and width, and after adding a height, it is not called a rectangle.

This example is not suitable for demonstration, the more the demonstration, the more chaotic. . I won't continue the demonstration. We probably know that there is such a method.

The structure contains a private pointer member that is often seen in many great code, although it may not implement object inheritance, so you should try to master it.

 

Polymorphism

多态Literally means multiple forms. Polymorphism is used when there is a hierarchy between classes and the classes are related by inheritance.

Polymorphism means that when a member function is called, different functions will be executed according to the type of object calling the function.

For example, an example of polymorphic C++ (the C++ code comes from the rookie tutorial):

Swipe left and right to view all codes >>>

#include <iostream> 
using namespace std;

// 基类  
class Shape 
{
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual int area()
      {
         cout << "Parent class area" <<endl;
         return 0;
      }
};

// 派生类Rectangle
class Rectangle: public Shape
{
   public:
      Rectangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      { 
         cout << "Rectangle class area" <<endl;
         return (width * height); 
      }
};

// 派生类Triangle
class Triangle: public Shape
{
   public:
      Triangle( int a=0, int b=0):Shape(a, b) { }
      int area ()
      { 
         cout << "Triangle class area" <<endl;
         return (width * height / 2); 
      }
};

// 程序的主函数
int main( )
{
   Shape *shape;
   Rectangle rec(10,7);
   Triangle  tri(10,5);
 
   // 存储矩形的地址
   shape = &rec;
   // 调用矩形的求面积函数 area
   shape->area();
 
   // 存储三角形的地址
   shape = &tri;
   // 调用三角形的求面积函数 area
   shape->area();
   
   return 0;
}

The results of compiling and running are:

A keyword is used in the code:, virtualwhich is a C++ keyword. The function modified with the virtual keyword in the base class is called 虚函数.

This virtual function feels a bit like a weak definition. First define a weak/virtual function, and then define the real function with the same name elsewhere. The real function is actually used.

In this example, when redefining the virtual function area defined in the base class in the derived class, it will tell the compiler not to statically link to the function, but to choose to call the real function according to the type of object called.

If this example does not use virtualto modify the area function in the base class, the output of the above example is:

Obviously, if it is not virtualmodified, all areas in the base class are used.

Benpian note we also need to know a knowledge: 虚函数表. Specific introduction such as (picture screenshot from Baidu Encyclopedia):

This note will no longer expand on C++ related knowledge, and interested friends can check the information and learn by themselves. Let's take a look at an example of how to implement the appeal in the C language:

Analysis of C language polymorphism examples

In this section, we use C language to implement the functions of the above examples. Look at the specific implementation below:

1. Virtual function table

First, we can use 函数指针to simulate the virtual function table of C++:

/* 模拟C++的虚函数表 */
typedef struct _Ops
{
 int (*area)(void);
}Ops;

2. The base class Shape:

/* 基类 */  
typedef struct _Shape 
{
 Ops ops;
 int width;
 int height;
}Shape;

3. Derived classes Rectangle, Triangle

/* 派生类Rectangle */
typedef struct _Rectangle
{
 Shape shape;
 char rectangle_name[20];
}Rectangle;

/* 派生类Triangle */
typedef struct _Triangle
{
 Shape shape;
 char triangle_name[20];
}Triangle;

4. The area functions corresponding to the two derived classes

/* Rectangle的area函数 */
int rectangle_area(void)
{
 printf("Rectangle class area\n");
}

/* Triangle的area函数 */
int triangle_area(void)
{
 printf("Triangle class area\n");
}

5. Main function/test function

Swipe left and right to view all codes >>>

/* 主函数 */
int main(void)
{
 Rectangle rectangle;
 memset(&rectangle, 0, sizeof(Rectangle));
 rectangle.shape.ops.area = rectangle_area; /* 与自己的area函数做绑定 */

 Triangle triangle;
 memset(&triangle, 0, sizeof(Triangle));
 triangle.shape.ops.area = triangle_area; /* 与自己的area函数做绑定 */

 Shape *shape;

 shape = (Shape*)&rectangle;
 shape->ops.area();

 shape = (Shape*)&triangle;
 shape->ops.area();
 
 return 0;
}

The results of compiling and running are:

The result obtained in the C++ example is the same. That is, when the parent class pointer shape is used to manipulate two subclasses, different functions are called when using the same interface:

The above implements a simple polymorphic function.

In this example, we have only one operation function (virtual function), namely the area function.

If there are multiple operation functions, we can create another structure variable (function table) to wrap these functions in a layer, which will be clearer.

In this example, there is the following correspondence:

Because there is only one operation function, there is no function table to wrap one layer. We can add another function table, such as:

If there are multiple functions, it is even more necessary to build a function table:

to sum up

C language is not an object-oriented language. It is more difficult to fully implement some object-oriented features like C++. However, in the embedded development process, C language is widely used, and in large projects, a good software Frameworks can help us develop more effectively, so the object-oriented thinking is extremely important.

Guess you like

Origin blog.csdn.net/u012846795/article/details/108354126