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
■ Mas, e se forem muitos(as):
Antes de cada chamada, as variáveis
globais deveriam ser carregadas
com os alvos do subprograma
As variáveis ou as expressões das quais se quer calcular o
fatorial?
Incômodo!!!
Os vetores a serem ordenados?
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);
begin
x := x + 3; y := y + x;
end;
begin
a := 10; b := 20;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
end.
Procedimento
xxx embutido
em ppp
Comandos do
programa ppp
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);
end.
10
?
a
20
?
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);
end.
10
a
20
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;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
end.
10
a
y
20
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;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
end.
10
a
y
20
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;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
end.
10
a
y
20
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;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
end.
10
a
y
20
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;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
end.
10
a
y
33
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;
xxx (a, b);
write (“a = ”, a, “; b = ”, b);
end.
10
a
y
33
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);
end.
10
a
No vídeo a = 10; b = 33
33
b
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
#include <stdio.h>
#include <stdlib.h>
Resultado
Antes de ff, a = 5
Durante ff, a = 6
Depois de ff, a = 5
Digite algo para encerrar:
void ff (int a) {
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
Seja
Exemplo:
sua execução:
seja o programa
#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
i
8
j
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;
}
#include <stdio.h>
#include <stdlib.h>
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
3
i
8
j
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;
}
Chamada de trocar e passagem de argumentos:
#include <stdio.h>
#include <stdlib.h>
aux
p
q
3
i
8
j
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;
}
p e q receberam &i e &j - são ponteiros
#include <stdio.h>
#include <stdlib.h>
aux
p
q
3
i
8
j
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;
}
#include <stdio.h>
#include <stdlib.h>
aux
3
p
q
3
i
8
j
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;
}
aux recebe conteúdo do local apontado por p (i.é, *p)
#include <stdio.h>
#include <stdlib.h>
aux
3
p
q
3
i
8
j
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;
}
#include <stdio.h>
#include <stdlib.h>
aux
3
p
q
8
i
8
j
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;
}
*p recebe conteúdo do local apontado por q (*q)
#include <stdio.h>
#include <stdlib.h>
aux
3
p
q
8
i
8
j
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;
}
#include <stdio.h>
#include <stdlib.h>
aux
3
p
q
8
i
3
j
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;
}
*q recebe conteúdo de aux
Desalocação das variáveis de Trocar:
#include <stdio.h>
#include <stdlib.h>
aux
3
p
q
8
i
3
j
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;
}
#include <stdio.h>
#include <stdlib.h>
Antes de trocar, i = 3; j = 8
Depois de trocar, i = 8; j = 3
Pressione ...
Resultado
void trocar (int *p, int *q){
int aux;
aux = *p; *p = *q; *q = aux;
}
8
i
3
j
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;
}
Exercícios 8.3:
1. Dado o programa ao
lado contendo variáveis
globais, locais e funções
com passagem de
argumentos por valor e
por referência, mostrar o
que será escrito no vídeo
pela sua execução
#include <stdio.h>
int i = 58, j = 49;
void gg (int i, int j, int k, int m) {
printf ("%7d%7d%7d%7d\n\n", i, j, k, m);
}
void ff (int p, int q, int *r, int *s) {
int k, m;
gg (p, q, *r, *s); k = 100; m = 200;
p = -1; q = -2; *r = -3; *s = -4;
gg (i, j, k, m); gg (p, q, *r, *s);
}
main () {
int i, j, k, m;
i = 10; j = 20; k = 30; m = 40; gg (i, j, k, m);
{
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
702
704
. .
= 17 + 683
= 11 + 691
= 3 + 701
. . . . .
1098 =
1100 =
5 + 1093
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
indireta:
- F2 invoca F1
- F1 invoca F2 antes da
declaração de F2
- O compilador não aceitará
- Invertidas as funções, o
problema continua
- - - - int F1 (int a, float x) {
- - - - chamada de F2()
- - - - }
int F2 (float y, char c )
{
- - - - chamada de F1()
- - - - }
Solução: Protótipos para funções
int
int
Protótipo de uma função: é a
- - declaração da função feita
int
separadamente de sua definição
}
No ponto em que F1 invoca F2, essa int
{
última já está declarada acima
}
int
Não é necessário colocar os nomes {
dos parâmetros, mas somente os
}
tipos
Na definição, todos os tipos devem
ser os mesmos do protótipo
F2-(float,
char);
F1-(int
a, float x)
- - - - - - chamada
de F2()
F1-(int
- - a,
- -float x)
- - - - F2chamada
(float y,
de F2()
char c
- - - - - - - - F2chamada
(float y,
de char
F1() c
- - - - - - - - chamada de F1()
- - - - -
}
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
auxiliares dos programas
Eles podem ser colocados no
início, juntamente com as
declarações globais
Então a ordem das definições das
funções pode ser qualquer
Diretivas de préprocessamento
Declarações
globais
Protótipos das
funções
Funções
auxiliares
Função
main
Programa
Pode-se organizar o programa de
forma a colocar primeiramente a
função main
Depois vêm aquelas invocadas pela
main; depois, aquelas invocadas por
essas últimas
E assim por diante
Essa ordenação é interessante ao se
utilizar a metodologia top-down para
o desenvolvimento de programas
Diretivas de préprocessamento
Declarações
globais
Protótipos das
funções
Função
main
Funções
auxiliares
Programa
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
#include <stdio.h>
#include <conio.h>
#include "arq2.c"
int a = 1, b = 2, c = 3;
int main ( ) {
printf ("%3d\n", f( ));
printf ("%3d%3d%3d\n", a, b, c);
getch();
}
arq2.c
int f () {
extern int a;
int b, c;
a = b = c = 4;
return (a + b + c);
}
arq1.c
#include <stdio.h>
#include <conio.h>
#include "arq2.c"
int a = 1, b = 2, c = 3;
int main ( ) {
printf ("%3d\n", f());
printf ("%3d%3d%3d\n", a, b, c);
getch();
}
arq2.c
int f () {
extern int a;
int b, c;
a = b = c = 4;
return (a + b + c);
}
12
4
2
3
No
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
int main () {
d
int b, c, d;
a = 10; b = 20; c = 30;
d = ff (b, &c);
printf (“- - -”, a, b, c, d);
}
x
y
int ff (int x, int *y) {
int z;
z = x + a + *y;
a = 1; *y = 2;
return z;
}
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
int main () {
d
int b, c, d;
a = 10; b = 20; c = 30;
d = ff (b, &c);
printf (“- - -”, a, b, c, d);
}
x
y
int ff (int x, int *y) {
int z;
z = x + a + *y;
a = 1; *y = 2;
return z;
}
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
while (i < nn) i = i + j;
Analisador
léxico
while
Analisador
sintático
Analisador
semântico
Gerador de
código
intermediário
Otimizador de
código
intermediário
Gerador de
código objeto
Programa-fonte
(caracteres)
i
(
=
i
int
---
nn
int
---
j
int
---
Tabela de
símbolos
i
i
<
+
nn
j
)
Sequência de
átomos
;
Árvore
sintática
while
<
i
=
nn
i
Código
+
objeto
load i
R1: T1 = i < nn
i nnj
R1: sub
JF =
T1iR2
R1: T1
< nn
JZ R2
T2
=
i
+
j
JF T1 R2
JP R2
Código
T2+ j
i = i
load i
intermediário
JUMP R1
add j
R2: - - - - st i
Exemplo:
um
J R1
R2:
- - - - compilador
Analisador
léxico
Analisador
sintático
Analisador
semântico
Gerador de
código
intermediário
Otimizador de
código
intermediário
Gerador de
código objeto
while (i < nn) i = i + j;
Programa-fonte
(caracteres)
Seria incômodo e dispendioso o trânsito
dos átomos, da árvore sintática, da tabela
de símbolos e do código intermediário
pelas funções do compilador
Fica a cargo do programador
escolher o melhor modo de
comunicação entre os
módulos de seu programa
Código
objeto
load i
R1: sub nn
JZ R2
JZ R2
load i
add j
st i
J R1
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>
#include <stdlib.h>
int i;
Variáveis estáticas locais
são de uso privativo de seu
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);
a += 3;
}
int main () {
for (i = 1; i <= 10; i++)
f ();
system ("pause");
return 0;
}
Resultado
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
Sn =
Para n = 5:
1, para n = 1
n + Sn-1, para n > 1
S5 = 5 + S 4
= 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
n! =
Para n = 5:
-1, para n < 0
1, para 0 ≤ n ≤ 1
n * (n-1)!, para n > 1
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
mdc (m, n) =
∞, para m = 0 e n = 0
m, para m > 0 e n = 0
n, para m = 0 e n > 0
mdc (n, m%n), para m e n > 0
Para m = 48 e n = 64:
mdc (48, 64) = mdc (64, 48)
= mdc (48, 16)
= 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>
#include <stdlib.h>
int fat (int
int f;
if (n < 0)
else if (n
else f = n
return f;
}
n) {
Exemplo 1: cálculo de fatoriais
n! =
-1, para n < 0
1, para 0 ≤ n ≤ 1
n * (n-1)!, para n > 1
f = -1;
<= 1) f = 1;
* fat(n-1);
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>
int fat (int
int f;
if (n < 0)
else if (n
else f = n
return f;
}
Exemplo: execução da
função fat recursiva
n) {
f = -1;
<= 1) f = 1;
* fat(n-1);
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
int f;
if (n < 0)
else if (n
else f = n
return f;
}
n) {
f = -1;
<= 1) f = 1;
* fat(n-1);
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;
}
5
n
Valor digitado: 5
#include <stdio.h>
#include <stdlib.h>
int fat (int
int f;
if (n < 0)
else if (n
else f = n
return f;
}
n) {
f = -1;
<= 1) f = 1;
* fat(n-1);
fat – v1
fat – v2
fat – v3
n
n
4
n
3
f
24
f
6
5
f 120
f = 5*fat(4)
f = 4*fat(3)
f = 3*fat(2)
fat – v5
fat – v4
n
n
1
2
int main() {
f 1
f 2
char c; int n;
f = 2*fat(1)
printf ("Calculo do fatorial de n"); f = 1
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