Recebi pelo menos uma questão a respeito de como é que podem organizar o vosso código de forma a que este possa ser exportado para outros projetos e as inclusões possam estar mais corretas entre os vários ficheiros.
À partida gostava que vissem o problema de organização dos vossos códigos da seguinte forma:
Há sempre um ficheiro onde estará a função main()
. Este ficheiro pode ter o nome que vocês quiserem, mas por motivos de simplificação vamos chamar-lhe de main.cpp
. Este ficheiro servirá de ponto de partida para a execução do vosso código, dado que é o ficheiro que terá a primeira função a executar (a função main()
).
Por cada classe que quiserem criar, criem um par de ficheiros Nome.h
/Nome.cpp
, em que Nome
é o nome da vossa classe. Evitem criar classes dentro do ficheiro main.cpp
.
Dentro destes ficheiros, no ficheiro Nome.h
deverá estar a declaração das vossas classes:
class Nome {
public:
Nome();
Nome(int a, int b);
void Method1();
private:
int a_;
int b_;
};
No ficheiro Nome.cpp
deverá estar a implementação das vossas classes, cujo ficheiro deverá começar pela inclusão do Nome.h
:
#include "Nome.h"
Nome::Nome() {
a_ = 0;
b_ = 2;
}
Nome::Nome(int a, int b) : a_(a), b_(b) {}
void Nome::Method1() {
std::cout << "Hello" << std::endl;
}
No C++ e da forma como estamos a gerir os nossos projetos, estamos a usar o CMake para montar o projeto. Desta forma, precisamos de ter um ficheiro chamado CMakeLists.txt
onde juntamos as várias peças criando um executável add_executable()
com nome executable_name
e com a lista de ficheiros completa à frente:
cmake_minimum_required(VERSION 3.0)
project(ExampleCode)
set(CMAKE_CXX_STANDARD 17)
add_executable(executable_name main.cpp Nome.h Nome.cpp)
Um de vocês questionou-me sobre como fazer o overloading do operador << e >> numa classe que tenham criado e denotou um problema que pode acontecer com a manipulação dos objetos istream
.
Vamos por partes. Comecemos por considerar uma classe chamada Carro
que tem dois atributos engine_
e owner_name_
, privados:
class Carro {
...
private:
string engine_;
string owner_name_;
};
Agora queremos, numa função como a função main()
conseguir fazer o seguinte:
Carro carro;
cout << carro << endl;
Acontece que para que isto seja possível, o operador operator<<
definido com um objeto Carro
não está definido num objeto do tipo ostream
, que é o tipo do objeto cout
. Para o usas precisamos, portanto, de o definir. Para o definir precisamos de declarar a seguinte expressão na classe Carro
.
#include <ostream>
class Carro {
public:
...
friend ostream& operator<<(ostream& os, const Carro& carro);
...
};
Note-se que este método que adicionámos diz-se ser friend uma vez que ele está definido na classe ostream
que não é nossa, mas sim parte da biblioteca nativa da linguagem do C++. Por esta mesma razão, e porque dentro desta função iremos querer aceder aos atributos engine_
e owner_name_
, precisamos de permitir que uma classe aceda aos atributos privados de outra, sem que haja uma relação entre ambas. Para isto dizemos que a função operator<<
da classe ostream
é amiga da classe Carro
, para que esta possa, então, aceder aos campos de Carro
, privados. Conseguimos, portanto, no nosso ficheiro Carro.cpp
, fazer a seguinte implementação.
#include "Carro.h"
...
ostream& operator<<(ostream& os, const Carro& carro) {
os << "Carro tem motor " << engine_ << " e tem dono " << owner_name_ << "." << endl;
return os;
}
Em relação ao operador de inserção, isto é, ao operador operator>>
a sua implementação é análoga. Na verdade, a assinatura desta função é semelhante à anterior usando agora um objeto do tipo istream
(e não ostream
) e tendo um segundo argumento que não é const
, contrariamente ao caso anterior (dado que desta vez iremos precisar de editar este objeto que passa por referência como argumento).
#include <istream>
#include <ostream>
class Carro {
public:
...
friend ostream& operator<<(ostream& os, const Carro& carro);
friend istream& operator>>(istream& is, Carro& carro);
...
};
A sua implementação deverá contar, contudo, com alguma função de interação com o cliente para que este saiba de que valor está o programa, neste ponto à espera. Assim sendo, poderemos ter uma implementação semelhante à seguinte: