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
Download

CES-10 Teoria Cap 8-b