FACIN logo

Modularização de Programas em C

Marcelo Cohen, FACIN - PUCRS

Motivação: Por que modularizar ?

Solução: Modularização

Agrupam-se as funções/classes relacionadas em módulos. Para cada módulo são utilizados dois arquivos:

Para usar uma função/classe em outro módulo, basta então incluir o arquivo .h correspondente

Criação do header> da biblioteca de funções (2)

Para exemplificar, criaremos uma biblioteca simples de funções matemáticas. Primeiro, escrevemos os protótipos das funções no arquivo bibfunc.h:

float fatorial(int v);
float somatorio(int v);
...

Criação do header> da biblioteca de funções (2)

Porém, se por engano esse arquivo for incluído mais de uma vez, haverá uma duplicidade de todas as definições. Como isso pode acontecer facilmente, acrescentamos 3 linhas no arquivo .h:

#ifndef BIBFUNC_H
#define BIBFUNC_H

float fatorial(int v);
float somatorio(int v);
...
#endif

Implementação das funções

A seguir, escrevemos o arquivo bibfunc.cpp, que contém a implementação das funções definidas anteriormente.

#include "bibfunc.h"

float fatorial(int v) {
  int res = 1;
  for(int i=1; i<=v; i++)
    res = res * i;
  return res;
}
...

Programa Principal

Finalmente, o programa principal deve ser implementado em um arquivo separado, como por exemplo main.cpp:

#include <iostream>
#include "bibfunc.h"

using namespace std;

int main()
{
    cout << "Digite um valor: ";
    int v;
    cin >> v;
    cout << "Fatorial: " << fatorial(v);
    cout << "Somatório: " << somatorio(v);
    ...
}

Compilação dos Módulos

Para compilar o programa completo, é preciso indicar todos os fontes na linha de comando:

Porém, essa estratégia tem algumas implicacões:

Para um programa tão pequeno, isso não faz tanta diferença...

Mas se o programa for composto por muitos módulos, compilá-los separadamente pode ter um custo considerável, além de não ser muito prático.

Compilação Modular: Makefiles

Um Makefile é um roteiro de compilação:

O nome do arquivo deve ser preferencialmente Makefile.
Para utilizá-lo, basta digitar o comando make no terminal.

all: main

main: main.o bibfunc.o
	g++ -o main main.o bibfunc.o

main.o: main.cpp
	g++ -c main.cpp

bibfunc.o: bibfunc.cpp
	g++ -c bibfunc.cpp

Compilação Modular: Makefiles (2)

Os comandos de compilação não parecem meio repetitivos ? Podemos melhorar o Makefile, criando regras genéricas:

CXXFLAGS = -Wall -g    # Opções do compilador: todos warnings e debug info

PROG = main
FONTES = main.cpp bibfunc.cpp
OBJETOS = $(FONTES:.cpp=.o)

$(PROG): $(OBJETOS)
	g++ $(CXXFLAGS) $(OBJETOS) -o $@

clean:
	-@ rm -f $(OBJETOS) $(PROG)

Compilação Modular: Makefiles (3)

Mas e como especificar dependências automáticas para os .h ?
Uma forma é incluir uma regra que use o comando makedepend:

...
clean:
	-@ rm -f $(OBJETOS)

depend:
	makedepend -- ${CFLAGS} -- ${FONTES}

Ao digitarmos make depend, o comando makedepend será ativado, e verificará as dependências de cada módulo, incluindo estas no final do próprio Makefile:

...
depend:
	makedepend -- ${CFLAGS} -- ${FONTES}

# DO NOT DELETE

main.o: bibfunc.h
bibfunc.o: bibfunc.h

Modularização de Classes

Com classes, o princípio é o mesmo: agrupam-se as classes relacionadas em módulos e para cada módulo são novamente utilizados dois arquivos:

Para usar uma classe em outro módulo, basta então incluir o arquivo .h correspondente

Declaração da classe Carro

Para exemplificar, utilizaremos uma classe Carro, que permite controlar o abastecimento e movimentação de um veículo. Primeiro, escrevemos a declaração da classe Carro no arquivo Carro.h:

#ifndef CARRO_H
#define CARRO_H

class Carro {

    public:
        Carro();

        bool abastecer(float litros);
        bool mover(float km);

        float getTanque();
        float getDistancia();

    private:
        float tanque;
        float distancia;

};
#endif

Implementação da classe Carro

A seguir, escrevemos o arquivo Carro.cpp, que contém a implementação dos métodos da classe Carro.

#include "Carro.h"

Carro::Carro() {
    tanque = 0;
    distancia = 0;
}

bool Carro::abastecer(float litros) {
    if(tanque + litros <= 50)
        tanque += litros;
    ...
}

...

Programa Principal

Finalmente, o programa principal deve ser implementado em um arquivo separado, como por exemplo main.cpp:

#include <iostream>
#include "Carro.h"

using namespace std;

int main()
{
    Carro carro1, carro2;

    carro1.abastecer(20);
    carro2.abastecer(30);

    bool moveu1 = carro1.mover(200);
    if(moveu1==false)
        cout << "Carro 1: Não há combustível suficiente!" << endl;
    ...
}

Compilação Modular: Alternativas ao make

Já existem alguns sistemas alternativos ao make:

Exercícios

Exercícios (2)