950 likes | 1.09k Views
Capítulo IX – Ponteiros. 9.1 – Introdução 9.2 – Relação entre ponteiros e variáveis indexadas 9.3 – Alocação dinâmica de memória 9.4 – Variáveis indexadas, ponteiros e estruturas como parâmetros e elementos de retorno 9.5 – Subprogramas como parâmetros 9.6 – Encadeamento de estruturas.
E N D
Capítulo IX – Ponteiros 9.1 – Introdução 9.2 – Relação entre ponteiros e variáveis indexadas 9.3 – Alocação dinâmica de memória 9.4 – Variáveis indexadas, ponteiros e estruturas como parâmetros e elementos de retorno 9.5 – Subprogramas como parâmetros 9.6 – Encadeamento de estruturas
9.3 – Alocação Dinâmica de Memória 9.3.1 – Dispositivos para alocação dinâmica • Muitas vezes é desejável reservar espaço para vetores e matrizes somente em tempo de execução • Exemplo: seja a seguinte declaração: int A[100][100];
int A[100][100]; • Neste exemplo, supõe-se que o número de linhas e colunas de A devam ser lidos durante a execução desse programa • Caso eles sejam bem menores que 100, haverá grande desperdício de memória • Caso sejam maiores que 100, o espaço reservado não será suficiente para guardar os elementos de A • É conveniente alocar espaço para os elementos dessa variável depois de conhecidos os números de linhas e de colunas
Na Linguagem C, a alocação de espaço em tempo de execução para vetores e matrizes só pode ser feita se esses forem declarados como ponteiros • Esse tipo de alocação denomina-se alocação dinâmica • Há uma região da memória ocupada pelo programa denominada heap, destinada a essas alocações dinâmicas
A alocação pode ser feita pela função malloc do arquivo stdlib.h • malloc recebe como argumento o número de bytes a ser alocado • mallocentão reserva na heap esse número de bytes de forma contígua • O valor retornado por mallocé o endereço do primeiro desses bytes (um ponteiro) • Esses bytes ficam indisponíveis para novas alocações
Exemplo: para alocar um vetor de 7 elementos do tipo int: int *V; V = (int *) malloc (7 * sizeof (int)); 28 bytes V int V pode ser usada como vetor 4 bytes A conversão (int *) é necessária pois malloc retorna um ponteiro do tipo void heap
Tamanho dos vetores: 7 Vetor A: 28 39 84 27 82 49 10 Vetor B: 94 27 68 17 83 72 39 Vetor C: 94 39 84 27 83 72 39 Digite algo para encerrar: Exemplo: seja o programa à esquerda #include <stdio.h> #include <stdlib.h> typedef int *vetor; int main () { int m, i; vetor A, B, C; printf ("Tamanho dos vetores: "); scanf ("%d", &m); A = (int *) malloc (m*sizeof(int)); B = (int *) malloc (m*sizeof(int)); C = (int *) malloc (m*sizeof(int)); printf ("\nVetor A: "); for (i = 0; i < m; i++) scanf ("%d", &A[i]); printf ("\nVetor B: "); for (i = 0; i < m; i++) scanf ("%d", &B[i]); printf ("\nVetor C: "); for (i = 0; i < m; i++) C[i] = (A[i] > B[i])? A[i]: B[i]; for (i = 0; i < m; i++) printf ("%5d", C[i]); printf ("\n\n"); system ("pause"); return 0; } No vídeo
O lay-out da área ocupada pelo programa é planejado pelo compilador • O gerenciamento durante a execução fica por conta do próprio programa • A área total reservada para o programa é fixa • As regiões das instruções e das variáveis globais também tem tamanho fixo
As outras regiões tem tamanho variável durante a execução • A área de dados das funções destina-se a guardar os parâmetros, as variáveis locais e informações operacionais das versões ativas de funções num dado momento • Essa área varia de tamanho, pois essas versões de funções não ficam ativas o tempo todo
Quando uma versão de função é chamada para execução, sua área de dados é carregada na memória • O carregamento é feito a partir da fronteira com a área desocupada • Quando sua execução é encerrada, sua área é retirada da memória, aumentando a área desocupada
A área heap aumenta a partir de sua fronteira com a área desocupada • Isso acontece quando uma alocação dinâmica de memória é feita (mallocou outras do gênero) • A área de dados das funções aumenta para baixo e a heap aumenta para cima
Nesse processo, se o programador não tomar cuidado, essas duas áreas podem se encontrar • Aí, esgota-se a capacidade da área desocupada • Novas chamadas de funções e novas alocações dinâmicas ficam impossibilitadas
A função free torna a deixar disponível a área reservada numa alocação dinâmica • Seu parâmetro é um ponteiro • Ela re-disponibiliza a área previamente alocada e apontada por ele • Deve-se usar essa função toda vez que uma alocação não tiver mais utilidade para o programa
Ver programa a seguir 9.3.2 – Alocação dinâmica de matrizes • Usa-se ponteiro para vetor de ponteiros:
matriz é o tipo ponteiro para ponteiros de int A partir deste ponto, A pode ser usada como uma matriz comum #include <stdio.h> #include <stdlib.h> typedef int **matriz; int main () { int m, n, i, j; matriz A; printf ("Dimensoes de uma matriz: "); scanf ("%d%d", &m, &n); A = (int **) malloc (m * sizeof(int*)); for (i = 0; i < m; i++) A[i]= (int *) malloc (n * sizeof(int)); printf ("\nElementos da matriz:\n\n"); for (i = 0; i < m; i++) { printf ("\tLinha %d: ", i); for (j = 0; j < n; j++) scanf ("%d", &A[i][j]); } printf ("\nConfirmacao: \n\n"); for (i = 0; i < m; i++) { for (j = 0; j < n; j++) printf ("%5d", A[i][j]); printf ("\n"); } printf ("\n\n"); system ("pause"); return 0; }
Capítulo IX – Ponteiros 9.1 – Introdução 9.2 – Relação entre ponteiros e variáveis indexadas 9.3 – Alocação dinâmica de memória 9.4 – Variáveis indexadas, ponteiros e estruturas como parâmetros e elementos de retorno 9.5 – Subprogramas como parâmetros 9.6 – Encadeamento de estruturas
9.4 – Variáveis Indexadas, Ponteiros e Estruturas como Parâmetros e Elementos de Retorno 9.4.1 – Variáveis indexadas e ponteiros como parâmetros • Em C, quando um dos parâmetros de uma função for declarado como variável indexada, na realidade ele será um ponteiro
Caso o argumento correspondente seja o nome de uma variável indexada: • O endereço correspondente aos seu nome é passado ao ponteiro-parâmetro • Os elementos da variável-argumento não são copiados para a função • Então essa passagem de argumento é por referência • Toda alteração nos elementos da variável-parâmetro terá efeito sobre aqueles da variável-argumento • Outros possíveis argumentos: ponteiros e endereços
Exemplo: seja o programa à esquerda #include <stdio.h> #include <stdlib.h> void Alterar (int B[]) { B[1] = B[3] = 7; } int main () { int i, A[10] = {0}; printf ("Vetor inicial : "); for (i = 0; i <= 9; i++) printf ("%3d", A[i]); Alterar (A); printf ("\n\nVetorintermediario: "); for (i = 0; i <= 9; i++) printf ("%3d", A[i]); Alterar (&A[4]); printf ("\n\nVetor final : "); for (i = 0; i <= 9; i++) printf ("%3d", A[i]); printf ("\n\n"); system ("pause"); return 0; } O parâmetro B de Alterar é um ponteiro Não é necessário colocar a dimensão Poderia ser int *B
B #include <stdio.h> #include <stdlib.h> void Alterar (int B[]) { B[1] = B[3] = 7; } int main () { int i, A[10] = {0}; printf ("Vetor inicial : "); for (i = 0; i <= 9; i++) printf ("%3d", A[i]); Alterar (A); printf ("\n\nVetorintermediario: "); for (i = 0; i <= 9; i++) printf ("%3d", A[i]); Alterar (&A[4]); printf ("\n\nVetor final : "); for (i = 0; i <= 9; i++) printf ("%3d", A[i]); printf ("\n\n"); system ("pause"); return 0; } 0 0 7 0 7 0 0 7 0 0 7 0 0 0 B[0] B[1] B[2] B[3] B[4] B[0] B[1] B[5] B[2] B[6] B[3] B[7] B[4] B[8] B[5] B[9] A A[3] A[4] A[5] A[6] A[0] A[2] A[1] A[7] A[9] A[8]
Vetor inicial : 0 0 0 0 0 0 0 0 0 0 Vetor intermediario: 0 7 0 7 0 0 0 0 0 0 Vetor final : 0 7 0 7 0 7 0 7 0 0 Pressione ... #include <stdio.h> #include <stdlib.h> void Alterar (int B[]) { B[1] = B[3] = 7; } int main () { int i, A[10] = {0}; printf ("Vetor inicial : "); for (i = 0; i <= 9; i++) printf ("%3d", A[i]); Alterar (A); printf ("\n\nVetorintermediario: "); for (i = 0; i <= 9; i++) printf ("%3d", A[i]); Alterar (&A[4]); printf ("\n\nVetor final : "); for (i = 0; i <= 9; i++) printf ("%3d", A[i]); printf ("\n\n"); system ("pause"); return 0; } Resultado
#include <stdio.h> #include <stdlib.h> voidImprimirMatriz (int V[][10], int x, int y) { int i, j; printf ("Endereco(V) = %d; Conteudo(V) = %d;\n\n", &V, V); printf ("Endereco(V[0][0]) = %d\n\n", &V[0][0]); for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf ("%5d", V[i][j]); } printf ("\n"); } } int main () { int A[10][10], i, j, m = 5, n = 7; printf ("A = %d\n\n", A); printf ("Endereco(A[0][0]) = %d\n\n", &A[0][0]); for (i = 0; i < m; i++) for (j = 0; j < n; j++) A[i][j] = (i+1)*(j+1); ImprimirMatriz (A, m, n); getch (); } Exemplo com matriz bidimensional
#include <stdio.h> #include <stdlib.h> voidImprimirMatriz (int V[][10], int x, int y) { int i, j; printf ("Endereco(V) = %d; Conteudo(V) = %d;\n\n", &V, V); printf ("Endereco(V[0][0]) = %d\n\n", &V[0][0]); for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf ("%5d", V[i][j]); } printf ("\n"); } } int main () { int A[10][10], i, j, m = 5, n = 7; printf ("A = %d\n\n", A); printf ("Endereco(A[0][0]) = %d\n\n", &A[0][0]); for (i = 0; i < m; i++) for (j = 0; j < n; j++) A[i][j] = (i+1)*(j+1); ImprimirMatriz (A, m, n); getch (); } No parâmetro V, a 1ª dimensão é dispensada Resultado
#include <stdio.h> #include <stdlib.h> voidImprimirMatriz (int V[][10], int x, int y) { int i, j; printf ("Endereco(V) = %d; Conteudo(V) = %d;\n\n", &V, V); printf ("Endereco(V[0][0]) = %d\n\n", &V[0][0]); for (i = 0; i < x; i++) { for (j = 0; j < y; j++) { printf ("%5d", V[i][j]); } printf ("\n"); } } int main () { int A[10][10], i, j, m = 5, n = 7; printf ("A = %d\n\n", A); printf ("Endereco(A[0][0]) = %d\n\n", &A[0][0]); for (i = 0; i < m; i++) for (j = 0; j < n; j++) { A[i][j] = (i+1)*(j+1); } ImprimirMatriz (A, m, n); printf ("\n\n"); system ("pause"); return 0; } V A A[0][0] Resultado
Exemplo: Fusão de 2 vetores ordenados • O programa a seguir realiza as seguintes operações: • Lê os dados sobre os dois vetores • Ordena-os pelo Bubble-Sort • Funde os dois vetores pelo método Merge-Sort, obtendo um terceiro vetor ordenado • O método Merge-Sort apresentado só funciona quando os 2 vetores já estão ordenados
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; j i k 3 5 8 8 11 13 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; j k i 3 3 5 8 8 11 13 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; j k i 3 3 4 5 8 8 11 13 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; k j i 3 3 4 5 5 8 8 11 13 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; k j i 3 3 4 5 5 8 5 8 11 13 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; k j i 3 3 4 5 5 8 5 8 11 6 13 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; k j i 3 3 4 5 5 8 5 8 11 6 7 13 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; j i k 3 3 4 5 5 8 5 8 11 6 13 7 8 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; j i k 3 3 4 5 5 8 5 8 6 11 7 13 16 8 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 8 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; j i k 3 3 4 5 5 8 5 8 11 6 7 13 8 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 8 8 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; j i k 3 3 4 5 5 8 5 8 11 6 7 13 8 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 8 8 9 V3
V3[k] = (V1[i] < V2[j]) ? V1[i] : V2[j]; j i k 3 3 4 5 5 8 8 5 6 11 13 7 8 16 V1 O Método Merge-Sort: 4 5 6 7 8 9 V2 8 8 9 11 13 16 V3 A seguir, o programa
O tipo dos parâmetros dos protótipos devem ser os mesmos nas definições das funções /* Declaracoesglobais */ #include <stdio.h> #include <stdlib.h> typedef char logic; const logic TRUE = 1, FALSE = 0; typedef int vetor[20]; /* Prototipos das funcoesauxiliares */ void LerVetor (vetor, int*); void EscreverVetor (vetor, int); void BubbleSort (vetor, int); void MergeSort (vetor, vetor, vetor, int, int, int*);
V1, V2 e V3 são variáveis indexadas int main () { int m, n, p; vetor V1, V2, V3; printf ("FUSAO DE DOIS VETORES ORDENADOS"); /* Leitura dos dois vetores */ printf ("\n\nLeitura do vetor V1: \n"); LerVetor (V1, &m); printf ("\nLeitura do vetor V2: \n"); LerVetor (V2, &n); /* Escrita dos dois vetores lidos */ printf ("\n\nVetor V1 inicial: \n\n"); EscreverVetor (V1, m); printf ("\n\nVetor V2 inicial: \n\n"); EscreverVetor (V2, n); Passagem de V1, V2, m e n: por referência Eles serão alterados pela função LerVetor Passagem de V1 e V2 : por referência Os elementos não precisam ser copiados para a função
Passagem de V1 e V2: por referência Eles serão alterados pela função BubbleSort /* Ordenacao e escrita dos dois vetores ordenados */ BubbleSort (V1, m); BubbleSort (V2, n); printf ("\n\nVetor V1 ordenado: \n\n"); EscreverVetor (V1, m); printf ("\n\nVetor V2 ordenado: \n\n"); EscreverVetor (V2, n); /* Fusao dos dois vetores num terceiro */ MergeSort (V1, V2, V3, m, n, &p); Passagem de V3 e p: por referência Eles serão alterados pela função MergeSort Passagem de V1 e V2: por referência Os elementos não precisam ser copiados para a função
/* Escrita do vetor resultado da fusao */ printf ("\n\nFusao V3 dos vetores V1 e V2:\n\n"); EscreverVetor (V3, p); /* Fechamento da tela */ printf ("\n\n"); system ("pause"); return 0; }
Na função main: LerVetor (V1, &m); LerVetor (V2, &n); /* FuncaoLerVetor para ler os elementos de um vetor */ voidLerVetor (vetor V, int *n) { int i; printf ("\n\tNumero de elementos: "); scanf ("%d", n); printf ("\n\tSeus %d elementos: ", *n); for (i = 0; i <= *n-1; i++) scanf ("%d", &V[i]); } /* FuncaoEscreverVetor para escrever os elementos de um vetor */ voidEscreverVetor (vetor V, int n) { int i; for (i = 0; i <= n-1; i++) printf ("%4d", V[i]); } Não é &n, pois n já é o endereço alvo Local apontado por n V[i] coincide com os elementos V1[i] e V2[i] dos argumentos V1 e V2
Na função main: BubbleSort (V1, m); BubbleSort (V2, n); /* FuncaoBubbleSort para ordenar os elementos de um vetor */ voidBubbleSort (vetor V, int n) { int i, p, aux; logic trocou; p = n-2; trocou = TRUE; while (p>=0 && trocou) { trocou = FALSE; i = 0; while (i <= p) { if (V[i] > V[i+1]) { aux = V[i]; V[i] = V[i+1]; V[i+1] = aux; trocou = TRUE; } i = i+1; } p = p-1; } } V[i] e V[i+1] coincidem com os elementos correspondentes dos argumentos V1 e V2
Percurso em V1 ou V2 acabou Na função main: MergeSort (V1, V2, V3, m, n, &p); /* FuncaoMergeSort para fundir dois vetores ordenados num terceiro tambem ordenado */ voidMergeSort (vetor V1, vetor V2, vetor V3, int m, int n, int *p) { int i, j, k; *p = m + n; for (i = j = k = 0; i < m && j < n; k++) if (V1[i] < V2[j]) { V3[k] = V1[i]; i++; } else { V3[k] = V2[j]; j++; } for (; i < m; i++, k++) V3[k] = V1[i]; for (; j < n; j++, k++) V3[k] = V2[j]; } O tamanho do vetor final é calculado pela MergeSort Percurso em V1 e V2 ainda não acabou
9.4.2 – Variáveis indexadas e ponteiros como valores retornados • Quando se deseja produzir uma variável indexada dentro de uma função e retornar seus elementos, deve-se usar um ponteiro • O programa a seguir faz alocação dinâmica de uma matriz dentro de uma função auxiliar • As dimensões são passadas por referência e lidas na própria função • O ponteiro para os elementos da matriz é o valor de retorno
#include <stdio.h> #include <stdlib.h> typedef int **matriz; matrizLerMatriz (int*, int*); void EscreverMatriz (matriz, int, int); int main () { int m, n; matriz A; printf ("Leituradamatriz A\n\n"); A = LerMatriz (&m, &n); printf ("\nMatriz A lida:\n\n"); EscreverMatriz (A, m, n); printf ("\n\n"); system ("pause"); return 0; } O valor retornado de LerMatriz é atribuído a A
Na função main: A = LerMatriz (&m, &n); matrizLerMatriz (int *nlin, int *ncol) { matrizMatRetorno; int i, j; printf ("Digite as 2 dimensoes: "); scanf ("%d%d", nlin, ncol); MatRetorno = (int **) malloc (*nlin * sizeof(int*)); for (i = 0; i < *nlin; i++) MatRetorno[i]= (int *) malloc (*ncol * sizeof(int)); printf ("\nDigiteoselementosdamatriz\n\n"); for (i = 0; i < *nlin; i++) { printf ("\tLinha %d: ", i); for (j = 0; j < *ncol; j++) scanf ("%d", &MatRetorno[i][j]); } return MatRetorno; } Alocação do vetor de ponteiros O valor retornado é um ponteiro
void EscreverMatriz (matriz Mat, int m, int n) { int i, j; for (i = 0; i < m; i++) { for (j = 0; j < n; j++) printf ("%5d", Mat[i][j]); printf ("\n"); } } Na função main: EscreverMatriz (A, m, n);
9.4.3 – Estruturas como parâmetros e valores retornados • Em C, estruturas podem ser declaradas como parâmetros, passadas como argumentospor valor e por referência e seus valores podem ser retornados de funções • Na passagem de argumento por valor e no retorno de uma função, há uma cópia de toda a estrutura, de um módulo para outro • O programa a seguir realiza operações com números complexos • É usada uma estrutura com a parte real e a parte imaginária de um complexo
#include <stdio.h> #include <conio.h> #include <conio2.h> #include <stdlib.h> #include <math.h> structcomplexo {float real, imag;}; typedefstructcomplexocomplexo; void WriteMenu (void); complexo Soma (complexo, complexo); complexoSubtracao (complexo, complexo); complexoMultiplicacao (complexo, complexo); complexoDivisao (complexo, complexo); Nas funções para operações com complexo, todos os parâmetros e os valores retornados são estruturas