Agnor's HQ

Aula 12 - Tratamento de caracteres

Conceitos-Chave:

- caracter, um "símbolo" (pode ser uma letra, sinais de pontuação, etc) do tipo char
- string, conjunto de caracteres (uma palavra ou uma frase, por exemplo)
- arrays de caracteres, um conjunto de caracteres, logo uma string
- apontadores para strings, apontam para conjuntos de caracteres, não sendo preciso dimensionar o apontador
- classe string, biblioteca standard do C++, que tem o objectivo de ajudar na manipulação de strings

Tratamento de caracteres em C

Desde a entrada de C++ que se começou a utilizar a classe string para o tratamento de caracteres. No entanto, como ainda muitos programadores utilizam arrays de caracteres (era o que havia em C) achei importante ensinar pelo menos o básico.

Em C uma string pode ser definida através de um array de caracteres (tipo char), o que é lógico, uma vez que uma string é um conjunto de caracteres. Já dei uma introdução sobre isto na lição 7, por isso acho melhor darem uma olhadela antes para reverem como definir um array de caracteres.

Como qualquer array poderemos alterar como quisermos os valores presentes.

#include <iostream>
using namespace std;

int main()
{
    char nome[10] = "agnor";    //começa com 10 caracteres pois serão necessários
                                //mais à frente

    cout << nome << endl;
    cin.get();

    //vou mudar o nome para começar com letra maiúscula

    nome[0] = 'A';  //altera o primeiro carácter (nº 0) com a letra A

    cout << nome << endl;
    cin.get();

    //vou mudar acrescentar um s HQ no fim do nome

    nome[5] = 's';
    nome[6] = ' ';  //espaço
    nome[7] = 'H';
    nome[8] = 'Q';

    cout << nome << endl;
    cin.get();

    return 0;
}

Exemplo 12.1 - Alterar caracteres num array de caracteres

Embora nos dê um maior grau de controlo, torna-se pouco viável utilizar arrays de caracteres para strings que sejam alteradas constantemente. Isso pode ser solucionado se utilizarmos apontadores para caracteres:

#include <iostream>
using namespace std;

int main()
{
    char *nome = "agnor";

    cout << nome << endl;
    cin.get();

    nome = "Agnor";

    cout << nome << endl;
    cin.get();

    nome = "Agnor's HQ";

    cout << nome << endl;
    cin.get();

    return 0;
}

Exemplo 12.2 - Usar apontadores para strings

Os apontadores para caracteres tornam muito mais fácil esta tarefa.

Como se trata de um apontador não podemos manipular a string de origem, logo temos um menor grau de controlo, uma vez que não podemos alterar apenas uma letra, temos que alterar a string toda. O apontador nome está a apontar para um local na memória que contém a string que criamos, "Agnor".

Iniciar uma string sem definirmos um valor

Para iniciar uma string não temos que, necessariamente, atribuir-lhe logo um valor. Porém temos que definir o número de caracteres que a string possui:

char string[50];
string = "Agnor";

Podemos também usar apontadores para caracteres facilmente:

char* string;
string = "Agnor"

Exemplo 12.3 - Inverter os caracteres de uma palavra

#include <iostream>
using namespace std;

int main()
{
    char string[8] = "exemplo";

    char dest[8]; //cria uma variável com o mesmo numero de caracteres de string

    for (int j = 0; j < 7; j++)
    {
        dest[j] = string[6-j];  //copia os valores de string para dest inversamente
    }

    dest[7] = '\0'; //não esquecer de colocar o terminador

    cout << "String inicial: " << string << endl;
    cout << "String invertida: " << dest << endl;

    cin.get();

    return 0;
}

Exemplo 12.3 - Inverter os caracteres de uma palavra

As primeiras linhas de código são fáceis de entender. Criamos uma string inicial (string) e uma string que vai ser a invertida (dest). Ambos têm, obviamente, o mesmo nº de caracteres (não esquecer o caracter terminador).

Depois criamos uma condição for em que string vai ser copiado inversamente:

Programa:

dest[0] = string[6] //caracter 'o'
dest[1] = string[5] //caracter 'l'
dest[2] = string[4] //caracter 'p'
dest[3] = string[3] //caracter 'm'
dest[4] = string[2] //caracter 'e'
dest[5] = string[1] //caracter 'x'
dest[6] = string[0] //caracter 'e'

Depois inserimos o caracter terminador (\0) no fim do array e mostramos no ecrã o resultado. Simples!

Agora decidi complicar um bocado para podermos ter um controlo muito maior no programa:

#include <iostream>
using namespace std;

int main()
{
    char string[] = "exemplo";

    int n = sizeof(string) - 1; //n é o nº de caracteres da variavel string,
                                //excluindo o caracter terminador \0

    char dest[n+1]; //cria uma variável com o mesmo numero de caracteres de string

    for (int j = 0; j < n; j++)
    {
        dest[j] = string[n-j-1];  //copia os valores de string para dest inversamente
        			//sem copiar o caracter terminador, obviamente
    }

    dest[n] = '\0'; //não esquecer de colocar o terminador

    cout << "String inicial: " << string << endl;
    cout << "String invertida: " << dest << endl;

    cin.get();

    return 0;
}

Exemplo 12.4 - Inverter os caracteres de uma palavra (versão 2.0)

Basicamente todos os principios foram explicados no programa anterior a este. Vou só explicar a função sizeof.

Basicamente a função sizeof diz-nos o número de bytes ocupados por uma variável. Se fizessemos sizeof(n) o resultado seria 4 (o tipo int ocupa 4 bytes). Como cada char ocupa 1 byte e temos um array com 8 variáveis do tipo char (uma para cada caracter + uma para o caracter terminador) ocupará 8 bytes.

Com este programa já poderemos alterar à vontade a string inicial sem ser necessário modificar mais alguma coisa no programa.

Strings em C++

Como C++ é uma evolução do C, é natural que o C++ tenha uma forma mais fácil de manipular strings.

Em C (como podemos observar acima) manipular strings dá algum trabalho ao programador (tendo mesmo que criar algumas funções para o ajudar). Em C++ foi criada uma classe que facilita a vida do programador: a classe string.

Classe String

Para podermos aceder à classe string temos que, primeiro, incluí-la no programa, do mesmo modo que incluímos a classe iostream:

#include <string>

Outra coisa importante, o que esta classe faz é dar ao programador uma forma simples e poderosa de manipular strings, sem estar a "sujar as mãos" em código desnecessário. Vejam o seguinte programa:

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string String1;    //iniciamos uma string sem atribuirmos um valor
                       //notem que não temos que atribuir nenhum tamanho

    string String2("Bem Vindo ao mundo das Strings em C++");   //temos que iniciar
                                                               //as strings desta forma
    string String3("Agnor's HQ");
    string String4(String3);      //podemos iniciar facilmente uma string com outra string

    cout << "String2: " << String2 << endl;
    cout << "String3: " << String3 << endl;
    cout << "String4: " << String4 << endl;

    String1 = String3;    //olhem! Nem é preciso redemensionar o array...
    String3 = String2;    //é tudo feito pela classe string

    cout << "\nString1: " << String1 << endl;
    cout << "String2: " << String2 << endl;
    cout << "String3: " << String3 << endl;
    cout << "String4: " << String4 << endl;

    cin.get();

    return 0;
}

Exemplo 12.5 - Usando a classe string

Pode parecer um pouco confuso as diferentes formas de inicialização e, por isso, não quero entrar em mais detalhe na classe String, sem falar antes em classes (próxima aula).

Para acabar vou só mostrar que podemos aceder a apenas um caracter (que é exactamente a mesma coisa que em C) com um programa que mostra cada letra de uma string:

#include <iostream>
#include <string>
using namespace std;

int main()
{
    string String("Agnor");

    for (int j = 0; j < 5; j++) //mostra cada letra sequencialmente
    {
        cout << j+1 << "a letra: " << String[j] << endl;
    }

    cin.get();
    return 0;
}

Exemplo 12.6 - Mostrar uma letra de cada vez com a classe string

Exemplo final - Jogo ADIVINHA A PALAVRA

O seguinte jogo é baseado no da Forca. O jogador tem tentativas ilimitadas para acertar na palavra escolhida pelo jogador. Contém algumas coisas que ainda não deverão perceber ( string.size() e string.replace(), por exemplo), mas que serão abordadas mais à frente. Fiquem com o jogo:

/********************************
Jogo: Adivinhar a palavra
Autor: João Portela a.k.a. Agnor

http://agnor.gamedev-pt.net/
*********************************/

// Includes do programa

#include <iostream>
#include <windows.h>
#include <string>
using namespace std;

// Declaração de funções

//Desc: retorna o número de caracteres numa string
//Exemplo: int x = tamanho("ola");
int tamanho(string str);

//Desc: verifica se a letra que está na posição index das duas strings são iguais
//Exemplo: bool teste = verificarLetra("mesa", "casa", 2);
bool verificarLetra(string tentativa, string source, int index);

//Desc: verifica se as duas strings são iguais
//Exemplo: bool teste = verificarPalavra("mesa", "casa");
bool verificarPalavra(string tentativa, string source);

//Desc: Retorna um número aleatório, entre [de] e [ate]
//Exemplo: int x = getRandom(0, 3);
int getRandom(int de, int ate);

//Desc: Mostra o menu de jogo e retorna a opção escolhida
//Exemplo: int opcao = menu();
int menu();

//Desc: mostra a mensagem de vencedor
//Exemplo: venceu();
void venceu();

//Desc: A lógica do jogo em si. Source é a string que contém
//      a palavra "vencedora" e descobertas é a string que diz
//      quais letras estão certas. Retorna true, se o jogador
//      acertou na string (source)
//Exemplo: bool ganhou = logica_jogo("gato", descobertas);
bool logica_jogo(string source, string &descobertas);

//Desc: cria um novo jogo
//Exemplo: novo_jogo();
void novo_jogo();

int main()
{
    bool done = false;

    while (!done)	//inicia um ciclo de jogo. Só sai dele quando done for igual a true
    {
        cout << "Bem vindo ao jogo: ADIVINHA A PALAVRA" << endl;
        cout << "Este jogo e uma modificacao do jogo da Forca" << endl;
        cout << "Criado por Joao Portela aka Agnor" << endl;
        cout << "http:////agnor.gamedev-pt.net//" << endl;

        int escolha_menu = menu();	//escolha_menu vai assumir o
									//valor escolhido pelo utilizador

        switch (escolha_menu)
        {
            case 1:
                novo_jogo();	//inicia-se um novo jogo
                break;
            case 2:
                done = true;	//sai do ciclo de jogo
        }
    }

	cout << "Obrigado por jogar este jogo." << endl;

    cin.get();
    return 0;
}

// Definição de Funções

int tamanho(string str)
{
    return (int)str.size();	//retorna o nº de caracteres de uma string (explicarei mais tarde)
}

bool verificarLetra(string tentativa, string source, int index)
{
    if (tentativa[index] == source[index])	//se as letra forem iguais, no mesmo index...
    {
        return true;	//ao fazer return, sai da função
    }

    return false;
}

bool verificarPalavra(string tentativa, string source)
{
    if (tentativa == source)	//se as strings forem iguais...
    {
        return true;
    }

    return false;
}

int getRandom(int de, int ate)
{
    int random;
    ate -= de;
    random = rand() % (ate + 1) + de;
    return random;
}

int menu()
{
    int escolha;

    cout << "Escolha uma opcao: " << endl;
    cout << "\n1 - Novo Jogo" << endl;
    cout << "2 - Sair\n" << endl;

    cin >> escolha;

    return escolha;
}

void venceu()
{
    cout << "\nParabens! Venceu o jogo!\n" << endl;
	cin.get();
	cin.get();
}

bool logica_jogo(string source, string &descobertas)
{
    string tentativa;

    cout << "\n\nA palavra escolhida pelo jogo tem " << tamanho(source) << " letras" << endl;
    cout << "Letras ja descobertas: " << descobertas << endl;
    cout << "\nInsira a sua tentativa: ";

    cin >> tentativa;

    if (tamanho(source) != tamanho(tentativa))	//só para assegurar que têm o mesmo nº de caracteres
    {
        cout << "\nNao tem o mesmo numero de letras!" << endl;
		cin.get();
		return false;
    }

    if (verificarPalavra(tentativa, source))
    {
        return true;
    }

    else
    {
        for (int i = 0; i < tamanho(source); i++)	//percorre todas as letras da string
        {
            if (verificarLetra(tentativa, source, i))	//se as letras forem iguais
            {
                descobertas[i] = source[i];	//iguala as descobertas
            }

            else
            {
                descobertas[i] = '-';	//esse caracter fica com o valor '-',
										//pois ainda não foi descoberto
            }
        }
    }

    return false;
}

void novo_jogo()
{
	srand(GetTickCount());

    string source[10];	//cria um array de strings, uma especie de colecção de palavras

    source[0] = "casa";	//algumas palavras...
    source[1] = "mesa";
    source[2] = "rato";
    source[3] = "manteiga";
    source[4] = "luar";
    source[5] = "tecla";
    source[6] = "manto";
    source[7] = "canto";
    source[8] = "rio";
    source[9] = "escola";

    string descobertas;	//os caracteres que foram descobertos pelo utilizador

    int indice = getRandom(0,9);	//escolhe a posição da palavra na lista acima

    for (int x = 0; x < tamanho(source[indice]); x++)
    {
	    descobertas.resize(tamanho(source[indice]));
	    //faz com que descobertas fique com o mesmo tamanho de source, para o caso de haver
	    //para podermos alterar os caracteres em baixo (será explicado depois)

	    descobertas[x] = '-';	//põe todas as letras com o símbolo '-' (não descobertas)
    }

    bool vencer = false;

    while (!vencer)	//enquanto não vencer...
    {
        vencer = logica_jogo(source[indice], descobertas);
    }

    venceu();	//mostra a mensagem de vencedor
}


Exemplo 12.7 - Jogo ADIVINHA A PALAVRA

Há alguns conceitos que não usei assim tão frequentemente ao longo das lições:

bool logica_jogo(string source, string &descobertas);

Nos argumentos da função usámos uma referência para uma string, para podermos não só aceder ao valor de descobertas, mas também para o poder alterar. Assim evitamos o uso de uma variável global.

return (int)str.size();

Esta exige uma maior dose de explicação.

Pensem que str é um objecto qualquer (neste caso é uma palavra). Ao usarmos o '.' (ponto), estamos a aceder aos atributos desse objecto. Assim, estamos a aceder ao atributo size() do objecto string. Este é um dos conceitos de Programação Orientada a Objectos, que iremos dar na próxima aula. Basta saberem que isto faz com que obtenhamos o número de caracteres de uma string.

Outra coisa também nos prende a atenção, o (int):

A função size() (a que está dentro do objecto str) retorna um valor do tipo size_t, um valor que os standards de C++ definem como sendo um valor próprio para o tamanho (nº de caracteres) de strings. Na verdade, o "coração" desse tipo de variáveis é um unsigned int, ou seja um inteiro com números só positivos. Como só apresenta números positivos, tem o dobro da capacidade de um int normal. Como tal, se tentarmos converter um size_t para um int, o compilador dar-nos-á um aviso: "Possível perda de dados". Isto porque, se o número de caracteres for na ordem dos biliões, o tipo int poderá não suportá-lo. Mas como sabemos que isso é impossível (nenhuma string que iremos fazer terá esse número de caracteres), ao usarmos (int) é a nossa maneira de dizermos ao compilador que não nos importamos que o size_t seja convertido para int, e o compilador já não nos vai chatear.

descobertas.resize(tamanho(source[indice]));

Dentro do objecto descobertas temos uma função chamada resize, que tem a função de preparar a string para um determinado número de caracteres. As string podem parecer "mágicas", mas fazer o seguinte código seria errado:

string str = "ola"; //três caracteres str[3] = 'b'; //errado, pois só há 3 caracteres na string!

E a forma correcta:

string str = "ola"; //três caracteres str.resize(4); str[3] = 'b';

Uso de plicas ' em vez de aspas "

Provavelmente repararam que usei '-' em vez de "-". Isto porque ao utilizarmos "-" estamos a definir uma string inteira, não só um caracter. Fazer char p = "a"; daria erro. A forma correcta, quando queremos utilizar apenas um caracter, é char p = 'a';

Final da aula 12 de C++:

Próxima aula -> C++ 13 - Classes - Parte I

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