Vetores e Matrizes

editar

Façamos uma pequena revisão de conceitos:

  • Vetores e matrizes são variáveis compostas homogêneas, ou seja, são agrupamentos de dados que individualmente ocupam o mesmo tamanho na memória e são referenciados pelo mesmo nome, geralmente são individualizadas usando-se índices.
  • Vetores distinguem-se das matrizes apenas pela característica de ter dimensão (1 x n) ou (n x 1), essencialmente vetores são matrizes linha ou matrizes coluna.

Em linguagem "C" vetores e matrizes são usados abundantemente para compor estruturas de dados necessárias para composição de diversos recursos. Esta usa, mais explicitamente, vetores de caracteres para definir cadeias de texto, o que é conhecido como o mais trivial uso de vetores. Além deste recurso, o "C" também define meio de criação de matrizes tipo (n x m), provendo, desta forma os recursos necessários para criação destes conjuntos de dados.

A linguagem "C++" suporta os mesmos recursos e permite a criação de matrizes de objetos. Uma vez que um objeto é essencialmente um tipo de dado criado pelo programador, todas as características básicas legadas aos "tipos" em geral são observados nos tipos criados (classes de objetos).

Vetores

editar

Os vetores em C++ seguem a mesma notação da linguagem "C", via de regra declara-se o tipo seguido de um asterisco. Para acessar o valor apontado pela variável usa-se um asterisco de forma semelhante, como pode ser visto no trecho de código abaixo:

int *x;
int a = 3;

x = &a;

cout <<" O valor do conteúdo da posição 0x";       // O valor da posição 0x23A0209112 
cout << hex << x << "de memória é " << *x << endl; // de memória é 3

Matrizes

editar

Podemos imaginar que uma matriz é um conjunto de vetores que ocupam uma determinada área de memória referenciada por um nome comum. Matrizes de tipos primitivos são conseguidas através de associações do operador [ ], como por exemplo:

char A[32][16];
int B[12][26][10];

Definindo nossas classes de objetos poderemos declarar matrizes de objetos:

class Record
{ int D;
  float X,Y;
  char desc[12];

  public:
   Record();

   void addFData(float A, float B);
   float getFDataX();
   float getFDataY();
   ...
   ...
   ...
};

void function()
{
 Record A[32][16];
 ...
 ...
 ...

Ou seja, podemos adotar a mesma sintaxe para criar matrizes de objetos. Este procedimento pode ser usado com o cuidado de se avaliar antes a quantidade de memória disponível para que a matriz não ultrapasse esse limite físico, muitas vezes delimitada pelo hardware ou pelo sistema operacional. Lembremos que, um objeto precisa do espaço equivalente a soma de suas variáveis internas para ser alocado na memória.

Declarando arranjo

editar

Os arrays permitem fazer o seguinte:

int a1, a2, a3,….a100; 	é equivalente a ter 	int a[100];

Ou seja permite declarar muitas variáveis de uma forma bem simples, poupa escrita e é bastante compreensível.

  • O número que está dentro de brackets [] é o size declarator. Ele é que vai permitir ao computador dizer quantas variáveis a serem geradas e logo quanta memória deverá ser reservada. A memória reservada para as variáveis vão ser todas seguidas, um int a seguir ao outro
  • Há uma forma para não dizer o valor deste size declarator, mas isso apenas acontece antes da compilação, ou seja o compilador é que vai fazer esse preenchimento por nós, visualizando o nosso código e contanto os membros que colocámos. Isto é um automatismo dos compiladores recentes. chama-se a isto iniciação implícita que vamos ver nesta secção.
  • As variáveis geradas pelos arrays vão ser todos do mesmo tipo.
  • Reparem que o valor do size declarator é um número. É literal, ele não vai mudar quando o programa estiver a correr. Por isso quando não soubermos o número de elementos o que fazemos?

Veja uma tentativa:

 #include <iostream>
 using namespace std;
 int main ()
 {
   int numTests;
   cout << "Enter the number of test scores:";
   cin >> numTests;
   int testScore[numTests];
   return 0;
 }

Isto vai dar um erro de compilação, porque o array está a espera de uma constante e não uma variável. Há uma maneira de contornar isto que é através da memória dinâmica que vamos dar mais tarde, num capitulo próprio, pois isto vai envolver muitos conceitos.

Constantes

editar

Reparem que há uma diferença entre literais e constantes, apesar de em ambos os casos o valor não é alterado durante a execução do programa, a constant é um nome que representa o valor, o literal é o valor.

Declarar constantes

editar

É exatamente como declarar uma variável com duas diferenças:

  1. A declaração começa com a palavra const. Isto vai dizer ao compilador que é uma constante e não uma variável
  2. Teremos de atribuir logo o valor na declaração, ou seja, é fazer a iniciação

Exemplo:

const int numTests = 3;

Portanto se tentarmos colocar um outro valor ao numTest, isso vai dar um erro de compilação

Array index

a[100] é composto por a[0], a[1],… a[99] ( De a[0], a[1],… a[99] existe 100 posições)

Pergunta: Porque é que o índex começa em zero e não um? Ou seja temos as 100 posições que pedimos mas o índex começa no zero e não no 1. A razão disto tem a ver com offset – que refere ao valor adicionado para o endereço base para produzir a segunda address. Bem não entendi bem! Eu explico de novo: O endereço (address) do primeiro elemento do array, é o mesmo do que o endereço base do próprio array. ah espera aí, o que estão a dizer é que o endereço do array é igual ao do primeiro elemento do array. Assim o valor que teria de ser adicionado, ao endereço base do array, para conseguirmos o endereço do primeiro elemento seria zero. Agora sim, percebi!

Erro: Um erro comum é esquecer que o index começa no zero, e portanto quando se querem referir ao último elemento, esquecem-se que têm de subtrair uma unidade. O que advém desse esquecimento é que podem estar a alterar memória pertencente a uma variável, instrução,..de um outro programa. – Ou seja vai existir violação de dados.

Se o array for declarado globalmente em vez de ser localmente, então cada elemento é inicializado ao valor defaut que é zero.

Iniciação

editar

Iniciação, se bem se recordam é atribuir um valor a uma variável ao mesmo tempo que declaramos a variável. Podemos fazer a iniciação de um array de 2 maneiras:

1) explicit array sizing

int testScore[3] 	= { 74, 87, 91 };
float milesPerGallon[4] = { 44.4, 22.3, 11.6, 33.3};
char grades[5] 		= {'A', 'B', 'C', 'D', 'F' };
string days[7] 		= {"Sunday", "Monday", "Tuesday", "Wednesday","Thursday", "Friday", "Saturday"};

Pergunta: O que é que acontece se dermos mais valores de atribuição do que elementos do array?

int a[3] = { 74, 87, 91, 45 };

Isto vai dar um erro de compilação “too many initializers”

Pergunta: O que é que acontece se tivermos menos valores do que os elementos?

int a[3] = { 74 };

Não acontece nada simplesmente não temos valores para a[1] e a[2]. Porém em alguns compiladores os elementos não inicializados ficam com os valores defaut, que no caso dos ints é 0 no caso dos floats é 0.0 e nos caracteres é o caractere nulo ("\0"). No entanto se não inicializarmos um dos elementos, os restantes elementos terão de ser não inicializados pois caso contrário teremos um erro de compilação


2) implicit array sizing

int testScore[ ] 	= { 74, 87, 91 };
float milesPerGallon[ ] = { 44.4, 22.3, 11.6, 33.3};
char grades[ ] 		= {'A', 'B', 'C', 'D', 'F' };

Aqui o compilador faz o trabalho por nós, conta os elementos e preenche o número de elementos

Caracter array

editar
char name[ ] 	= {'J', 'e', 'f', 'f', '\0' };
char name[ ]	= "Jeff";

Ambas as inicializações são permitidas. Porém tomar atenção á ultima iniciação! Quando colocámos as aspas duplas o compilador acrescenta o "\0" na array que cria! Não tem []!! Esta até costuma ser a preferida pelos programadores, é ao estilo de strings (Na verdade as strings são arrays de char mas vamos falar disso num capitulo próprio)

O char "\0" é o escape sequence para caracterer null. Este escape sequence sinaliza ao cout o fim do character array. É o último elemento do array preenchido! Se não tivéssemos este carácter apareceriam estranhos caracteres a seguir ao "jeff", chamados "caracteres-lixo". (porquê?) Isto não significa que o último elemento deva ser sempre o null carácter

Arrays de várias dimensões

editar

Podemos ter duas dimensões

tipo_da_variável nome_da_variável [altura][largura];

como também poderíamos ter infinitas

tipo_da_variável nome_da_variável [tam1][tam2] ... [tamN];


Iniciando

editar
float vect [6] = { 1.3, 4.5, 2.7, 4.1, 0.0, 100.1 };
int matrx [3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
char str [10] = { 'J', 'o', 'a', 'o', '\0' };
char str [10] = "Joao";
char str_vect [3][10] = { "Joao", "Maria", "Jose" };

Peguemos no exemplo:

int a [2][3]={1,2,3,4,5,6,}


Na memória teríamos as coisas assim. ou seja os elementos são seguidos e do mesmo tipo

a[0][0] a[0][1] a[0][2] a[1][0] a[1][1] a[1][2]
1 2 3 4 5 6

Portanto ter int a [2][3] é equivalente a ter int a [6] o nome que se dá é que é diferente.

Pergunta: será pedido espaço par 6 ints ou antes um espaço com o tamanho de 6 ints? Como nós sabemos que os arrays os elementos têm endereços de memoria consecutivos, por isso, não podem ser pedidos 6 ints, pois se fosse esse o caso, poderia acontecer que eles não ficassem juntos.

Const Constant arrays

editar
const int daysInMonth [] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

Recordar que temos de inicializar quando queremos fazer uma constante array

Atribuir valores ao array

editar
#include <iostream>
using namespace std;
int main ()
{
  int testScore[3];
  cout << "Enter test score #1: ";
  cin >> testScore[0];
  cout << "Enter test score #2: ";
  cin >> testScore[1];
  cout << "Enter test score #3: ";
  cin >> testScore[2];
  cout << "Test score #1: " << testScore[0] << endl;
  cout << "Test score #2: " << testScore[1] << endl;
  cout << "Test score #3: " << testScore[2] << endl;
  return 0;
}

Podemos atribuir o valor 1 a 1, mas para poupar escrita de programação é melhor utilizar as funções anteriormente revistas como o for

#include <iostream>
using namespace std;
int main ()
{
  int testScore[3];
  for (int i = 0; i < 3; i++)
  {
     cout << "Enter test score #" << i + 1 << ": ";
     cin >> testScore[i];
  }
  for (i = 0; i < 3; i++)
  {
     cout << "Test score #" << i + 1 << ": " << testScore[i] << endl;
  }
  return 0;
}

Ou melhor ainda podemos usar uma constante, para tornar o nosso código mais abstracto.

#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
   int testScore[MAX];
   for (int i = 0; i < MAX; i++)
   {
      cout << "Enter test score #" << i + 1 << ": ";
      cin >> testScore[i];
   }
   for (i = 0; i < MAX; i++)
   {
      cout << "Test score #" << i + 1 << ": " << testScore[i] << endl;
   }
   return 0;
}


Lembram-se da história de termos o endereço do array igual ao endereço do 1º elemento do array?

#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
   int testScore[3] = { 74, 87, 91 };
   cout <<   testScore[0] <<"\n";
   cout <<   testScore <<"\n";		//array base e não um elemento particular do array
   return 0;
}

Pois bem vemos que quando mandamos imprimir o array, ele dá um endereço. Pois o valor do nome do array é o endereço do array.

Arrays como statements de funções

editar

Pegando no programa

#include <iostream>
using namespace std;
const int MAX = 3;
int main ()
{
  int testScore[MAX];
  for (int i = 0; i < MAX; i++)
  {
     cout << "Enter test score #" << i + 1 << ": ";
     cin >> testScore[i];
  }
  for (i = 0; i < MAX; i++)
  {
     cout << "Test score #" << i + 1 << ": " << testScore[i] << endl;
  }
  return 0;
}

Vamos torná-lo mais modular, escrevendo uma função para atribuir valores ao array e outa função para mostrar os valores do array

#include <iostream>
using namespace std;
void assignValues(int[], int);
void displayValues(int[], int);
const int MAX = 3;
int main ()
{
  int testScore[MAX];
  assignValues(testScore, MAX);
  displayValues(testScore, MAX);
  return 0;
}
void assignValues(int tests[], int num)
{
  for (int i = 0; i < num; i++)
  {
     cout << "Enter test score #" << i + 1 << ": ";
     cin >> tests[i];
  }
}
void displayValues(int scores[], int elems)
{
for (int i = 0; i < elems; i++)
  {
     cout << "Test score #" << i + 1 << ": "<< scores[i] << endl;
  }
}


Arrays como argumentos de funções

editar
// arrays as parameters
#include <iostream>
using namespace std;
void printarray (int array[], int length) /*função com 2 argumentos,um deles é um array */
{
 for (int n=0; n<length; n++)
     cout << array[n] << " ";
 cout << "\n";
}
int main ()
{
 int a[] = {5, 10, 15};
 printarray (a,3);     //passo array como argumento
 return 0;
}

Este exemplo por acaso está muito curioso

Pergunta: mas agora deveríamos perguntar se neste caso tínhamos uma passagem por valor ou referência.

Quando um array é passado para uma função, a função recebe não a cópia do array mas invés disso o endereço, address do primeiro elemento do array, que é igual ao valor do array (base).

Assim todas as modificações que se efectuarem na função que foi chamada irão repercutir-se no array passado.

Vamos confirmar:

 #include <iostream>
 using namespace std;
 void doubleThem(int a[], int size);
 int main()
 {
    int a;
    int myInts[10] = {1,2,3,4,5,6,7,8,9,10};
    doubleThem(myInts, 10);	//passei o array base
    for (a=0; a<10; a++)
    {
        cout << myInts[a] <<”\t;
    }
    return 0;
 }
 void doubleThem(int a[], int size)
 {
    int i;
    for (i = 0; i < size; i++)
    {
        a[i] = 2 * a[i];
    }
 }