Resumo da Aula 9

Sumário

1  Matrizes clássicas

Servem para guardar vários valores de um dado tipo.  Têm dimensão fixa, indicada durante a definição.  Acede-se aos elementos por indexação.  Cada elemento de uma matriz pode ser usado como uma simples variável.

1.1  Definição

1.1.1  Sintaxe

tipo nome[número_de_elementos];

1.1.2  Exemplos

int const dimensão = 30;
int mi[10];         // matriz com 10 int.
char mc[80];        // matriz com 80 char.
float mf[dimensão]; // matriz com 30 float.
double md[3];       // matriz com 3 double.
Importante:  O número de elementos de uma matriz tem de ser um valor constante (i.e., um valor literal ou uma constante) de um tipo aritmético inteiro.  Não é possível construir uma matriz clássica usando um número de elementos variável.  Mas é possível (e em geral aconselhável) usar constantes em vez de valores literais: int n = 50;
int const m = 100;
int matriz_de_inteiros[m];   // ok, m é uma constante.
int matriz_de_inteiros[300]; // ok, 300 é um valor literal.
int matriz_de_inteiros[n];   // errado! n não é uma constante.

1.1  Indexação

Seja a definição

int m[10];

Para atribuir o valor 5 ao sétimo elemento da matriz pode-se escrever:

m[6] = 5; // atribui o inteiro 5 ao 7º elemento da matriz.

ou

int a = 6;
m[a] = 5; // atribui o inteiro 5 ao 7º elemento da matriz.

Importante:  Os índices de uma matriz começam em zero.  Na matriz m acima o índice 10 não corresponde a nenhum elemento da matriz!

1.2  Inicialização

As matrizes podem ser inicializadas no momento da definição com os chamados inicializadores:
int m[4] = {10, 20, 0, 0};
ou int m[] = {10, 20, 0, 0}; // dimensão implícita de 4 elementos. É possível omitir valores na inicialização, sendo os elementos respectivos inicializados implicitamente com zero:

int grande[4] = {1, 2}; // inicializa os elementos da matriz com 1, 2, 0 e 0.
int grande[100] = {};   // inicializa toda a matriz com 0 (zero).

1.3  Matrizes multi-dimensionais

Em C++ não existe o conceito de matrizes multi-dimensionais.  Mas a sintaxe de definição de matrizes permite a definição de matrizes cujos elementos são outras matrizes, o que acaba por ter o mesmo efeito prático.  Assim, a definição:

    int m[3][4];

é interpretada como significando int (m[3])[4]; que significa: "m é uma matriz com três elementos, cada um dos quais é uma matriz com quatro elementos do tipo int".

A indexação destas matrizes faz-se usando tantos índices quantas as "matrizes dentro de matrizes" (incluindo a exterior), ou seja, tantos índices quantas as dimensões da matriz.  Para m conforme definida acima:

    m[1][3] = 4;       // atribui 4 ao elemento na linha 1 (2ª linha), coluna 3 (4ª coluna) da matriz,
                       // ou melhor, ao elemento 3 da matriz que é o elemento 1 da matriz m.
    int i = 0, j = 1;
    m[i][j] = m[1][3]; // atribui 4 à posição (0, 1) da matriz.

1.4  Matrizes como parâmetros de rotinas

É possível usar matrizes como parâmetros de rotinas:

int const colunas = 3;

void multiplicaMatrizPorEscalar(double matriz[][colunas], int const linhas,
                                double const escalar)
{
    for(int i = 0; i != linhas; ++i)
        for(int j = 0; j != colunas; ++j)
            matriz[i][j] *= escalar;
}

Importante:  O C++ ignora sempre a primeira dimensão de matrizes definidas como parâmetros de uma rotina, sendo necessário usar um parâmetro adicional para indicar esta dimensão (é para isso que é utilizado o parâmetro linhas).  As outras dimensões têm de ser especificadas por valores constantes (no caso apresentado a segunda dimensão é indicada pela constante colunas).

1.4.1  Exemplo

int const linhas = colunas;
double identidade[linhas][colunas] = {};

// Inicialização:
for(int i = 0; i != linhas; ++i)
    for(int j = 0; j != colunas; ++j)
        if(i == j)
            identidade[i][j] = 1.0;

// Para multiplicar todos os elementos da matriz por 3,3:
multipicaMatrizPorEscalar(identidade, linhas, 3.3);

Importante:  Uma matriz é sempre passada por referência ainda que tal não seja indicado no cabeçalho da rotina (isto é uma pequena mentira inofensiva, como se verá no futuro).  Por isso qualquer alteração feita aos valores dos elementos de uma matriz parâmetro de uma rotina tem sempre reflexo na matriz passada como argumento.  Se tal for indesejável, pode-se indicar que os elementos da matriz são constantes, o que impedirá a alteração dos valores dos elementos da matriz dentro da rotina.  Por exemplo:

#include <iostream>
#include <iomanip> // para poder usar setw.

using namespace std;

void mostraMatriz(int const matriz[][colunas], const int linhas)
{
    for(int i = 0; i != linhas; ++i) {
        for(int j = 0; j != colunas; ++j)
            cout << setw(10) << matriz[i][j];
        cout << endl;
    }
}

1.5  Restrições na utilização de matrizes

  1. Não é possível devolver matrizes em funções (mas podem-se passar como argumento a procedimentos, sendo sempre passadas por referência).
  2. Não são permitidas atribuições ou comparações entre matrizes.

2  Vectores

Na biblioteca padrão do C++ há uma classe modelo, chamada vector, que pode substituir com vantagem as matrizes unidimensionais.  Esta classe modelo permite obter novos tipos de dados (classes, na realidade) representando vectores de itens de um tipo à escolha.

As classes obtidas através da classe modelo vector têm várias vantagens em relação às matrizes:

Para definir variáveis usando a classe modelo vector é necessário adicionar ao topo do programa a linha:

#include <vector>

Para definir uma variável que seja um vector é necessário indicar qual o tipo dos seus itens.  Por exemplo, para definir um vector v que guarde itens do tipo int é necessário dar a instrução:

vector<int> v;

Neste caso o vector é construído vazio, i.e., com zero itens.

Também é possível definir um vector de modo a ter um determinado número inicial de itens.  Por exemplo, pode-se construir um vector com 10 itens do tipo int com a seguinte instrução:

vector<int> v(10);

Neste caso os 10 itens são inicializados com o valor zero.

É possível saber qual a dimensão (número de itens) de um vector utilizando o método size() (um método é uma forma especial de rotina), como no exemplo abaixo:

cout << v.size();

ou

vector<int>::size_type dimensão = v.size();

(vector<int>::size_type é sinónimo de um dos inteiros sem sinal disponível na linguagem C++.)

É possível alterar a dimensão de um vector, quer adicionando um elemento ao fim do vector com o método push_back()

v.push_back(x);

quer com o método resize()

v.resize(20);

No primeiro caso a dimensão aumenta de uma unidade e o valor passado como argumento é usado para inicializar o novo item construído.  No segundo caso a dimensão do vector passará a ser a indicada (20, neste caso).

Abaixo pode-se ver um pequeno exemplo de como são construídos dois vectores, preenchidos com 10 valores lidos do teclado e finalmente mostrados no ecrã:

3  Cadeias de caracteres

A biblioteca padrão do C++ disponibiliza uma classe, chamada string, que permite manipular cadeias de caracteres representando, por exemplo, texto.

Para definir uma variável deste tipo (da classe string) é necessário acrescentar ao topo do programa a linha:

#include <string>

A definição de uma variável deste tipo pode ser feita de várias formas.  Por exemplo:

string c1("Uma cadeia de caracteres qualquer");

string c2; // a cadeia fica vazia!

A uma variável do tipo string podem-se concatenar caracteres ou outras cadeias de caracteres:

c1 += " mais outra..."; // concatena " mais outra..." ao que já estiver em c1.
string c2(":");
c2 += ')';              // no fim destas duas instruções c2 contém ":)".

As cadeias de caracteres são indexáveis, tais como as matrizes clássicas e os vectores.  Por exemplo:

string nome = "Manel";
nome[0] = 'P';
nome[2] = 'p';

Certas rotinas necessitam de ter acesso directo ao conteúdo de uma variável do tipo string na forma de uma cadeia de caracteres clássica, i.e., de uma matriz de caracteres terminada de uma forma especial.  Para isso é necessário usar o método c_str():

string nome_do_ficheiro = "ficheiro.txt";
nome_do_ficheiro.c_str();

Este método é particularmente útil para obter um argumento compatível com os construtores das classes ifstream e ofstream, usadas para estabelecer canais de ligação a ficheiros (e que serão vistas mais tarde).

É possível saber o comprimento de uma cadeia de caracteres usando a expressão:

s1.length()