Revista Do Linux
EDIÇÃO DO MÊS
 Gráficos
 Comandos Avançados
 Portáteis

 Capa
 Entrevista
 CD
 Distro
 GNU
 Evento
 Depoimento
 Programação

 

Curso de Linguagem C

Simplificadamente podemos definir estruturação como a reunião das rotinas comuns em blocos, sendo o programa final a execução ordenada desses blocos

Nesta quarta parte de nosso curso de linguagem C, iremos discutir a estruturação de um programa, aprofundando os conceitos de funções e seus argumentos. A partir desses conceitos, vamos revisar o programa desenvolvido no artigo anterior, melhorando sua usabilidade.

Estruturação

Como já vimos nos exemplos dos artigos das edições anteriores, quando desenvolvemos um programa, utilizamos uma série de comandos para realizar uma determinada tarefa. Da mesma forma que muitas dessas tarefas se baseiam na repetição de determinados comandos, através de laços (for, while), um programa também pode repetir seqüências inteiras de comandos já utilizados em outro programa desenvolvido anteriormente.

Por exemplo, vamos apresentar um programa simples que faz a exponenciação de um número x por y (calcula x elevado a y). Se lembrarmos que a exponenciação nada mais é do que a multiplicação do número x por ele mesmo y vezes, teremos uma rotina simples:

main()
{
	int x;
	int y;
	int i;
	int total;
	x = 16;
	y = 4;
	total = 1;
	for (i=1; i <= y; i++)
	 total = total * x;
	printf("Resultado = %d\n", total);
}

Caso estivéssemos desenvolvendo um programa matemático que realizasse diversas operações de exponenciação, poderíamos repetir a mesma seqüência sempre que necessário, correndo o risco de erros de digitação, ou poderíamos também realizar diversas vezes uma operação de copiar e colar (cut and paste).

Para permitir uma melhor construção dos programas e evitar repetições de seqüências de comandos, a programação utiliza o conceito de estruturação. Simplificadamente podemos definir estruturação como a reunião das rotinas comuns em blocos, sendo o programa final a execução ordenada deles. A técnica estruturada de desenvolvimento é largamente utilizada em todas as linguagens modernas.

Funções em C

A linguagem C realiza a estruturação de um programa a partir do agrupamento dos comandos em blocos denominados funções. Lembre que na primeira parte de nosso curso apresentamos a função main (principal), que deve sempre estar presente para definir um programa.

Utilizando a estruturação em funções, poderíamos reescrever o programa de exponenciação de forma mais modularizada:

main()
{
	int x;
	int y;

	x = 16;
	y = 4;
	expon(x, y);
}
int expon(int a, int b)
{
	int i;
	int total;
	total = 1;
	for (i=1; i <= b; i++)
		total = total * a;
	printf("Resultado = %d\n", total);
}

Vamos estudar com mais detalhe a função que criamos. Repare que ela está descrita logo após o fim da função principal, indicado pelo caractere fecha-chave }.

Da mesma forma que declaramos uma variável descrevendo seu tipo e seu nome, uma função também é declarada descrevendo seu resultado, que no caso é um número inteiro, junto com seu nome expon e declarando entre parênteses os valores que são necessários para sua execução — no caso dois inteiros, que serão referenciados internamente na função pelos nomes a e b. Esses valores são chamados de argumentos de uma função, e podem ser de vários tipos, como caracteres, vetores ou números.

Após a declaração, descrevemos o conjunto de comandos integrantes da função, entre os caracteres abre-chave e fecha-chave, da mesma forma que foi feito com a função main.

Em C, uma função pode representar um valor, assim como as funções matemáticas. No exemplo, a função expon representa um número inteiro. Mas qual número? Para isso, a linguagem disponibiliza o comando return, que serve para especificar o valor que a função representa, ou seja, o seu valor de retorno. Assim, podemos realizar uma nova mudança no programa:

main()
{
	int x;
	int y;
	int result;
	x = 25;
	y = 2;
	result = expon(x, y);
	printf("Resultado = %d\n", result);
}
int expon(int a, int b)
{
	int i;
	int total;
	total = 1;
	for (i=1; i <= b; i++)
		total = total * a;
	return(total);
}

Como podemos ver, a função expon agora tem uma finalidade bem específica, que é o cálculo da exponenciação, retornando o valor inteiro resultante.

Outro efeito da característica modular do C é que as funções podem ser definidas em qualquer ordem, pois serão organizadas posteriormente pelo compilador. Assim, se posicionarmos a função expon antes da função main, o resultado não será alterado. Experimente!

Variáveis

No programa exemplo, podemos notar que, dentro da função expon, declaramos as variáveis inteiras i e total. Essas variáveis são utilizadas internamente na função, mas não são reconhecidas pela função main. Se tentarmos imprimir o valor dessa variável:

main()
{
	int x;
	int y;
	int result;
	x = 25;
	y = 2;
	result = expon(x, y);
	printf("i = %d\n", i);
	printf("Resultado = %d\n", result);
}

a compilação do programa será interrompida, com uma mensagem de erro:

# /root> gcc pot.c -o pot
pot.c: In function ‘main’:
pot.c:25: ‘i’ undeclared (first use in this function)
pot.c:25: (Each undeclared identifier is reported only once
pot.c:25: for each function it appears in.)
make: *** [pot] Error 1

Variáveis que são válidas internamente a uma função são chamadas de variáveis locais. Se for necessário que um valor seja referenciado em duas funções diferentes, podemos definir uma variável antes da declaração dessas funções:

main()
{
	/* comandos da funcao main */
}
int fnc1(int x)
{
	/* comandos da funcao fnc1 */
}
int a;
int fnc2(int x)
{
	/* comandos da funcao fnc2 */
}
int fnc3(int x)
{
	/* comandos da funcao fnc3 */
}

No exemplo acima, a variável a é reconhecida pelas funções fnc2 e fnc3, mas não pela função fnc1.

Quando uma variável é declarada antes de qualquer uma das funções de um programa, ela é chamada de variável global, pois poderá ser referenciada e modificada em qualquer uma das funções seguintes. Embora a utilização de variáveis globais seja aparentemente facilitadora do desenvolvimento, veremos em artigos futuros que o seu uso deve ser reduzido, para facilitar o entendimento e as alterações de um programa.

Modificações

Vamos agora modificar o programa que desenvolvemos no artigo anterior para o cálculo de números primos. Primeiro, vejamos novamente o código fonte original:

#inc
lude <stdio.h>
main()
{
	int i;
int j;
int qtdprimos;
int naoprimo;
int primos[50];
	printf("2 eh primo\n");
	primos[0] = 2;
	qtdprimos = 1;
	for(i=3; i<=50; i++)	{
		for (j=0, naoprimo=0; j < qtdprimos; j++)	{
			if ((i % primos[j]) == 0)	{
				naoprimo = 1;
				break;
			}
		}
		if ( naoprimo == 0 )	{
			primos[qtdprimos] = i;
			qtdprimos++;
			printf("%d eh primo\n", i);
		}
	}
}

Repare que, por ser um exemplo, esse programa tem uma funcionalidade bastante limitada, pois só realiza uma tarefa: mostrar os números primos entre 2 e 50. Dizemos que programas assim têm pouca usabilidade.

Uma forma de torná-lo um pouco mais útil seria possibilitar a definição de até qual número se deseja calcular os primos. Poderíamos então separar a função de cálculo e fornecer o valor limite como argumento dessa função. Dessa forma, teríamos:

#include <stdio.h>
main()
{
	calcprimos(35);
}
int calcprimos(int limite)
{
	int i;
int j;
int qtdprimos;
int naoprimo;
int primos[50];
	printf("2 eh primo\n");
	primos[0] = 2;
	qtdprimos = 1;
	for(i=3; i<=limite; i++)	{
		for (j=0, naoprimo=0; j < qtdprimos; j++)	{
			if ((i % primos[j]) == 0)	{
				naoprimo = 1;
				break;
			}
		}
		if ( naoprimo == 0 )	{
primos[qtdprimos] = i;
			qtdprimos++;
			printf("%d eh primo\n", i);
		}
	}
}

Nesse caso, não houve uma grande diferença entre o programa original e o alterado. Podemos dizer que "trocamos seis por meia dúzia". Apenas demos um pouco mais de modularidade ao programa, mas sem aumentar muito sua usabilidade.

Repare que continuamos com um limitante do valor máximo (50), devido à existência do vetor para armazenar os números primos anteriores. Esse valor é necessário pois não é possível modificar o tamanho de um vetor durante a execução de um programa.

Se pudéssemos, em vez de definir o limite do cálculo no programa, informar esse limite no momento de executá-lo e, de alguma forma, modificar o tamanho do vetor durante a execução, adequando-se ao limite, teríamos, finalmente, um programa útil, que serviria para determinar se um dado número é primo ou não.

A linguagem C fornece comandos que permitem resolver os problemas citados, mas para entendê-los será necessário apresentar os conceitos de apontadores e alocação de memória, que serão vistos no próximo artigo. Serão definições bastante importantes e fundamentais para o desenvolvimento de programas. Prepare-se!

Fernando K. Noda
fknoda@global.net

 

A Revista do Linux é editada pela Conectiva S/A
Todos os Direitos Reservados.

Política de Privacidade
Anuncie na Revista do Linux