Respostas que um programador Java busca quando começa a programar em C++

Há alguns semestres eu leciono matérias como Programação III ou Programação Aplicada de Computadores, que focam em programação orientada a objetos e incluem em seus programas linguagens como Java e C++. Ensinar Java é bem tranquilo, visto que programo nesta linguagem desde 1999, sempre fiz parte de JUGs (mesmo durante o doutorado), escrevi artigos para revistas sobre Java, enfim… me mantive atualizado.

Já em C++ minha experiência era bem limitada em comparação. Antes de começar a lecionar usando esta linguagem, só a tinha utilizado uma vez durante o meu curso de graduação, na disciplina Linguagens de Programação com o prof. Flávio Varejão. Na época, o currículo da Ciência da Computação não incluía uma disciplina específica para Programação OO.

O resultado é que fui aprendendo bastante sobre a linguagem ao longo destes quase 3 anos como professor do DI/Ufes. Criei até um utilitário C++ com funções de tokenização (tipo java.lang.String.split()), formatação de datas (tipo java.text.SimpleDateFormat) e de números (tipo java.text.NumberFormat).

Este semestre, no entanto, resolvi fazer diferente das últimas vezes que dei essa matéria e implementei o trabalho prático não só em Java, mas também em C++. Descobri que tinha muita coisa necessária no processo de “tradução” de um programinha simples em Java para C++ que eu estava ainda por descobrir. Montei, então, esse “FAQ” com algumas dicas para quem segue nessa mesma estrada do Java para o C++. Espero que ajude!

 

Como faço pra verificar que uma string contém uma data válida (ParseException em Java)?

Partindo do utilitário DateUtils, poderíamos adicionar um novo método para fazer isso:

[cc_cpp]
bool validDate(const string& str, const string& formato) {
struct tm tm;
return strptime(str.c_str(), formato.c_str(), &tm);
}
[/cc_cpp]

 

Como faço pra verificar que uma string contém um número inteiro válido (ParseException em Java)?

Para conter essa função criei um novo utilitário, StringUtils:

[cc_cpp]
bool isNumber(string& s) {
if (s.empty()) return false;
for (int i = 0; i < s.size(); i++) if (! isdigit(s.at(i))) return false; return true; } [/cc_cpp] A função poderia ser adaptada para aceitar também números reais, aceitando também o separador (vírgula ou ponto).

 

Como fazer java.lang.String.trim() em C++?

Essa resposta eu achei num post do StackOverflow e, adaptando o código, adicionei ao utilitário StringUtils as seguintes funções (e includes):

[cc_cpp]
#include
#include
#include
#include
#include

string& ltrim(string &s) {
s.erase(s.begin(), find_if(s.begin(), s.end(), not1(ptr_fun(isspace))));
return s;
}

string& rtrim(string &s) {
s.erase(find_if(s.rbegin(), s.rend(), not1(ptr_fun(isspace))).base(), s.end());
return s;
}

string& trim(string& s) {
return ltrim(rtrim(s));
}
[/cc_cpp]

 

Como faço comparação de strings com Collator (para considerar letras com acentos)?

Essa eu recebi por e-mail do prof. João Paulo Andrade Almeida, que por sua vez recebeu de seu aluno de Programação III, Eduardo Dalapicola. Adicionei mais uma para o StringUtils:

[cc_cpp]
bool stringCompare(string s1, string s2) {
const collate& col = use_facet >(locale());
transform(s1.begin(), s1.end(), s1.begin(), ::tolower);
transform(s2.begin(), s2.end(), s2.begin(), ::tolower);
const char* pb1 = s1.data();
const char* pb2 = s2.data();
return (col.compare(pb1, pb1 + s1.size(), pb2, pb2 + s2.size()) < 0); } [/cc_cpp]

 

Como faço para ordenar um vetor de objetos, tipo java.util.Collections.sort()?

Na biblioteca [cci_cpp][/cci_cpp], existe a função [cci_cpp]sort()[/cci_cpp], que aceita como parâmetros iteradores que marquem a posição de início e fim do que deve ser ordenado e uma função de comparação. Para ordenar um [cci_cpp]vector v[/cci_cpp] inteiro, basta usar suas marcações de início e fim — [cci_cpp]v.begin()[/cci_cpp] e [cci_cpp]v.end()[/cci_cpp] e, como terceiro parâmetro, uma função de comparação com assinatura [cci_cpp]bool nome(const X, const X)[/cci_cpp].

Um exemplo concreto para ficar mais fácil de entender, digamos que exista uma classe [cci_cpp]Emprestimo[/cci_cpp], com métodos para obter a data do empréstimo e o nome do tomador do empréstimo. Caso eu queira ordenar um [cci_cpp]vector emprestimos[/cci_cpp] (vindo de Java, usei ponteiros em todas as coleções), eu chamaria a função assim:

[cc_cpp]
sort(emprestimos.begin(), emprestimos.end(), comparaEmprestimos);
[/cc_cpp]

E a função de comparação seria definida assim:

[cc_cpp]
bool comparaEmprestimos(const Emprestimo* esq, const Emprestimo* dir) {
int diff = difftime(esq->getDataEmprestimo(), dir->getDataEmprestimo());
if (diff != 0) return (esq != dir) && (diff >= 0);
return stringCompare(esq->getTomador(), dir->getTomador());
}
[/cc_cpp]

Notem que usei aqui a função de comparação de strings com collator, apresentada anteriormente. Além disso, repare que quando o mesmo ponteiro é passado nos dois argumentos, ele retorna falso (por conta de [cci_cpp](esq != dir)[/cci_cpp]) e isso é fundamental, se não a ordenação fica toda bagunçada (ao menos na minha experiência).

 

Como faço coleções ordenadas, tipo java.util.TreeSet e java.util.TreeMap?

As classes-template [cci_cpp]set[/cci_cpp] e [cci_cpp]map[/cci_cpp] (presentes nas bibliotecas homônimas) já possuem ordenação implementada. Ao declarar a coleção, você especifica na configuração do template não só qual o elemento que será armazenado mas também uma classe comparadora para aquele elemento. Note que desta vez é uma classe comparadora e não uma função como na ordenação com [cci_cpp]sort()[/cci_cpp] descrita anteriormente.

Vamos ao exemplo, e desta vez vou usar um bem complexo pra exemplificar conjuntos e mapas simultaneamente! Digamos que você queira mapear pessoas à produções artísticas (filmes, livros, séries) em que ela esteja envolvida. Temos então um mapa da classe [cci_cpp]Pessoa[/cci_cpp] para um conjunto da classe [cci_cpp]Midia[/cci_cpp], sendo que o mapa de pessoas deve ser ordenado e o conjunto de mídias também! Declarei a coleção da seguinte forma:

[cc_cpp]
map, PessoaComparator> producoes;
[/cc_cpp]

E as classes de comparação:

[cc_cpp]
class MidiaComparator {
public:
bool operator()(const Midia* esq, const Midia* dir) const;
};

class PessoaComparator {
public:
bool operator()(const Pessoa* esq, const Pessoa* dir) const;
};
[/cc_cpp]

Enfim, a implementação da sobrecarga do operador [cci_cpp]()[/cci_cpp]:

[cc_cpp]
bool MidiaComparator::operator()(const Midia* esq, const Midia* dir) const {
return (esq != dir) && stringCompare(esq->nome, dir->nome);
}

bool PessoaComparator::operator()(const Pessoa* esq, const Pessoa* dir) const {
return (esq != dir) && stringCompare(esq->nome, dir->nome);
}
[/cc_cpp]

Note que precisamos novamente do [cci_cpp](esq != dir)[/cci_cpp] (explicada anteriormente), usei também a comparação de strings com collator (apresentada anteriormente) e que as classes comparadoras devem ter acesso às propriedades (normalmente privativas) das classes que elas comparam (facilmente resolvido com um [cci_cpp]friend class PessoaComparator;[/cci_cpp] na classe [cci_cpp]Pessoa[/cci_cpp], por exemplo.

 

Como faço pra lançar uma exceção tipo java.io.FileNotFoundException quando um arquivo que quero ler não existe?

Basta usar o método [cci_cpp]good()[/cci_cpp] da classe [cci_cpp]ifstream[/cci_cpp], assim:

[cc_cpp]
ifstream in(nomeArquivo);
if (! in.good()) {
in.close();
throw FileNotFoundException();
}
// Restante do código de leitura, normal…
[/cc_cpp]

Obviamente a classe [cci_cpp]FileNotFoundException[/cci_cpp] deve ser definida, ela não existe na API do C++.

 

Como faço para verificar se um elemento existe num mapa? Ou para substitui-lo?

Em Java, dado um mapa [cci_java]m[/cci_java], o método [cci_java]m.get(chave)[/cci_java] retorna nulo se a chave não está presente no mapa. Em C++, isso lança uma exceção. O que você precisa fazer é verificar se o mapa possui aquela chave antes de tentar obter seu valor:

[cc_cpp]
if (mapa.count(chave) == 0) { /* Faça alguma coisa. */ }
[/cc_cpp]

Outra diferença é que, em Java, adicionar um novo par [cci_java]m.add(chave, valor)[/cci_java] para uma chave que já existe substitui o antigo valor associado à chave. Já em C++ isso adiciona um novo valor à chave (se ela tinha [cci_cpp]count() == 1[/cci_cpp] antes, passa a ter [cci_cpp]count() == 2[/cci_cpp])! Para substituir um valor, podemos fazer assim:

[cc_cpp]
map::iterator itGen = qtdGen.find(genero);
if (itGen != qtdGen.end()) itGen->second += 1;
[/cc_cpp]

O exemplo acima foi retirado de um código que conta quantas vezes um gênero aparece em um conjunto de mídias. O comando [cci_cpp]find()[/cci_cpp] recebe um [cci_cpp]Genero*[/cci_cpp] e retorna um iterador do mapa que aponta para o primeiro par que possua essa chave. Em seguida verificamos se havia de fato um par (caso contrário ele aponta para o marcador [cci_cpp]end()[/cci_cpp] do mapa) e incrementamos o valor associado àquela chave (manipulando o atributo [cci_cpp]->second[/cci_cpp]).

 

Como faço pra usar Locales?

Segue código de exemplo que me foi enviado pelo prof. João Paulo:

[cc_cpp]
#include // std::cout
#include // std::locale
#include // std::istringstream
using namespace std;
int main()
{
// obtem o locale
locale brasilLocale(“pt_BR.UTF-8”);
int n; // inteiro que queremos ler
// aplica o locale brasileiro à entrada padrão
// para que esta passe a ser interpretada como queremos
cin.imbue(brasilLocale);
// teste de leitura da entrada padrão
cin >> n; // permitirá entrada como “5.001” = cinco mil e um
// mostra o resultado
cout << n; } [/cc_cpp]