Marcelo Cohen, FACIN - PUCRS
Agrupam-se as classes relacionadas em módulos. Para cada módulo são utilizados dois arquivos:
Para usar uma classe em outro módulo, basta então incluir o arquivo .h correspondente
Para exemplificar, utilizaremos o mesmo exemplo da composição: um produto com data de validade. Primeiro, escrevemos a declaração da classe Data no arquivo Data.h:
#include <string>
class Data {
public:
Data(int umDia=1,int umMes=1, int umAno=2000);
void set(int umDia, int umMes, int umAno);
void get(int& umDia, int& umMes, int& umAno);
std::string toString(void);
std::string extenso(void);
private:
int dia,mes,ano;
static const std::string meses[];
};
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 DATA_H
#define DATA_H
#include <string>
class Data {
public:
Data(int umDia=1,int umMes=1, int umAno=2000);
...
};
#endif
A seguir, escrevemos o arquivo Data.cpp, que contém a implementação dos métodos da classe Data (e a definição da constante meses).
#include <iostream>
#include <iomanip>
#include <sstream>
#include "Data.h"
using namespace std;
const string Data::meses[] = {
"janeiro", "fevereiro", "março", "abril", ...
};
Data::Data(int umDia,int umMes, int umAno) {
set(umDia,umMes,umAno);
}
...
A classe Produto deve ser declarada de forma semelhante, no arquivo Produto.h:
#ifndef PRODUTO_H
#define PRODUTO_H
#include "Data.h"
class Produto {
public:
Produto(int cod,std::string descr,const Data& val);
int getCodigo() { return codigo; }
std::string getDescricao() { return descricao; }
Data& getData() { return validade; }
std::string toString(void);
private:
int codigo;
std::string descricao;
Data validade;
};
#endif
A classe Produto deve então ser implementada no arquivo Produto.cpp:
#include <iostream>
#include <sstream>
#include "Produto.h"
using namespace std;
Produto::Produto(int cod,string descr,const Data& val) {
codigo = cod;
descricao = descr;
validade = val;
}
...
Finalmente, o programa principal deve ser implementado em um arquivo separado, como por exemplo testadataproduto.cpp:
#include <iostream>
#include "Data.h"
#include "Produto.h"
using namespace std;
int main()
{
Data d1(30,10,2006);
cout << d1.toString() << endl;
cout << d1.extenso() << endl;
Produto p1(1005,"Leite longa vida",d1);
cout << p1.toString() << endl;
}
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.
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: testa
testa: testadataproduto.o Produto.o Data.o
g++ -o testa testadataproduto.o Produto.o Data.o
testadataproduto.o: testadataproduto.cpp
g++ -c testadataproduto.cpp
Produto.o: Produto.cpp
g++ -c Produto.cpp
Data.o: Data.cpp
g++ -c Data.cpp
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
FONTES = testadataproduto.cpp Produto.cpp Data.cpp
OBJETOS = $(FONTES:.cpp=.o)
all: testa
testa: $(OBJETOS)
g++ $(CXXFLAGS) $(OBJETOS) -o testa
clean:
-@ rm -f $(OBJETOS)
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
testadataproduto.o: Data.h Produto.h
Produto.o: Produto.h Data.h
Data.o: Data.h
Já existem alguns sistemas alternativos ao make: