Baixe o app para aproveitar ainda mais
Prévia do material em texto
2019.2 ESTUTURAS DE DADOS CCT0637 Aula 4 PROFESSOR: EDIBERTO MARIANO 1 2019.2 CONTEÚDOS 2 · Alocação Dinâmica de Memória (parte I) · Fundamentos · Aplicação em C++: Ponteiros · Operador de endereço (&) . Operador de indireção (*) . Operador seta (->) 2019.2 3 Na alocação dinâmica o espaço de memória, que as variáveis irão utilizar durante a execução do programa, é definido enquanto o programa está em execução. Ou seja, quando não se sabe ao certo quanto de memória será necessário para o armazenamento das informações, podendo ser determinadas, sob demanda, em tempo de execução conforme a necessidade do programa. Alocação Dinâmica de Memória No padrão C ANSI existem quatro funções para alocação dinâmica de memória: 1. malloc() 2. calloc() 3. realloc() 4. free() Todas elas pertencem a biblioteca <stdlib.h>. int *vetor; vetor = malloc(100); 2019.2 4 O espaço necessário para a representação dos dados pode ser alocado à medida que se torne necessário, através da alocação dinâmica. Alocação Dinâmica de Memória Uma estrutura armazenada através de encadeamento apresenta seus nodos alocados em posições aleatórias na memória, e não lado a lado. Dessa forma, existem várias vantagens no uso de representar os dados por encadeamento. Compartilhamento de memória Maleabilidade Facilidade para inserção Remoção de componentes int *p = new int; As estruturas dinâmicas são assim chamadas, pois podem fazer alocação de memória em tempo de execução e terem seus tamanhos alterados de acordo com a demanda. 2019.2 5 Alocação Dinâmica de Memória Alocação dinâmica de 80 bytes na memória Assumindo que um valor do tipo inteiro ocupa 4 bytes na memória. int *p; p = (int *)malloc(20*sizeof(int)); 2019.2 6 Alocação Stática de Memória Na alocação estática o espaço de memória, que as variáveis irão utilizar durante a execução do programa, é definido no processo de compilação. Exemplos Não sendo possível alterar o tamanho desse espaço durante a execução do programa. char a; Espaço reservado para um valor do tipo char. O char ocupa 1 byte na memória. double matriz[3][3]; Espaço reservado para nove(3x3) valores do tipo double. O double ocupa 8 bytes na memória, portanto 3x3x8=72 bytes. int vetor[10]; Espaço reservado para dez valores do tipo int. O int ocupa 4 bytes na memória, portanto 4x10=40 bytes. 2019.2 7 A utilização de ponteiros em linguagem C é uma das características que tornam a linguagem tão flexível e poderosa. Ponteiros ou apontadores, são variáveis que armazenam o endereço de memória de outras variáveis. Dizemos que um ponteiro “aponta” para uma varíável quando contém o endereço da mesma. Os ponteiros podem apontar para qualquer tipo de variável. Portanto temos ponteiros para int, float, double, etc. PONTEIROS 2019.2 8 · Ponteiros são fundamentais para a programação bem sucedida em C++: - Passagem de parâmetros por referência; - Alocação dinâmica de memória; - Aumentar a eficiência de certar rotinas. · Essencialmente, um ponteiro nada mais é do que uma variável que ao invés de conter um valor, contém um endereço de memória. · O conceito de utilizar espaço em memória não é em absoluto novo. De fato, linguagens de máquina utilizam tal conceito todo o tempo. Definições 2019.2 9 Ponteiros são muito úteis quando uma variável tem que ser acessada em diferentes partes de um programa. Neste caso, o código pode ter vários ponteiros espalhados por diversas partes do programa, “apontando” para a variável que contém o dado desejado. Caso este dado seja alterado, não há problema algum, pois todas as partes do programa tem um ponteiro que aponta para o endereço onde reside o dado atualizado. Por que usar ponteiros? 2019.2 10 Manipular elementos de matrizes e vetores. Razões para se usar ponteiros Receber argumentos em funções que necessitem modificar o argumento original. Criar estruturas de dados complexas, como listas encadeadas e árvores binárias, onde um item deve conter referências à outro. Passar strings (vetor de caracteres) de uma função para outra. 2019.2 11 - Alocação dinâmica de memória - Manipulação de arrays. - Para retornar mais de um valor em uma função. - Referência para listas, pilhas, árvores e grafos. Sintaxe de declaração de ponteiro tipo *nome_ponteiro; ou <tipo> *<nome_variavel>; Onde temos: tipo : é o tipo de dado da variável cujo endereço o ponteiro armazena. *nome_ponteiro : O nome da variável ponteiro. O asterisco * neste tipo de declaração determina que a variável será um ponteiro. Situações onde ponteiros são úteis 2019.2 12 Declaração de um ponteiro: int *ptr; Declaração de vários Ponteiros Exemplo da declaração de diversos ponteiros: int *iptr; float *fptr; char *cptr; Como um ponteiro armazena um endereço de memória e não um valor específico, no entanto, muito comumente, deseja-se acessar o valor armazenado pela variável apontada por um ponteiro. Desta forma, o tipo do ponteiro serve para instruir o compilador na maneira pela qual o dado pontado será acessado. Essencialmente, o tipo de dados e a codificação do dado (como os bits serão interpretados, necessário por exemplo para tipos com base na notação de ponto flutuante). Declaração de Ponteiros 2019.2 13 Ponteiros são inicializados ao definirmos um endereço para o qual eles devem apontar. Ponteiros não inicializados são também chamados de "ponteiros Selvagens". A não inicialização de ponteiros é um dos principais fatores para erros em tempo de execução em programas escritos na linguagem C++. Tais erros são difíceis de identificar e corrigir, porque o tempo decorrido entre o acesso ilegal à memória de um ponteiro e a efetiva ocorrência do erro é imprevisível. Uma maneira de garantir que um ponteiro declarado não apontará para um lugar qualquer da memória é atribuir o valor NULL para o mesmo. int *iptr = null; Inicialização de Ponteiros 2019.2 14 "*" conteúdo / indireção: aplicado a um variável tipo ponteiro, retorna o conteúdo do endereço apontado pelo ponteiro; "&" endereço: aplicado a uma variável, retorna seu endereço de memória. Exemplo da utilização de ambos os operadores unários: int count; int *iptr; iptr = &count; cout << *iptr; Operadores de Ponteiros 2019.2 15 #include <stdio.h> //PARA USO DO PRINTF #include<iostream> using namespace std; #include <conio.h> int main(void) { int num = 40; //num é a variável que será apontada pelo ponteiro int *ptr; //declaração de variável ponteiro ptr = # //atribuindo o endereço da variável num ao ponteiro cout<<"CRIANDO E USANDO PONTEIRO\n\n"; cout<<"PROF: EDIBERTO MARIANO\n\n"; //printf ("Conteudo da variavel num : %d\n", num); //printf ("Endereço da variavel num : %x \n", &num); //printf ("Conteudo da variavel ponteiro ptr: %x", ptr); cout << "Conteudo da variavel num : " << num; cout << "\n\nEndereço da variavel num : " << # cout << "\n\nConteudo da variavel ponteiro ptr : " <<ptr; getch(); return(0); } Programa utilizando ponteiro 2019.2 16 SAÍDA Programa utilizando ponteiro 2019.2 17 A "seta" consiste de um sinal de menos e um maior (->). Para começar e deixar mais claro, definimos uma estrutura simples com dois campos. struct { int num; double valor; } Teste; O passo seguinte é definir um ponteiro para essa estrutura. struct Teste *Teste; A partir do ponteiro podemos ter acesso a um campo da estrutura usando um seletor “->” (uma flecha). Teste-> num = 10; Teste-> valor = 5.0; O mesmo resultado pode ser obtido com uso do deferenciador () e um ponto. (*Teste).num = 10; (*Teste).valor = 5.0; Operador Seta e Dereferenciador e um ponto 2019.2 18 #include <iostream> using namespace std; #include<conio.h> typedef struct data Data; //PARA TRABALHAR COM DATA struct data { short dia; short mes; int ano; }; int main (void){ Data data; //variável data do tipo struct data Data *hoje; //ponteiro hoje para um tipo struct data hoje = &data; //hoje aponta para o endereço de data //dados sendo inseridos navariável data hoje->dia = 27; hoje->mes = 8; hoje->ano = 2019; //mostrando o que está gravado no endereço contido em hoje cout << "\n\n\tPROFESSOR: EDIBERTO MARIANO - PONTEIRO\n\n"<<endl; cout << "\tUsando o proprio ponteiro e uma seta (->) para indicar o membro\n\n"<<endl; cout << "\tData registrada : "; cout << hoje->dia <<"/"<< hoje->mes <<"/"<< hoje->ano << endl; getch(); } Programa usando o próprio ponteiro e uma seta (->) para indicar o membro 2019.2 19 SAÍDA Programa usando o próprio ponteiro e uma seta (->) para indicar o membro. 2019.2 20 #include <iostream> using namespace std; #include<conio.h> typedef struct data Data; //PARA TRABALHAR COM DATA struct data { short dia; short mes; int ano; }; int main (void){ Data data; //variável data do tipo struct data Data *hoje; //ponteiro hoje para um tipo struct data hoje = &data; //hoje aponta para o endereço de data //dados sendo inseridos na variável data (*hoje).dia = 27; (*hoje).mes = 8; (*hoje).ano = 2019; //mostrando o que está gravado no endereço contido em hoje cout << "\n\n\tPROFESSOR: EDIBERTO MARIANO - PONTEIRO\n\n"<<endl; cout << "\tData registrada : "; cout << (*hoje).dia <<"/"<< (*hoje).mes <<"/"<< (*hoje).ano << endl; getch(); } Programa usando um dereferenciador entre parênteses e um ponto (.) para indicar o membro 2019.2 21 SAÍDA Programa usando um dereferenciador entre parênteses e um ponto (.) para indicar o membro 2019.2 22 int *ptr; ptr = &meu_array[0]; /* aponta nosso ponteiro, para o primeiro inteiro em nosso array */ Há duas formas de trabalhar com ponteiros para vetor. 1. Usar índices no ponteiro como se ele fosse um vetor: 2. Usar o que chamamos de aritmética de ponteiros. Ponteiros e Arrays 2019.2 23 #include<iostream> using namespace std; #include<conio.h>> int main (void){ int vetor[2]; int *v; // ponteiro v = vetor; //USANDO INDICES NO PONTEIRO COMO SE ELE FOSSE UM VETOR v[0] = 224; v[1] = 768; cout << "\n\n\tPROFESSOR: EDIBERTO MARIANO - PONTEIRO e ARRAY\n\n"<<endl; cout << "\n\n\tUSANDO INDICES NO PONTEIRO COMO SE ELE FOSSE UM VETOR\n\n"<<endl; //printf ("vetor[0] = %d\n", vetor[0]); //printf ("vetor[1] = %d\n\n", vetor[1]); cout << "\n\tvetor[0] = " << vetor[0]; cout << "\n\tvetor[1] = " << vetor[1]; //system ("pause"); getch(); } Ponteiros e Arrays 1. Usar índices no ponteiro como se ele fosse um vetor: 2019.2 24 SAÍDA Ponteiros e Arrays 1. Usar índices no ponteiro como se ele fosse um vetor: 2019.2 25 Aritmética de Ponteiros Aritmética de ponteiros consiste em modificar o valor do ponteiro para ele indicar o próximo endereço de memória do vetor. Exemplificando, seria algo como: ponteiro = endereço do índice 0 do vetor; *(ponteiro+indice1) = qualquer valor para ser gravado nesse índice; *(ponteiro+indiceN) = qualquer valor para ser gravado nesse índice; Ex.: Se tivermos um ponteiro para um vetor de inteiro, quando formos calcular o terceiro espaço faremos *(ponteiro+3). Internamente será calculado o seguinte → ponteiro + 3 * o tamanho de int (4 bytes). Então, 3× 4 = 12 bytes. ponteiro = 0x00001100; ponteiro + 12 bytes; novo ponteiro = 0x00001100C; Apesar de ser uma conta simples, não precisamos nos preocupar com isso. Porque o próprio sistema cuida de executar este cálculo. Ponteiros e Arrays 2. Usar o que chamamos de aritmética de ponteiros. 2019.2 26 #define MAX 3 int main (void){ int vetor [MAX], i, valor, *v; v = &vetor[0]; cout << "\n\n\tPROFESSOR: EDIBERTO MARIANO - PONTEIRO e ARRAY"<<endl; cout << "\n\n\tUSANDO ARITMETICA DE PONTEIROS"<<endl; cout << "\n\tENTRADA DE DADOS\n"<<endl; printf ("\tDigite um valor para ser gravado no :\n\n"); printf ("\tindice\tEndereco de Memoria\n"); for (i=0; i<MAX; i++) { //printf ("\t[%d]\t%p\t\t-> ", i, (v+i)); cout << "\t" << i << "\t" << v+i << "\t : "; cin >> valor; getchar(); *(v+i) = valor; //valor é gravado no endereço apontado pelo ponteiro } system ("cls"); cout << "\n\n\tPROFESSOR: EDIBERTO MARIANO - PONTEIRO e ARRAY\n\n"<<endl; cout << "\n\n\tUSANDO ARITMETICA DE PONTEIROS\n\n"<<endl; cout << "\n\n\tSAIDA DE DADOS E VALORES GRAVADOS NO VETOR\n\n"<<endl; //printf ("Os valores gravados no vetor foram:\n"); for (i=0; i<MAX; i++) { //printf("vetor[%d], ponteiro (%p) = %d\n", i, (v+i), vetor[i]); cout << "\tVetor [" << i << "]" <<" Ponteiro = " << v+i << " Valor = " << vetor[i] << "\n"; } system ("pause"); } Ponteiros e Arrays 2. Usar o que chamamos de aritmética de ponteiros. 2019.2 27 SAÍDAS Ponteiros e Arrays 2. Usar o que chamamos de aritmética de ponteiros. 2019.2 28 ++ incremento unitário -- decremento unitário Suponha que p é um ponteiro do tipo inteiro e suponha que o tipo inteiro na arquitetura de destino do exemplo sejam representados por 2 bytes cada. Considere o seguinte trecho de código: int vet[10]; int *iptr; iptr = vet; iptr++; Explicação p++ simplesmente faz o ponteiro apontar para a posição seguinte *(p++) nesse caso, faz um pós-incremento, então ele primeiro usa o valor apontado e depois de terminar a operação faz o ponteiro apontar para a posição seguinte. caso fosse *(++p) ele incrementaria antes de usar o ponteiro. *(p+5) utiliza o ponteiro 5 posições a frente, sem mexer no ponteiro. (*p)+=4 soma ao valor apontado 4 unidades. Ponteiros Operandos unários 2019.2 29 #include <stdio.h> #include <string.h> int main() { char *nome = "EDIBERTO"; int i = 0; int *pi = &i; /* Incremento por posicao*/ printf("\nEXIBINDO ARRAY INCREMENTANDO O PONTEIRO:\n\n"); for (i=0; i<strlen(nome);i++) { printf("Caracter: %c | Endereco: %p , %d\n",*(nome+i),(nome+i),(nome+i)); } /* Incremento por tipo de dado*/ printf("\n\nNumero: %d | Endereco: %p , %d\n",i,&i,&i); printf("\n\nConteudo apontado pelo ponteiro: %d \nEndereco para onde o ponteiro aponta: %p %d\n",*pi,pi,pi); printf("\n\nIncrementando o ponteiro pelo tipo\n\n"); printf("%d , %d <-- Moveu %d bytes por que um int tem: %d bytes\n",i+1,(int)(pi+1), (int)(pi+1)-(int)(pi),sizeof(int)); return 0; } Ponteiros Operandos unários 2019.2 30 SAÍDA Ponteiros Operandos unários 2019.2 31 #include<iostream> using namespace std; int main() { int x, *p, y; x = 4; cout << x; //x = 4 p = &x; cout << "\nValor de p = " <<p; // 0x7ffcea5cb618 (varia a cada execução) y = *p; cout << "\nValor de y = " <<y; // 4 y = 7; cout << "\nValor de *p = " <<*p; // 4 (*p)++; cout << "\nValor de *p = " <<*p; // 5 cout << "\nValor de x = " <<x; // 5 (*p) = (*p) + y; cout << "\nValor de x = " <<x; // 5 + 7 = 12 return(0); } Exemplo Ponteiros 2019.2 32 EXERCÍCIO 01 – Codifique um programa em C++ que: - Atribua via programa um valor inteiro à uma variável; - Exiba o conteúdo da variável e o endereço de memória da mesma. EXERCÍCIO 02 – Codifique um programa em C++ que: - Atribua (leia) via teclado (console) um valor inteiro à uma variável; - Exiba o conteúdo da variável e o endereço de memória da mesma. EXERCÍCIO 03 – Codifique um programa em C++ que: - Atribua (leia) via teclado (console) três valores inteiro; - Após os valores informados, exiba-os na tela com seus respectivos endereços de memória Aula 04 Exercícios 2019.2 33 EXERCÍCIO 04 – Ao executarmos o seguinte código, qual resultado será apresentado na tela? #include<iostream> using namespace std; int funcao (int *valor1, int *valor2) { *valor1 = 50; *valor2 = 200; return 100; } int main() { int *a, b; a = &b; b = 100; funcao(a, a); cout << *a; cout <<" e "<< b; } Aula 04 Exercícios 2019.2 34 EXERCÍCIO 05 – O que acontece se o código abaixo for compilado em C++? #include<iostream> using namespace std; int funcao (int valor1, int valor2) { *valor1 = 50; *valor2 = 200; return 100; } int main() { int *a, b; a = &b; b = 100; funcao(a, a); cout << *a; cout <<" e "<< b; } Aula 04 Exercícios 2019.2 35 EXERCÍCIO 06 – Codifique um programa em C++ que: Atribua (leia) via teclado (console) a data do seu nascimento (dia, mês e ano); Após os valores informados, exiba-osna tela. OBS. Utilize a struct data conforme abaixo, usando ponteiro e seta para indicar o membro da estrutura. struct data { short dia; short mes; int ano; }; EXERCÍCIO 07 – Codifique um programa em C++ que: Atribua (leia) via teclado (console) a data do seu nascimento (dia, mês e ano); Após os valores informados, exiba-os na tela. OBS. Utilize a struct data conforme abaixo, usando dereferenciador entre parênteses e um ponto (.) para indicar o membro da estrutura. struct data { short dia; short mes; int ano; }; Aula 04 Exercícios 2019.2 36 EXERCÍCIO 08 – Dado o array descrito abaixo: int A[10]; Crie dois ponteiros que apontem sucessivamente para as duas primeiras posições do array A. EXERCÍCIO 09 – De acordo o código em C++ abaixo: int vet[10]; int *iptr; iptr = vet; iptr++; Descreva o que faz cada opção abaixo: iptr++ *( iptr++) *(++iptr) *( iptr+5) (*iptr)+=4 Aula 04 Exercícios 2019.2 37 Aula 04 EXERCÍCIO 10 – Um apontador é uma variável que contém o endereço de outra variável. Apontadores são muito usados, em parte porque são, às vezes, a única forma de se expressar um processo de computação e, em parte, porque, normalmente, implicam um código mais compacto e eficiente que o obtido de outras formas. Considerando o uso de apontadores nas linguagens procedurais atuais, julgue os itens que se seguem com E se for errado e C se for certo. ( ) O uso de apontadores como argumentos de funções que realizem chamada por valor não permite alteração de seus conteúdos de memória na função. ( ) Com apontadores, é possível criar funções que retornem estruturas de dados complexas. ( ) Para recuperar o valor da variável apontada, basta que o apontador seja atribuído a uma variável de mesmo tipo. ( ) Arranjos de apontadores podem ser usados em funções que recebam como argumentos um número variável de cadeias de caracteres. ( ) Em geral, para que um arranjo seja percorrido por um ponteiro, para cada elemento do arranjo deve-se adicionar ao ponteiro o número de bytes que o elemento ocupa em memória a) E - C - E - C - E b) C - C - E - E - E c) E - E - C - C - E d) E - C - E - C - C e) C - E - C - E - C 2019.2 38 Aula 04 EXERCÍCIO 11 – A respeito de Alocação Dinâmica de Memória, quais as vantagens no uso de representar os dados por encadeamento? EXERCÍCIO 12 – Qual o valor de x no final do programa? #include<iostream> using namespace std; int main() { int x, *p, y; x = 2; p = &x; y = *p; y = 5; (*p)++; (*p) = (*p) - y; return(0); }
Compartilhar