Aula 18 - Técnicas Avançadas de C++ - Parte II
Conceitos-Chave:
- STL, Standard Template Library, uma biblioteca que faz parte dos padrões do C++ e que acrescenta muitas funcionalidades, como a biblioteca string
- template, mecanismo do C++ que cria uma variável genérica, que pode assumir qualquer tipo de variável (incluindo classes criadas pelo programador)
- namespace, permitem agrupar dentro delas variáveis, funções ou classes, de modo a evitar confusões com variáveis/funções/classes com o mesmo nome
- biblioteca string, biblioteca que faz parte da STL e que engloba recursos para tratamento de strings
STL - Introdução
Nesta aula vou abordar STL (Standard Template Library). STL é uma biblioteca criada para ajudar o programador, com várias estruturas de dados e algoritmos. É importante aprender a trabalhar com esta biblioteca, uma vez que o código que contém é muito útil (aposto que a irão utilizar pelo menos uma vez) e faz parte dos standards do C++. Vocês já utilizaram esta biblioteca, pois a classe string é uma das várias presentes nela ( e como devem ter visto, é muito útil).
Templates em funções
A biblioteca chama-se STL, pois utiliza o mecanismo de templates do C++. Este mecanismo permite criar uma espécie de "varíavel genérica", que poderá operar com qualquer tipo de variáveis. Na última aula aprendemos que poderíamos utilizar diferentes funções com o mesmo nome, mas com diferentes tipos de variáveis. Com templates podemos criar uma função genérica, que poderá operar com todos os tipos de variáveis/classes. Por exemplo, seria óptimo se pudéssemos criar apenas uma função em alternativa às 3 seguintes:
int somar (int a, int b);
double somar (double a, double b);
char somar (char a, char b);
template <class Tipo>
Tipo somar (Tipo a, Tipo b);
template <class identificador> declaracao_da_funcao;
ou
template <typename identificador> declaracao_da_funcao;
No exemplo acima utilizei a primeira declaração, mas não há nenhuma diferença entre as duas (aliás, o próximo exemplo vai seguir a segunda, para verem que é igual). Podemos dar um nome qualquer ao identificador (no exemplo acima escolhi o nome "Tipo", que deve dar para a maior parte dos casos). Fica então um exemplo de um programa que utilize os templates
#include <iostream>
using namespace std;
template <typename Tipo>
Tipo somar (Tipo a, Tipo b)
{
return a + b;
}
int main()
{
cout << "Int - 2+3 - " << somar (2, 3) << endl;
cout << "Double - 1.53+3.14 - " << somar (1.53, 3.14) << endl;
cout << "Char - '1'+'2' - " << somar ('1', '2') << endl;
cin.get();
return 0;
}
Nota:
A soma de dois caracteres (tipo char) pode-vos parecer estranho. Se estiveram atentos às primeiras aulas, sabem que uma variável do tipo char é representada por um número (em binário). Esse número é, posteriormente, convertido num carácter (seguindo uma tabela).
Por exemplo, ao seguir uma tabela ASCII, sabemos que o valor de '1' é de 49 e o valor de '2' é de 50. A soma dos dois é igual a 99, que dá o valor de 'c'.
Também podemos criar um template que consiga trabalhar com dois tipos diferentes. Por exemplo, a função divisão:
#include <iostream>
using namespace std;
template <class T, class U>
T somar (T a, U b)
{
return a + b;
}
int main()
{
cout << "Double e Int - 3.43 + 2 - " << somar (3.43, 2) << endl;
//notem que devemos colocar o double primeiro, pois como é um numero decimal,
//a função deverá retornar o tipo double.
cin.get();
return 0;
}
Normalmente o compilador consegue perceber quais os tipos de variáveis a usar, mas no caso de não o conseguir, podemos arranjar isso facilmente, por exemplo:
int x = somar <double, int> (a, b);
Templates em classes
Também se podem utilizar templates em classes. Como já devem ter percebido como funciona, deixo só um exemplo completo:
#include <iostream>
using namespace std;
template <class T>
class C_Output //cria uma classe chamada C_Output
{
public:
T getVar(); //retorna o tipo que for colocado no template
void setVar(T var);
void print ();
private:
T variavel;
};
int main()
{
C_Output<double> x; // cria um objecto da classe C_Output
// o tipo da template vai ser double
C_Output<string> str; //a template vai ser do tipo string
x.setVar(3.14);
x.print();
str.setVar("Ola");
str.print();
cin.get();
return 0;
}
template <class T>
T C_Output<T>::getVar()
{
return variavel;
}
template <class T>
void C_Output<T>::setVar(T var)
{
variavel = var;
}
template <class T>
void C_Output<T>::print()
{
cout << variavel << endl;
}
As primeiras linhas acho que se percebem, deixo só uma explicação para as definições dos métodos da classe C_Output:
template <class T>
T C_Output<T>::getVar()
{
return variavel;
}
Se a função não usasse o template e fosse do tipo int, então ficaria algo como:
int C_Output::getVar()
{
return variavel;
}
Como podem ver as alterações nem são muitas. Adicionamos primeiro a instrução de que iríamos utilizar um template ( template <class T> ), para a função poder trabalhar com qualquer tipo de variáveis.
Depois colocamos o tipo de variável que a função retorna; como pode retornar qualquer tipo de variável, escolhemos o tipo T, da template.
Depois colocamos o identificador da classe (C_Ouput) e especificamos o tipo de variável que o template iria assumir; como não sabemos ainda qual é o tipo, então poderemos colocar T.
Nota:
Podem usar qualquer tipo de variáveis em templates, incluindo classes criadas por vocês.
Namespaces
Desde a aula nº 1 que usamos namespaces, mas só agora vou explicar para que servem. Basicamente, os namespaces servem para agrupar variáveis/funções/classes dentro de um nome, de modo a evitar confusões quando trabalhamos com, por exemplo, outras bibliotecas.
Isto torna-se muito útil quando, por exemplo, querem criar um motor de jogo (ou game engine) e querem dar nomes simples às classes/funções/etc. que criam, sem que estas entrem em conflito com outras funções que, por acaso, tenham o mesmo nome.
O exemplo mais simples:
#include <iostream>
using namespace std;
namespace dias_mes
{
int numero = 30; //pode ser 31 ou 28 ou 29, mas isto é só um exemplo
}
namespace dias_semana
{
int numero = 7;
}
int main()
{
cout << dias_mes::numero << endl;
cout << dias_semana::numero << endl;
cin.get();
return 0;
}
E um exemplo mais prático:
#include <iostream>
using namespace std;
namespace Engine_Grafica
{
void init()
{
cout << "Engine Grafica iniciada!" << endl;
}
}
namespace Engine_Som
{
void init()
{
cout << "Engine de Som iniciada!" << endl;
}
}
int main()
{
Engine_Grafica::init();
Engine_Som::init();
cin.get();
return 0;
}
Using namespace
Ao fazermos using namespace ... estaremos a "pedir" ao compilador para automaticamente inserir a namespace quando necessário. Desde a primeira aula têmos "pedido" ao compilador para automaticamente incluir a namespace std. Esta namespace refere-se à biblioteca padrão (standard) do C++, que inclui o cout, o endl, o cin, etc.
Caso não colocassemos using namespace std; no inicio do programa, teríamos que fazer o seguinte:
#include <iostream>
//não estamos a colocar o using namespace std;
namespace Engine_Grafica
{
void init()
{
std::cout << "Engine Grafica iniciada!" << std::endl;
}
}
namespace Engine_Som
{
void init()
{
std::cout << "Engine de Som iniciada!" << std::endl;
}
}
int main()
{
Engine_Grafica::init();
Engine_Som::init();
std::cin.get();
return 0;
}
Outro exemplo:
#include <iostream>
using namespace std; //usar a namespace std
namespace Engine_Grafica
{
void init()
{
cout << "Engine Grafica iniciada!" << endl;
}
}
namespace Engine_Som
{
void init()
{
cout << "Engine de Som iniciada!" << endl;
}
}
int main()
{
using namespace Engine_Grafica; //usar a namespace Engine_Grafica
init(); //não precisamos de colocar aqui a namespace
Engine_Som::init(); //mas precisamos, caso queiramos aceder à Engine_Som
cin.get();
return 0;
}
Para acabar o assunto das namespaces: Quando utilizamos o using globalmente (onde declaramos as variáveis globais), então este é válido para o programa todo. Se utilizarmos o using num bloco, este é apenas válido para esse mesmo bloco. Por exemplo:
#include <iostream>
using namespace std; //incluir a namespace globalmente
namespace Engine_Grafica
{
void init()
{
cout << "Engine Grafica iniciada!" <<endl;
}
}
namespace Engine_Som
{
void init()
{
cout << "Engine de Som iniciada!" << endl;
}
}
int main()
{
{
using namespace Engine_Grafica; //usar a namespace Engine_Grafica neste bloco
init(); //não precisamos de colocar aqui a namespace
}
{
using namespace Engine_Som; //usar a namespace Engine_Som neste bloco
init(); //nem aqui :P
}
cin.get();
return 0;
}
STL
Depois desta longa introdução, vamos finalmente conhecer um pouco da STL.Na verdade, vocês já utilizaram um componente da STL: a classe string. Como a matéria de STL é mesmo muito extensa (há livros inteiros sobre ela), e, embora pretenda tentar explicar resumidamente o seu uso, deixo-vos aqui apenas explicada a classe string (desta vez mais aprofundado).
Na próxima aula, pretendo explicar conceitos mais avançados, como iteradores e contentores, para poder explicar mais alguns componentes de STL
Strings
Comecei a falar sobre a classe string na aula 12, no entanto só falei sobre como iniciar um objecto da classe string (e imprimi-la no ecrã através do cout). Agora vou falar de alguns métodos úteis dentro da classe string.
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str1;
string str2;
string str3;
str1 = "Bem vindo";
if (str2.empty()) //é de esperar que esteja vazia...
{
cout << "A string 2 esta vazia!" << endl;
}
int tamanho = str1.size(); //retorna o comprimento da str1
cout << "A string 1 tem " << tamanho << " caracteres." << endl;
cout << "Insira o seu nome: ";
cin >> str2;
str3 = str1 + " " + str2;
// a str3 vai ser igual à str1, mais um espaço, mais a str2
cout << "\n\n" << str3 << endl;
tamanho = str3.size();
str3.insert(tamanho, ", esta a gostar do programa?"); //insere no fim
cout << str3 << endl;
tamanho = str2.size(); //o tamanho do nome
cout << "\nInsira outro nome: ";
cin >> str2; //utiliza a str2 para armazenar outro nome
str3.replace(10, tamanho, str2);
//substitui o nome. Começa na posiçao 10 (depois de "Bem vindo ") e prolonga-se
//durante o tamanho do nome (ou seja, substitui o nome :P)
cout << "\n\n" << str3 << endl;
str3.erase(0, 10); //apaga os primeiros 10 caracteres ( a frase "Bem vindo ")
cout << str3 << endl;
int pos = str3.find("gostar");
//procura a primeira ocorrência de "gostar" na str3 e retorna a posição do 1º caracter.
cout << "\ngostar esta na posicao " << pos << endl;
str1 = str3; //a str1 vai assumir o valor da str3
if (str1 == str3) //é de esperar que sim
{
cout << "\nA str1 e str3 sao iguais!" << endl;
}
cin.get();
cin.get();
return 0;
}
Acabei por dar muita matéria, mas vamos com calma.
string::empty()
Se a string estiver vazia, retorna true (1). Se não estiver vazia, retorna false, (0).
string::size()
Retorna o número de caracteres que a string possui.
string::operator +
Podemos juntar duas strings através do uso do operador soma (+). Acho que pelo exemplo vocês viram como funcionava.
string::insert()
Insere uma string dentro de outra string, a partir da posição que especificarmos. A sua definição é:
string& insert(size_type pos, const string& str);
Em que pos é a posição a partir da qual vai ser inserida a string e str a string a ser inserida.
PS: O tipo de variável size_type equivale ao unsigned int.
string::replace()
O método replace() substitui uma substring (uma string dentro doutra string), por outra string e a sua definição é esta:
string& replace(size_type pos, size_type n, const string& str);
Em que pos é o começo da substring que queremos apagar, n é o número de caracteres que queremos substituir e str é a string que queremos colocar.
string::erase()
Apaga uma substring dentro da string. A definição é:
string& erase(size_type pos=0, size_type n=npos);
Em que pos é a posição a partir do qual queremos apagar (se não colocarmos nada é a partir do primeiro caracter) e n o número de caracteres que queremos apagar (se não colocarmos nada apaga até ao fim da frase).
string::find()
size_type find (const string& str, size_type pos=0) const;
size_type find (char ch, size_type pos=0) const;
Procura pela primeira ocurrência da substring str (ou o caracter ch), a partir da posição pos (que é 0 se não colocarmos nada, desde o início).
Próxima aula -> C++ 19 - Técnicas Avançadas de C++ - Parte 3
Fazer o download do código fonte dos exemplos da aula
Fazer o download da aula em PDF (Brevemente)
Ir para o topo da página