Resumo da Aula 9

Sumário

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 conhecido em tempo de compilação (i.e., um valor literal ou uma constante conhecida em tempo de compilação) 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 conhecidas em tempo de compilação 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]; // elementos por inicializar, contêm lixo!

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!
Formas de identificar elementos de uma matriz
Número de ordem do elemento Posição ou índice
0
1
2
3
etc. etc.

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 número_de_colunas = 3;

void multiplicaMatrizPorEscalar(double matriz[][número_de_colunas],
                                int const número_de_linhas,

                                double const escalar)
{
    for(int i = 0; i != número_de_linhas; ++i)
        for(int j = 0; j != número_de_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).  As razões (obscuras) para tão aberrante comportamento serão desvendadas mais tarde.

1.4.1  Exemplo

int const número_de_linhas = número_de_colunas;
double matriz_identidade[número_de_linhas][número_de_colunas] = {};

// Inicialização:
for(int i = 0; i != número_de_linhas; ++i)
    for(int j = 0; j != número_de_colunas; ++j)
        if(i == j)
            matriz_identidade[i][j] = 1.0;

// Para multiplicar todos os elementos da matriz por 3,3:
multipicaMatrizPorEscalar(matriz_identidade, número_de_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á mais tarde).  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;

int const número_de_colunas = 3;

void mostraMatriz(int const matriz[][número_de_colunas],
                  int const número_de_linhas)

{
    for(int i = 0; i != número_de_linhas; ++i) {
        for(int j = 0; j != número_de_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.

Vectores

Na biblioteca padrão do C++ há uma classe genérica, chamada vector, que pode substituir com vantagem as matrizes unidimensionais.  Esta classe genérica permite obter novos tipos de dados, ou melhor, classes, representando vectores de itens de um tipo à escolha.

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

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

#include <vector> // assume-se a presença de using namespace std; mais à frente.

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 item ao fim do vector com o método push_back()

v.push_back(novo_item);

quer com o método resize()

v.resize(20 /* nova dimensão */);

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ã:

Cadeias de caracteres

(Esta secção é uma actualização da secção O tipo string, do Resumo da Aula 4.)

A biblioteca padrão do C++ disponibiliza um tipo (na realidade uma classe, como se verá) chamado string que permite manipular cadeias de caracteres representando, por exemplo, texto ou palavras.  As variáveis deste tipo no fundo consistem em sequências de variáveis do tipo char, às quais se acede através de uma indexação, pois cada variável tem um número de ordem na sequência.

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!

string c3(10, '-'); // uma cadeia com 10 caracteres '-'.

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

c1.length()

Esta expressão é de um tipo aritmético inteiro sem sinal não especificado (i.e., pode ser unsigned short int, unsigned int ou unsigned long int, consoante a implementação).  Por isso, a expressão é do tipo string::size_type, que é sinónimo de um dos três possíveis tipos referidos.

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

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

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