1 É mais difícil comandar do que
obedecer. F. Nietzsche.
PASSAGEM DE PARÂMETROS
C
omo visto no laboratório 9, uma função é composta por quatro partes distintas:• Nome da função
• Tipo de retorno
• Lista de parâmetros
• Corpo da função
PT1: Criar um programa que possui uma
função Maior cuja lista de parâmetros contém dois números inteiros a e b e que retorna o resultado de max{a,b}.
#include <stdlib.h> #include <stdio.h>
int Maior(int a, int b)
{ return (a > b) ? a:b; } main() { int a, b, max;
printf(“Entre com a e b:”); scanf(“%d %d”,&a,&b); max = Maior(a,b);
printf(“Max{a,b} = %d \n”,max); }
A função Maior do PT1 possui as seguintes propriedades:
Nome da função Maior Tipo de retorno int Lista de parâmetros int a, int b
Corpo da função return (a>b)? a:b;
Observe que o número e o tipo dos argumentos passados à função devem corresponder ao número e ao tipo dos parâmetros com que esta foi definida. Uma observação importante é que uma função que não retorne qualquer tipo, isto é que retorne void, denomina-se de procedimento.
PE1: Modifique a função Maior do PT1
para que ao invés de retornar o maior entre dois valores, apenas imprima na tela o maior valor, e portanto tenha tipo de retorno igual a void.
PASSAGEM POR VALOR
A
troca de valores entre variáveis é um problema freqüente de programação e pode ser usado na ordenação de um vetor. Uma troca é realizada em três passos e sempre com o auxílio de uma variável auxiliar.PT2: Crie um programa com uma função
troca que realiza a troca de valores entre duas variáveis.
#include <stdio.h>
void troca(int a, int b); // protótipo
main() { int a, b;
puts(“Dois nums inteiros: ”); scanf(“%d %d”, &a, &b); printf(“Antes de troca \n”); printf(“a= %d e b=%d \n”,a,b); troca(a,b);
printf(“Depois de troca \n”); printf(“a= %d e b=%d \n”,a,b); }
void troca(int a, int b)
{ int tmp; tmp = a; a = b; b = tmp; }
Observe que no PT2 não é realizada a troca de valores. Para entender o que aconteceu, é necessário compreender dois conceitos:
• Escopo de variáveis.
• Passagem por valor e referência.
Quando a função main() é executada, são criadas as variáveis a e b. Por terem sido declaradas na função main() sua visibilidade (ou seja, o escopo para se manipular estas) é restrita à esta função. Quando a função
troca() é invocada, novas variáveis a e b,
visíveis apenas dentro da função troca(), são criadas, suspendendo temporariamente a visibilidade de a e b criadas em main(). A chamada de troca() em main() porém, resulta na passagem da cópia dos valores de a e b de main() para as novas variáveis a e b que são criadas em troca(). Esta situação corresponde ao seguinte esquema:
Função main() Função troca() a = 1; a = 1;
b = 4; b= 4; troca(a,b); comandos;
a=1; a = 4;
b=4; b = 1;
Assim, dentro da função troca() quaisquer alterações de a e b não resultarão em modificações de a e b em main(), pois o que está sendo alterado é apenas uma cópia.
2
Ou seja, o PT2 corresponde a um programa onde é realizada a passagem de parâmetros por valor, sendo enviadas para a função cópias dos valores originais.
PASSAGEM POR REFERÊNCIA
U
ma forma alternativa é enviar parâmetros por referência, ou seja, o que é enviado para a função não é uma cópia do valor da variável e sim uma referência a esta. Assim, qualquer alteração dos parâmetros realizada na função corresponde a uma alteração nas variáveis originais. Para realizar a passagem por referência ponteiros devem ser utilizados. A estratégia a ser adotada é: (1)Passar o endereço de uma variável, obtido através do operador & e armazenado em uma variável do tipo ponteiro.(2) Dentro da função, usar ponteiros para alterar os locais para onde eles apontam e que correspondem aos valores das variáveis originais.
PT3: Reescrever o PT2 utilizando a
passagem de parâmetros por referência.
#include <stdio.h>
void troca(int *a, int *b); // protótipo
main() { int a, b;
puts(“Dois nums inteiros: ”); scanf(“%d %d”, &a, &b); printf(“Antes de troca \n”); printf(“a= %d e b=%d \n”,a,b); troca(&a,&b);
printf(“Depois de troca \n”); printf(“a= %d e b=%d \n”,a,b); }
void troca(int *a, int *b)
{ int tmp; tmp = *a; *a = *b; *b = tmp; }
Note que a variável tmp continua a ser declarada do tipo int, pois seu propósito é armazenar um dos valores e não endereço.
PASSAGEM DE VETORES
D
iferentemente das variáveis em que existe a passagem por valor ou referência, a passagem de vetores para funções é sempre por referência. A sintaxe de passagem de um vetor pode ser feita com:tipo nome[];
onde: tipo – corresponde ao tipo dos elementos do vetor, nome – é o nome atribuído ao vetor e [] indica que a variável é do tipo vetor. O “[]” pode ser utilizado sem um valor, pois em C não interessa qual a dimensão do vetor que é passado a uma função, mas sim o tipo dos seus elementos.
PT4: Crie uma função inic() que inicia um
vetor de inteiros e teste a mesma.
#include <stdio.h>
void inic(int s[], int n)
{ int i; for (i=0;i<n;i++) s[i]=0; } printV(int t[], int n) { int i; for(i=0; i < n; i++) printf(" [%d] ",t[i]); printf("\n"); } main() { int v[10], i; inic(v,10);
// Impressao apos inicializacao.
printV(v,10); // Mudando valores de v. for(i=0;i<10;i++) v[i]=i;
// Impressao apos atribuicao.
printV(v,10); }
Note que a função void inic(int s[], int n) recebe um vetor de inteiros (sem indicar qual a sua dimensão) e um inteiro que indica qual o número de elementos a iniciar.
PE2: Implemente e teste a função float
max(float v[], int n) que recebe um vetor de números reais e o número de elementos a considerar e cujo retorno é o maior número dentre os n primeiros elementos do vetor. A passagem de vetores com mais de uma dimensão para uma função é realizada indicando no cabeçalho desta, obrigatoriamente, o número de elementos de cada um das n-1 dimensões à direita.
3
Apenas a dimensão mais à esquerda pode ser omitida, colocando-se [] ou um asterisco. É, no entanto, habitual colocar todas as dimensões dos vetores.
PT5: Construir uma função initM() que
inicializa uma matriz e outra função mostraM() que mostra os elementos de uma matriz.
#include <stdio.h> #define DIM 3
void initM(int s[ ][DIM]) // sem 1 DIM
{ int i, j; for (i=0;i<DIM;i++) for (j=0; j <DIM;j++) s[i][j] = 0; }
void mostraM(int s[DIM][DIM]) // 2DIM
{ int i, j;
for (i=0; i<DIM;i++) { for (j=0;j<DIM;j++) printf(“ [%d] ”,s[i][j]); printf(“\n”); } } main() { int s[DIM][DIM]; initM(s); mostraM(s); }
PE3: Adicione uma nova função modEle ao PT5 cujos parâmetros são s, i, j e num, tal
que modifica o elemento s[i][j] com o valor num. Teste para i =1, j = 2 e num = 10.
PE4: Modifique o PT5 para que a função
mostraM ao invés de mostrar os valores inteiros contidos em s, mostre os caracteres associados a cada valor inteiro positivo.
Dica: use uma conversão explícita com
(char)s[i][j] e mude a função initM para inicializar os elementos de s com o valor 1. Uma outra forma de manipular os valores contidos em um vetor através de funções é utilizando ponteiros. Para tanto, basta lembrar que se v for um vetor ou um ponteiro para o primeiro elemento de um vetor (lembre-se que p = v ↔ p = &v[0]), então o elemento cujo índice é i pode ser obtido usando v[i] ou *(v+i) . Ou seja:
v[i] ↔ *(v+i)
Isto só é possível, pois quando um vetor é declarado, os seus elementos são alocados em espaços vizinhos de memória:
EXEMPLO E1:
char v[10] = “Ola”;
v v+1 v+2 v+3
‘O’ ‘l’ ‘a’ ‘\0’
1000 1001 1002 1003
É importante ter em mente que sempre que uma função é invocada utilizando um vetor como um parâmetro, o vetor não é recebido em sua totalidade, mas apenas o endereço inicial do vetor, afinal v ↔ &v[0].
Por esse motivo, o vetor que é definido no cabeçalho de uma função pode ser referenciado através de um ponteiro:
PT6: Construir e testar uma função que
fornece o tamanho de uma String.
#include <string.h> #include <stdio.h>
int strlen(char *s)
{ char *ptr = s; // guardar endereço inicial
while (*s != ‘\0’) // contar até o fim s++;
return (int) (s-ptr); }
main()
{ char Nome[100];
printf(“Digite uma String: ”); gets(Nome); printf(“length = %d \n”,strlen(Nome)); }
Utilizando o Exemplo E1, observe que ao final do while da função strlen o valor de s será 1003 ao passo que ptr terá o valor 1000. Ao se fazer (s-ptr) o resultado é 3, o que corresponde ao tamanho da String (lembre que 1 caractere ocupa 1 byte na memória).
PE5: Modifique strlen() tal que seja
impresso o valor do endereço de memória de ptr e de s antes e depois do while da função strlen().
PT7: Adicionar ao PT6 e testar uma função char * strcpy(char *dest, char *orig) que
copia o conteúdo da string orig na string
dest.
char *strcpy(char *dest, char *orig)
{ char *tmp = dest; while (*dest = *orig) { dest++;
orig++; } return tmp; }
4
main() { int i;
char N1[100], N2[100], *N3; printf("Digite a String1: "); gets(N1); printf("length = %d \n",strlen(N1)); printf("Digite a String2: "); gets(N2); printf("length = %d \n",strlen(N2)); Nome3 = strcpy(N1,N2);
for(i = 0; i < strlen(N2); i++) printf("%c",*(N3+i)); printf("\n");
}
Observe que o while da função strcpy() só irá parar quando o caractere ‘\0’ que indica fim da string for copiado.
Observe, ainda, que no main() do PT3 a chamada a função foi troca(&a,&b), mas no main do PT7 foi utilizado strcpy(N1, N2). Ou seja, em uma utilizou-se o operador de endereço & e noutra não. Segue um resumo de quando empregar endereços de variáveis na passagem de parâmetros para funções:
• Se a variável for um vetor, seu nome corresponde ao endereço do seu primeiro elemento.
• Se a variável não for um vetor, então, caso se queira alterar a mesma, ela deve ser precedida de & ao se invocar a função.
Estes conceitos estão resumidos no PT8.
PT8: Construir e testar uma função Calc
cuja lista de parâmetros tem um vetor e retorna o valor mínimo e máximo dos seus elementos.
#include <stdio.h>
void Calc(float *v, int num, float *xmin, float *xmax)
{ int i;
*xmin = *xmax = *v; // Valor inicial v[0]
for (i=0; i<num; i++) { if (v[i]<*xmin) *xmin=v[i]; if (v[i]>*xmax) *xmax=v[i]; } } main() {float Vetor[]={10,20,30,40,50,11,125,-33}; float Maior, Menor;
Calc(Vetor, 9, &Menor, &Maior); printf("Maior Elemento %f\n",Maior); printf("Menor Elemento %f\n",Menor); }
ALOCAÇÃO DINÂMICA
N
o laboratório 10 foi visto que a manipulação de ponteiros permite o emprego de alocação de memória para vetores e matrizes em tempo de execução (alocação dinâmica de memória). A alocação dinâmica de memória pode ser empregada em conjunto com a passagem por referência de variáveis nas funções. O PT9 ilustra isso.PT9: Refazer o PT8 empregando alocação
dinâmica de memória para o vetor v e preenchendo v com valores aleatórios entre 1 e 6 através de uma função void preencheV(int *V, int n, int a, int b), onde: a é o parâmetro do menor valor (1) e b é o parâmetro do maior valor (6).
#include <stdio.h> #include <time.h>
void Calc(float *v, int num, float *xmin, float *xmax);
void preencheV(float *V, int n, int a, int b); void printV(float *V, int n);
main()
{float *V, Maior, Menor; int n; printf(“Tamanho de V:”); scanf(“%d”,&n); V = (float *) calloc(n,sizeof(float)); preencheV(V, n, 1, 6); printV(V,n);
Calc(V, n, &Menor, &Maior);
printf("Maior Elemento %f\n",Maior); printf("Menor Elemento %f\n",Menor); free(V); }
void printV(float *V, int n)
{int i;
for (i=0; i < n; i++) printf(“[%f]”,*(V+i)); printf(“\n”); }
void preencheV(float *V, int n, int a, int b)
{ int i; srand(time(0)); for (i=0; i < n; i++)
*(V+i) = rand()%(b-a+1) + a;}
void Calc(float *v, int num, float *xmin, float *xmax)
{ int i;
*xmin = *xmax = *v; // Valor inicial v[0]
for (i=0; i<num; i++) {if (v[i]<*xmin) *xmin=v[i]; if (v[i]>*xmax) *xmax=v[i]; } }
5
MATRIZES
N
o laboratório 10 foi visto que uma variável do tipo ponteiro pode servir para manipular os elementos de um vetor, ou ainda, funcionar como um vetor. É possível, ainda, utilizar ponteiros para manipular os elementos de uma matriz. Para tanto, será necessário empregar ponteiros de ponteiros. O Exemplo E2 e o PT10 ilustram como empregar ponteiros de ponteiros para se manipular os elementos de uma matriz. EXEMPLO E2:float **M;
// Constrói vetor de m posições capaz de // armazenar m elementos do tipo float *.
M = (float **) calloc(m,sizeof(float *));
for (i=0; i < m; i++)
// Cada elemento M[i] possui um // endereço que representa o primeiro // elemento de um vetor de tamanho n.
M[i] = (float *) calloc(n,sizeof(float *)); M[0] M[1] M[2] 5000 6000 7000 8000 8004 8008 M[0][0] M[0][1] M[0][2] M[0][3] 1.0 2.0 3.0 4.0 5000 5004 5008 5012 M[1][0] M[1][1] M[1][2] M[1][3] 5.0 6.0 7.0 8.0 6000 6004 6008 6012 M[2][0] M[2][1] M[2][2] M[2][3] 9.0 10.0 11.0 12.0 7000 7004 7008 7012
PT10: Gerar aleatoriamente uma M (3 x 4)
cujos elementos são valores aleatórios entre 1 e 12. Construa M utilizando alocação dinâmica de memória.
#include <stdio.h> #include <time.h>
void preencheM(float **M, int m, int n, int
a, int b);
void printM(float **M, int m, int n);
main() {float **M; int i, m, n;
printf(“Linha e Coluna de M:”); scanf(“%d%d”,&m, &n);
M = (float **) calloc(m,sizeof(float *));
for (i=0; i < m; i++)
M[i] = (float *) calloc(n,sizeof(float)); preencheM(M, m, n, 1, 12);
printM(M, m, n); free(M); }
void printM(float **M, int m, int n)
{int i, j;
for (i=0; i < m; i++) {
for (j=0; j < n; j++)
printf(“ [%5.2f] ”,*(*(M+i)+j)); printf(“\n”); } }
void preencheM(float **M, int m, int n, int
a, int b)
{ int i, j; srand(time(0)); for (i=0; i < m; i++) for (j=0; j < n; j++)
M[i][j] = rand()%(b-a+1) + a;}
Observe que no PT10 a função preencheM utiliza a notação de ponteiros para acessar os elementos da matriz M: *(*(M+i)+j). Para ilustrar o funcionamento desta notação, imagine que i = 1 e j = 2 e use o esquema descrito no Exemplo E2. Assim: *(*(M+i)+j)= *(*(8000+i)+j)= *(*(8004)+j) = *(6000+j)= *(6008)= 7.0. Note que o mesmo valor de M seria obtido se fossem usados índices de matriz, ou seja, M[1][2]. A partir deste fato, a função preencheM usa a notação com índices M[i][j] para preencher a matriz M.
PE6: Modifique o PT10 para mostrar o
maior e o menor valor da matriz M através de uma função void Calc(float **M, int m,
int n, float *xmin, float *xmax).
NOVOS TIPOS
N
o laboratório 9 foi visto como definir novos tipos de variáveis. Pode-se construir alocar dinamicamente vetores cujos elementos são novos tipos tal como ilustrado no PT11:PT11: Preencher e imprimir um cadastro
utilizando um vetor do tipo PESSOA.
#include <stdlib.h> #include <stdio.h> #include <conio.h>
// Definindo o novo tipo PESSOA.
typedef struct PESSOA
{ char nome[100]; float salario; char sexo; } PE;
6
// Protótipos das funções.
void preencheVP(PE *V, int n); void printVP(PE *V, int n);
main() { PE *V; int n; printf(“Tamanho de V:”); scanf(“%d”,&n); V = (PE *) calloc(n,sizeof(PE)); preencheVP(V, n); printVP(V,n);
// Liberando memória alocada para V.
free(V); }
// Impressão de um vetor cujos // elementos são do tipo PE.
void printVP(PE *V, int n)
{ int i;
for (i=0; i < n; i++) {
printf(“--- \n”); printf(“Cadastro %4d: \n”, i+1); printf(“Nome: %s \n”, (*(V+i)).nome); printf(“Salario: %f \n”, (*(V+i)).salario); printf(“Sexo: %c \n”, (*(V+i)).sexo); printf(“--- \n\n”); }
printf(“\n”); }
// Preenchendo um vetor cujos // elementos são do tipo PE.
void preencheVP(PE *V, int n)
{ int i;
for (i=0; i < n; i++) {
printf(“Cadastro %d \n”, i+1); printf(“Entre com o nome: ”); // *(V+i). nome equivale a usar // (V+i)->nome !
fflush(stdin); gets((V+i)->nome);
printf(“Entre com salario: ”); scanf(“%f”,&((V+i)->salario)); printf(“Entre com sexo: ”); (V+i)->sexo = getche(); fflush(stdin);
system(“CLS”); }
}
No PT11 a função printVP é responsável por imprimir cada campo de cada elemento do vetor preencheVP. Para tanto, é utilizado a notação de ponteiro (*(V+i)).nome para imprimir a informação do nome do i-ésimo cadastro. Já a função preencheVP é responsável por armazenar a informação do cadastro de uma pessoa, mas o faz utilizando outra notação, (V+i)->nome, para acessar o campo nome do i-ésimo elemento de V.
PROGRAMAS BÁSICOS
PB1: Crie e teste a função void Troca3(int a, int b, int c) que retorna a = c, b = b e c = a.
Dica: Uma forma de resolver o problema é reimplementar o PT3, e outra é realizar chamadas à função troca dentro da função Troca3.
PB2: Generalize o PB1, criando e testando a
função void Inversão(int v []) que inverte a posição que os elementos ocupam em um vetor v.
Exemplo:
v = {20, -40, 50, 70} v = {70, 50, -40, 20}
PB3: Teste e crie uma função que dado um
vetor v, imprime seus elementos na forma de coeficientes de um polinômio.
Exemplo:
Nome do polinômio: P Grau do polinômio: 3
Entre com os coeficientes: 20 -40 50 70 P(x) = 20 -40*x + 50*x^2 + 70*x^3
PB4: Implemente e teste a função void
transpor(int v[VMAX][VMAX]) que transpõem a matriz v com MAX por MAX elementos.
PB5: Reimplemente e teste o PB2 e o PB4
usando ponteiros.
PB6: Escreva uma versão iterativa e outra
recursiva de uma função int Fib(int n) que retorna o n-ésimo termo da seqüência de Fibonacci dada por:
− + − = = = c.c. ), 2 ( ) 1 ( 1 se , 1 0 n se , 0 ) ( n f n f n n f
PB7: Usando o PB6, escreva uma função void seqFib(int v[], int n) que retorna os n
primeiros termos da seqüência de Fibonacci no vetor v.
7
PB8: Reimplemente o PB7 utilizando
ponteiros.
PB9: Teste a função misterio com o
seguinte código: char *misterio(char *s) { if (*s == ‘\0’) putchar(‘\n’); else { putchar(*s); misterio(s+1); } }
Diga o que ela faz e qual comando em C é equivalente.
PB10: Reimplemente o PB4 usando
ponteiros para passagem da matriz v como parâmetro da função transpor().
PROGRAMAS AVANÇADOS
PA1: O código de PT5 pode ser modificado
para se criar um Jogo da velha da seguinte forma:
#include <stdio.h> #define DIM 3 #define ESPACO ‘ ’
void inic(char s[][DIM])
{ int i, j;
for (i=0; i < DIM; i++) for (j=0; j < DIM; j++) s[i][j]= ESPACO; }
void mostra(char s[DIM][DIM])
{
int i, j;
for (i=0; i < DIM; i++)
{
for (j=0; j < DIM; j++)
printf(“%c %c”,s[i][j], j==DIM-1? ‘ ’: ‘ |’ ); if (i != DIM-1) printf(“\n- - - ”); putchar(‘\n’); } } main() { char Velha[DIM][DIM]; int posx, posy;
char ch = ‘0’; // Caractere das jogadas
int n_jogadas = 0; inic(Velha);
while (1) // Laço infinito
{
mostra(Velha);
printf(“\nIntroduza a Posição de Jogo Linha e Coluna”);
scanf(“%d %d”,&posx,&posy); // Índices do vetor começam em 0
posx-- ; posy--; // Posição livre if (Velha[posx][posy] == ESPACO) { Velha[posx][posy] = ch = (ch == ‘0’)? ‘X’ : ‘0’; n_jogadas++; } else
printf(“Posição ja ocupada \n Jogue de novo !! \n”); if (n_jogadas ==DIM*DIM) break; } mostra(Velha); } Pede-se:
• Utilizar o comando system("CLS"); para limpar a tela após cada jogada.
• Inserir uma alteração tal que o jogo da velha termine quando algum jogador completar alguma linha, coluna ou diagonal.
PA2: Reescreva PB7 usando alocação
dinâmica de memória para construir o vetor v. Dica: Neste caso, a passagem de parâmetros deve ser realizada utilizando ponteiros.
PA3: Escreva, usando recursão, a função char *strchr(char *str, char ch) que retorna
o endereço em que se encontra o caractere ch na string str. Caso o caractere não exista, na string, então será devolvido NULL.