Advertisement

Vetores e Matrizes

Nessa aula iremos compreender como funciona os vetores e matrizes. Esse conceito é bastante importante dentro das estruturas de dados e existem em várias linguagens de programação. Os vetores e matrizes em linguagem C são importantes para o armazenamento e manipulação dos dados. Em estrutura de dados, os vetores podem ser usados para implementação de pilhas, filas e até mesmo árvores. 


Nesta aula iremos entender como funcionam as estruturas básicas de alocação de memória estática que são os vetores. Ao final desta aula você será capaz de entender:
  • O que são vetores;
  • Como os vetores são usados;
  • Como os vetores se comportam na memória dos computadores;
  • O que são matrizes;
  • Como representar matrizes;

1.    Vetores

A forma mais trivial de criarmos um conjunto de dados é usando vetores. Assim como diversas outras linguagens de programação, a linguagem C também permite a definição de vetores.

int v[10];

A declaração acima diz que v é um vetor de inteiros dimensionado com 10 elementos, isto é, reservamos um espaço de memória contínuo para armazenar 10 valores inteiros. Assim, se cada int ocupa 4 bytes, a declaração acima reserva um espaço de memória de 40 bytes, como ilustra a figura abaixo.

144
140
136
132
128
124
120
116
112
108
104



O acesso a cada elemento do vetor é feito através de uma indexação da variável v. Observamos que, em C, a indexação de um vetor varia de zero a n-1, onde n representa a dimensão do vetor. Assim:


v[0] // → primeiro elemento de v
v[1] // → segundo elemento de v
//...
v[9] // → último elemento de v

Mas:

v[10] //→ ERRO (invasão de memória)

A linguagem C também suporta aritmética de ponteiros. É possível também somar e subtrair ponteiros, porém, é preciso que o valor do ponteiro resultante aponte para dentro da área reservada para o vetor. Se p representa um ponteiro para um inteiro, p+1 representa um ponteiro para o próximo inteiro armazenado na memória, isto é, o valor de p é incrementado de 4 (mais uma vez assumindo que um inteiro tem 4 bytes). Com isto, num vetor temos as seguintes equivalências:
    v+0 // → aponta para o primeiro elemento do vetor
    v+1 // → aponta para o segundo elemento do vetor
    v+2 // → aponta para o terceiro elemento do vetor
    // ...
    v+9 // → aponta para o último elemento do vetor

Portanto, escrever &v[i] é equivalente a escrever (v+i). De maneira análoga, escrever v[i] é equivalente a escrever *(v+i) (é lógico que a forma indexada é mais clara e adequada). O uso da aritmética de ponteiros nesse caso é perfeitamente válido, pois os elementos dos vetores são armazenados de forma contínua na memória.

Os vetores também podem ser inicializados na declaração:

int v[5] = { 5, 12, 16, 21, 22 };



ou simplesmente:

int v[] = { 5, 12, 16, 21, 22 };



No último exemplo, a linguagem faz o dimensionamento do vetor pelo número de elementos inicializados.

Poderíamos definir os vetores como:
Os vetores são espaços de memória que são alocados para armazenar um conjunto de dados que posteriormente pode ser acessado através de seu índice.

No entanto esta é uma abstração criada para melhorar a compreensão do programador da utilidade desta ferramenta. Os vetores são na verdade ponteiros de espaços de memória que são alocados e podem ser acessados posteriormente realizando operações de desreferenciação.

Passagem de vetores para funções


Quando desejamos enviar um vetor para uma função, é preciso passar o endereço da primeira posição do vetor. Nesse caso, ao passarmos um valor de endereço, a função que foi chamada deve ter um parâmetro do tipo ponteiro que armazena esse valor. Por exemplo: se passamos uma para uma função um vetor de inteiros, devemos inserir um parâmetro do tipo int*, que é capaz de armazenar endereços de inteiros. Quando dizemos “vou passar um vetor para uma função” devemos interpretar como “vou passar o endereço inicial do vetor”. Os elementos dos vetores não são copiados para a dentro função, o argumento que foi copiado consiste apenas em um endereço do primeiro elemento.

Acompanhe o exemplo:

#include <stdio.h>
#include <stdlib.h>

void imprimir_vetor ( int n , int *v ) {
    int i;
    for (i = 0; i < n ; i++)
         printf("|%d|", v[i]);
    printf("\n\n\n");
}

void imprimir_matriz ( int n , int m[][3] ) {
    int i,j;
    for (i = 0; i < n; i++){
        for (j = 0; j < n; j++){
            printf("|%d|", m[i][j]);
        }
        printf("\n");
    }
    printf("\n\n\n");

}

int main(){
    int v1[5] = { 5, 10, 15, 20, 25 };
    int v2[] = { 5, 10, 15, 20, 25 };
    int m1[4][3] =  {{5,10,15},
                    {20,25,30},
                    {35,40,45},
                    {50,55,60}};
    int m2[4][3] = {{1,2,3},{4,5,6},{7,8,9},{10,11,12}};
    int m3[4][3] = {1,2,3,4,5,6,7,8,9,10,11,12};

    imprimir_vetor(5, v1);
    imprimir_vetor(5, v2);

    imprimir_matriz(4, m1);
    imprimir_matriz(4, m2);
    imprimir_matriz(4, m3);

    system("pause");

}
  
A saída do programa é 2 4 6, pois os elementos do vetor serão incrementados dentro da função.

2 – Matrizes


Quando falamos em conjuntos unidimensionais estamos nos referindo à vetores. No entanto, a linguagem C também permite a construção de conjuntos bi ou multidimensionais. Matrizes representadas por conjuntos bidimensionais de valores numéricos. Todas as matrizes que são apresentadas aqui poderiam ser estendidas para conjuntos de dimensões maiores.

Os vetores bidimensionais podem ser declarados estaticamente. Por exemplo, para ao declararmos uma matriz de valores reais com 4 linhas e 3 colunas, fazemos:

float mat[4][3];

Esta declaração reserva na memória um espaço para armazenar 12 elementos da matriz. Esses elementos são armazenados de maneira contínua, organizados linha a linha.

60.0
55.0
50.0
45.0
40.0
35.0
30.0
25.0
20.0
15.0
10.0
5.0


Veja como essa tabela poderia ser representada no formato de uma matriz:

float m[4][3] = {{5.0,10.0,15.0},
                {20.0,25.0,30.0},
                {35.0,40.0,45.0},
                {50.0,55.0,60.0}};
Os elementos das matrizes podem ser acessados através de uma indexação dupla: mat[i][j]. O primeiro índice, i, acessa a linha e o segundo, j, acessa a coluna. Dado que em C a indexação começa em 0 (zero), por exemplo: o elemento da primeira linha e primeira coluna é acessado por mat[0][0]. Depois que fazemos a declaração estática de uma matriz, a variável que representa a matriz, mat no exemplo acima, representa um ponteiro para o primeiro “vetor-linha”, composto por 3 elementos. Com isto, mat[1] irá apontar para o primeiro elemento do segundo “vetor-linha”, e assim por diante.

 As matrizes também poderão ser inicializadas na declaração:

	float matriz[4][3] = {{1,2,3},{4,5,6},{7,8,9},{14,15,16}};



Ou podemos inicializar sequencialmente:

	float matriz[4][3] = {1,2,3,4,5,6,7,8,9,10,11,12};



O número de elementos por linha poderá ser omitido na inicialização. No entanto, o número de colunas deve, obrigatoriamente, ser fornecido:


float matriz[][3] = {1,2,3,4,5,6,7,8,9,10,11,12};


Passando matrizes por funções


Conforme foi dito, as matrizes criadas estaticamente são representadas por um ponteiro para um “vetor-linha” com o número de elementos da linha. Ao passar uma matriz para uma função, o parâmetro da função deve ser deste tipo. Infelizmente, a sintaxe usada para representar este tipo é obscura. O protótipo da função que recebe a matriz declarada acima seria:


void f (..., float (*mat)[3], ...);

Uma segunda opção seria declarar o parâmetro como matriz, podendo também omitir o número de linhas:

void f (..., float mat[][3], ...);

O acesso aos elementos da matriz de dentro da função é feito normalmente usando indexação dupla. É interessante recomendar que, quando possível, faça o uso de matrizes alocadas estaticamente, visto que elas são mais simples de manipular (dado que seus limites são conhecidos). 

Referências


CELES, Waldemar e RANGEL, Lucas; Apostila de estrutura de dados; 2002; Pontifícia Universidade Católica; Rio de Janeiro; 2002. Acessado em 21/12/2020



Nenhum comentário

Conta pra mim sua opinião!

Fale comigo