Baixe o app para aproveitar ainda mais
Prévia do material em texto
326 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Unidade VI Nesta unidade, abordaremos definitivamente a linguagem C. As noções de pseudocódigo e de todas as suas estruturas já foram ensinadas e sempre deverão ser usadas no desenvolvimento de programas. Passaremos a estudar técnicas e rotinas consagradas, que serão um atalho no desenvolvimento de programas mais complexos. Para o completo entendimento, necessitaremos entender como as linguagens, em especial a linguagem C, administram a memória RAM, o espaço disponível para os nossos programas serem executados. 6 ALOCAÇÃO DINÂMICA DA MEMÓRIA Ao declarar um vetor, para dimensioná-lo, era necessário saber de antemão quantos elementos iriam compô-lo. Tínhamos de prever o número máximo de elementos no vetor durante o processo da codificação. O pré-dimensionamento do vetor é um fator que limita a programação. Uma solução seria superdimensionar o vetor para tentar contornar essa falta de elementos livres durante a execução do programa, mas isso acarreta um desperdício de memória, o que é inaceitável em diversas aplicações, como aplicativos portáteis, em que sempre estamos sujeitos a ter falta de memória. A linguagem C oferece meios de utilizar e racionalizar o uso da memória durante a execução do programa, ou seja, podemos alocar memória dinamicamente. Esse recurso permite ao programa alocar elementos ou registros dinamicamente, sem desperdício de memória. 6.1 O uso da memória Basicamente, existem três maneiras de reservarmos espaço de memória para o armazenamento de informações, sem o rigor técnico. A primeira maneira é por meio do uso de variáveis globais (e estáticas). Nessa categoria de variáveis, o espaço reservado para uma variável existirá enquanto o programa estiver sendo executado. A segunda maneira é usando variáveis locais. Nessa categoria, o espaço na memória existe apenas no período em que a função que declarou a variável está sendo executada, sendo liberado assim que a execução da função terminar. Como consequência, a função que chama não pode fazer referência ao espaço local da função chamada. As variáveis globais ou as locais podem ser simples ou vetores. Para os 327 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO vetores, é necessário informar o número máximo de elementos, caso contrário o compilador não terá a informação sobre o tamanho do espaço a ser reservado. A terceira maneira de reservar a memória é solicitar ao programa que aloque dinamicamente um espaço na memória durante sua execução. Esse espaço permanece reservado até que seja liberado explicitamente pelo programa, por meio de comando específico. Para o entendimento do processo de uso da memória, vamos fazer uma simulação da execução de um programa. 1. Vamos supor que esta seja uma memória RAM de um computador sem nenhum programa sendo executado: RAM Figura 333 – Memória RAM sem programa 2. Ao colocarmos um programa para ser executado, o sistema operacional carregará o código compilado na memória: 328 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI RAM Programa Figura 334 – Colocação do programa na memória 3. Uma vez com o código na memória, o sistema passa a ser comandado pelo programa. A primeira etapa é reservar o espaço para as constantes e as variáveis globais, pois essas são estáticas, ou seja, não serão criadas constantes nem variáveis globais durante a execução do resto do programa: 329 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO RAM Programa Variáveis globais e constantes Figura 335 – Com o código na memória, o programa passa a comandar o sistema. 4. Cada vez que uma determinada função é chamada, o sistema reserva o espaço necessário para as variáveis locais da função. Simultaneamente, a pilha do controle da chamada das funções armazena a sequência de chamada e o estado geral das memórias operacionais. Conforme as funções são chamadas, a memória livre cresce, e a pilha é atualizada. Quando as funções são encerradas, o sistema consulta na pilha o ponto em que a função foi chamada, retornando o programa nesse ponto e liberando o espaço das variáveis e da pilha criados dinamicamente: 330 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI RAM Programa Variáveis locais Memória livre Pilha Variáveis globais e constantes Figura 336 – Comportamento do sistema quando as funções são chamadas e encerradas 6.2 Ponteiro de variáveis Na cidade de São Paulo existe um edifício famoso, o prédio da Gazeta, diante do qual, sempre que há uma comemoração da conquista de um campeonato, qualquer que seja o time paulista, a torcida se concentra, a fim de festejar. Vamos supor que você não seja da cidade de São Paulo, gostaria que comemorar o campeonato do seu time e queira chegar ao prédio da Gazeta a partir de um terminal de ônibus, de uma estação de trem ou de um aeroporto. Indo de táxi, pode pedir ao taxista para levá-lo ao prédio da Gazeta, ou então para a Avenida Paulista, nº 900. Pelo que vimos até agora, cada vez que uma variável é declarada, um espaço é alocado na memória para armazenar valores. Tecnicamente, é feito mais do que a simples reserva. O programa tem uma tabela, em que armazena o nome da variável, o endereço e o tipo de valor que será armazenado. Assim, ao escrevermos int a;, o programa reserva um espaço, normalmente de 4 bytes, por ser inteiro, no primeiro espaço livre da memória que encontrar. Em seguida, cria uma linha em uma tabela, na qual armazena o endereço e o tipo associado à variável criada nesse momento. Em muitas ocasiões, seria interessante poder acessar o conteúdo de um local da memória de outras maneiras que não fossem pelo nome ou pelo endereço de memória, por exemplo, da mesma maneira que o prédio da Gazeta pode ser localizado pelo seu endereço ou pelo seu nome. 331 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO A linguagem C tem uma maneira especial de uma variável armazenar endereços. Essa variável se chama variável ponteiro, ou, simplesmente, ponteiro. A declaração de uma variável é feita da seguinte forma: <tipo> *nome; Por exemplo: int *p; O programa reserva um espaço na memória para uma variável chamada p, que irá guardar um endereço, e esse endereço armazenado conterá uma informação do tipo inteiro. O símbolo * identifica que uma variável é do tipo ponteiro. Para acessar os endereços de memória, a linguagem oferece dois operadores unários: • & (“endereço de”), aplicado a variáveis, resulta no endereço da posição da memória reservada para a variável; • * (“conteúdo de”), aplicado a variáveis do tipo ponteiro, acessa o conteúdo do endereço de memória armazenado pela variável ponteiro. Vamos analisar o seguinte trecho de programa: int a; int *p; a=5; p = &a; *p = 6; /*variável inteiro */ int a; /*variável ponteiro p/ inteiro */ int *p; /* a recebe o valor 5 */ a=5; /* p recebe o endereço de a (diz-se p aponta para a) */ p = &a; /* conteúdo de p recebe o valor 6 */ *p = 6; Memória RAM 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 332 Re visã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Variáveis Figura 337 – Memória RAM livre Inicialmente, a memória RAM (nesse caso, uma memória fictícia) está livre, e o conteúdo pode ser qualquer sobra de outros programas que utilizaram esse espaço. /*variável inteiro */ int a; /*variável ponteiro p/ inteiro */ int *p; /* a recebe o valor 5 */ a=5; /* p recebe o endereço de a (diz-se p aponta para a) */ p = &a; /* conteúdo de p recebe o valor 6 */ *p = 6; Memória RAM 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 Variáveis a 101 int Figura 338 – Reserva de bytes e criação de tabela O programa reserva quatro bytes, em que serão armazenados os valores de a. Ao mesmo tempo, cria uma tabela com as referências da variável criada. /*variável inteiro */ int a; /*variável ponteiro p/ inteiro */ int *p; /* a recebe o valor 5 */ a=5; /* p recebe o endereço de a (diz-se p aponta para a) */ p = &a; /* conteúdo de p recebe o valor 6 */ *p = 6; 333 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Memória RAM 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 Variáveis a 101 int *p 105 int Figura 339 – Reserva de espaço para valores de p e atualização da tabela O programa reserva o espaço correspondente a um endereço de memória em que serão armazenados os valores de uma variável ponteiro p e atualiza a tabela. /*variável inteiro */ int a; /*variável ponteiro p/ inteiro */ int *p; /* a recebe o valor 5 */ a=5; /* p recebe o endereço de a (diz-se p aponta para a) */ p = &a; /* conteúdo de p recebe o valor 6 */ *p = 6; Memória RAM 101 102 103 104 105 106 107 108 109 110 5 111 112 113 114 115 116 117 118 119 120 Variáveis a 101 int *p 105 int Figura 340 – Atribuição de valor à variável a /*variável inteiro */ int a; /*variável ponteiro p/ inteiro */ int *p; /* a recebe o valor 5 */ a=5; /* p recebe o endereço de a (diz-se p aponta para a) */ p = &a; /* conteúdo de p recebe o valor 6 */ *p = 6; 334 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Memória RAM 101 102 103 104 105 106 107 108 109 110 5 101 111 112 113 114 115 116 117 118 119 120 Variáveis a 101 int *p 105 int Figura 341 – O endereço é passado de a para p A variável p recebe endereço de a (note o operador unário &), passando a apontar para o endereço onde está a. /*variável inteiro */ int a; /*variável ponteiro p/ inteiro */ int *p; /* a recebe o valor 5 */ a=5; /* p recebe o endereço de a (diz-se p aponta para a) */ p = &a; /* conteúdo de p recebe o valor 6 */ *p = 6; Memória RAM 101 102 103 104 105 106 107 108 109 110 6 101 111 112 113 114 115 116 117 118 119 120 Variáveis a 101 int *p 105 int Figura 342 – O valor 6 é atribuído à variável a Aqui se encontra a grande diferença: no endereço apontado por p, receberá o valor 6, ou seja, a variável a passará a conter o valor 6. Nesse exemplo temos as duas maneiras pelas quais é possível trabalhar com memória em C: uma por meio do nome da variável, e a outra, pelo endereço desta. 6.3 Passagem de valores por valor e por referência Quando estudamos funções vimos que, quando passamos informações para dentro de uma função, fazemos isso por meio de parâmetros. Também foi dito que uma função é aquela que devolve um e apenas um valor, ou seja, não devolve mais de um valor. O uso de ponteiros amplia a possibilidade de utilização dos parâmetros e solução de problemas de retornos múltiplos. 335 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Dizemos que há uma passagem de parâmetros por valor quando conteúdos (e não endereços) são passados para as funções. No exemplo a seguir, os valores de a e b são passados para a função troca. #include<stdio.h> void troca(int a, int b){ int temp; temp=a; a=b; b=temp; } void main(){ int a=7,b=5; troca(a,b); printf(“\na=%d\nb=%d\n”,a,b); } Figura 343 – Saída mostrando a passagem de parâmetros por valor O resultado de a é 7 e o de b é 5, pois efetivamente as variáveis a e b são locais da função main( ). Quando os parâmetros passados são endereços, dizemos que foram passados por referência, como no exemplo a seguir. #include<stdio.h> void troca(int *a, int *b){ int temp; temp=*a; *a=*b; *b=temp; } void main(){ int a=7,b=5; troca(&a,&b); printf(“\na=%d\nb=%d\n”,a,b); } 336 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Figura 344 – Saída do programa passando parâmetros por referência Apesar de os programas serem praticamente idênticos, o resultado é bem diferente, pois o mecanismo de trabalho foi bem diverso. Exemplo de aplicação Para entender o que acontece em cada um dos casos, vamos fazer o teste de mesa nos próprios códigos. • Passagem por valor #include<stdio.h> void troca(int a, int b){ int temp; temp=a; a=b; b=temp; } void main(){ int a=7,b=5; troca(a,b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 Variáveis:main Figura 345 – Início do programa 337 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO #include<stdio.h> void troca(int a, int b){ int temp; temp=a; a=b; b=temp; } void main(){ int a=7,b=5; troca(a,b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 111 112 113 114 115 116 117 118 119 120 Variáveis:main a 101 int b 105 int Figura 346 – Criação das variáveis a e b As variáveis a e b da função são criadas e inicializadas. Nesse momento, o programa reserva os espaços na memória RAM e atualiza a tabela das variáveis da função main( ). #include<stdio.h> void troca(int a, int b){ int temp; temp=a; a=b; b=temp; } void main(){ int a=7,b=5; troca(a,b); printf(“\na=%d\nb=%d\n”,a,b); } 338 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 111 112 113 114 115 116 117 118 119 120 7 5 Variáveis:main a 101 int b 105 int Variáveis:troca a 109 int b 113 int Figura 347 – Reserva de memória para as próximas variáveis e criação de tabela para as variáveis locais Ao encontrar a chamada para a função troca, o programa continua reservando espaço da memória para as variáveis que forem aparecendo e cria uma nova tabela para as suas variáveis locais, armazenando os parâmetros de entrada. Em seguida, passa a executar a função troca. #include<stdio.h> void troca(int a, int b){ int temp; temp=a; a=b; b=temp; } void main(){ int a=7,b=5; troca(a,b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 111 112 113 114 115 116 117 118119 120 7 5 Variáveis:main a 101 int b 105 int Variáveis:troca a 109 int b 113 int temp 117 int Figura 348 – O programa cria a variável temp 339 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO #include<stdio.h> void troca(int a, int b){ int temp; temp=a; a=b; b=temp; } void main(){ int a=7,b=5; troca(a,b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 111 112 113 114 115 116 117 118 119 120 7 5 7 Variáveis:main a 101 int b 105 int Variáveis:troca a 109 int b 113 int temp 117 int Figura 349 – Temp recebe o conteúdo da variável a da função troca #include<stdio.h> void troca(int a, int b){ int temp; temp=a; a=b; b=temp; } void main(){ int a=7,b=5; troca(a,b); printf(“\na=%d\nb=%d\n”,a,b); } 340 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 111 112 113 114 115 116 117 118 119 120 5 5 7 Variáveis:main a 101 int b 105 int Variáveis:troca a 109 int b 113 int temp 117 int Figura 350 – A variável a recebe o conteúdo da variável b da função troca #include<stdio.h> void troca(int a, int b){ int temp; temp=a; a=b; b=temp; } void main(){ int a=7,b=5; troca(a,b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 111 112 113 114 115 116 117 118 119 120 5 7 7 Variáveis:main a 101 int b 105 int Variáveis:troca a 109 int b 113 int temp 117 int Figura 351 – A variável b recebe conteúdo de temp A variável b recebe o conteúdo da variável temp da função troca, e o programa retorna para o ponto de chamada da função main( ). 341 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO #include<stdio.h> void troca(int a, int b){ int temp; temp=a; a=b; b=temp; } void main(){ int a=7,b=5; troca(a,b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 111 112 113 114 115 116 117 118 119 120 5 7 7 Variáveis:main a 101 int b 105 int Variáveis:troca a 109 int b 113 int temp 117 int Tela: a=7 b=5 Figura 352 – Exibição dos conteúdos de a e b da função main( ) A função main( ) exibe os conteúdos das variáveis a e b da própria função main( ), aqueles valores que estão nos endereços da tabela Variáveis:main. Passagem por referência #include<stdio.h> void troca(int *a, int *b){ int temp; temp=*a; *a=*b; *b=temp; } void main(){ int a=7,b=5; troca(&a,&b); printf(“\na=%d\nb=%d\n”,a,b); } 342 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Memória RAM 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 Variáveis:main Figura 353 – Início do programa O programa começa com a memória vazia, executando a função main( ). #include<stdio.h> void troca(int *a, int *b){ int temp; temp=*a; *a=*b; *b=temp; } void main(){ int a=7,b=5; troca(&a,&b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 111 112 113 114 115 116 117 118 119 120 Variáveis:main a 101 int b 105 int Figura 354 – Criação e inicialização das variáveis a e b As variáveis a e b da função são criadas e inicializadas. Nesse momento, o programa reserva os espaços na memória RAM e atualiza a tabela das variáveis da função main( ). Até esse ponto, os dois programas são iguais. #include<stdio.h> void troca(int *a, int *b){ int temp; temp=*a; *a=*b; *b=temp; } void main(){ 343 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO int a=7,b=5; troca(&a,&b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 &101 111 112 113 114 115 116 117 118 119 120 &105 Variáveis:main a 101 int b 105 int Variáveis:troca *a 109 int *b 111 int Figura 355 – Recebimento dos ponteiros Na chamada da função é que começam as diferenças. A função troca recebe dois ponteiros do tipo inteiro, lembrando sempre que ponteiros armazenam endereços, e não valores absolutos. Na passagem de parâmetro, são endereços de memória, em virtude do operador unário & aplicado em a e em b. #include<stdio.h> void troca(int *a, int *b){ int temp; temp=*a; *a=*b; *b=temp; } void main(){ int a=7,b=5; troca(&a,&b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 &101 111 112 113 114 115 116 117 118 119 120 &105 Variáveis:main a 101 int b 105 int Variáveis:troca *a 109 int *b 111 int temp 113 int Figura 356 – O programa cria normalmente a variável temp da função troca 344 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI #include<stdio.h> void troca(int *a, int *b){ int temp; temp=*a; *a=*b; *b=temp; } void main(){ int a=7,b=5; troca(&a,&b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 &101 111 112 113 114 115 116 117 118 119 120 &105 7 Variáveis:main a 101 int b 105 int Variáveis:troca *a 109 int *b 111 int temp 113 int Figura 357 – Chamada da função Na chamada da função é que começam as diferenças. #include<stdio.h> void troca(int *a, int *b){ int temp; temp=*a; *a=*b; *b=temp; } void main(){ int a=7,b=5; troca(&a,&b); printf(“\na=%d\nb=%d\n”,a,b); } 345 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Memória RAM 101 102 103 104 105 106 107 108 109 110 7 5 &101 111 112 113 114 115 116 117 118 119 120 &105 7 Variáveis:main A 101 int B 105 int Variáveis:troca *a 109 int *b 111 int temp 113 int Figura 358 – Armazenamento do valor 7 em temp A variável temp recebe o conteúdo apontado pela variável a da função troca. Como a variável a aponta para o endereço 101 e é do tipo inteiro, o valor 7 é armazenado em temp. #include<stdio.h> void troca(int *a, int *b){ int temp; temp=*a; *a=*b; *b=temp; } void main(){ int a=7,b=5; troca(&a,&b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 5 5 &101 111 112 113 114 115 116 117 118 119 120 &105 7 Variáveis:main A 101 int B 105 int Variáveis:troca a 109 int b 111 int temp 113 int Figura 359 – O endereço &101 recebe o valor do endereço &105 O endereço apontado pela variável a da função troca recebe o conteúdo apontado pela variável b da função troca, ou seja, no endereço &101, receberá o valor que está no endereço&105. 346 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI #include<stdio.h> void troca(int *a, int *b){ int temp; temp=*a; *a=*b; *b=temp; } void main(){ int a=7,b=5; troca(&a,&b); printf(“\na=%d\nb=%d\n”,a,b); } Memória RAM 101 102 103 104 105 106 107 108 109 110 5 5 &101 111 112 113 114 115 116 117 118 119 120 &105 7 Variáveis:main A 101 int B 105 int Variáveis:troca a 109 int b 111 int temp 113 int Figura 360 – O endereço &105 recebe o valor 7 do temp O endereço apontado pela variável b da função troca recebe o conteúdo da variável temp dessa função, ou seja, no endereço &105, receberá o valor 7 do temp. O programa retorna para o ponto em que foi chamado. #include<stdio.h> void troca(int *a, int *b){ int temp; temp=*a; *a=*b; *b=temp; } void main(){ int a=7,b=5; troca(&a,&b); printf(“\na=%d\nb=%d\n”,a,b); } 347 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Memória RAM 101 102 103 104 105 106 107 108 109 110 5 5 &101 111 112 113 114 115 116 117 118 119 120 &105 7 Variáveis:main A 101 int B 105 int Variáveis:troca a 109 int b 111 int temp 113 int Tela: a=5 b=7 Figura 361 – As variáveis a e b da função principal estão com os valores trocados Nesse exemplo, vimos que a passagem de valor por referência permite que variáveis criadas em uma função possam ser alteradas fora dela. Essa técnica se torna importante, principalmente, pelo fato de a linguagem C não passar vetores e matrizes como parâmetros de uma função. 6.4 Passagem de vetores para funções Como dito, a linguagem C não passa vetores como parâmetros de função; assim, a maneira que temos para fazê-lo é passando o endereço da primeira posição do vetor. Dessa forma, para receber um vetor, devemos colocar uma declaração do mesmo tipo do vetor com a indicação de que é um ponteiro (usando o *). Exemplo de aplicação Dado o programa a seguir: include <stdio.h> void incrvetor (int n, int *v) { int i; for (i = 0; i < n; i++) v[i]++; } void main ( ) { int a[ ] = {1, 3, 5}; incrvetor(3, a); printf(“%d %d %d \n”, a[0], a[1], a[2]); } 348 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Na função main() é criado o vetor inteiro a[], que será passado como parâmetro para a função incvetor(). Assim, a função recebe o endereço do vetor a pela declaração int * v. Nesse caso, o vetor é chamado de a na função main() e, dentro da função incvetor(), é tratado como v. Como ambos têm o mesmo endereço, ambos se referem ao mesmo espaço de memória. O resultado desse programa é: Figura 362 – Saída do programa com passagem de vetor na função O programa passa como parâmetros um número e um vetor; este tem como elementos os valores {1, 3, 5}. A função recebe o endereço do vetor, refere-se a ele como v e acrescenta 1 a cada elemento; assim, o vetor é alterado para {2, 4, 6}. Lembrete As bibliotecas de funções são anexadas ao programa principal pela diretiva de pré-compilação #include. Vimos até agora as bibliotecas stdio.h e string.h. 6.5 Funções da biblioteca-padrão Muitas vezes, o uso de vetores e matrizes fica limitado pela necessidade de sabermos antecipadamente a quantidade de elementos que serão necessários. O interessante seria termos condições de criar e destruir elementos sem a limitação (apenas da máquina, e não da lógica), conforme forem surgindo as necessidades de uso. A biblioteca stdlib.h possui algumas funções que nos permitem criar e trabalhar dinamicamente, ou seja, durante a execução de um certo trecho do programa. Para isso, precisamos entender algumas funções e utilizar os conhecimentos do uso da memória que vimos até agora. A melhor forma de usarmos todas as funções necessárias a partir deste momento do curso é mostrando que, fazendo manualmente, o trecho int *v; v = (int*) malloc(10*sizeof(int)); 349 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO é equivalente a simplesmente declararmos: int v[10]; Para estudarmos com detalhes o trecho, vamos analisar minuciosamente como é escrita a segunda linha. Começamos com a memória fictícia vazia; temos o endereço começando do 101 e, logo abaixo, o seu byte. 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 Variáveis: nome endereço tipo Figura 363 – Memória fictícia vazia, começando do 101 Para termos uma referência: int *v; 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 Variáveis: *v 101 int Figura 364 – O ponteiro v é alocado na posição 101 350 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI A primeira função é o malloc(int), memory allocation ou alocação de memória. A função malloc reserva a quantidade de bytes que é passada como parâmetro e retorna o endereço em que esse espaço de memória foi reservado. Assim, se quisermos reservar o espaço de dez elementos inteiros para um vetor, imaginando que cada elemento de inteiro use 2 bytes (esse é um número puramente fictício, pois cada compilador pode ter um tamanho diferente), poderemos reservar 20 bytes de memória. malloc(20) Considerando que 20 é 10* 2, dez elementos de 2 bytes. A função devolverá o valor &103, que é o endereço em que o espaço foi alocado. 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 Variáveis: *v 101 int Figura 365 Neste momento, se fizermos v=malloc(20); o programa irá acusar erro, pois existe um conflito de informações: a variável v é um ponteiro apontando para inteiro, e o espaço de memória não corresponde à informação de um valor inteiro. Voltando ao número 20, não sabemos ao certo se a quantidade de bytes que uma variável do tipo inteiro ocupa é realmente 2 bytes. Para descobrir quantos bytes um tipo ocupa, a biblioteca nos fornece a função sizeof(tipo), tamanho de. Essa função retorna um número inteiro que um tipo ocupa, seja ele preexistente, seja criado por nós com otypedef. 351 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Assim, por exemplo, se fizermos sizeof( int ) ele devolverá a quantidade de bytes utilizados por uma variável do tipo int. Então, se quisermos um vetor de 10 elementos, inteiros, bastará fazer: 10 x sizeof(int) Assim, se colocarmos malloc(10*sizeof(int)) teremos o tamanho correto para alocarmos 10 números inteiros. 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 Variáveis: *v 101 int Figura 366 – Alocados dez números inteiros Agora o espaço alocado está correto, mas a expressão v= malloc(10*sizeof(int)) continua acusando erro, pois v é um ponteiro inteiro, e esse espaço alocado não corresponde ao de inteiro. Vimos na Unidade II um item chamado conversão de tipos, o chamado cast. Agora, isso será útil, pois transforma qualquer coisa em um tipo de variável. Nesse caso, servirá para formatar ou adequar o espaço alocado. Como o tipo de v é um ponteiro inteiro, aplicaremos a conversão do tipo int *. 352 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Assim, a expressão fica: v=(int *)malloc(10*sizeof(int)); Com isso, a informação devolvida pelo malloc fica completa: um endereço e o tipo correspondente. Ao aplicarmos o cast, a memória ficará assim: 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 &103 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 Variáveis: *v 101 int Figura 367 – Cast aplicado Para liberar um espaço de memória alocado dinamicamente, utiliza-se a função free da biblioteca stilib.h. Essa função recebe como parâmetro o ponteiro da memória a ser liberada, e o espaço alocado é liberado para outros usos futuros. Assim, para liberar o vetor v, fazemos: free(v); Observação Os ponteiros também têm uma aritmética própria. Ao fazermos uma soma de um número inteiro a um ponteiro, este apontará para o endereço com o avanço de múltiplos correspondente ao tamanho do tipo definido para ele. 353 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Assim, no exemplo anterior, podemos ter as seguintes equivalências: Tabela 12 – Relação entre o ponteiro e o índice de um vetor Expressão Fazer a expressão Equivale a fazer a expressão int *x = v+0 printf(“%d”,*x) printf(“%d”,v[0]) int *x = v+1 printf(“%d”,*x) printf(“%d”,v[1]) int *x = v+2 printf(“%d”,*x) printf(“%d”,v[2]) int *x = v+3 printf(“%d”,*x) printf(“%d”,v[3]) int *x = v+4 printf(“%d”,*x) printf(“%d”,v[4]) 6.6 Recursividade Vimos que os vetores são limitados porque, desde o começo, precisamos saber a sua dimensão, e tivemos como solução o uso da alocação dinâmica da memória, para casos em que, a cada execução do programa, as suas dimensões podem mudar. Agora existem casos em que não só a memória, mas também o próprio programa tem de se adaptar a uma situação diferente a cada momento. Não é prático alterar o programa antes de cada execução. Para solucionar alguns desses problemas, podemos utilizar a recursividade. Existem algumas situações que só podem ser contornadas por meio dela. A recursividade é a possibilidade de uma função se chamar, ou, então, voltar a ser chamada após a execução de funções que ela mesma chamou. Isso quer dizer que, a cada chamada, o programa abre um novo espaço na memória para a sua execução. A cada chamada recursiva, é guardada uma cópia dos parâmetros, de modo que não percamos os valores dos parâmetros das chamadas anteriores. Em princípio, enquanto houver memória para a recursividade, a função poderá ser reutilizada. As funções recursivas têm duas características importantes: • ponto de parada: geralmente, um limite superior ou inferior da regra geral; • regra geral: reduz a resolução do problema por meio da invocação recursiva de casos menores, resolvidos mediante a resolução de casos ainda menores, e assim sucessivamente, até atingir o ponto de parada, que finaliza o método. As funções que não são recursivas são chamadas de iterativas. Exemplo de aplicação Multiplicação de inteiro: #include <stdio.h> int mult(int x, int y){ if (y == 1){ 354 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI return(x); }else{ return(x + mult(x, y-1)); } } void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } mult(2,3) 2+mult(2,2) 2+mult(2,1) 2+ 2 2+ 4 6 Figura 368 – Modelo de chamada e retorno recursivos A função se chama sucessivamente e, a cada chamada, o valor do parâmetro y decresce, até chegar ao ponto de parada, (y==1), e passa a ter retornos sucessivos, até a primeira chamada. Fazendo o teste de mesa, temos: #include <stdio.h> int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } 355 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO #include <stdio.h> int mult(int x, int y){ } void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } Memória RAM 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 Variáveis:main mult(2,3) Figura 369 – A função main chama a função mult( ), passando como parâmetros 2 e 3 #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 111 112 113 114 115 116 117 118 119 120 Variáveis:main mult(2,3) Variáveis:mult x 101 int y 103 int Figura 370 – A função mult( ) é executada, criando na memória as variáveis x e y e recebendo os valores passados pela função main( ) 356 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 111 112 113 114 115 116 117 118 119 120 Variáveis:main mult(2,3) Variáveis:mult x 101 int y 103 int Figura 371 – Como y (na posição 103) não é 1, o programa passa para o else #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } int mult(int x, int y){if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } 357 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 2 2 111 112 113 114 115 116 117 118 119 120 Variáveis:main mult(2,3) Variáveis:mult x 101 int y 103 int mult(2,2) Figura 372 – Executando a função mult( ) No else, a primeira operação a ser executada é a chamada função mult( ), agora com os parâmetros x=2 e y-1, ou seja, 2. #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } 358 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 2 2 111 112 113 114 115 116 117 118 119 120 Variáveis:main mult(2,3) Variáveis:mult x 101 int y 103 int mult(2,2) Variáveis:mult x 105 int y 107 int Figura 373 – Uma nova função mult( ) é aberta, e são criadas as variáveis x e y na memória #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } 359 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 2 2 111 112 113 114 115 116 117 118 119 120 Variáveis:main mult(2,3) Variáveis:mult x 101 int y 103 int mult(2,2) Variáveis:mult x 105 int y 107 int Figura 374 – Como y vale 2, o else é executado #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } 360 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 2 2 111 112 113 114 115 116 117 118 119 120 Variáveis:main mult(2,3) Variáveis:mult x 101 int y 103 int mult(2,2) Variáveis:mult x 105 int y 107 int mult(2,1) Figura 375 – Uma nova instância (rotina) do mult é chamada, agora com os parâmetros 2 e 1 #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } 361 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 2 2 2 111 112 113 114 115 116 117 118 119 120 1 Variáveis:main mult(2,3) Variáveis:mult x 101 int y 103 int mult(2,2) Variáveis:mult x 105 int y 107 int mult(2,1) Variáveis:mult x 109 int y 111 int Figura 376 – Na nova instância, novos x e y são criados na memória #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } int mult(int x, int y){ if (y == 1){ return(x); 362 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI }else{ return(x + mult(x, y-1)); } } Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 2 2 2 111 112 113 114 115 116 117 118 119 120 1 Variáveis:main mult(2,3) Variáveis:mult x 101 int y 103 int mult(2,2) Variáveis:mult x 105 int y 107 int 2 Variáveis:mult x 109 int y 111 int Figura 377 – Execução do return Como o y chegou ao 1, dizemos que chegou ao ponto de parada, em que a função não é mais chamada recursivamente. Nesse ponto, o programa executa o return(x), ou seja, retorna o conteúdo de x, que é 2, para a função que o chamou. #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } int mult(int x, int y){ if (y == 1){ 363 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO return(x); }else{ return(x + mult(x, y-1)); } } Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 2 2 2 111 112 113 114 115 116 117 118 119 120 1 Variáveis:main mult(2,3) Variáveis:mult x 101 int y 103 int 4 Variáveis:mult x 105 int y 107 int 2 Figura 378 – Retorno do valor 4 Ao receber de volta o resultado da função mult que foi chamado, a rotina soma com x e retorna, agora, 4. #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } int mult(int x, int y){ if (y == 1){ return(x); }else{ return(x + mult(x, y-1)); } } 364 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 2 2 2 111 112 113 114 115 116 117 118 119 120 1 Variáveis:main 6 Variáveis:mult x 101 Int y 103 Int 4 Figura 379 – Retorno do valor 6 Recebendo o valor de mult, a rotina retorna x + 4, que vale 6. #include <stdio.h> void main ( ) { printf(“\n %d x %d = % d”, 2,3,mult(2,3)); } Memória RAM 101 102 103 104 105 106 107 108 109 110 2 3 2 2 2 111 112 113 114 115 116 117 118 119 120 1 Variáveis:main 6 Figura 380 – Recursivamente, há o retorno do programa ao ponto em que foi chamado No exemplo da multiplicação, o ponto de parada é >. (y == 1) Regra geral: x + mult(x, y-1) 365 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Observação Um dos exemplos clássicos de recursividade é a implementação do jogo Torre de Hanói em uma simples rotina: void moveHanoi(int ndiscos, char origem, char destino, char auxiliar){ if(ndiscos > 0) { moveHanoi(ndiscos -1,origem, auxiliar, destino); printf(“mover de %c para %c\n”,origem, auxiliar); moveHanoi(ndiscos -1, auxiliar, destino,origem); } } Uma vez determinada a quantidade de discos, e os pinos (‘a’, ‘b’, ‘c’) origem, destino e auxiliar, recursivamente, os pinos são alternados para serem movidos e esvaziados. LembreteO limite físico das funções recursivas é o amanho da memória RAM, pois, a cada nova chamada, a pilha das funções consome a memória livre. Observação A recursividade é tão importante que alguns problemas computacionais somente podem ser resolvidos por meio dela. Programas como saída de labirinto, simulação de jogos de xadrez e simulação meteolorógica são exemplos que usam a recursividade. Saiba mais Um programa para percorrer um labirinto só pode ser feito por meio da recursividade. Cada labirinto tem um caminho diferente do outro, e, para adaptar-se a todos eles, o programa deve, repetidamente, percorrer caminhos não percorridos e voltar até o ponto em que algum caminho ainda não tenha sido percorrido, em caso de não achar saída. O uso da recursividade torna-se o ideal para esse tipo de situação. 366 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI A implementação está disponível no documento de autoria do professor Fernando Vanini: VANINI, F. Funções recursivas. Unicamp, 7 ago. 2008. p. 14. Apresentação de aula. Disponível em: <http://www.ic.unicamp.br/~vanini/mc202/ apresentacoes/aula3.pdf>. Acesso em: 28 maio 2014. Resumo Nesta unidade, passamos a utilizar a linguagem como protótipo. A primeira coisa que fizemos foi ver como funcionam o gerenciamento e a alocação da memória. Toda variável criada possui três propriedades: o seu nome, o seu tipo e o endereço da memória em que está alocada. Utilizando as informações tipo e endereço, também podemos trabalhar no programa como se fosse uma variável. Isso é possível graças aos ponteiros, variáveis que têm a informação do endereço e do tipo que contém esse local. Graças a essa dupla atribuição, rotinas diferentes podem trabalhar com o mesmo dado, simultaneamente, assim como passar strings e vetores entre funções. Funções da biblioteca stdlib.h permitem criar novas variáveis dinamicamente, usando as funções malloc() e sizeof(), bem como fazendo um cast no tipo. Em seguida, estudamos a recursividade, na qual a função, em algum momento, volta a se chamar. Com as sucessivas chamadas, em algum instante, é preciso quebrar a cadeia de chamadas e retornar uma a uma as chamadas efetuadas, de trás para frente. Esse evento, que provoca a interrupção, fazendo a função parar de chamar-se, é denominado ponto de parada, e as mudanças efetuadas nos dados a cada chamada denominam-se regra geral. Vale lembrar que existem situações que somente é possível resolver por meio da recursividade. 367 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO Exercícios Questão 1. A passagem de valor por referência permite que variáveis criadas em uma função possam ser alteradas fora dela. Esta técnica passa a ser importante principalmente pelo fato de a linguagem C não passar vetores e matrizes como parâmetros de uma função. Com base nessas informações, escolha a alternativa que contém a afirmativa correta: A) A linguagem C não passa vetores como parâmetro de função, portanto a maneira que temos para fazê-lo é passando o endereço da primeira posição do vetor. Assim, para se receber um vetor, deve-se colocar uma declaração do mesmo tipo do vetor com a indicação que não é um ponteiro. B) Muitas vezes o uso de vetores e matrizes fica limitado pela necessidade de sabermos antecipadamente a quantidade de elementos que serão necessários para seu uso. Seria interessante termos condições de criar e destruir elementos com a limitação da máquina e da lógica. C) A biblioteca stdlib.h possui algumas funções que nos permitem criar e trabalhar dinamicamente, ou seja, durante a execução de um certo trecho do programa. Desta forma, não precisamos entender sobre as funções e uso da memória. D) A função malloc reserva a quantidade de bytes que é passado como parâmetro e retorna o endereço em que este espaço de memória foi reservado. Assim, se quisermos reservar o espaço para um vetor de dez elementos inteiros, imaginamos que cada elemento de inteiro use 4 bytes. E) A conversão de tipos (cast) transforma qualquer coisa em um tipo de variável. Neste caso ela servirá para formatar ou adequar o espaço alocado. Para liberar um espaço de memória alocado dinamicamente, a biblioteca oferece a função free. Esta função recebe como parâmetro o ponteiro da memória a ser liberada. Resposta correta: alternativa E. Análise das alternativas A) Alternativa incorreta. Justificativa: a linguagem C não passa vetores como parâmetro de função, portanto a maneira que temos para fazê-lo é passando o endereço da primeira posição do vetor. Assim, para receber um vetor, deve-se colocar uma declaração do mesmo tipo do vetor com a indicação que é um ponteiro. 368 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 Unidade VI B) Alternativa incorreta. Justificativa: muitas vezes o uso de vetores e matrizes fica limitado pela necessidade de sabermos antecipadamente a quantidade de elementos que serão necessários para seu uso. O interessante seria termos condições de criar e destruir elementos sem a limitação da máquina, e não a limitação da lógica. C) Alternativa incorreta. Justificativa: a biblioteca stdlib.h possui algumas funções que nos permitem criar e trabalhar dinamicamente, ou seja, durante a execução de um certo trecho do programa. Para isso precisamos entender algumas funções e utilizar os conhecimentos do uso da memória. D) Alternativa incorreta. Justificativa: a função malloc reserva a quantidade de bytes que é passado como parâmetro e retorna o endereço em que este espaço de memória foi reservado. Assim, se quisermos reservar o espaço para um vetor de dez elementos inteiros, imaginamos que cada elemento inteiro use 2 bytes. E) Alternativa correta. Justificativa: a conversão de tipos (cast) transforma qualquer coisa em um tipo de variável. Neste caso, ela servirá para formatar ou adequar o espaço alocado. Para liberar um espaço de memória alocado dinamicamente, a biblioteca oferece a função free. Essa função recebe como parâmetro o ponteiro da memória a ser liberada. Questão 2. Vetores são limitados porque desde o começo precisamos saber a sua dimensão. Temos como solução o uso da alocação dinâmica da memória para casos em que a cada execução do programa as suas dimensões possam mudar. Existem casos em que não só a memória, mas o próprio programa, têm que se adaptar a uma situação diferente a cada momento. Não é prático alterar o programa antes de cada execução. Para solucionar alguns destes problemas pode-se utilizar a recursividade. Com base nessas informações escolha a afirmativa correta: A) A recursividade é a possibilidade de uma função chamar a si mesma ou então voltar a ser chamada após a execução de funções que a própria função chamou. A cada chamada ao programa abre-se um novo espaço na memória para a sua execução. B) A cada chamada recursiva é guardada uma cópia dos parâmetros de modo a não se perderem os valores dos parâmetros das chamadas anteriores. Em princípio, enquanto não houver memória para a recursividade a função poderá ser reutilizada. C) As funções recursivas têm duas características importantes: ponto de parada (é o limite inferior ou superior da regra geral) e regra específica (reduz a resolução do problema através da invocação recursiva de casos menores). 369 Re vi sã o: Ju lia na - D ia gr am aç ão : J ef er so s / M ár ci o - 02 /0 6/ 20 14 LINGUAGEM E TÉCNICAS DE PROGRAMAÇÃO D) Com as sucessivas chamadas,em algum momento é preciso quebrar a cadeia de chamadas e retornar, uma a uma, cada chamada efetuada de frente para trás. E) Um evento recursivo, que provoca a interrupção e faz a função parar de chamar a si próprio, chama-se ponto de parada, e ao conjunto de mudanças efetuadas nos dados a cada chamada chama-se regra específica. Resolução desta questão na plataforma.
Compartilhar