Baixe o app para aproveitar ainda mais
Prévia do material em texto
2ºAula Ponteiros – parte II Objetivos de aprendizagem Ao término desta aula, vocês serão capazes de: • compreender como funcionam os ponteiros para vetores e matrizes; • entender como usar os ponteiros que apontam para ponteiros e; • saber como usar ponteiros para registros. Olá, Prosseguindo os nossos estudos a respeito de ponteiros, nesta aula, vamos ver alguns usos mais avançados de ponteiros. Veremos como podemos criar e usar ponteiros que apontam para arrays (vetores e matrizes), ponteiros que apontam para outros ponteiros, e por fim, ponteiros que apontam para registros. Lembre-se de ler atentamente este material. Se preferir, você pode executar os programas mostrados para entender melhor os conceitos apresentados. Se tiver alguma dúvida, manifeste no fórum ou no quadro de avisos da disciplina. Bons estudos! Estrutura de Dados I 14 1 - Usando ponteiros em vetores (arrays unidimensionais) 2 - Usando ponteiros em matrizes (arrays multidimensionais) 3 - Alocação dinâmica de memória para vetores 4 - Ponteiros para Ponteiros 5 - Ponteiros para Registros 1 - Usando ponteiros em vetores (arrays unidimensionais) Nesta seção, vamos aprender como usar ponteiros em arrays. Primeiramente, vamos mostrar como funciona a declaração de ponteiros. A declaração é similar ao que você viu anteriormente. Para vetores, não precisamos especificar o tamanho do array. Além disso, se criarmos um vetor por meio de uma variável comum, não precisamos usar o operador & para pegar o seu endereço, pois o nome da variável que corresponde a um vetor por si só contém o endereço do vetor. Para entendermos isso, vamos a mais um exemplo: Exemplo 1.8 - Testando o uso de ponteiros para vetores Execute o programa e veja. O valor da primeira posição será exibido. Seções de estudo Isso significa que quando passamos o nome de um vetor, sem especificar qual é o seu índice para o ponteiro, estamos passando o endereço de memória da primeira posição do vetor. Figura 1 – Figura ilustrativa que mostra uma variável p, que recebeu o endereço da primeira posição do vetor v. Fonte: MARTINEZ, 2009, p. 338. Podemos também especificar para o ponteiro a posição de memória que armazena um elemento de índice específico do vetor. Vamos demonstrar: Exemplo 1.9 - Testando a forma direta de se declarar ponteiros Comentamos anteriormente ao comando de exibição que esperamos que os valores 0 e 30, equivalentes as posições 0 e 3 do vetor, seriam exibidas. Quando executamos o programa, observamos que o comportamento esperado foi feito, mostrando que podemos fazer essa forma de passagem de 15 endereço para declararmos uma posição específica do vetor. Outra forma que podemos utilizar é por meio de um ponteiro que aponta para posição do elemento inicial do vetor, criar um novo ponteiro, simplesmente adicionando quantas posições queremos avançar no vetor. Isso mesmo: podemos fazer operações aritméticas (adição e subtração) com endereços de memória para vetores, para que possamos chegar a um elemento em especial. Como sempre, vamos a mais um programa que demonstra essa capacidade. Exemplo 1.10 - Testando a navegação entre os elementos do vetor usando ponteiros Executamos esse código e vimos que a propriedade aritmética é verdadeira. O primeiro e o quarto item do vetor são exibidos na tela. Assim, podemos fazer várias operações usando essa possibilidade que a linguagem C++ oferece para nós programadores. Vamos a algumas outras possibilidades, dado o fato que o ponteiro apt recebeu o endereço de memória do vetor vet, de 10 posições: 1. Para salvar o número 5 no índice 4 (5ª posição do vetor), posso fazer isso: *(apt + 4) = 5, o que seria equivalente a escrever vet[4] = 5; 2. Posso usar os operadores de incremento e decremento para fazer a transição entre os elementos do vetor, da seguinte forma: apt++ e apt-- (lembre- se de não usar o asterisco nesse caso); 3. Se eu sei a quantidade de elementos de um vetor, posso verificar a última posição do vetor da seguinte forma: apt + n, onde n é o número de elementos do vetor. Para testar se o ponteiro aponta para uma posição válida do vetor, posso testar se o endereço do ponteiro é menor que o ponteiro com a posição final do vetor. Isso é semelhante quando testamos os índices de um vetor, pois como os índices começam com zero, os índices do vetor vão de zero a n-1. Pode parecer complicado isso. Mas, para que você entenda como funciona de uma vez por todas, vamos a mais um programa. O programa que apresentamos a seguir realiza a soma de cinco valores presentes em um vetor. São empregados neste programa dois loops, sendo que o primeiro realiza a leitura dos valores e o segundo, a soma propriamente dita. O primeiro loop usa uma variável contadora para controlar os índices do vetor. Usamos apenas um ponteiro para controlar esse vetor. Para propiciar o armazenamento dos dados lidos do teclado, usamos a forma especificada no item 1 acima. Já para o segundo loop criamos um novo ponteiro, chamado de apt_fim, cujo endereço é somado a 5 (o número de elementos do vetor). Assim, esse ponteiro passa a ser usado como uma variável de controle para verificarmos se estamos usando índices válidos do nosso vetor. E o ponteiro apt é incrementado a cada nova interação, passando a apontar para o próximo item do vetor a cada loop executado. Estrutura de Dados I 16 Vamos ao código. Exemplo 1.10 - Interando entre índices do vetor Executamos esse programa e inserimos os números. Nesse exemplo, inseri os valores 1, 2, 3, 4 e 5. A soma deve dar 15. Como você pode perceber, o comportamento esperado ocorreu sem nenhum erro. Por último, vamos falar de como usar vetores em funções. Você sabe que por questões de economia de memória, a linguagem C++ (e a sua antecessora C) adotam a passagem de parâmetro por referência quando os parâmetros são vetores e matrizes. Nesta seção, você viu que uma variável que declaramos como vetor é na verdade, um ponteiro para a primeira posição do vetor. Assim, nada impede que você declare ponteiros para vetores como parâmetros do vetor. O trecho de código a seguir mostra uma função para somar itens de um vetor, declarada da forma tradicional, como você já viu em outras disciplinas: A segunda forma que apresentamos usa um ponteiro como parâmetro. Como uma variável vetor é considerado um ponteiro para o primeiro elemento do vetor, as duas funções poderiam ser chamadas usando um mesmo trecho de código, como, por exemplo: Agora que você está familiarizado sobre ponteiros para vetores, vamos para ponteiros em matrizes ou segundo a nomenclatura oficial da linguagem C++, arrays multidimensionais. 2 - Usando ponteiros em matrizes (arrays multidimensionais) Usar ponteiros em matrizes é quase igual que usar ponteiros apontando para vetores. Apenas algumas questões pontuam a diferença de uso entre esses dois tipos de arrays. A primeira diferença é que, na notação de ponteiros, uma matriz é vista como um vetor gigante. Ao invés de ser uma tabela, uma matriz é alocada na memória sequencialmente, linha por linha. Assim, em uma matriz de duas linhas por duas 17 colunas, o compilador aloca primeiro o índice 0,0, depois o índice 0,1. Encerrada a primeira linha, é alocada a segunda linha, com os índices 1,0 e 1,1. A figura a seguir mostra como funciona a alocação de memória para matrizes. Figura 2 – Representação da alocação de espaço na memória para uma matriz. Fonte: MARTINEZ, 2009, p. 349. Dessa maneira, saiba que trabalhar com ponteiros para matrizes é igual a trabalhar com ponteiros para vetores. A declaração é mesma, a atribuição de valores é similar (não se esqueça da quantidade de índices a serem trabalhados). Porém, se você deseja usar operadores aritméticos para iterar entre as posições do vetor, saiba que devemos entender que a matriz é trabalhada como se fosse um vetor, e que a ordem de interação nesse caso é linha por linha, como esclarecemos na imagemacima. Mas, para deixar ainda mais claro, vamos a mais um exemplo. Esse exemplo realiza a leitura (dentro da função principal) e a escrita na tela (através da função exibeMat) de valores presentes em uma matriz de 3 linhas por 3 colunas. Exemplo 1.12 - Testando ponteiros para matrizes A tela a seguir mostra a execução do programa. A seguir, vamos abordar a alocação dinâmica de memória para vetores. 3 - Alocação dinâmica de memória para vetores Podemos alocar memória para vetores e matrizes de forma dinâmica na linguagem C++, usando o já conhecido operador new, com algumas adaptações. Para alocar memória dinamicamente a um vetor, usamos a seguinte sintaxe: Nesse trecho de código, estamos alocando memória para o vetor valores um vetor de dez números do tipo double. A partir da alocação desse vetor, podemos trabalhar com esse ponteiro da mesma forma que mostramos anteriormente. Quando o vetor não for necessário, podemos desalocar a memória reservada com o operador delete, da seguinte forma: O código a seguir mostra um programa que trabalha com a alocação dinâmica para vetores. Ele aloca um vetor de cinco números inteiros, e em seguida realiza a leitura e a soma dos valores do vetor. Quando a operação é terminada, desalocamos a memória desse ponteiro e verificamos se essa Estrutura de Dados I 18 desalocação foi feita com êxito. Exemplo 1.13 - Testando alocação dinâmica de memória para vetores Executamos o programa e vimos que o comportamento esperado ocorreu. Agora, vamos ver como funcionam ponteiros para ponteiros. 4 - Ponteiros para ponteiros O título desta seção é verdadeiro. É possível criar ponteiros que apontam para ponteiros, que por sua vez, apontam para valores armazenados na memória. Para entendermos esse conceito, vamos relembrar alguns conceitos. Quando escrevemos: Estamos declarando uma variável. Por sua vez, quando escrevemos esse trecho: Sabemos que está declarando um ponteiro para um número inteiro e que estamos atribuindo o endereço de memória da variável número para o ponteiro apt. Supondo que as variáveis apt e número estão alocadas nos endereços hipotéticos de memória 95 e 65, respectivamente, temos a seguinte disposição de memória: Figura 3 – Figura ilustrativa que mostra a disposição de memória entre as variáveis apt e número. Fonte: Acervo pessoal. Podemos declarar um ponteiro para outro ponteiro da seguinte forma: Os dois asteriscos indicam a sua situação de ponteiro. Essa variável só aceita um endereço de memória de outro ponteiro, do mesmo tipo da sua declaração. Assim, não podemos atribuir o endereço de memória da variável número, mas sim da variável apt, da seguinte forma: Supondo que apt_duplo está alocada na posição 152, temos a seguinte configuração de memória: Figura 4 – Figura ilustrativa que mostra a disposição de memória entre as variáveis apt_duplo, apt e número. Fonte: Acervo pessoal. Assim, temos as seguintes possibilidades (considerando 19 esse cenário hipotético): O programa a seguir testa essas possibilidades. Declaramos essas variáveis, e comparamos essas possibilidades. Exemplo 1.14 - Testando ponteiros para ponteiros Os endereços de memória podem variar, mas observe que quando fazemos a comparação entre os endereços de memória e os valores, obtemos valores iguais. Estamos chegando ao final. Mas, antes de encerrarmos esse assunto, vamos a mais uma possibilidade: Ponteiros para registros. 5 - Ponteiros para Registros Você sabe que os registros são variáveis compostas heterogêneas, possibilitando misturar variáveis de diversos tipos. Podemos criar ponteiros para variáveis do tipo registro. Antes de entendermos como funciona, vamos ver a declaração do nosso registro de teste: Podemos declarar uma nova variável desse tipo da seguinte forma: Da mesma forma como uma variável apontadora comum, podemos declarar um ponteiro e atribuir um endereço de memória alocado para uma variável de um registro da seguinte forma: Uma vez declarado esse ponteiro, e realizada a atribuição do endereço no ponteiro, podemos acessar os elementos deste registro de duas formas: • Com desreferenciamento, usando o operador asterisco (*) e o tradicional operador de ponto; • Sem desreferenciamento, usando o operador seta (->) para acessar os elementos do registro, sem a necessidade de usar o operador asterisco. A tabela a seguir mostra essas possibilidades de acesso aos elementos do registro: Tabela 1 - Situações equivalentes de acesso aos elementos do registro. Em cada linha é apresentada uma sequência equivalente de acesso a um elemento do registro. Na variável original Com desreferenciamento Sem desreferenciamento reg.número (*reg).número reg->número Fonte: Acervo pessoal. O programa a seguir faz o teste de um ponteiro apontando para um registro. Estrutura de Dados I 20 Exemplo 1.15 - Testando ponteiros para registros A imagem a seguir mostra o programa em execução. Além disso, podemos declarar ponteiros para vetores de registros. Sua declaração e uso obedecem à mesma forma com ponteiros para vetores de tipos primitivos, mas obedecendo as regras para o acesso aos dados de um registro de uma determinada posição. O programa a seguir ilustra o uso de ponteiros para vetores de registros. Neste exemplo, declaramos um vetor de três posições. O programa realiza a leitura e apresenta na tela os dados. Vamos ao código. Exemplo 1.15 - Testando ponteiros para vetores de registros A tela a seguir mostra uma situação de execução do programa: E com isso, finalizamos a nossa aula. Na próxima aula, veremos sobre recursividade e noções de complexidade de algoritmos. 21 Retomando a aula Chegamos ao final da nossa segunda aula. Vamos relembrar os conceitos iniciais? 1 - Usando ponteiros em vetores (arrays unidimensionais) Nessa seção, aprendemos que uma variável que é declarada como um vetor é na verdade um apontador para a primeira posição do vetor. Vimos também que existem muitas formas de se trabalharem com declaração de índices para ponteiros. Observamos que para acessar posições diferentes do vetor, basta somar (ou subtrair) o número de posições que queremos chegar a um vetor, pegar o endereço de memória do índice do vetor diretamente (usando o já conhecido operador &) ou simplesmente incrementar ou decrementar o ponteiro usando os operadores ++ e --. 2 - Usando ponteiros em matrizes (arrays multidimensionais) Nessa seção, aprendemos que podemos usar ponteiros para matrizes da mesma forma que trabalhamos como vetores. Devemos ter em mente é uma matriz é alocada na memória sequencialmente, sendo cada linha da matriz concatenada com a próxima linha. 3 - Alocação dinâmica de memória para vetores Nessa seção, verificamos que podemos usar as técnicas de alocação dinâmica de memória da linguagem C++ para vetores, usando os operadores new e delete. Quando um vetor é alocado dinamicamente, podemos utilizar da mesma forma como esclarecemos nas seções anteriores. 4 - Ponteiros para Ponteiros Nessa seção, vimos que podemos fazer ponteiros para ponteiros, usando o conceito da indireção dupla. Devemos declarar esses ponteiros com dois asteriscos, e esse ponteiro só deve receber endereços de memória para ponteiros do mesmo tipo declarado. 5 - Ponteiros para Registros Nessa seção, vimos que podemos fazer ponteiros para variáveis do tipo registro. Sua declaração é feita da mesma forma que uma variável comum. Mas, para acessar seus elementos, existem duas formas. Uma com o operador asterisco, chamada de com desreferenciamento, e outra que não exige o operador asterisco, mas usa-se o operador seta ao invés do ponto. Essa forma é chamada de sem desreferenciamento. HOLZNER, Steven; ANTUNES, Alvaro Rodrigues. C++ : black book. São Paulo: Makron Books do Brasil, 2001. JAMSA, Kris; KLANDERM LARS; SANTOS, Jeremias René D. Pereira dos. Programando em C/C++ : a bíblia. São Paulo: Makron Books do Brasil, 1999. KENT, Jeff. C++ desmistificado.Rio de Janeiro: Alta Books, 2004. MARTINEZ, Fábio Henrique Viduani. Programação de Computadores I. UFMS, 2009. Disponível em: <http://www.facom.ufms. br/~montera/progiv1.pdf>. Acesso em: 19 mai. 2018. TENENBAUM, Aaron M.; AUGENSTEIN, Moshe J.; LANGSAN, Yedidyah. et al. Estruturas de dados usando C. São Paulo: Pearson Makron Books; São Paulo: Makron Books do Brasil; São Paulo: McGraw-Hill, 2013. Vale a pena ler FEOFILOFF, Paulo. Endereços e ponteiros. 2018. Disponível em: <https://www.ime.usp.br/~pf/ algoritmos/aulas/pont.html>. Acesso em: 19 mai. 2018. SANCHES, Bruno Crivelari. Matrizes Dinâmicas. PontoV, 2009. Disponível em: <http://www.pontov. com.br/site/cpp/46-conceitos-basicos/57-matrizes- dinamicas>. Acesso em: 19 mai. 2018. Vale a pena acessar Vale a pena Minhas anotações
Compartilhar