Você está na página 1de 78

Capítulo VIII – Subprogramação

8.1 – Introdução
8.2 – Escopo de validade de declarações
8.3 – Parâmetros e passagem de argumentos
8.4 – Prototipação de subprogramas
8.5 – Classes de alocação
8.6 – Recursividade
8.3 – Parâmetros e Passagem de
Argumentos
8.3.1 – Importância do uso de parâmetros
 É comum subprogramas atuarem sobre um determinado
valor ou uma determinada variável para produzir um
resultado ou realizar uma tarefa

 Exemplos:

 Calcular o fatorial do valor guardado numa variável


 Ordenar os elementos de um vetor
 Trocar os valores de duas variáveis entre si
 Variáveis globais poderiam ser usadas
 Nos exemplos anteriores:
 O valor cujo fatorial se deseja estaria numa variável
global
 O vetor a ser ordenado seria global
 As variáveis para a troca de valores seriam também
globais
Antes de cada chamada, as variáveis
■ Mas, e se forem muitos(as): globais deveriam ser carregadas
com os alvos do subprograma
 As variáveis ou as expressões das quais se quer calcular
o fatorial?
 Os vetores a serem ordenados?
Incômodo!!!
 Os pares de variáveis a trocarem entre si seus valores?
 Para evitar esse carregamento a cada chamada, usam-se
parâmetros e argumentos

 Como já foi visto, na chamada de um subprograma, os


valores dos argumentos são calculados e armazenados cada
um em seu respectivo parâmetro

 Parâmetro é uma variável local especial de um


subprograma, também automática, distinta de suas outras
variáveis locais

 O subprograma então realiza sua tarefa, atuando


diretamente sobre seus parâmetros

 O programa do número de combinações usa parâmetros e


argumentos, dispensando variáveis globais
8.3.2 – Modos de passagem de argumentos
 As linguagens tradicionais de programação apresentam
dois importantes modos de passagem de argumentos aos
respectivos parâmetros:

 Passagem por valor


 Passagem por referência

 Passagem por valor: o argumento é o valor de uma


expressão ou de uma variável, valor esse calculado e
carregado no parâmetro

 Passagem por referência: o argumento deve ser uma


variável, sendo que o respectivo parâmetro é alocado
coincidindo com o endereço da mesma
Exemplo: passagem de argumentos em Pascal
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer); Procedimento


begin xxx embutido
x := x + 3; y := y + x; em ppp
end;

begin
a := 10; b := 20; Comandos do
xxx (a, b); programa ppp
write (“a = ”, a, “; b = ”, b);
end. A palavra var antes da
declaração de y sinaliza
que a passagem é por
referência
Seja a execução deste programa:
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer);


begin
x := x + 3; y := y + x;
end;

begin
a := 10; b := 20;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
? ?
end. a b
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer);


begin
x := x + 3; y := y + x;
end;

begin
a := 10; b := 20;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
10
? 20
?
end. a b
Chamada da procedure xxx:
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer);


begin
x := x + 3; y := y + x;
end;

begin
a := 10; b := 20;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
10 20
end. a b
Alocação dos parâmetros e passagem de argumentos:
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer);


begin
x := x + 3; y := y + x; 10
end; x

begin
a := 10; b := 20;
y
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
10 20
end. a b
O valor de a foi copiado em x
A variável y foi alocada coincidindo com b
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer);


begin
x := x + 3; y := y + x; 10
end; x

begin
a := 10; b := 20;
y
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
10 20
end. a b
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer);


begin
x := x + 3; y := y + x; 13
end; x

begin
a := 10; b := 20;
y
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
10 20
end. a b
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer);


begin
x := x + 3; y := y + x; 13
end; x

begin
a := 10; b := 20;
y
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
10 20
end. a b
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer);


begin
x := x + 3; y := y + x; 13
end; x

begin
a := 10; b := 20;
y
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
10 33
end. a b
Desalocação dos parâmetros:
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer);


begin
x := x + 3; y := y + x; 13
end; x

begin
a := 10; b := 20;
y
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
10 33
end. a b
program ppp;
var a, b: integer;

procedure xxx (x: integer; var y: integer);


begin
x := x + 3; y := y + x; a não foi alterada (por valor)
end;
b foi alterada (por referência)
begin
a := 10; b := 20;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
10 33
end. a b

No vídeo a = 10; b = 33
 Quando se deseja que o argumento sofra alterações dentro
do subprograma chamado: usar passagem por referência

 Quando o subprograma mudar o valor de um parâmetro,


mas não se deseja que isso altere o valor do argumento de
chamada: usar passagem por valor

 Quando um subprograma não altera o valor de um


parâmetro, pode-se usar os dois tipos de passagem

 Se o parâmetro for uma variável estruturada (matriz ou


estrutura), pode-se economizar memória usando passagem
por referência

 O parâmetro correspondente é alocado numa região já


utilizada pelo programa
 A Linguagem C só trabalha com passagem por valor

 A passagem por referência é simulada por argumentos


do tipo endereço e parâmetros do tipo ponteiro

 Isso é visto logo a seguir


8.3.3 – Passagem por valor em C

 A função destinada ao cálculo de fatorial vista neste


capítulo utiliza passagem por valor

 O valor do argumento é calculado e depositado no


parâmetro

 Depois da execução, mesmo que função mudasse o valor


desse parâmetro, as variáveis envolvidas no cálculo dos
argumentos não sofrem alteração
 Numa chamada de função aparece primeiramente seu
nome e, a seguir, entre parêntesis, sua lista de argumentos

 Essa lista deverá ser vazia se a função não tiver


parâmetros, mas o uso dos parêntesis é obrigatório

 Os argumentos devem ser em mesmo número que os


parâmetros e devem ser compatíveis com os mesmos

 Caso um argumento seja o nome de uma variável, apenas


seu valor é transmitido ao parâmetro

 Depois da execução, o valor dessa variável não terá sofrido


nenhuma alteração
 Exemplo: seja o programa
Resultado
Antes de ff, a = 5
#include <stdio.h>
Durante ff, a = 6
#include <stdlib.h> Depois de ff, a = 5

void ff (int a) { Digite algo para encerrar:


a += 1;
printf ("Durante ff, a = %d\n", a);
}

int main ( ) {
int a = 5;
printf ("Antes de ff, a = %d\n", a);
ff (a);
printf ("Depois de ff, a = %d\n", a);
printf ("\n\n"); system ("pause"); return 0;
}
8.3.4 – Passagem por referência em C

 Às vezes, é desejável que a variável argumento seja


alterada conforme a mudança sofrida pelo parâmetro
correspondente

 Em vez de passar o seu valor, passa-se o seu endereço

 A seguir, um programa ilustrativo para trocar os valores de


duas variáveis da função main
 Exemplo:
Seja seja o programa
sua execução:

#include <stdio.h>
#include <stdlib.h>

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}

int main ( ) {
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
Seja sua execução:

#include <stdio.h>
#include <stdlib.h>

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}
3 8
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
#include <stdio.h>
#include <stdlib.h>

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}
3 8
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
Chamada de trocar e passagem de argumentos:

#include <stdio.h> aux p q


#include <stdlib.h>

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}
3 8
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
p e q receberam &i e &j - são ponteiros
#include <stdio.h> aux p q
#include <stdlib.h>

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}
3 8
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
#include <stdio.h> aux p q
#include <stdlib.h> 3

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}
3 8
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
aux recebe conteúdo do local apontado por p (i.é, *p)
#include <stdio.h> aux p q
#include <stdlib.h> 3

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}
3 8
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
#include <stdio.h> aux p q
#include <stdlib.h> 3

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}
8 8
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
*p recebe conteúdo do local apontado por q (*q)
#include <stdio.h> aux p q
#include <stdlib.h> 3

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}
8 8
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
#include <stdio.h> aux p q
#include <stdlib.h> 3

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}
8 3
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
*q recebe conteúdo de aux
Desalocação das variáveis de Trocar:

#include <stdio.h> aux p q


#include <stdlib.h> 3

void trocar (int *p, int *q){


int aux;
aux = *p; *p = *q; *q = aux;
}
8 3
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
Antes de trocar, i = 3; j = 8
Depois de trocar, i = 8; j = 3
#include <stdio.h>
#include <stdlib.h> Pressione ...

Resultado
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
8 3
int main ( ) { i j
int i = 3, j = 8;
printf ("Antes de trocar, i = %d; j = %d\n", i, j);
trocar(&i, &j);
printf ("Depois de trocar, i = %d; j = %d\n", i, j);
printf ("\n\n"); system ("pause"); return 0;
}
Exercícios 8.3:
#include <stdio.h>
int i = 58, j = 49;
void gg (int i, int j, int k, int m) {
1. Dado o programa ao lado printf ("%7d%7d%7d%7d\n\n", i, j, k, m);
}
contendo variáveis void ff (int p, int q, int *r, int *s) {
globais, locais e funções int k, m;
gg (p, q, *r, *s); k = 100; m = 200;
com passagem de p = -1; q = -2; *r = -3; *s = -4;
argumentos por valor e gg (i, j, k, m); gg (p, q, *r, *s);
}
por referência, mostrar o main () {
que será escrito no vídeo int i, j, k, m;
i = 10; j = 20; k = 30; m = 40; gg (i, j, k, m);
pela sua execução {
int j, k;
i = 1; j = 2; k = 3; m = 4;
gg (i, j, k, m); ff (i, j, &k, &m);
}
gg (i, j, k, m);
}
2. A conjectura de Goldbach diz que todo número inteiro,
par, maior que 2, é a soma de dois números primos.
Computadores têm sido muito usados para testar essa
conjectura e nenhum contra-exemplo foi encontrado até
agora.
a) Escrever uma função que receba como argumento por
valor um número inteiro positivo e que retorne 1, se tal
número for primo, ou então zero, em caso contrário.

b) Escrever um programa principal para comprovar que tal


conjectura é verdadeira dentro de um intervalo lido, no
campo dos inteiros maiores que 2, usando
obrigatoriamente como subprograma a função elaborada
no item a deste exercício. Por exemplo, se o intervalo lido
for [700, 1100], o programa pedido deve ser produzir no
vídeo um resultado semelhante ao do próximo slide
No intervalo [ 700, 1100 ], todo número par é a soma de
dois primos, a saber:
 
700 = 17 + 683
702 = 11 + 691
704 = 3 + 701
. . . . . . .

1098 = 5 + 1093
1100 = 3 + 1097
Capítulo VIII – Subprogramação

8.1 – Introdução
8.2 – Escopo de validade de declarações
8.3 – Parâmetros e passagem de argumentos
8.4 – Prototipação de subprogramas
8.5 – Classes de alocação
8.6 – Recursividade
8.4 – Prototipação de Subprogramas
 Nos programas em C, funções devem ser declaradas antes
de serem invocadas

 Há uma diferença entre declarar e definir uma função

 Declarar: dizer o nome da função, o tipo de seus


parâmetros e o tipo do valor por ela retornado

 Definir: programar a função, ou seja, declará-la,


estabelecer suas declarações locais e seus comandos

 Até agora: declaração das funções no ato de sua definição


 Há situações em que é interessante ou até necessário que
uma função seja invocada antes de ser definida

 É o caso de programas recursivos, que são assuntos do


último tópico deste capítulo
Exemplo: recursividade - - - - -
int F1 (int a, float x) {
indireta: - - - - -
chamada de F2()
- F2 invoca F1 - - - - -
- F1 invoca F2 antes da }
int F2 (float y, char c )
declaração de F2 {
- - - - -
- O compilador não aceitará
chamada de F1()
- Invertidas as funções, o - - - - -
}
problema continua
Solução: Protótipos para funções
int
- - F2-(float,
- - char);
int
- - F1-(int
- - a, float x) {
Protótipo de uma função: é a - - - - - - - -
declaração da função feita - - - chamada
- - de F2()
separadamente de sua definição int F1-(int
- - -
a,-float x) {
} - - - - -
No ponto em que F1 invoca F2, essa int F2chamada
(float de
y, F2()
char c )
{ - - - - -
última já está declarada acima } - - - - -
int F2chamada
(float de
y, F1()
char c )
Não é necessário colocar os nomes { - - - - -
dos parâmetros, mas somente os } - - - - -
tipos chamada de F1()
- - - - -
Na definição, todos os tipos devem }
ser os mesmos do protótipo

Forma geral de um protótipo:


Tipo Nome (lista dos tipos dos parâmetros);
 Protótipos para as funções usadas nos programas deste
capítulo:

 int fat (int);


 void sss (void);
 void ff (void);
 void ff (int);
 void trocar (int *, int *);

 Quando a função não tem parâmetros, coloca-se void


entre parêntesis

 Quando o parâmetro é um ponteiro, coloca-se o asterisco


‘*’ depois do tipo
 Pode-se adquirir o hábito de fazer
protótipos para todas a funções Diretivas de pré-
auxiliares dos programas processamento
Declarações
globais
 Eles podem ser colocados no
Protótipos das
início, juntamente com as funções
declarações globais
Funções
auxiliares
 Então a ordem das definições das Função
funções pode ser qualquer main

Programa
 Pode-se organizar o programa de
forma a colocar primeiramente a Diretivas de pré-
função main processamento
Declarações
 Depois vêm aquelas invocadas pela globais
main; depois, aquelas invocadas por Protótipos das
essas últimas funções
Função
 E assim por diante main
Funções
 Essa ordenação é interessante ao se auxiliares
utilizar a metodologia top-down
para o desenvolvimento de Programa
programas
No capítulo sobre ponteiros serão vistos protótipos de funções
com parâmetros do tipo variáveis indexadas, estruturas e funções
Capítulo VIII – Subprogramação

8.1 – Introdução
8.2 – Escopo de validade de declarações
8.3 – Parâmetros e passagem de argumentos
8.4 – Prototipação de subprogramas
8.5 – Classes de alocação
8.6 – Recursividade
8.5 – Classes de Alocação
8.5.1 - Generalidades

 Toda variável e função em C tem, além do tipo, outro


atributo, relacionado com a forma de ser alocada durante a
execução do programa

 Esse atributo é a classe de alocação


 Há 4 classes de alocação de variáveis e funções, a saber:

variáveis automáticas, externas, estáticas e em


registradores

 As palavras reservadas para elas são respectivamente

auto, extern, static e register.

 Essas palavras são colocadas antes do tipo, numa


declaração de variáveis e função

 Exemplos:

extern int i; static float a; register int j;


 Como já visto, as variáveis automáticas só ocupam espaço
na memória quando seu bloco de declaração está no ar

 Todas as variáveis locais vistas até agora neste capítulo são


automáticas

 Quando não se especifica a classe de uma variável local, o


compilador entende que ela é automática

 Então, como já foi visto:


int a, b; equivale a auto int a, b;

 Por essa razão, a palavra reservada auto é raramente usada


nos programas
8.5.2 – Variáveis externas

 Em C, toda variável declarada fora do escopo de qualquer


função, ou seja, toda variável global, pertence à classe das
variáveis externas

 Ela ocupa espaço de memória durante toda a execução do


programa

 A palavra extern pode ser usada em sua declaração, mas na


maioria dos casos é dispensável

 Se int a = 1; for uma declaração global então ela


equivale a extern int a = 1;
 O principal uso da palavra extern se dá em programas
divididos em mais de um arquivo

 Muitas vezes é desejável compilar cada arquivo


separadamente

 Exemplo: seja o programa a seguir, dividido em dois


arquivos arq1.c e arq2.c
arq1.c arq2.c
#include <stdio.h> int f () {
#include <conio.h> extern int a;
#include "arq2.c" int b, c;
int a = 1, b = 2, c = 3; a = b = c = 4;
int main ( ) { return (a + b + c);
printf ("%3d\n", f( )); }
printf ("%3d%3d%3d\n", a, b, c);
getch();
}
arq1.c arq2.c
#include <stdio.h> int f () {
#include <conio.h> extern int a;
#include "arq2.c" int b, c;
int a = 1, b = 2, c = 3; a = b = c = 4;
int main ( ) { return (a + b + c);
printf ("%3d\n", f()); }
printf ("%3d%3d%3d\n", a, b, c);
getch(); 12 No
} 4 2 3 vídeo

 Na compilação separada do arquivo arq2.c, a declaração


extern int a;
indica que a variável a é global e está declarada em outro
arquivo

 Para uma compilação conjunta, esta declaração é


dispensável
 A habilidade em compilar arquivos separadamente é
importante ao ser escrever grandes programas

 O código-fonte desses programas costuma ser organizado


em diversos arquivos

 Cada arquivo pode conter uma ou mais funções, ou somente


declarações globais, ou protótipos de funções, etc.

 Ocorrendo erros de compilação, só os arquivos com erros


precisam ser re-compilados

 Os arquivos corretos são dispensados disso e, sendo


numerosos, ganha-se tempo com essa dispensa
8.5.3 – Comunicação entre funções

 Funções se comunicam entre si através de variáveis


globais, parâmetros por valor, parâmetros por referência e
valores retornados
a Exemplo
int a;

b c x y z
int main () { d int ff (int x, int *y) {
int b, c, d; int z;
a = 10; b = 20; c = 30; z = x + a + *y;
d = ff (b, &c); a = 1; *y = 2;
printf (“- - -”, a, b, c, d); return z;
} }
 Variáveis globais: comunicação nos 2 sentidos
 Parâmetros por valor: da invocadora para a invocada
 Parâmetros por referência: comunicação nos 2 sentidos
 Valores retornados: da invocada para a invocadora
a
int a;

b c x y z
int main () { d int ff (int x, int *y) {
int b, c, d; int z;
a = 10; b = 20; c = 30; z = x + a + *y;
d = ff (b, &c); a = 1; *y = 2;
printf (“- - -”, a, b, c, d); return z;
} } retorno
 Variáveis globais dificultam a modularidade e
portabilidade das funções

 Parâmetros e valores retornados são decididamente


preferíveis, quando isso é desejado

 Analogia: aparelho de som para automóveis

 Má portabilidade: com muitos cabos elétricos do carro


a ele conectados, haverá dificuldade para retirá-lo e
colocá-lo em outro carro

 Boa portabilidade: se não houver cabos a ele


conectados, essa dificuldade é nula
 Uma função com variáveis globais a ser disponibilizada
para a comunidade exige de quem vai utilizá-la:

 Conhecimento da existência dessas variáveis

 Adição das mesmas ao conjunto de variáveis globais de


seu programa

 Uma função sem variáveis globais é muito mais simples de


ser reutilizada
 Há casos no entanto, em que o uso de variáveis globais é
providencial

 Um grande programa com inúmeros subprogramas


atuando numa mesma grande base de dados

 As variáveis dessa base podem ser globais

 Não haverá a necessidade de, na maioria das chamadas


de subprogramas, fazer passagem de argumentos

 Evita-se transporte desnecessário de informações entre


os subprogramas, mesmo que seja apenas de ponteiros
Programa-fonte
Analisador while (i < nn) i = i + j; (caracteres)
léxico
while ( i < nn ) Sequência de
Analisador átomos
sintático i = i + j ;

i int --- Árvore


Analisador while
nn int --- sintática
semântico
j int ---
< =
Gerador de Tabela de
código símbolos Código
i nn i +
objeto
intermediário
load i
R1: T1 = i < i nnj
R1: sub
Otimizador de nn
R1: T1 = i <
JZ R2
código nn JF T1 R2
Código JP R2
intermediário T2 T1
JF = iR2+ j
intermediário load i
T2+ j
i = i
add j
JUMP R1
Gerador de st i
R2: - - - - - Exemplo: um
J R1
código objeto
compilador
R2: - - - - -
Programa-fonte
Analisador while (i < nn) i = i + j; (caracteres)
léxico

Analisador Seria incômodo e dispendioso o trânsito


sintático dos átomos, da árvore sintática, da tabela
de símbolos e do código intermediário
Analisador
pelas funções do compilador
semântico

Gerador de
código Código
intermediário Fica a cargo do programador objeto
escolher o melhor modo de load i
R1: sub nn
Otimizador de comunicação entre os JZ R2
código módulos de seu programa JZ R2
intermediário load i
add j
Gerador de st i
J R1
código objeto
R2: - - - - -
8.5.4 – Variáveis em registradores

 A Linguagem C permite que o programador expresse sua


preferência por registradores, na alocação de uma
variável

 Registradores da CPU são de acesso muito mais rápido


que o das palavras da RAM

 Assim, os programas poderão ficar mais rápidos

 A declaração register int i;


indica que a variável inteira i deve, se possível, ser
alocada num registrador
 Há limitação para o uso de registradores nos programas

 Devido à sua sofisticada e cara tecnologia, eles são em


número muito menor do que o das palavras da RAM

 Nem todas as variáveis de um grande programa poderão


caber no conjunto de registradores de um computador

 Portanto o programador deve escolher as variáveis mais


referenciadas para serem alocadas em registradores

 Fortes candidatas para essa alocação são as variáveis


usadas em controle de laços

 Variáveis em registradores, tais como as automáticas, só


permanecem alocadas durante a execução de seu bloco
8.5.5 – Variáveis estáticas

 A declaração static int a;

diz que a variável inteira i é estática

 Variáveis estáticas podem ser locais ou externas

 Diferentes propriedades e utilidades têm as variáveis


estáticas locais e externas

 Uma variável estática local tem seu valor conservado entre


duas execuções consecutivas de seu bloco de declaração

 Ao contrário, as variáveis automáticas perdem esse valor


Exemplo: seja o programa:
#include <stdio.h>
Variáveis estáticas locais
#include <stdlib.h>
são de uso privativo de seu
int i;
bloco de declaração

void f () {
static int a = 0; int b = 5;
printf ("a = %3d; b = %3d;", a, b);
b = i + 10;
printf (" b = %3d;\n", b);
Resultado
a += 3;
}
int main () {
for (i = 1; i <= 10; i++)
f ();
system ("pause");
return 0;
}
 Variáveis estáticas externas oferecem um importante
serviço de privacidade, fundamental para a modularização
de programas

 Seu escopo de validade começa no ponto da declaração e


vai somente até o final de seu arquivo

 Se o programa tiver outros arquivos, essa variável não é


visível em suas funções

 Então, pode-se escrever um módulo com um conjunto de


funções que tenham uso privativo de certas variáveis

 Desse conjunto, uma função pode ser a líder e as outras


podem ser suas auxiliares
Capítulo VIII – Subprogramação

8.1 – Introdução
8.2 – Escopo de validade de declarações
8.3 – Parâmetros e passagem de argumentos
8.4 – Prototipação de subprogramas
8.5 – Classes de alocação
8.6 – Recursividade
8.6 – Recursividade

8.6.1 – Definições matemáticas recursivas

 Recursividade é um expediente muito usado para se


estabelecer certas definições matemáticas

 Uma entidade de uma certa classe é definida em função de


entidades menores da mesma classe
 Exemplo 1: seja Sn a soma dos n primeiros inteiros
positivos
1, para n = 1
n + Sn-1, para n > 1
Sn =
S 5 = 5 + S4
Para n = 5: = 5 + (4 + S3)
= 5 + (4 + (3 + S2))
= 5 + (4 + (3 + (2 + S1)))
= 5 + (4 + (3 + (2 + 1)))
= 5 + (4 + (3 + 3))
= 5 + (4 + 6)
= 5 + 10
= 15
 Exemplo 2: cálculo de fatoriais

-1, para n < 0


n! =
1, para 0 ≤ n ≤ 1
n * (n-1)!, para n > 1

Para n = 5: 5! = 5 * 4!
= 5 * (4 * 3!)
= 5 * (4 * (3 * 2!))
= 5 * (4 * (3 * (2 * 1!)))
= 5 * (4 * (3 * (2 * 1)))
= 5 * (4 * (3 * 2))
= 5 * (4 * 6)
= 5 * 24
= 120
 Exemplo 3: cálculo do mdc de números não-negativos

∞, para m = 0 e n = 0
m, para m > 0 e n = 0
mdc (m, n) =
n, para m = 0 e n > 0
mdc (n, m%n), para m e n > 0

mdc (48, 64) = mdc (64, 48)


= mdc (48, 16)
Para m = 48 e n = 64:
= mdc (16, 0)
= 16
8.6.2 – Subprogramas recursivos

 Subprograma recursivo: invoca a si próprio direta ou


indiretamente

 Recursividade direta: um subprograma invoca a si próprio


em seus próprios comandos

 Recursividade indireta: um primeiro subprograma invoca


outro, que invoca outro, ... , que invoca outro, que invoca o
primeiro
#include <stdio.h> Exemplo 1: cálculo de fatoriais
#include <stdlib.h>
-1, para n < 0
int fat (int n) { n! = 1, para 0 ≤ n ≤ 1
int f; n * (n-1)!, para n > 1
if (n < 0) f = -1;
else if (n <= 1) f = 1;
else f = n * fat(n-1);
return f;
}

int main() {
char c; int n;
printf ("Calculo do fatorial de n");
printf ("\n\n\tDigite n: "); scanf ("%d", &n);
printf ("\n\tFat(%d) = %d", n, fat(n));
printf ("\n\n"); system ("pause"); return 0;
}
#include <stdio.h> Exemplo 2: cálculo de mdc’s
#include <stdlib.h>
int mdc (int m, int n) { ∞, p/ m = 0 e n = 0
int r; mdc (m, n) = m, p/ m > 0 e n = 0
m = abs(m); n = abs(n); n, p/ m = 0 e n > 0
if (m==0 && n==0) r = -1; mdc (n, m%n),
else if (m == 0) r = n; p/ m e n > 0
else if (n == 0) r = m;
else r = mdc(n, m%n);
return r;
}
int main() {
char c; int m, n;
printf ("Calculo do mdc de m e n");
printf ("\n\n\tDigite m e n: "); scanf ("%d%d", &m, &n);
printf ("\n\tmdc(%d, %d) = %d", m, n, mdc(m, n));
printf ("\n\n"); system ("pause"); return 0;
}
8.6.3 – Execução de subprogramas recursivos

 Quando um subprograma invoca a si mesmo, inicia-se uma


nova execução (nova versão) desse subprograma

 Porém a versão que faz a invocação continua ativa

 Em programas recursivos, pode haver várias versões ativas


de um mesmo subprograma recursivo

 Cada qual com suas variáveis locais

 As variáveis de versões diferentes têm o mesmo nome mas


são entidades distintas
#include <stdio.h>
#include <stdlib.h>
Exemplo: execução da
função fat recursiva
int fat (int n) {
int f;
if (n < 0) f = -1;
else if (n <= 1) f = 1;
else f = n * fat(n-1);
return f;
}

int main() {
char c; int n;
printf ("Calculo do fatorial de n");
printf ("\n\n\tDigite n: "); scanf ("%d", &n);
printf ("\n\tFat(%d) = %d", n, fat(n));
printf ("\n\n"); system ("pause"); return 0;
}
#include <stdio.h>
#include <stdlib.h>

int fat (int n) {


int f;
if (n < 0) f = -1;
else if (n <= 1) f = 1;
else f = n * fat(n-1);
return f;
}

int main() {
char c; int n;
printf ("Calculo do fatorial de n");
printf ("\n\n\tDigite n: "); scanf ("%d", &n); 5
printf ("\n\tFat(%d) = %d", n, fat(n));
n
printf ("\n\n"); system ("pause"); return 0;
}
Valor digitado: 5
#include <stdio.h>
#include <stdlib.h>
fat – v1 fat – v2 fat – v3

int fat (int n) { n 5 n 4 n 3


int f;
f 120 f 24 f 6
if (n < 0) f = -1;
f = 5*fat(4) f = 4*fat(3) f = 3*fat(2)
else if (n <= 1) f = 1;
else f = n * fat(n-1);
return f;
fat – v5 fat – v4
}
n 1 n 2
int main() {
f 1 f 2
char c; int n;
f=1 f = 2*fat(1)
printf ("Calculo do fatorial de n");
printf ("\n\n\tDigite n: "); scanf ("%d", &n); 5
printf ("\n\tFat(%d) = %d", n, fat(n));
n
printf ("\n\n"); system ("pause"); return 0;
}
Valor digitado: 5
Observação: funções recursivas envolvendo vetores,
matrizes e estruturas serão apresentadas no próximo
capítulo
Exercícios 8.6:

1. Uma importante função teórica conhecida como função de


Ackermann tem a seguinte formulação recursiva:

Ackermann (m, n) =

Escrever uma função inteira recursiva em C que tenha como


parâmetros duas variáveis inteiras longas m e n e que
calcule e retorne o valor de Ackermann (m, n), utilizando a
definição acima. Tal função deve retornar -1 (menos 1) se o
valor de m ou de n forem negativos

Você também pode gostar