Introdução aos Objectos/Objectos em C: Tipos de Dados Abstractos

From Wiki**3

< Introdução aos Objectos

Os exercícios seguintes ilustram a definição de tipos de dados abstractos e de suas instâncias. Estas instâncias são semelhantes aos objectos suportados por linguagens como o C++ ou o Java, mas, como estão implementados em C, algumas das operações têm de ser definidas explicitamente pelo programador.

Os exemplos tocam também no aspecto da reutilização de código: em linguagens OO, é algo que surge "naturalmente". Nestes exemplos, é necessário mais trabalho. Os exemplos são implementados em C++ no final (apenas para uma comparação mais directa com C: deixa-se como exercício a implementação em Java ou em outras linguagens de programação).

Animal simples

A tarefa é a modelação e implementação em C (ficheiros .h e .c) de uma estrutura de dados que represente uma versão simples do conceito “animal”.

Um animal tem como características o nome (_name), a idade (_age) e o peso (_weight).

Devem ser ainda implementadas as funções newAnimal e destroyAnimal. A função newAnimal reserva memória suficiente para representar um animal e permite inicializar os seus campos (através dos argumentos da função). Por simplicidade, assume-se que o campo _name tem comprimento fixo máximo de 16 caracteres (incluindo o terminador). A função destroyAnimal liberta os recursos associados ao animal.

Outras funções a implementar:

  • função de comparação – equalsAnimal –, por forma a considerar que dois animais são iguais se as suas características forem iguais
  • funções de acesso às variáveis de um animal: getAnimalName, getAnimalAge, getAnimalWeight (dado um animal, retornam um dos seus campos)
  • função printAnimal que, quando aplicada a um animal, apresenta os seus dados (usa-se printf para apresentar cada campo do animal)
  • programa – main – que ilustra a utilização das funções anteriores

Interface do conceito "Animal"

Ficheiro Animal.h
 1 #ifndef __ANIMAL_H__
 2 #define __ANIMAL_H__
 3 
 4 // this typedef is used to hide the concept's implementation details
 5 typedef struct animal *Animal;
 6 
 7 Animal newAnimal(const char *name, int age, double weight);
 8 void destroyAnimal(Animal animal);
 9 
10 int         equalsAnimal(Animal animal1, Animal animal2);
11 const char *getAnimalName(Animal animal);
12 int         getAnimalAge(Animal animal);
13 double      getAnimalWeight(Animal animal);
14 void        printAnimal(Animal animal);
15 
16 #endif

Implementação do conceito "Animal"

Ficheiro Animal.c
 1 #include <string.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include "Animal.h"
 5 
 6 struct animal {
 7   char   _name[16];
 8   int    _age;
 9   double _weight;
10 };
11 
12 Animal newAnimal(const char *name, int age, double weight) {
13   Animal animal = (Animal)malloc(sizeof(struct animal));
14   if (animal != NULL) {
15     strcpy(animal->_name, name);
16     animal->_age = age;
17     animal->_weight = weight;
18   }
19   return animal;
20 }
21 
22 void destroyAnimal(Animal animal) {
23   if (animal)
24     free(animal);
25 }
26 
27 const char *getAnimalName  (Animal animal) { return animal->_name;   }
28 int         getAnimalAge   (Animal animal) { return animal->_age;    }
29 double      getAnimalWeight(Animal animal) { return animal->_weight; }
30 
31 /* note that we require animal1 and animal2 to be valid animals: any of
32    them being NULL pointers will result in a false comparison. */
33 int equalsAnimal(Animal animal1, Animal animal2) {
34   if (animal1 == NULL || animal2 == NULL) return 0;
35   return !strcmp(getAnimalName(animal1), getAnimalName(animal2)) &&
36       getAnimalAge(animal1) == getAnimalAge(animal2) &&
37       getAnimalWeight(animal1) == getAnimalWeight(animal2);
38 }
39 
40 void printAnimal(Animal animal) {
41   printf("== Animal ==\n");
42   printf("Name:   %s\n", getAnimalName(animal));
43   printf("Age:    %d\n", getAnimalAge(animal));
44   printf("Weight: %g\n", getAnimalWeight(animal));
45 }

Programa exemplo

Ficheiro main.c
 1 #include <stdio.h>
 2 #include "Animal.h"
 3 
 4 int main() {
 5   Animal a1 = newAnimal("Tareco", 12, 3.4);  // could be a cat...
 6   Animal a2 = newAnimal("Piloto", 1, 12.3);  // is it a dog??
 7 
 8   printAnimal(a1);
 9   printAnimal(a2);
10 
11   printf("a1==a1? %s\n", equalsAnimal(a1, a1) ? "yes" : "no");
12   printf("a1==a2? %s\n", equalsAnimal(a1, a2) ? "yes" : "no");
13 
14   destroyAnimal(a1);
15   destroyAnimal(a2);
16 
17   return 0;
18 }

Como compilar e executar?

O programa é constituído por dois módulos independentes, que têm de ser compilados (conversão de C para código binário) e "linkados" (ligação de todos os módulos binários e bibliotecas adicionais), para produção do executável.

gcc -c Animal.c -o Animal.o
gcc -c main.c   -o main.o

A opção -o foi apresentada como o valor por omissão, apenas para explicitar o que acontece como resultado do comando.

gcc -o main main.o Animal.o

Note-se que o comando "gcc" não é simplesmente o compilador de C: é um programa capaz de chamar o compilador em si (quando se usa com a opção -c) ou capaz de invocar o "linker", se não for dito nada em contrário (último comando acima).

A execução é simples (o comando seguinte executa o programa "main" que está na directoria actual, i.e., "."):

 ./main

Gato simples

A tarefa é a modelação e implementação em C (ficheiros .h e .c) de uma estrutura de dados que represente uma versão simples do conceito “gato”.

Um gato tem como características o nome (_name), a idade (_age), o peso (_weight), o volume do ronronar (_purrLevel) e o grau de suavidade do pêlo (_fluffiness).

Devem ser ainda implementadas as funções newCat e destroyCat. A função newCat reserva memória suficiente para representar um gato e permite inicializar os seus campos (através dos argumentos da função). Por simplicidade, assuma que o campo _name tem comprimento fixo máximo de 16 caracteres (incluindo o terminador). A função destroyCat liberta os recursos associados ao gato.

Outras funções a implementar:

  • função de comparação – equalsCat –, por forma a considerar que dois gatos são iguais se as suas características forem iguais
  • funções de acesso às variáveis de um gato: getCatName, getCatAge, getCatWeight, getCatPurrLevel, getCatFluffiness (dado um gato, retornam um dos seus campos)
  • função printCat que, quando aplicada a um gato, apresenta os seus dados (usa-se printf para apresentar cada campo do gato)
  • programa – main – que ilustra a utilização das funções anteriores

Interface do conceito "Cat"

Ficheiro Cat.h
 1 #ifndef __CAT_H__
 2 #define __CAT_H__
 3 
 4 typedef struct cat *Cat;
 5 
 6 Cat newCat(const char *name, int age, double weight, int purrLevel,
 7            double fluffiness);
 8 void destroyCat(Cat cat);
 9 
10 int         equalsCat(Cat cat1, Cat cat2);
11 const char *getCatName(Cat cat);
12 int         getCatAge(Cat cat);
13 double      getCatWeight(Cat cat);
14 int         getCatPurrLevel(Cat cat);
15 double      getCatFluffiness(Cat cat);
16 void        printCat(Cat cat);
17 
18 #endif

Implementation do conceito "Cat"

Notar a repetição de muitas definições (relativamente a Animal).

Ficheiro Cat.c
 1 #include <string.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include "Cat.h"
 5 
 6 struct cat {
 7   char   _name[16];
 8   int    _age;
 9   double _weight;
10   int    _purrLevel;
11   double _fluffiness;
12 };
13 
14 Cat newCat(const char *name, int age, double weight, int purrLevel,
15            double fluffiness) {
16   Cat cat = (Cat)malloc(sizeof(struct cat));
17   if (cat != NULL) {
18     strcpy(cat->_name, name);
19     cat->_age = age;
20     cat->_weight = weight;
21     cat->_purrLevel = purrLevel;
22     cat->_fluffiness = fluffiness;
23   }
24   return cat;
25 }
26 
27 void destroyCat(Cat cat) {
28   if (cat)
29     free(cat);
30 }
31 
32 const char *getCatName      (Cat cat) { return cat->_name;   }
33 int         getCatAge       (Cat cat) { return cat->_age;    }
34 double      getCatWeight    (Cat cat) { return cat->_weight; }
35 int         getCatPurrLevel (Cat cat) { return cat->_purrLevel; }
36 double      getCatFluffiness(Cat cat) { return cat->_fluffiness; }
37 
38 /* note that we require cat1 and cat2 to be valid cats: any of
39    them being NULL pointers will result in a false comparison. */
40 int equalsCat(Cat cat1, Cat cat2) {
41   if (cat1 == NULL || cat2 == NULL) return 0;
42   return !strcmp(getCatName(cat1), getCatName(cat2)) &&
43       getCatAge(cat1) == getCatAge(cat2) &&
44       getCatWeight(cat1) == getCatWeight(cat2) &&
45       getCatPurrLevel(cat1) == getCatPurrLevel(cat2) &&
46       getCatFluffiness(cat1) == getCatFluffiness(cat2);
47 }
48 
49 void printCat(Cat cat) {
50   printf("== Cat ==\n");
51   printf("Name:       %s\n", getCatName(cat));
52   printf("Age:        %d\n", getCatAge(cat));
53   printf("Weight:     %g\n", getCatWeight(cat));
54   printf("Purr level: %d\n", getCatPurrLevel(cat));
55   printf("Fluffiness: %g\n", getCatFluffiness(cat));
56 }

Programa exemplo

Ficheiro main.c
 1 #include <stdio.h>
 2 #include "Cat.h"
 3 
 4 int main() {
 5   Cat c1 = newCat("Tareco", 12, 3.4, 3, 3.1);
 6   Cat c2 = newCat("Pantufa", 1, 12.3, 2, 2.7);
 7 
 8   printCat(c1);
 9   printCat(c2);
10 
11   printf("c1==c1? %s\n", equalsCat(c1, c1) ? "yes" : "no");
12   printf("c1==c2? %s\n", equalsCat(c1, c2) ? "yes" : "no");
13 
14   destroyCat(c1);
15   destroyCat(c2);
16 
17   return 0;
18 }

Como compilar e executar?

Tal como primeiro exemplo, o programa é constituído por dois módulos independentes, que têm de ser compilados (conversão de C para código binário) e "linkados" (ligação de todos os módulos binários e bibliotecas adicionais), para produção do executável. Neste caso, o módulo Animal.o não é utilizado e não aparece nos comandos:

gcc -c Cat.c  -o Cat.o
gcc -c main.c -o main.o

A opção -o foi apresentada como o valor por omissão, apenas para explicitar o que acontece como resultado do comando.

gcc -o main main.o Cat.o

Note-se que o comando "gcc" não é simplesmente o compilador de C: é um programa capaz de chamar o compilador em si (quando se usa com a opção -c) ou capaz de invocar o "linker", se não for dito nada em contrário (último comando acima).

A execução é simples (o comando seguinte executa o programa "main" que está na directoria actual, i.e., "."):

 ./main

Saída do programa

== Cat ==
Name:       Tareco
Age:        12
Weight:     3.4
Purr level: 3
Fluffiness: 3.1
== Cat ==
Name:       Pantufa
Age:        1
Weight:     12.3
Purr level: 2
Fluffiness: 2.7
c1==c1? yes
c1==c2? no

Gato menos simples

Como se pode observar acima, existe alguma repetição na definição de animal e de gato. Como o conceito de gato contém alguma informação do conceito de animal, seria conveniente a sua reutilização.

A tarefa é a modelação e implementação em C (ficheiros .h e .c) de uma estrutura de dados que represente uma versão do conceito “gato”, mas considerando-o como uma animal caracterizado pelo volume do ronronar (_purrLevel) e pelo grau de suavidade do pêlo (_fluffiness).

Tal como anteriormente, devem ser ainda implementadas as funções newCat e destroyCat. A função newCat reserva memória suficiente para representar um gato e permite inicializar os seus campos (através dos argumentos da função). Por simplicidade, assuma que o campo _name tem comprimento fixo máximo de 16 caracteres (incluindo o terminador). A função destroyCat liberta os recursos associados ao gato.

Note-se que o facto de o gato ser um animal não altera a necessidade de implementação das funções associadas ao gato.

Assim, as funções a implementar são:

  • função de comparação – equalsCat –, por forma a considerar que dois gatos são iguais se as suas características forem iguais
  • funções de acesso às variáveis internas de um gato: getCatName, getCatAge, getCatWeight, getCatPurrLevel, getCatFluffiness (dado um gato, retornam um dos seus campos). Note-se que algumas destas características são fornecidas pelo conceito “animal”
  • função printCat que, quando aplicada a um gato, apresenta os seus dados (usa-se printf para apresentar cada campo específico do gato)
  • programa – main – que ilustra a utilização das funções anteriores

Interface do conceito "Cat"

Notar que a interface é necessariamente igual à do exemplo anterior, pois é característica do conceito "gato".

Ficheiro Cat.h
 1 #ifndef __CAT_H__
 2 #define __CAT_H__
 3 
 4 typedef struct cat *Cat;
 5 
 6 Cat newCat(const char *name, int age, double weight, int purrLevel,
 7            double fluffiness);
 8 void destroyCat(Cat cat);
 9 
10 int         equalsCat(Cat cat1, Cat cat2);
11 const char *getCatName(Cat cat);
12 int         getCatAge(Cat cat);
13 double      getCatWeight(Cat cat);
14 int         getCatPurrLevel(Cat cat);
15 double      getCatFluffiness(Cat cat);
16 void        printCat(Cat cat);
17 
18 #endif

Implementação do conceito "Cat"

Notar as partes onde é utilizado o conceito "animal".

Ficheiro Cat.c
 1 #include <string.h>
 2 #include <stdlib.h>
 3 #include <stdio.h>
 4 #include "Animal.h"
 5 #include "Cat.h"
 6 
 7 // note that, since a cat is an animal, part of the cat will
 8 // be implemented as an animal.
 9 struct cat {
10   Animal _animal;
11   int _purrLevel;
12   double _fluffiness;
13 };
14 
15 Cat newCat(const char *name, int age, double weight, int purrLevel,
16            double fluffiness) {
17   Cat cat = (Cat)malloc(sizeof(struct cat));
18   if (cat != NULL) {
19     // use previously defined animal allocator
20     cat->_animal = newAnimal(name, age, weight);
21     if (cat->_animal == NULL) {
22       free(cat);
23       cat = NULL;
24     }
25     else {
26       cat->_purrLevel = purrLevel;
27       cat->_fluffiness = fluffiness;
28     }
29   }
30   return cat;
31 }
32 
33 // destroying the cat has to undo its construction
34 void destroyCat(Cat cat) {
35   if (cat) {
36     // first, release the animal part
37     destroyAnimal(cat->_animal);
38     // then, release the rest of the cat
39     free(cat);
40   }
41 }
42 
43 // these functions are implemented based on the animal versions
44 const char *getCatName      (Cat cat) { return getAnimalName  (cat->_animal); }
45 int         getCatAge       (Cat cat) { return getAnimalAge   (cat->_animal); }
46 double      getCatWeight    (Cat cat) { return getAnimalWeight(cat->_animal); }
47 
48 // these are cat-specific functions
49 int         getCatPurrLevel (Cat cat) { return cat->_purrLevel; }
50 double      getCatFluffiness(Cat cat) { return cat->_fluffiness; }
51 
52 /* note that we require cat1 and cat2 to be valid cats: any of
53    them being NULL pointers will result in a false comparison. */
54 int equalsCat(Cat cat1, Cat cat2) {
55   if (cat1 == NULL || cat2 == NULL) return 0;
56   return equalsAnimal(cat1->_animal, cat2->_animal) &&
57       getCatPurrLevel(cat1) == getCatPurrLevel(cat2) &&
58       getCatFluffiness(cat1) == getCatFluffiness(cat2);
59 }
60 
61 // note that the output here is slightly different, because it uses the
62 // default animal implementation.
63 // we could have used the animal interface to obtain individual fields.
64 void printCat(Cat cat) {
65   printf("== Cat ==\n");
66   printAnimal(cat->_animal);
67   printf("Purr level: %d\n", getCatPurrLevel(cat));
68   printf("Fluffiness: %g\n", getCatFluffiness(cat));
69 }

Programa exemplo

Notar que este programa é idêntico ao do caso anterior, uma vez que depende exclusivamente da interface do conceito "gato" (que não mudou) e não da sua implementação.

Ficheiro main.c
 1 #include <stdio.h>
 2 #include "Cat.h"
 3 
 4 int main() {
 5   Cat c1 = newCat("Tareco", 12, 3.4, 3, 3.1);
 6   Cat c2 = newCat("Piloto", 1, 12.3, 2, 2.7);
 7 
 8   printCat(c1);
 9   printCat(c2);
10 
11   printf("c1==c1? %s\n", equalsCat(c1, c1) ? "yes" : "no");
12   printf("c1==c2? %s\n", equalsCat(c1, c2) ? "yes" : "no");
13 
14   destroyCat(c1);
15   destroyCat(c2);
16 
17   return 0;
18 }

Como compilar e executar?

Neste caso, o conceito de gato depende do conceito de animal inicialmente definido. Deste modo, o programa é constituído por três módulos a compilar separadamente (conversão de C para código binário) e "linkados" (ligação de todos os módulos binários e bibliotecas adicionais), para produção do executável.

gcc -c Animal.c -o Animal.o
gcc -c Cat.c    -o Cat.o
gcc -c main.c   -o main.o

A opção -o foi apresentada como o valor por omissão, apenas para explicitar o que acontece como resultado do comando.

gcc -o main main.o Cat.o Animal.o

Note-se que o comando "gcc" não é simplesmente o compilador de C: é um programa capaz de chamar o compilador em si (quando se usa com a opção -c) ou capaz de invocar o "linker", se não for dito nada em contrário (último comando acima).

A execução é simples (o comando seguinte executa o programa "main" que está na directoria actual, i.e., "."):

 ./main

Saída do programa

Notar que a apresentação do conceito "gato" expõe a sua implementação como parte "animal".

== Cat ==
== Animal ==
Name:   Tareco
Age:    12
Weight: 3.4
Purr level: 3
Fluffiness: 3.1
== Cat ==
== Animal ==
Name:   Piloto
Age:    1
Weight: 12.3
Purr level: 2
Fluffiness: 2.7
c1==c1? yes
c1==c2? no

Discussão