C/Funcións

En Galilibros, o Wikibooks en galego.
< C
C
← Volver a Instrucións de control Funcións Seguir con Directrices para o preprocesador


Un programa en C está formado pola función principal (o código que se executa ao chamar ao programa) e, xeralmente, outras funcións, xa formen parte da biblioteca estándar de funcións de C ou sexan funcións escritas polo propio usuario. Toda instrución atópase por tanto dentro dunha función.

As funcións son bloques de instrucións que poden chamarse en calquera parte do código da función principal ou doutra función. As funcións realizan unha serie de tarefas a partir dun ou varios datos que reciben como argumentos, e como resultado desas tarefas as funcións devolven un valor (opcionalmente) na función dende a que foron chamadas.

Dado que unha mesma función pode chamarse varias veces para traballar con distintos valores, as funcións son unha ferramenta moi potente á hora de programar. Entre os beneficios que achega o uso de funcións están os seguintes:

  • Claridade no código. Ao reducir fragmentos de código a funcións, a estrutura do código queda moito máis clara, o que facilita non só a comprensión do código, senón a busca e solución de erros.
  • Evitar repeticións innecesarias de código. En moitas ocasións, ao longo dun programa existen certos grupos de instrucións que conforman unha operación que nos gustaría repetir máis dunha vez no programa. Repetir as instrucións cada vez que sexan necesarias pode resultar traballoso, e dificulta o mantemento do código: se aparece un erro nesa operación, haberá que corrixilo en todas as súas repeticións. Estes problemas soluciónanse mediante o uso de funcións que conteñan a operación.
  • Independencia. As funcións son relativamente independentes do reto do código (das funcións que a chaman). As funcións poden modificar variables globais ou punteiros, pero limitándose a aqueles que se lle fornezan na chamada á función.

Antes de analizar os compoñentes das funcións cómpre ter en conta algunhas cousas relativas ao vocabulario usado ao tratar con funcións:

  • Cando falamos de que unha función utiliza outra función, dicimos que a primeira “chama” á segunda.
  • Os datos que se lle envían a unha función para que traballe con eles son os “argumentos”. Estes, vistos dende o punto de vista da función chamada, adóitanse denominar “parámetros”.
  • Cando unha función responde á que a chamou con algún dato, a función “devolve” ese dato.

Compoñentes das funcións[editar]

As funcións en C están baseadas en tres compoñentes: definición, declaración e chamada. Destes tres compoñentes, só un deles é estritamente necesario para que a función forme realmente parte do código fonte: a definición. Esta é a parte que describe o funcionamento da función. Cando nunha parte do código queremos que se faga a tarefa para a que a función se creou, chámase a dita función para que faga dita tarefa. E a declaración da función só é unha presentación da mesma, un xeito de que o compilador coñeza a función en caso de que se atope cunha chamada á mesma antes de dar coa súa definición.

Así e todo, o máis habitual é atoparse cos tres compoñentes. Mesmo nos casos en que non sería necesario.

Definición dunha función[editar]

A definición de unha función é a función en si mesma. É un algoritmo que pode recibir ningún, un ou varios valores ─chamados “parámetros”─, e que pode devolver ningún ou un valor ─chamado “valor de saída”─. Consta dunha serie de instrucións que rematan coa saída da función (return).

A definición dunha función consta de dúas partes ben diferenciadas: a cabeceira e o corpo.

A cabeceira consta de tres partes ben diferenciadas: (1) o tipo de dato de saída, (2) o identificador único ou nome da función e (3) os tipos de dato e identificadores das variables cuxos valores se van recibir para traballar con eles.

As variables estre parénteses son variables locais da función. Defínense durante a execución da función ─tras ser esta chamada por outra función─, inicializadas cos valores da chamada, e non as pode usar outra función. É dicir, as variables cos datos de entrada só teñen “xurisdición” dentro da función para a que se definen. É coma unha declaración de variables corrente, sendo a única diferencia respecto ás outras declaracións de variables que a variable se inicializa cun valor que se lle fornece na chamada á función, que se realiza dende outra función.

O corpo da función confórmao o bloque de instrucións que segue á cabeceira. Cómpre ter en conta que para definir a saída da función (devolvendo ou non un valor) no corpo utilízase a instrución de control return. Non fai falla utilizala para aqueles casos en que a función non devolve ningún valor, nos cales a función rematará en canto a execución chegue ao final de bloque, pero utilizar a instrución de control para especificar a saída da función nunca está de máis. Nótese que ademais o return permite saír da función antes de chegar ao final do bloque.

A sintaxe fundamental da definición dunha función sería a seguinte:

tipodesaída identificador(tipo1 variable1, tipo2 variable2, ..., tipon variablen){ // Cabeceira
  // Instrucións que conforman a función.
  // As variables cos datos cos que se chama á función
  // definidas na cabeceira poden usarse libremente.
}

Un exemplo de definición dunha función podería ser o seguinte:

int Produto(int operando1, int operando2){
  int resultado;
  resultado = operando1 * operando2;
  return resultado; // O seu tipo debería coincidir co tipo de
                    // saída definido na cabeceira.
}

Por suposto, o código anterior podería resumirse no seguinte:

int Produto(int operando1, int operando2){
  return operando1 * operando2;
}

Chamada a unha función[editar]

Ao chamar a unha función, execútanse as instrucións da definición de dita función utilizando uns valores para as variables de entrada establecidos na propia chamada. As funcións poden recibir (ou non) constantes, variables, expresións, e poden ademais devolver (ou non) un dato como resultado dos procesos contidos na definición da función.

A sintaxe fundamental da chamada a unha función sería a seguinte:

variablecalquera = identificador(valor1, valor2, ..., valorn);

Un exemplo de chamada a unha función podería ser o seguinte:

resultadode3por5 = Produto(3, 5);

E outro exemplo, desta vez sen que a función devolva nada, podería ser o seguinte:

Saudar("Carlos"); // Imaxínese que logo imprime en pantalla: «Ola, Carlos!».

Paso por valor e paso por referencia[editar]

Á hora de pasarlle a unha función unha variable como argumento, pódese facer de dous xeitos: paso por valor, ou paso por referencia.

O caso máis común é o paso por valor. Neste caso, o que se lle pasa á función é o valor que contén a variable. Para pasarlle unha variable a unha función por valor, abonda con escribir o identificador da función. Ao executarse a función, esta crea unha nova variable cuxo identificador queda definido na definición da función, e inicialízaa co valor da variable que se lle pasa.

Porén, tamén é posible pasarlle a unha función, en vez do valor da variable, o seu enderezo na memoria. Neste caso, a función non recibe o valor que contén a variable, senón o enderezo na memoria. Dese modo, a función non traballará co valor, senón cun punteiro á variable en si mesma, de xeito que poderá modificar a variable durante a execución da función. Isto é o que se coñece coma “paso por referencia”. Para facelo, é necesario preceder o identificador da función do carácter &.

funcion1(variable); // Aquí prodúcese un paso por valor.
funcion2(&variable); // Aquí prodúcese un paso por referencia.

Declaración dunha función[editar]

A declaración ou prototipo dunha función precísase para que cando o compilador chegue á chamada dunha función antes de chegar á definición da mesma, este xa saiba da súa existencia e coñeza:

  • a cantidade de argumentos que ten que recibir e o seu tipo de dato,
  • o tipo de dato que devolverá a función e
  • o identificador da función.

Isto quere dicir que, en caso de que a definición da función vaia antes das chamadas á mesma, non sería preciso incluír a declaración da función. De todos xeitos, é costume que a primeira función que apareza no código fonte sexa a principal, e a primeira función adoita ser a que contén a meirande parte das chamadas a funcións, polo que situar as declaracións de todas as funcións ao comezo do programa e logo situar a definición da función principal e tras ela as demais impedirá calquera tipo de problema.

A declaración dunha función ten que aparecer entre as directrices para o preprocesador e a definición da función principal. É dicir, comparten espazo coas declaracións de variables globais.

As partes de que consta a declaración dunha función son ben similares ás da cabeceira da definición. De feito, poden ser iguais, se ben na declaración non fai falla especificar o identificador das variables locais nas que se gardarán os argumentos fornecidos á función na chamada.

Velaquí uns exemplos de declaracións dunha función:

int IdentificadorDaFuncion1(int parametro1, float parametro2, float parametro3) // Esta leva os identificadores das variables que se han usar na función
int IdentificadorDaFuncion2(double, double) // Esta non, simplemente leva os tipos de datos que vai recibir na chamada

A función principal[editar]

A función principal é fundamental, e representa o punto de inicio da execución dun programa. A súa sintaxe fundamental é a seguinte:

int main(void){
  // Instrucións que conforman o programa.
}

O seu tipo de saída sempre é signed int, o seu identificador sempre é main e, se non recibe argumentos, indícase mediante a palabra clave void ou deixando baleiro o contido dos parénteses.

É unha boa práctica subdividir a función principal noutras funcións, de xeito que esta quede case como un guión do que fai o programa, permitindo así unha lectura rápida e comprensión do mesmo.

Argumentos da función principal[editar]

Cando se executa un programa, a función recibe do sistema dous parámetros: un contador de argumentos e unha lista de argumentos.

O contador de argumentos adoita nomearse argc, abreviatura de argument count, «conta de argumentos». Trátase dun número enteiro que representa a cantidade de valores que se lle pasan ao programa antes de que comece a súa execución.

A lista de argumentos adoita denominarse argv, abreviatura de argument vector, «matriz de argumentos». É un punteiro a unha matriz de cadeas de caracteres de distintas lonxitudes. Cada unha destas cadeas conterá cada un dos argumentos que se fornezan na chamada ao programa.

Visto isto, a seguinte sería a declaración estándar da función principal naqueles programas que reciban argumentos:

int main(int argc, char * argv[]){
  // Instrucións que conforman a función principal.
}

Por convención, argv[0] conterá o nome co que se chamou ao executable (xeralmente será a ruta completa do ficheiro executable). Este valor fornéceo automaticamente o sistema operativo, polo que o valor mínimo de argc será un. O resto de celas da matriz conterán os argumentos cos que se chamou ao programa, en forma de cadeas de caracteres. Se se quere traballar cun argumento coma se se tratase doutro tipo de dato, será necesario realizar un proceso de conversión.

Xeralmente argc utilízase para controlar os argumentos fornecidos polo usuario ao chamar ao programa, xeralmente rematando a súa execución, non sen antes indicarlle ao usuario a forma correcta de chamar á función.

Funcións recursivas[editar]

Denomínase recursividade ao feito de que unha función se chama a si mesma, xa sexa directa ou indirectamente ─con outras funcións de por medio─. A súa sintaxe fundamental sería a seguinte:

función(parámetros){
  // [...]
  función();
  // [...]
}

Se se utiliza sen realizar cambios sobre os argumentos fornecidos, a función entraría nun ciclo sen fin. En cambio, ben usada a recursividade permite realizar en poucas liñas de código tarefas que resultarían sumamente longas ou complicadas de realizarse sen aproveitarse da recursividade.

Toda función recursiva necesita contemplar un “caso base”, que consiste ha condición de saída, para evitar entrar nun ciclo sen fin. Cando se recoñece un caso base, remata a recursividade.

Esta técnica úsase en tarefas tales como percorrer árbores de directorios, buscar o fin dunha lista encadeada, analizar unha estrutura de árbore nunha base de datos, buscar os factores primos de números, calcular o factorial dun número, etc.

O seguinte exemplo é dunha función recursiva que calcula o factorial dun número:

unsigned long factorial(unsigned int numero){
  if(!n)
    return 1; // 0! = 1 ← Caso base.
  else
    return n * factorial(--n); // Chamada á mesma función.
}

E o seguinte é un exemplo de recursividade indirecta, en que chamando á primeira función co argumento «z» conséguese o alfabeto ASCII en minúsculas ata o «z»:

void funcion1(unsigned char letra){
  if(letra > 'a') // Se a letra vai despois do «a» (valor ASCII maior) ← Caso base.
    funcion1(letra);
  printf("%uc ", letra); // Imprímese a letra.
}

void funcion2(unsigned char letra){
  funcion1(--letra); // Chamada á primeira función diminuíndo o valor da letra.
}

Funcións estáticas[editar]

Unha función considérase estática cando só vai ser chamada dende as funcións que se atopan no mesmo ficheiro no que está definida. Sabendo isto, o compilador pode compilala de xeito que se impida a súa chamada dende outros ficheiros.

Para declarar unha función como estática, abonda con engadirlle o modificador static, como se pode observar no seguinte exemplo:

static int Produto(int operando1, int operando2){
  return operando1 * operando2;
}

Bibliotecas de funcións[editar]

As bibliotecas de funcións son conxuntos de funcións feitas previamente que se poden aproveitar, evitando a necesidade de volver codificalas para cada un dos distintos programas que se realicen. Abonda con indicarlle ao compilador que se quere engadir a biblioteca ao executable.

Unha biblioteca de funcións non é máis que un ficheiro que contén as definicións dunha serie de funcións, así como definicións de estruturas de datos, unións, etc. Unha biblioteca pode mesmo utilizar as funcións doutra biblioteca de funcións.

Pero á hora de traballar de verdade coas funcións da biblioteca e utilizalas en programas, necesítanse dous ficheiros distintos: o ficheiro de cabeceira e a propia biblioteca compilada.

Ficheiro de cabeceira[editar]

O ficheiro de cabeceira será un ficheiro de texto, xeralmente coa extensión .h, que conterá os prototipos das funcións da biblioteca, así como os tipos de estruturas, unións ou enumeracións usadas se procede. Cando as bibliotecas son moi complexas, utilízanse xeralmente varios ficheiros distintos.

Unha vez creado o ficheiro, nos códigos nos que se utilicen as funcións da biblioteca haberá que incluír o ficheiro de cabeceira ─de xeito similar a como se fai para a biblioteca estándar de C─ mediante a directriz para o preprocesador #include "FICHEIRO", onde FICHEIRO será o ficheiro de cabeceira en cuestión.

Para máis informacións sobre a inclusión de ficheiros de cabeceira en códigos, vaiase ao capítulo sobre as directrices para o preprocesador.

Biblioteca compilada[editar]

O proceso necesario para compilar unha biblioteca de funcións ou para ligala a un programa durante o proceso de compilación escapa aos obxectivos deste libro. Para máis información léase, por exemplo, o libro sobre a GNU Compiler Collection.

Véxase tamén[editar]


C
← Volver a Instrucións de control Funcións Seguir con Directrices para o preprocesador