Prévia do material em texto
Ponteiros e alocação dinâmica de memória Apresentação Na linguagem C++, ponteiros são recursos importantes que podem ser utilizados para desempenhar diversas atividades envolvendo o acesso direto à memória. Por meio de ponteiros, programadores podem escrever algoritmos dinâmicos e otimizados, que consomem menos recursos e executam suas rotinas e funções de modo mais rápido e eficiente. Nesta Unidade de Aprendizagem, você vai estudar sobre ponteiros e alocação dinâmica de memória. Para isso, você deverá aprender a identificar um ponteiro, bem como verá o funcionamento da alocação dinâmica de memória e como é feita a implementação de um ponteiro. Bons estudos. Ao final desta Unidade de Aprendizagem, você deve apresentar os seguintes aprendizados: Identificar um ponteiro.• Reconhecer como funciona a alocação dinâmica de memória.• Implementar um ponteiro.• Desafio A série Fibonacci é representada por: 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, etc. Essa série começa com os elementos 0 e 1 e, na sequência, os elementos posteriores representam a soma dos dois elementos anteriores. Essa série foi descoberta pelo matemático Leonardo Fibonacci, a partir da observação do crescimento populacional de coelhos, e é conhecida como um exemplo clássico no aprendizado de linguagens de programação. Com base nisso, elabore um algoritmo em C++ que leia um número pelo teclado e apresente a sequência Fibonacci até o total de elementos desse número. Crie um vetor do tamanho do número lido e armazene a sequência nesse vetor. Por fim, imprima o conteúdo do vetor e a posição de memória de cada elemento, conforme o seguinte modelo: Entre o MAX: 15 0 0x2ca1a90 1 0x2ca1a94 1 0x2ca1a98 2 0x2ca1a9c 3 0x2ca1aa0 5 0x2ca1aa4 8 0x2ca1aa8 13 0x2ca1aac 21 0x2ca1ab0 34 0x2ca1ab4 55 0x2ca1ab8 89 0x2ca1abc 144 0x2ca1ac0 233 0x2ca1ac4 377 0x2ca1ac8 Declare o vetor que vai armazenar os elementos da série e a variável MAX por meio da alocação dinâmica de memória (new) e não se esqueça de liberar a memória no final da execução (delete). Infográfico A linguagem C++ possui a capacidade de acessar, diretamente, partes da memória para realizar diversas operações, como, por exemplo, declarar ponteiros para otimizar a execução de algoritmos. Um ponteiro, por sua vez, é uma variável que armazena o endereço de memória de outra variável do mesmo tipo de dado. A declaração de um ponteiro é realizada pela indicação do caractere * e o endereço de uma variável é atribuído a um ponteiro pela indicação do caractere &. Veja, no Infográfico a seguir, a declaração e a atribuição de variáveis e ponteiros, bem como a sua representação na memória. Conteúdo do livro Implementar sistemas pode se tornar uma atividade prazerosa quando o programador conhece e utiliza os melhores recursos fornecidos pela linguagem de programação utilizada. C++ é uma linguagem conhecida por disponibilizar diversos recursos que permitem escrever códigos enxutos, otimizados e de fácil entendimento. Entre esses recursos, destacam-se os ponteiros, que permitem acessar diretamente endereços de memória, bem como alocar memória de forma dinâmica, consumindo menos recursos para produzir programas com mais desempenho e eficiência. Para saber mais, acompanhe a leitura do capítulo Ponteiros e alocação dinâmica de memória, da obra Linguagem de programação, que serve como base teórica para esta Unidade de Aprendizagem. Boa leitura. LINGUAGEM DE PROGRAMAÇÃO Maurício de Oliveira Saraiva Ponteiros e alocação dinâmica de memória Objetivos de aprendizagem Ao final deste texto, você deve apresentar os seguintes aprendizados: � Identificar um ponteiro. � Reconhecer como funciona a alocação dinâmica de memória. � Implementar um ponteiro. Introdução Implementar sistemas pode se tornar uma atividade prazerosa quando o programador utiliza os melhores recursos fornecidos pela linguagem de programação, como a C++, a qual é conhecida por disponibilizar diversos recursos que permitem escrever códigos enxutos, otimizados e de fácil entendimento. Neste capítulo, você conhecerá um pouco sobre esses recursos, como os ponteiros, com os quais se acessa diretamente endereços de memória, bem como se aloca de forma dinâmica, consumindo menos recursos para produzir programas com mais desempenho e eficiência. Identificação de um ponteiro Quando se declara uma variável, o programa reserva na memória um espaço para acomodar um valor que seja do tamanho do tipo de dados dessa variável. Por exemplo, a seguinte expressão, escrita na linguagem C++, declara uma variável do tipo int, que destina um espaço de quatro bytes para armazenar o valor 30 em determinado endereço na memória (VOTRE, 2016). Sabendo que toda variável declarada está armazenada em algum local na memória, pode-se afirmar que ela também possui um endereço. Na linguagem C++, esse endereço é acessado pelo operador & (VOTRE, 2016). Um ponteiro, por sua vez, é uma variável que armazena o endereço de memória de outra variável, o qual faz referência a um valor específico, em vez de guardar um valor. Assim, ele faz uma referência indireta a um valor; já um nome de variável se refere diretamente a um valor (DEITEL; DEITEL, 2011). A declaração de um ponteiro é realizada pela indicação de um tipo de dado seguida do operador * antes do nome da variável. Isso significa que ela será um ponteiro para uma variável do mesmo tipo de dado, por exemplo, um inteiro. Um ponteiro sempre deve ser inicializado ao ser declarado. Nessa declaração, ele pode apontar para NULL, o que significa que não está se referindo a nenhuma variável ou diretamente ao seu endereço (DEITEL; DEITEL, 2011). Na Figura 1, você pode ver a ilustração do acesso de um ponteiro em uma variável. Nela, identifica-se que uma variável é dividida em duas partes, en- dereço e valor, que podem ser acessadas pelo ponteiro de forma independente — ele pode acessar tanto um como o outro. Ponteiros e alocação dinâmica de memória2 Figura 1. Estrutura de uma variável. ponteiro variável pIdade *pIdade 0x70fe34 30 endereço valor Para acessar o valor da variável a partir de um ponteiro que aponta para ela, utiliza-se o operador * junto à variável ponteiro (AGUILAR, 2008). No seguinte exemplo, *pIdade aponta para o valor da variável idade; e pIdade, para o seu endereço. Uma vez que o ponteiro aponta para uma variável, qualquer modificação que for realizada nele refletirá nessa variável, pois ele é uma referência indireta à ela. No exemplo a seguir, modificou-se o conteúdo de idade para 35 por meio do ponteiro pIdade. Um ponteiro pode, ainda, apontar para outro ponteiro, o que fará ambos se direcionarem ao endereço de memória da variável cujo primeiro ponteiro faz uma referência indireta. Considerando que um array é uma estrutura de elementos homogêneos armazenados em sequência na memória e, também, que um ponteiro faz uma referência a um endereço de memória, pode-se percorrer um array por meio de incremento ou decremento de ponteiros (SILVA FILHO, 2010). 3Ponteiros e alocação dinâmica de memória Isso é possível, porque um array disponibiliza o endereço do primeiro elemento armazenado na memória. Dessa forma, quando se incrementa um ponteiro, automaticamente, ele se posiciona no próximo elemento da estrutura, independentemente da quantidade de bytes que cada tipo de dado ocupa na memória (SILVA FILHO, 2010). No exemplo a seguir, o ponteiro pNota recebe o endereço do primeiro ele- mento do array nota. Perceba que a estrutura de repetição não utiliza o índice do array para imprimir seus elementos (Figura 2), pois eles são acessados por meio dos endereços de memória e do incremento do ponteiro a cada iteração. Figura 2. Array de elementos na memória. 0x70fe30 0x70fe34 0x70fe38 0x70fe3c 7 9 8 10 Por ser um array de inteiros, cada elemento da estrutura ocupa um espaço de quatro bytes na memória. Como ele reserva apenas espaços contínuos, a varredura é possível por meio doincremento do ponteiro e parte da primeira posição. Note, na Figura 2, que existe um acréscimo de quatro unidades (bytes) em cada posição da memória do array, pelos dois últimos dígitos de cada endereço: 0x70fe30, 0x70fe34, 0x70fe38 e 0x70fe3c. O último par tem uma letra, porque o endereço está apresentado em formato hexadecimal, cuja letra c indica o número 12. Ponteiros e alocação dinâmica de memória4 Funcionamento da alocação dinâmica de memória Como mencionado anteriormente, a declaração de variáveis reserva espaços endereçados na memória, e os ponteiros podem fazer referência a eles. No entanto, as declarações apresentadas utilizaram uma alocação estática de memória, pois suas variáveis foram declaradas pelo modo convencional, informando o tipo de dado e o seu nome. Ao utilizar variáveis estáticas, essa alocação ocorre no início da execução do programa, em que a quantidade de memória já é conhecida desde a fase de compilação para a criação do executável (AGUILAR, 2008). Contudo, dessa forma, os programadores podem reservar mais memória do que o preciso, desperdiçando recursos desnecessariamente. Para resolver isso, a linguagem C++ fornece meios para alocar memória em tempo de execução, por meio da alocação dinâmica de memória (AGUILAR, 2008). Na linguagem C, a alocação e a liberação dinâmicas de memória podem ser realizadas por meio das instruções memory allocation (malloc) e free, que reservam e liberam espaços na memória de acordo com a quantidade de bytes do tipo de dado utilizado (DEITEL; DEITEL, 2011). Já a linguagem C++, além de permitir o uso das instruções malloc e free, fornece os operadores new e delete, os quais podem ser utilizados em objetos de diversos tipos, como dados primitivos, estruturas e classes, entre outros (VOTRE, 2016). A seguir, você saberá mais sobre a alocação dinâmica por meio das ins- truções malloc e free e, depois, pelos operadores new e delete. Instruções malloc e free Para utilizar as instruções malloc e free, é preciso incluir a biblioteca no programa, por meio da seguinte instrução: A declaração de uma variável dinâmica deve ser realizada por meio de ponteiros, que fazem referência aos endereços de memória, os quais são reservados conforme a quantidade de bytes que o tipo de dados necessita. A instrução sizeof pode ser utilizada para identificar a quantidade de bytes necessária de acordo com o tipo de dado utilizado. Veja no seguinte 5Ponteiros e alocação dinâmica de memória exemplo que o tratamento da variável ocorre pela manipulação de um ponteiro (DEITEL; DEITEL, 2011). A liberação da memória reservada dinamicamente ocorre quando o pro- grama é encerrado, descarregado da memória. No entanto, as variáveis alocadas de forma dinâmica podem ser liberadas durante a execução, por meio da instrução free (DEITEL; DEITEL, 2011). A quantidade de bytes de cada tipo de dado pode variar de acordo com o sistema operacional utilizado. Há casos em que o tipo primitivo int pode ocupar dois ou quatro bytes, por isso, deve-se usar a instrução sizeof (DEITEL; DEITEL, 2011). É possível, ainda, declarar arrays de forma dinâmica em tempo de execu- ção. Para isso, multiplica-se o retorno de sizeof pelo número de elementos do array. Note que, apesar de o array nota ser um ponteiro, ele é tratado sem o operador *, pois possui um tratamento diferenciado. Ponteiros e alocação dinâmica de memória6 O resultado da execução deste script apresenta o conteúdo de cada posição do array e o endereço de memória dos elementos. Operadores new e delete Os operadores new e delete foram implementados em C++ e, portanto, não funcionam em compiladores que suportam apenas a linguagem C (VOTRE, 2016). Para apresentar a declaração de uma variável dinâmica em C++, utilizam- -se as seguintes instruções. Ou, Ambas declaram uma variável chamada pIdade do tipo int e atribuem o valor 30, porém, a segunda instrução declara e inicializa em uma única linha. Além disso, nota-se que não é preciso usar a instrução sizeof, pois o operador new identifica o tamanho automaticamente (VOTRE, 2016). Utilizam-se colchetes para declarar um array dinamicamente. O número entre eles indica a quantidade de elementos da estrutura (AGUILAR, 2008). 7Ponteiros e alocação dinâmica de memória Implementação de um ponteiro Para demonstrar a implementação de ponteiros em C++, você verá o de- senvolvimento de um programa que sorteia 10 números entre 50 e 100 e os armazena em um array declarado dinamicamente. Na sequência, usa-se ponteiros para percorrer o array do início até o final e, depois, do final até o início, para imprimir os números e suas respectivas posições de memória. Por fim, apresenta-se o maior e o menor elementos, bem como suas respectivas posições de memória. As linhas de 1 a 4 apresentam a inclusão das bibliotecas necessárias para gerar números aleatórios, a saída de dados e a declaração de uma constante chamada TOTAL, a qual contém o número máximo de elementos do array que será declarado. A linha 6 apresenta a declaração de um array de ponteiros com alocação dinâmica chamado nota, que possui 10 elementos. Já as linhas de 7 a 10 ilustram um laço de repetição, o qual produz e armazena números aleatórios entre 50 e 100 em cada posição do array, bem como imprime esses números e a sua respectiva posição de memória. As linhas entre 11 e 14 declaram as variáveis que armazenarão o maior e o menor elementos, bem como as posições de memória de cada um. Esses elementos são armazenados em variáveis do tipo int; e seus endereços, em variáveis ponteiros do mesmo tipo. Ponteiros e alocação dinâmica de memória8 Já nas linhas de 15 a 28, há uma estrutura de repetição que realiza 10 iterações. No entanto, o array é percorrido pelo incremento de ponteiros, indicado pela linha 25, e não pelo contador do laço while. Isso é possível, porque ele disponibiliza o endereço do primeiro elemento na memória e, ao ser incrementado, posiciona-se automaticamente no endereço do próximo elemento. Nessa estrutura de repetição, duas estruturas de controle identificam o maior e o menor elementos, bem como seus endereços, os quais são armazenados respectivamente nas variáveis maiorNotaEndereco e menorNotaEndereco. Perceba que usa-se *nota para recuperar o conteúdo da posição do vetor (linhas 18 e 22) e apenas nota quando se deseja pegar o endereço da posição (linhas 19 e 23) ou incrementar o array (linha 25). As linhas de 29 a 36 apresentam outra estrutura de repetição que realiza 10 iterações, as quais servem de apoio para percorrer o array pelos endereços de memória, por ponteiros. No entanto, dessa vez, o array é percorrido de trás para frente. Quando se percorre um array por ponteiros, um indicador registra a última posição acessada e o endereço é devolvido ao se realizar uma nova chamada do array. Como o trecho entre as linhas 16 e 27 o percorreu e incrementou seu endereço como último comando da estrutura while (linha 26), o ponteiro 9Ponteiros e alocação dinâmica de memória está acessando um endereço que ultrapassa os seus limites, o que é corrigido pela instrução nota-- (linha 29). Ao posicionar o ponteiro na última posição do array, deve-se decrementar o ponteiro de nota, a cada iteração, até chegar ao seu início — esse decre- mento é realizado pela instrução da linha 34. Já a variável cont serve apenas para controlar o contador de iterações da estrutura while, porém, ela não é utilizada para acessar os elementos do array, pois usam-se os ponteiros para essa finalidade. Por fim, são apresentados o maior e o menor elementos em conjunto aos seus respectivos endereços de memória por meio da instrução cout. Na Figura 3, você pode ver o resultado da execução do programa. Figura 3. Execução da implementação de ponteiros. Ponteiros e alocação dinâmica de memória10 AGUILAR, L. J. Programação em C++: algoritmos, estruturas de dados e objetos. 2. ed. Porto Alegre: McGraw Hill, 2008. DEITEL, P. J.; DEITEL, H.M. C: como programar. 6. ed. [São Paulo]: Pearson, 2011. SILVA FILHO, A. M. Introdução à programação orientada a objetos com C++. Rio de Janeiro: Elsevier, 2010. VOTRE, V. P. C++ explicado e aplicado. Rio de Janeiro: Alta Books, 2016. Ponteiros e alocação dinâmica de memória11 Dica do professor A linguagem C++ permite alocar memória dinamicamente, durante a execução de um programa. Essa alocação dinâmica é realizada por meio da instrução new, que reserva memória apenas quando o comando é executado, e não quando o programa é carregado pelo sistema operacional. Assista, na Dica do Professor, como declarar um vetor em tempo de execução, a partir da alocação dinâmica de memória, e como percorrê-lo por meio de um ponteiro. Aponte a câmera para o código e acesse o link do conteúdo ou clique no código para acessar. https://fast.player.liquidplatform.com/pApiv2/embed/cee29914fad5b594d8f5918df1e801fd/40ca6c4d54edb217ec9dc09b2f76ffe6 Exercícios 1) Marque a alternativa correta em relação a uma variável ponteiro que vai armazenar o endereço de uma variável do tipo float. A) Deve ser do tipo pointer, que significa tipo ponteiro. B) Deve ser do tipo float, pois a variável a ser apontada é float. C) Pode ser do tipo double, pois suporta o tipo float. D) Pode ser de qualquer tipo, pois é um ponteiro. E) Não possui um tipo definido, pois é um ponteiro. 2) Selecione a alternativa que apresenta corretamente a declaração de uma variável ponteiro chamada p, que recebe o endereço da variável chamada v, do tipo int, na linguagem C++: A) int *p= &v. B) int *p= *v. C) int &p= v. D) int &p= *v. E) int &p= &v. 3) Marque a alternativa que apresenta corretamente a declaração de uma variável ponteiro com alocação dinâmica, na linguagem C++, que define um valor padrão na sua criação: A) int x = new int. B) int *x = new int. C) int *x = new int (1). D) int &x = new int (1). Uma variável declarada como ponteiro deve ser do mesmo tipo da variável em que ela vai realizar o apontamento. Nesse caso, deve ser do tipo float. A declaração de uma variável ponteiro, na linguagem C++, é realizada pela indicação do tipo de dado, do caractere * e do nome da variável. Quando recebe o endereço de outra variável, este deve ser indicado pelo caractere &. Para declarar uma variável ponteiro com alocação dinâmica e definição de um valor padrão na sua criação, é preciso indicar que a variável é um ponteiro, por meio do asterisco, do uso da instrução new e de um valor entre parênteses após a definição do tipo. E) int &x = new int = NULL. 4) Dado o seguinte algoritmo em C++, selecione a opção que apresenta a saída correta, ou seja, os valores que são armazenados no código: int valor= 10; int *inteiro= new int; inteiro= &valor; *inteiro= 15; A) “inteiro” e “&inteiro” possuem o mesmo conteúdo. B) “*inteiro” e “valor” possuem o mesmo conteúdo. C) “inteiro” e “valor” possuem o mesmo conteúdo. D) “&inteiro” e “&valor” possuem o mesmo conteúdo. E) “inteiro” e “&valor” possuem o mesmo conteúdo. 5) Dado o seguinte algoritmo em C++, selecione a opção que apresenta a saída correta: int valor= 10; int *inteiro= new int(valor); valor= 15; A) valor é 10 e *inteiro é 10. B) valor é 10 e *inteiro é 15. C) valor é 15 e *inteiro é 10. D) valor é 15 e *inteiro é 15. E) valor é 15 e *inteiro é NULL. A variável “*inteiro” é declarada como ponteiro do tipo int. Na sequência, esse ponteiro aponta para o endereço de “valor”. Assim, um ponteiro, ao ser acessado com o caractere *, faz referência à variável que ele aponta. Portanto, se modificamos o conteúdo da variável “valor”, automaticamente “*inteiro” possuirá o mesmo conteúdo. Na prática JP da Silva é desenvolvedor na empresa Sistemas Ultra Inteligentes SA há alguns anos. Dentre suas habilidades, destaca-se a sua facilidade de implementar rotinas que manipulam strings na linguagem de programação C++. Para manipular strings, JP da Silva costuma usar ponteiros, por considerar que a execução é mais rápida e eficiente, pois, assim, ele consegue acessar diretamente os endereços de memória das variáveis e percorrer os caracteres de forma segura e ágil. Aponte a câmera para o código e acesse o link do conteúdo ou clique no código para acessar. https://statics-marketplace.plataforma.grupoa.education/sagah/09174db9-a71a-40a1-84de-0d8c942b4e90/0c7811ca-790a-4c9e-b3ef-66aaa9892b31.jpg Saiba + Para ampliar o seu conhecimento a respeito desse assunto, veja abaixo as sugestões do professor: Ponteiros - conceitos. Assista ao vídeo a seguir para saber mais sobre introdução a ponteiros. Aponte a câmera para o código e acesse o link do conteúdo ou clique no código para acessar. Ponteiros - operações. Assista o vídeo a seguir para saber mais sobre operações com ponteiros. Aponte a câmera para o código e acesse o link do conteúdo ou clique no código para acessar. Ponteiros com arrays. Assista o vídeo a seguir para conhecer mais sobre ponteiros com arrays. Aponte a câmera para o código e acesse o link do conteúdo ou clique no código para acessar. https://www.youtube.com/embed/SJzd9x2S2yg https://www.youtube.com/embed/cg1mnWupbTE https://www.youtube.com/embed/w_BBUJWS-50