C language interface and implementation method example

  • 2020-04-02 02:42:29
  • OfStack

In this paper, the interface and implementation method of C language are described in detail in the form of an example, which is of certain reference value for further mastering C language programming. Share with you for your reference. Specific analysis is as follows:

In general, a module has two parts: an interface and an implementation. The interface specifies what the module does, it declares the identifiers, types, and routines available to the code that USES the module, and it specifies how the module accomplishes the goal of its interface declaration. A given module usually has only one interface, but there may be many implementations that provide the functionality specified by the interface. Each implementation may use different algorithms and data structures, but they must conform to the instructions given by the interface. A client caller is a piece of code that USES a module to import the interface and implement the export interface. Because multiple client callers share the interface and implementation, using the implemented target code avoids unnecessary code duplication and helps avoid errors, since the interface and implementation are written and debugged only once and can be used many times.

interface

The interface only needs to specify the identifiers that the client caller might use, and should hide as many irrelevant presentation details and algorithms as possible so that the client caller does not have to rely on specific implementation details. This dependency -- coupling -- between the client caller and the implementation can cause errors when the implementation changes, and these errors can be difficult to fix when the dependency is buried in some hidden or ambiguous assumptions about the implementation, so a well-designed, well-described interface should minimize coupling.

The C language provides only the most basic support for the separation of interface and implementation, but simple conventions can bring huge benefits to the interface/implementation methodology. In C, the interface is declared in the header file, which declares the macros, types, data structures, variables, and routines that the client caller can use. The user imports the interface using the C preprocessing instruction #include.

The following examples illustrate some of the conventions, interfaces used in this article's interfaces:


extern int Arith_max(int x, int y);
extern int Arith_min(int x, int y);
extern int Arith_div(int x, int y);
extern int Arith_mod(int x, int y);
extern int Arith_ceiling(int x, int y);
extern int Arith_floor (int x, int y);

The name of the interface is Arith, the interface file is appropriately named Arith. H, and the name of the interface appears as a prefix in each identifier of the interface. Module names not only provide the appropriate prefix, but also help clean up the code for the client caller.

The Arith interface also provides some useful functions that are not available in the standard C library, and provides good definitions for starting and taking modules, which are not provided in standard C and are implementation-based only.

implementation

An implementation exports an interface that defines the necessary variables and functions to provide the functionality specified by the interface. In C, an implementation is provided by one or more.c files, and an implementation must provide the functionality specified by its exported interface. The implementation should include the. H file of the interface to ensure that its definition is consistent with the declaration of the interface.

Arith_min and Arith_max return the minimum and maximum values of their integer parameters:


int Arith_max(int x, int y) {
  return x > y ? x : y;
}
int Arith_min(int x, int y) {
  return x > y ? y : x;
}

Arith_div returns the quotient of y over x, and Arith_mod returns the corresponding remainder. So, when x is signed with y, Arith_div of x,y is equal to x over y, and Arith_mod of x,y is equal to x%y

When the symbols of x and y are different, the return value of the embedded operation of C depends on the specific implementation:

If minus 13/5 is equal to 2, minus 13%, 5 is equal to minus 3, if minus 13/5 is equal to minus 3, minus 13%, 5 is equal to 2

The standard library functions always round zero, so div(-13,2)=-2, and the semantics of Arith_div and Arith_mod are also defined: they always round to the left of the number line, so Arith_div(-13,5)= -3, and Arith_div(x,y) is the largest integer not exceeding the real number z, where z satisfies z*y=x.

Arith_mod(x,y) is defined as x-y*Arith_div(x,y). Thus Arith_mod (13, 5) = 13-5 * = 2 (or 3)

The functions Arith_ceiling and Arith_floor follow a similar convention, and Arith_ceiling(x,y) returns the smallest integer that is not less than the real quotient of x/y

Arith_floor(x,y) returns the largest integer not exceeding the real quotient of x/y

The complete implementation code is as follows:


#include "arith.h"
int Arith_max(int x, int y) {
  return x > y ? x : y;
}
int Arith_min(int x, int y) {
  return x > y ? y : x;
}
int Arith_div(int x, int y) {
  if (-13/5 == -2
  &&  (x < 0) != (y < 0) && x%y != 0)
    return x/y - 1;
  else
    return x/y;
}
int Arith_mod(int x, int y) {
  if (-13/5 == -2
  &&  (x < 0) != (y < 0) && x%y != 0)
    return x%y + y;
  else
    return x%y;
}
int Arith_floor(int x, int y) {
  return Arith_div(x, y);
}
int Arith_ceiling(int x, int y) {
  return Arith_div(x, y) + (x%y != 0);
}

Abstract data type

Abstract data type (ADT) is an interface that defines a data type and the various operations provided based on the value of that type

A high-level type is abstract because the interface hides its presentation details so that the client calling the program does not depend on them. The following is a normalized example of an abstract data type (ADT), the stack, which defines the type and five operations:


#ifndef STACK_INCLUDED
#define STACK_INCLUDED
#define T Stack_T
typedef struct T *T;
extern T   Stack_new (void);
extern int  Stack_empty(T stk);
extern void Stack_push (T stk, void *x);
extern void *Stack_pop (T stk);
extern void Stack_free (T *stk);
#undef T
#endif

implementation

Contains related header files:


#include <stddef.h>
#include "assert.h"
#include "mem.h"
#include "stack.h"
#define T Stack_T

Inside Stack_T is a structure that has a list of fields pointing to Pointers in the stack and a count of those Pointers:


struct T {
  int count;
  struct elem {
    void *x;
    struct elem *link;
  } *head;
};

Stack_new allocates and initializes a new T:


T Stack_new(void) {
  T stk;
  NEW(stk);
  stk->count = 0;
  stk->head = NULL;
  return stk;
}

Where NEW is an allocation macro instruction in another interface. NEW(p) allocates an instance of this structure and assigns its pointer to p, so it is used in Stack_new to assign a NEW Stack_T

When count=0, Stack_empty returns 1, otherwise returns 0:


int Stack_empty(T stk) {
  assert(stk);
  return stk->count == 0;
}

Assert (STK) implements a checkable runtime error that prohibits null Pointers from being passed to any function in the Stack.

Stack_push and Stack_pop from STK - > Add or remove elements from the head of the list that the head points to:


void Stack_push(T stk, void *x) {
  struct elem *t;
  assert(stk);
  NEW(t);
  t->x = x;
  t->link = stk->head;
  stk->head = t;
  stk->count++;
}
void *Stack_pop(T stk) {
  void *x;
  struct elem *t;
  assert(stk);
  assert(stk->count > 0);
  t = stk->head;
  stk->head = t->link;
  stk->count--;
  x = t->x;
  FREE(t);
  return x;
}

FREE is a release macro directive defined in another interface that frees the space to which the pointer argument points and then sets the argument to a null pointer


void Stack_free(T *stk) {
  struct elem *t, *u;
  assert(stk && *stk);
  for (t = (*stk)->head; t; t = u) {
    u = t->link;
    FREE(t);
  }
  FREE(*stk);
}

The complete implementation code is as follows:


#include <stddef.h>
#include "assert.h"
#include "mem.h"
#include "stack.h"
#define T Stack_T
struct T {
  int count;
  struct elem {
    void *x;
    struct elem *link;
  } *head;
};
T Stack_new(void) {
  T stk;
  NEW(stk);
  stk->count = 0;
  stk->head = NULL;
  return stk;
}
int Stack_empty(T stk) {
  assert(stk);
  return stk->count == 0;
}
void Stack_push(T stk, void *x) {
  struct elem *t;
  assert(stk);
  NEW(t);
  t->x = x;
  t->link = stk->head;
  stk->head = t;
  stk->count++;
}
void *Stack_pop(T stk) {
  void *x;
  struct elem *t;
  assert(stk);
  assert(stk->count > 0);
  t = stk->head;
  stk->head = t->link;
  stk->count--;
  x = t->x;
  FREE(t);
  return x;
}
void Stack_free(T *stk) {
  struct elem *t, *u;
  assert(stk && *stk);
  for (t = (*stk)->head; t; t = u) {
    u = t->link;
    FREE(t);
  }
  FREE(*stk);
}

I believe that this article has certain reference value to everyone's C program design.


Related articles: