0

Below is the program,

#include<stddef.h>
#include<stdlib.h>
#include<string.h>
#include<stdio.h>

#define INITIAL_ARRAY_SIZE 10

typedef struct{
  int *a;
  int lastItem; //Location of lastest element stored in array
  int size;     //Size of array
}List;



void newList(List **lptr, int size){
  *lptr = malloc(sizeof(List));
  (*lptr)->a = calloc(size, sizeof(int));
  (*lptr)->lastItem = -1;
  (*lpr)->size =0;

}

List* insertItem(List *lptr, int newItem){

  if(lptr->lastItem + 1 == lptr->size){

    List *newLptr = NULL;
    newList(&newLptr, 2*lptr->size);
    memcpy(newLptr->a, lptr->a, (lptr->lastItem)+1);
    newLptr->lastItem = lptr->lastItem;
    newLptr->size = 2*lptr->size;
    newLptr->a[++(newLptr->lastItem)] = newItem;

    free(lptr);
    return newLptr;

}
  lptr->a[++(lptr->lastItem)] = newItem;
  return lptr;
}

int main(void){
  List *lptr = NULL;
  newList(&lptr, INITIAL_ARRAY_SIZE);

  lptr = insertItem(lptr, 6);

  for(int i=0; i < INITIAL_ARRAY_SIZE;i++){
    printf("Item: %d\n", lptr->a[i]);
  }
  printf("last item value: %d", lptr->lastItem);
}

written to implement List using C array.

Above code is written to follow abstraction.

How to ensure encapsultion and polymorphism, in this code?

overexchange
  • 15,768
  • 30
  • 152
  • 347
  • Polymorphism? I don't see any here... – Jean-François Fabre Nov 10 '16 at 18:46
  • You're, at best, using the abstraction principle. There's no encapsulation or polymorphism there though. – itsme86 Nov 10 '16 at 18:46
  • Unrelated to your question, but your `insertItem` function is flawed. First of all when you do `sizeof` on a pointer, you get the size of the *pointer* and not what it points to. Secondly it will not build. Thirdly you create a brand new list structure that you will "forget" when the function returns. For the last thing you might want to read about the `realloc` function. There are also some other problems with the function. – Some programmer dude Nov 10 '16 at 18:48
  • http://stackoverflow.com/questions/351733/can-you-write-object-oriented-code-in-c – pm100 Nov 10 '16 at 18:50
  • 1) Your question is too broad. 2) Why pass a `List **`, where returning a `List *` would suffice? 3) As others stated, there is no polymorphism and you cannot have that in C, because you cannot have multiple identical names in the same scope. You can use manual name-mangling, though. I'm not sure if that is worth the effort, though, there is a point up to which OOP in C is fine and good readable. Beyond that one should take the step towards a true OOPL (which is not necessarily C++) to enhance readability and avoid programming errors or macro-bloat. – too honest for this site Nov 10 '16 at 18:59
  • @itsme86 How to ensure encapsulation in above code? – overexchange Nov 10 '16 at 19:11
  • Drop a `void *` in for your type, then create a list of functions per type you want to use with each having its own header. Rely on function name overloading (hope you have that) to get to each list type handled and called correctly. Perhaps use macro fun to try and handle generic typing between each type of function. It gets very, very ugly very quickly though, which is why we don't try to do this in 'C'. – Michael Dorgan Nov 10 '16 at 19:28
  • @MichaelDorgan As per your suggestions, I updated code accordingly [here](https://github.com/shamhub/Computing/blob/master/Array/array.c). Assuming, *Encapsulation* is about maintaining invariants of an ADT and *Abstraction* creates a barrier between representation and usage. Do you think polymorphism is followed in this updated code? – overexchange Nov 10 '16 at 21:24
  • 1
    Honestly, the answer below is even better than anything I could offer. I would study it carefully as well. – Michael Dorgan Nov 10 '16 at 23:57

1 Answers1

3

Encapsulation, abstraction, and polymorphism. Because these three things blur together, their meanings are fuzzy, and they're kinda difficult to do in C, here's how I'm defining them for this answer.

Encapsulation restricts, or in the case of C discourages, knowledge of how the underlying thing works. Ideally the data and methods are bundled together.

Abstraction hides complexity from the user, generally through a well defined interface applicable to multiple scenarios.

Polymorphism allows the same interface to be used for multiple types.

They build on each other. Very generally, encapsulation allows abstraction allows polymorphism.


First, let's start with an encapsulation violation.

  for(int i=0; i < INITIAL_ARRAY_SIZE;i++){
    printf("Item: %d\n", lptr->a[i]);
  }

INITIAL_ARRAY_SIZE is not part of lptr. It's some external data. If you pass lptr around, INITIAL_ARRAY_SIZE won't go with it. So that loop violates encapsulation. Your list is not well encapsulated. The size should be a detail that is either part of the List struct or not necessary at all.

You could add the size to the struct and use that to iterate.

  for(int i=0; i < lptr->size; i++){
    printf("Item: %d\n", lptr->a[i]);
  }

But this still has the user poking at struct details. To avoid this you could add an iterator and the user never knows about the size at all. This is like the C++ vector interface but more awkward because C lacks method calls.

ListIter iter;
int *value;

/* Associate the iterator with the List */
ListIterInit(&iter, lptr);

/* ListIterNext returns a pointer so it can use NULL to stop */
/* Otherwise you can't store 0 */
while( value = ListIterNext(&iter) ) {
    printf("Item: %d\n", *value);
}

Now the struct has full control over how things iterate and how it stores things.

This iterator interface is inspired by Gnome Lib Hash Tables.


This iterator interface also provides abstraction. We've removed details about the struct. Now it's a thing you just iterate through. You don't need to know how the data is stored or how much there is or even if it's stored at all. It could be generated on the fly for all you know. This is the beauty of the iterator pattern.

...except we still need to know the type. This can be fixed in two ways. First is by telling the list how big each element is. Rather than modifying yours, let's look at how Gnome Lib does it with their arrays .

GArray *garray = g_array_new (FALSE, FALSE, sizeof (gint));
for (i = 0; i < 10000; i++) {
  g_array_append_val (garray, i);
}

The array is told to store things sizeof(gint), which it remembers. Then all other array operations are encapsulated in a function. Even getting an element out is encapsulated.

gint g_array_index(garray, gint, 5);

This is done with a clever macro that does the typecasting for you.

The second option is to store all data as pointers. I'll leave it as an exercise for you to look at Gnome Lib's pointer arrays.


And will you look at that? We have polymorphism. A single array struct can now handle data of all types.

This isn't particularly easy to do in C. It involves some macro juggling. It's good to look at things like Gnome Lib to get, conceptually, how it's done. Maybe try to do it yourself for practice and understanding.

But for production just use things like Gnome Lib. There's a tremendous number of edge cases and little details that they've thought through.

Schwern
  • 153,029
  • 25
  • 195
  • 336