Agnor's HQ

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);

Assim, através do uso de templates, a tal função genérica ficaria assim:

template <class Tipo> Tipo somar (Tipo a, Tipo b);

Devem ter reparado que a sintaxe é bastante diferente do que estamos habituados a ver, mas vou tentar explicar tudo direitinho: Primeiro temos que ter em conta a sintaxe das templates:

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;
}


Exemplo 18.1 - Exemplo de Templates em Funções

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;
}


Exemplo 18.2 - Templates em Funções - Dois Tipos de Variáveis Diferentes

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;
}


Exemplo 18.3 - Templates em Classes

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.

Acho que devem entender o resto :P

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;
}


Exemplo 18.4 - Exemplo de Namespaces

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;
}


Exemplo 18.5 - Exemplo de Namespaces 2

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;
}


Exemplo 18.6 - Exemplo de Using Namespace

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;
}


Exemplo 18.7 - Exemplo de Using Namespace 2

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;
}


Exemplo 18.8 - Exemplo de Using Namespace 3

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;
}

Exemplo 18.9 - Exemplo de vários métodos da biblioteca string

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).

Final da aula 18 de C++:

Próxima aula -> C++ 19 - Técnicas Avançadas de C++ - Parte 3

Fazer o download do código fonte dos exemplos da aula WinZip
Fazer o download da aula em PDF (Brevemente)

Ir para o topo da página

Aulas de C++

Anúncios