Simple encapsulation and Inheritance Mechanism in pure C Language
0 inheritance is the basis of OO design
Inheritance is the basic part of OO design and also the basis for implementing polymorphism. C ++, C #, Objective-C, Java, PHP, JavaScript, and other languages designed for OO, the language itself provides direct support for implementation inheritance. The language that follows the C/Unix design philosophy never limits the programming style and provides basic support for implementing OO. Next we will look at how to implement inheritance in C language.
1. Meaning of inheritance at the memory layout Layer
Today, almost all programmers know the abstract meaning of inheritance, and familiar with examples of using rotten cats and dogs to inherit animals. Here, we leave the abstract world behind and go deep into the specific implementation of inheritance. Of course, different languages have different implementation mechanisms for inheritance, but it is very beneficial to understand a typical implementation details. C ++ is used as an example.
class B
{
int x;
int y;
int z;
};
class C : B
{
float f;
char s[10];
};
The code above indicates that subclass C inherits the parent class B. below is the memory layout of an instance (object) of Class C.
The C object consists of two parts. The red area inherits from B and the blue area is unique to itself. In this way, the red part can be regarded as a Class B object.
2. Use struct to implement inheritance in two ways. 2.1 the parent class object is a member of the subclass.
After understanding the inherited memory layout principle, it is very easy to use C to implement inheritance. The easiest way to think about it is as follows:
struct B
{
int x;
int y;
int z;
};
struct C
{
struct B objB;
float f;
char s[10];
};
The above code implements inheritance by including a B-type member in C. This method is very direct, but it is inconvenient to use.
struct C objC;
objC.objB.x = 10;
((struct B*)&objC)->x = 10;
To access the member x of the parent class, there are two methods: objC. objB. x; (struct B *) & objC)-> x = 10. Both methods do not seem straightforward enough. In the subclass method, the access to the parent class members is very frequent.
void c_member_method (struct C * pObjC)
{
pObjC-> objB.x = 20; / * Access the parent class members * /
pObjC-> f = 0.23f; / * access own members * /
}
The first method is more like OB style than OO.
The second method requires forced type conversion, and the syntax is not beautiful enough.
2.2 subclass contains all parent class members
struct C
{
int x;
int y;
int z;
float f;
char s[10];
};
All parent class members are used as child class members. In this way, the inherited members of the subclass object are very direct.
void c_member_method (struct C * pObjC)
{
pObjC-> x = 20; / * access to parent class members * /
pObjC-> f = 0.23f; / * access own members * /
}
void main ()
{
struct C objC;
objC.x = 10;
}
It looks good. In fact, there will be a big problem in the project: difficult to maintain! For example, every time you create a subclass, all the parent class members must be written as is. When the parent class definition changes, the subclass must make the same changes. Once the parent class has a relatively large size, maintaining this inheritance relationship will be a nightmare!
So how to solve it?
The method is ready-made, that is, using the pre-processing macro definition of C # define. as shown below:
#define B_STRUCT \
int x; \
int y; \
int z
struct B
{
B_STRUCT;
};
struct C
{
B_STRUCT;
float f;
char s[10];
};
When the inheritance level is deeper, for example, C inherits B and D inherits C, you can copy this method.
#define B_STRUCT \
int x; \
int y; \
int z
struct B
{
B_STRUCT;
};
#define C_STRUCT \
B_STRUCT; \
float f; \
char s[10]
struct C
{
C_STRUCT;
};
#define D_STRUCT \
C_STRUCT; \
double d
struct D
{
D_STRUCT;
};
With macro definition, this inheritance relationship can be easily implemented and maintained.
3. encapsulation and Inheritance of methods (member functions)
The C language does not have the concept of a member function and is not supported by the language itself. Using C language to implement true member functions is almost impossible unless embedded in assembly language. Instead of using the assembly language, it is better to use C ++ directly. Therefore, we do not pursue formal member functions, but only implement member functions in the sense-(operate on a given type object, and use the function name with the structure name prefix to name it. We will explain the above examples.
typedef struct B B;
static void b_member_function (B * pobjB) / * member function of class B * /
{
}
typedef struct C C;
static void c_member_function (C * pobjC) / * member function of class C * /
{
}
There are two scenarios for calling a member function: (1) calling a member function using external code; (2) calling a member function of the parent class in a subclass member function;
static void c_member_function (C * pobjC)
{
b_member_function ((B *) pobjC); / * Call the parent class member function from the child class member function * /
}
void main ()
{
C * pObjC = malloc (sizeof (C));
b_member_function ((B *) pObjC); / * External code calls member function * /
free (pObjC);
}
Both cases require forced conversion of the real parameter type to the parent type. The C compiler knows nothing about the type inheritance relationship and cannot automatically support the inheritance from the syntax. Therefore, it can only manually force the type conversion.
Some people like to simulate member functions and store the addresses of all member functions as pointer-type member variables inside the struct. As follows:
#define B_STRUCT \
int x; \
int y; \
int z; \
void (* pb_member_function1) (B *); \
void (* pb_member_function2) (B *, int arg)
struct B
{
B_STRUCT;
};
/ * Initialize B object at the same time * /
B * b = malloc (sizeof (B));
b-> pb_member_function1 = b_member_function1;
b-> pb_member_function2 = b_member_function2;
/* transfer */
b-> b_member_function1 (b);
In this way, the form is closer to the "member function", but it also brings additional memory overhead and amount of code. To reduce memory consumption, someone suggested not to completely store all member function pointers in the object, but to store only one pointer pointing to the member function address list. After all, all instances (objects) of the same type share the same group of functions.
/ * Member method table of type B * /
const struct B_MethodTable
{
void (* pb_member_function1) (B *);
void (* pb_member_function2) (B *, int arg);
} b_method_table {
b_member_function1,
b_member_function2,
};
#define B_STRUCT \
int x; \
int y; \
int z; \
struct B_MethodTable * pMethodTable;
struct B
{
B_STRUCT;
};
/ * Initialize B object at the same time * /
B * b = malloc (sizeof (B));
b-> pMethodTable = & b_method_table;
/* transfer */
b-> pMethodTable-> pb_member_function1 (b);
In this way, the memory usage and code usage are reduced to a certain extent, but the calling method of team member functions becomes very cumbersome and unnatural.
4. To what extent?
When using the C language for OO development, you must master a good degree. Do not overdo the simulation of the OO language C ++. If you fully simulate C ++, it is better to simply use C ++ directly.
Some language features cannot be simulated, such as Access qualifiers such as private and protected in C ++, and this pointer of member functions. We should pay more attention to the simulation in the sense and use naming rules and conventions to achieve OO. Never go against the design philosophy of C language: programmers control everything and are straightforward.
The grasp of this degree depends on the specific project scale and needs. It is found in practice and cannot provide the theoretical optimal value.