O Compilador de C: Empezando a Programar

De Wiki do Ciclo ASIR do IES de Rodeira
Revisión feita o 26 de xuño de 2014 ás 10:10 por Xavi (conversa | contribucións)
(dif) ← Revisión máis antiga | Revisión actual (dif) | Revisión máis nova → (dif)
Saltar á navegación Saltar á procura

Ciclo de Creación dun Programa en "C".

O editor de Textos.

Unha vez que o algoritmo está deseñado débese pasar á fase de resolución práctica do problema coa computadora. Para introducir o programa no ordenador o habitual é utilizar un editor de texto que permita escribir o noso programa e almacenalo nun ficheiro cun nome determinado.

O editor de texto estándar de Unix é o vi. Trátase dun editor de texto desenvolvido fai moitos anos, extremadamente potente e cun manexo moi particular. A versión máis moderna do vi chámase vim e proporciona algunhas características como resaltado de sintaxe para varias linguaxes de programación. En GNU/Linux hai dispoñibles moitos máis editores como o joe, o uso de un editor ou outro é unha cuestión de gustos.

Quizáis históricamente o editor de textos máis utilizado para programar sexa o Emacs e a súa versión para entorno de ventás XEmacs. Este editor case pode considerarse un IDE (Integrated Development Environment) aínda que hoxe existen outros IDE moito máis avanzados: anjuta, xwpe, kdevelop..... De especial interese é kdevelop, que ademáis de ser un IDE nos ofrece un entorno RAD (Rapid Application Development) e programación visual. glade é un entorno de desenvolvemento visual baseado no sistema de escritorio Gnome.

Borland ten tamén unha versión para GNU/Linux do seu entorno BC++ Builder e Delphi, chamado Kylix. Pero aínda que é de uso gratuito non é software libre.

A Compilación.

Unha vez que o programador remata de escribir o código fonte e de almacenalo nun arquivo, debe traducilo a código executable pola máquina. Este proceso chámase no seu conxunto compilación do código fonte. Nos seguintes apartados veremos en que partes se divide internamente o proceso de compilación.

O Preprocesador.

A maior parte das linguaxes de programación teñen unha serie de instruccións que en lugar de estar destiñadas a ser executadas polo procesador sirven para indicarlle certas cousas ó compilador, estas instruccións chámanse directivas de compilación.

O primeiro que o compilador de C fai é pasar o noso programa polo preprocesador que se encarga de interpretar cada unha das directivas que aparecen no programa. Ademais realízanse outras funcións complementarias, como quitar os comentarios ou realizar cálculos constantes (por exemplo si no programa indicamos 4+3, o preprocesador o substitúe por 7).

As directivas na linguaxe C comezan polo símbolo "#". Por exemplo, a directiva:

<c>

#include <stdio.h>

</c>

As directivas máis importantes que entende o preprocesador son:

  • directiva #include: Esta directiva é sustituida polo preprocesador polo ficheiro fonte indicado. A continuación da directiva debe especificarse o nome do arquivo, entre os símbolos < e > ou entre comillas dobres. Si se fai da primeira forma o ficheiro búscase nos directorios do compilador (en GNU/Linux /usr/include), si se pon da segunda forma o ficheiro búscase a partir do directorio actual.
  • directiva #define: Serve para dar nomes a valores. O preprocesador substituirá os nomes polos valores antes de realizar a compilación. Por exemplo:

<c>

#define PI 3.141592
#define Media(a, b) ((a+b)/2)

</c>

No primeiro caso substituirase no programa todas as aparicións de PI por 3.141592. Este tipo de definicións chámanse constantes. No segundo caso se poñemos no noso programa Media(4,7), o preprocesador o substituirá por ((4+7)/2). Este tipo de definicións chámanse macros.

Unha vez finalizado o preproceso, o noso programa queda listo para que poida comenzarse a súa traducción a código executable.

Creación do Código Obxecto.

Unha vez acabado o preproceso, o compilador traduce a código máquina todo o programa creando o que se coñece como código obxecto. Este código está en linguaxe máquina, pero aínda non é executable polo procesador xa que lle falta o código das funcións da librería estándar e a información necesaria para que o sistema operativo poda cargar e comezar a executar o programa.

O Enlazado.

A terceira etapa da compilación é o enlace do programa coas funcións presentes nas bibliotecas de funcións tamén chamadas librerías. Se o noso programa utiliza só funcións da biblioteca estándar de C non soe ser necesario indicarlle ó compilador donde están esas funcións. Sen embargo, hai excepcións coas librerías que non se usan dun xeito habitual, como a librería matemática libm.a. Para enlazar con estas librerías e coas creadas polo usuario (non estándares) é necesario utilizar o parámetro -l do compilador, por exemplo -lm para utilizar libm.a, ou -lncurses para utilizar libncurses.a.

Unha biblioteca ou librería é un conxunto de funcións que se traducen a código máquina e gárdanse, todas xuntas, nun único ficheiro. Hoxe en día é posible distinguir dous tipos:

  • de enlace estático: Tamén chamadas librerías estáticas. En GNU/Linux teñen normalmente extensión .a e comezan por lib e en DOS/Windows soen ter extensión .lib, estas librerías xúntanse co código obxecto do programa no momento de realizar o enlazado para crear un executable que inclúe tanto o código orixinal do programa como o código das librerías.
  • de enlace dinámico: Ou librerías dinámicas. En GNU/Linux teñen normalmente extensión .so e comenzan por lib e en Windows soen ter como extensión .dll (dinamic link library). Este tipo de librerías se cargan en tempo de execución cando se chama ás funcións correspondentes, e teñen como principal ventaxa que o tamaño do executable é moito menor, xa que non se inclúe o código das librerías e o aforro de memoria, xa que si varios programas fan uso dunha misma librería de enlace dinámico ésta cárgase so unha vez en memoria.

A utilización de bibliotecas de funcións proporciona unha maior velocidade no desenvolvemento das aplicacións proporcionando un nivel de abstracción maior, xa que ó largo do tempo se irán xuntando unha gran variedade de funcións para resolver múltiples problemas que logo poden ser utilizadas en múltiples ocasións. Polo tanto, convén ter en conta a hora de diseñar as funcións dos programas qué funcións poden ser dunha utilidade máis xeral que a do uso no programa que se está a desenvolver.

O Compilador gcc.

Utilizaremos principalmente o compilador de C e C++ de GNU (gcc). Este compilador é de libre distribución e atópase dispoñible para case tódalas plataformas UNIX existentes. Existen tamén versións de gcc para DOS (djgpp) e para Windows (Cygwin, MinGw).

O uso, dunha forma sinxela (xa que o gcc é un compilador moi complexo que admite moitos parámetros e compilación multiplataforma) é o seguinte:

Supoñamos que temos un ficheiro denominado miprog.c. Para compilalo, basta con escribir:

 gcc miprog.c

Esta instrucción xenera un ficheiro executable, de nome a.out. Para executar este programa, basta con invocalo directamente:

 ./a.out

Se queremos dar un nome distinto de a.out ó ficheiro executable (o máis habitual) , podemos indicalo coa opción -o:

 gcc miprog.c -o miprog

así o ficheiro xerado á saída chamarase miprog.

Se o noso programa utiliza a unha biblioteca de funcións distinta da estándar debemos indicarlle ó compilador que enlace o noso programa con esa biblioteca, utilizando a opción -l, por exemplo o seguinte programa se enlaza coa librería matemática libm.a e coa librería de manexo de terminal ncurses libncurses.a

 gcc miprog.c -o miprog -lm -lncurses

Se queremos aplicar únicamente o preproceso ó noso programa, podemos conseguilo utilizando a opción -E.

Se executamos:

gcc -E miprogr.c

o resultado da etapa de preproceso sairá por pantalla, non xerándose ningún ficheiro de saída. Se queremos ver o resultado desta etapa, podemos redirixir a saída a un ficheiro:

gcc -E miprogr.c > saida.txt
Compilación dun programa con varios módulos

Normalmente unha aplicación medianamente complexa consta de varios ficheiros fonte que terán que ser compilados por separado e enlazados todos xuntos. Estes ficheiros compilados por separado é posible unilos nunha librería estática ou dinámica si consideramos que poden ser de utilidade para outros proxectos.

Supoñamos que temos o código fonte do noso programa, de nome prog, en tres ficheiros, de nome prog1.c, prog2.c e prog3.c. Para compilar todos eles e enlazalos nun único executable temos que executar as ordes:

 gcc -c prog1.c prog2.c prog3.c
 gcc prog1.o prog2.o prog3.o -o prog

A primeira orde encárgase de preprocesar e compilar por separado cada un dos ficheiros de código C indicados. A segunda orde enlaza todos os ficheiros obxecto, xerando o programa prog. Dun modo máis común o procedimento consistirá en ir compilando os distintos módulos según os imos programando para eliminar os posibles erros sintácticos, para ó final combinalos no executable:

 gcc -c prog1.c
 gcc -c prog2.c
 gcc prog3.c -o prog prog1.o prog2.o
Utilización de bibliotecas de funcións con gcc

Cando queremos enlazar o noso programa cunha librería situada en un sitio distinto os configurados co comando [#ldconfig ldconfig] é necesario indicarllo coa opción -L. Por exemplo, supoñamos que fixemos unha biblioteca de funcións para cálculo estadístico, e a denominamos libstat.a. Supoñamos ademais que esta biblioteca se atopa no directorio lib, que é un subdirectorio do noso directorio de traballo (onde temos o código fonte). Entón para compilar o noso programa prog.c e enlazalo con esta biblioteca a orde será:

 gcc prog.c -o prog -L./lib -lstat

Si queremos crear as nosas propias librerías o procedimento a seguir é o seguinte:

Librerías Dinámicas
  • Compilar coas opcións -c (para non enlazar) e -fPIC (código cargable) todos os módulos que van a formar parte da librería:
 gcc  -c -fPIC modulo.c
  • Crear a librería
 gcc -shared -Wl,-soname,libnomelibreria.so.xxx -o libnomelibreria.so.xxx.yyy.zzz modulo.o modulo.o ...

O nome da librería debe comenzar por lib e ter como extensión .so.xxx.yyy.zzz donde xxx é o número de versión da librería, yyy o número de revisión e zzz o número de subrevisión. Para instalar a librería é necesario copiala en /usr/lib e a continuación executar ldconfig o que creará un enlace chamado libnomelibreria.so.xxx a libnomelibreria.so.xxx.yyy.zzz. Por último temos que crear un enlace chamado libnomelibreria.so a libnomelibreria.xxx.yyy.zzz.

Para utilizar a librería simplemente é necesario enlazala normalmente coa opción -l nomelibreria do gcc.

ldconfig é unha utilidade para configurar as librerías dinámicas e os directorios donde se atopan. A lista de directorios adicionais para buscar as librerías automáticamente atópase en /etc/ld.so.conf, menos os directorios estándar /usr/lib e /lib. Cada vez que se engade unha nova librería dinámica é necesario executar ldconfig engadindo antes si non está nun directorio estándar o directorio a /etc/ld.so.conf.

Librerías Estáticas

As librerías estáticas créanse compilando con normalidade (coa opción -c, sen enlazar) e xuntándoas con posterioridade co comando ar e preparando o arquivo resultante con ranlib. As posibles opcións poden consultarse co comando man.

O nome das librerías estáticas debe ter a forma de libnomelibreria.a, e e convinte si existe unha dinámica co mesmo nome diferencialas de algunha maneira, por exemplo poñendo _s ó final do nome: libnomelibreria_s.a, e para enlazala utilízase -lnomelibreria da mesma maneira que coas librerías dinámicas.

A Ferramenta Make.

No momento de realizar unha aplicación composta de moitos ficheiros fontes é complicado comprobar si temos actualizada a compilación dos distintos módulos en cada momento. Para solucionar este problema existe a ferramenta make. make xenera comandos para o shell a partir dun ficheiro chamado makefile. O formato dos makefiles é complexo, de modo que imos ver a súa forma máis básica no desenvolvemento de aplicacións en C. O seguinete exemplo constrúe unha aplicación chamada app a partir dos ficheiros fonte un.c, dous.c e tres.c utilizando a librería curses.

   app:     un.o   dous.o   tres.o   /usr/lib/libncurses.so
            gcc app.c -o app un.o  dous.o  tres.o  -lncurses
   un.o:    un.c
            gcc -c un.c
   dous.o:  dous.c
            gcc -c dous.c
   tres.o:  tres.c
            gcc -c tres.c
   clean:   rm -f *.o app

Cando chamamos a make sen argumentos o comando procesa un ficheiro chamado Makefile. Unha vez escrito o Makefile é posible construír a aplicación coa orde make. Esta orde comprobará que existen os ficheiros dos que depende a aplicación (especificados na primeira liña), e que a súa data e hora de modificación coinciden co ficheiro correspondente especificado máis abaixo. Si coincide, quere decir que os ficheiros que compoñen a aplicación están actualizados e si a aplicación aínda non está creada lévase a cabo a segunda liña pra creala. Si algunha data ou hora de modificación non coincide quere decir que é necesario compilar de novo o ficheiro, o que se fai co comando indicado na liña seguinte. Tamén é posible eliminar todos os ficheiros compilados e a aplicación escribindo make clean, de xeito que o facer de novo make se recompile todo.

Execución, Proba e Depuración dun Programa.

O compilador é capaz de detectar algúns dos erros que poden cometerse no desenvolvemento dun programa escrito en C. Os erros detectados polo compilador son de dúas clases: erros propiamente ditos (errors), e advertencias (warnings).

  • Os erros débense a cuestións sintácticas relacionadas coa linguaxe.
  • As advertencias non indican erros sintácticos, senón que chaman a atención ó programador sobre posibles usos incorrectos da linguaxe.

Unha compilación sen erros non garante que o programa faga o que teña que facer: só significa que o programa non contén erros sintácticos. Polo tanto, unha vez obtido o código executable, debe probarse o programa, para verificar que cumpre cos requisitos para os que foi desenvolvido. Mediante a depuración do programa corríxense os erros atopados ó probalo.

A verificación e depuración son procesos sucesivos mediante os que se comproba un programa cunha ampla variedade de datos de entrada, chamados datos de proba, que determinan se o programa ten erros.

Co fin de poder asegurar que o programa funciona correctamente, dentro de cada caso de proba distínguense varias fases:

  • Determinar as entradas que deberán producir unha saída dada.
  • Determinar a saída esperada para a entrada dada.
  • Executar o programa e comparar o resultado co esperado.

Para escoller adecuadamente os casos de proba pódense usar dous métodos diferentes:

  1. Probas funcionais ou de caixa negra: Céntranse no estudio da especificación do software, da análise das funcións que debe realizar, das entradas e das saídas. Normalmente non podemos executar tódalas posibilidades de funcionamento e tódalas combinacións de entradas e de saídas, senón que debemos buscar criterios que permitan elixir un subconxunto de casos tales que a súa execución aporte unha certa confianza en detectar os posibles defectos do software.
  1. Probas estructurais ou de caixa branca: Consisten en centrarse na estructura interna (implementación) do programa para elixir os casos de proba. Neste caso a proba ideal (exhaustiva) do software consistiría en probar tódolos posibles camiños de execución, a través das instruccións do código, que poidan trazarse. As probas exhaustivas son impracticables. O deseño de casos ten que basearse na elección de camiños importantes que ofrezan unha seguridade aceptable en descubrir defectos.

O depurador GDB.

Os depuradores son aplicacións que nos facilitan a tarefa de localizar os erros de lóxica nos programas permitindo a execución instrucción por instrucción e inspeccionar os valores almacenados nas variables do programa. Aínda que os depuradores son recursos moi útiles, deben utilizarse como última alternativa xa que a depuración "a man" normalmente é mais rápida e efectiva.

O depurador máis común en GNU/Linux chámase gdb e para poder utilizalo é necesario compilar o programa coa opción -g.

Podemos iniciar unha sesión de gdb da seguinte maneira:

  gdb [ficheiro_executable]

Veremos é un prompt (gdb) que nos indica que está esperando un comando. Os principais comandos de gdb son:

  • file : Carga o programa que se quere depurar.
  • list : Visualiza parte do código fonte que se utilizou para crear o executable.
  • run : Executa o programa ata o final ou ata un breakpoint.
  • watch : Examina o valor dunha variable.
  • break : Pon un punto de ruptura (breakpoint) no código . O punto de ruptura marca un punto donde se deterá a execución do programa permitindo executalo a partir de ahí paso a paso.
  • info break : Mostra información dos puntos de ruptura definidos.
  • delete NUMBREAKPOINT : Elimina o punto de ruptura indicado.
  • next : Executa unha liña do código fonte, sin entrar a depurar as funcións.
  • step : Executa unha liña do código fonte, entrando a depurar as funcións.
  • continue : Continúa a execución do programa hasta o final ou hasta que atopa un novo punto de ruptura.
  • print VARIABLE : Visualiza o valor actual da variable.
  • display VARIABLE : Cando se necesita visualizar sempre o valor dunha variable utilízase este comando. Mostra en pantalla o valor da variable cada vez que saca o prompt do depurador.
  • undisplay VARIABLE : Deshabilita a visualización da VARIABLE
  • set variable VARIABLE=VALOR ou print VARIABLE=VALOR : Asigna á VARIABLE o VALOR especificado.
  • kill : Remata o program que se está a depurar.
  • quit : Saír do gdb.
  • make : Permite recompilar o programa sen saír do gdb.
  • shell : Permite executar comandos UNIX sen saír do gdb.

A forma de parar a execución dun programa é poñendo puntos de ruptura co comando break seguido do lugar onde se desexa que se coloque o punto de ruptura, que pode ser ben o nome dunha función, un número de liña ou a dirección exacta dunha instrucción.

Existen tamén una puntos de ruptura especiais, denominados watchpoints que deteñen a execución ando o valor dunha expresión cambia o seu valor, sen ter que predecir o lugar exacto onde isto sucede watch VARIABLE.

O depurador asigna un número enteiro a cada breakpoint ou watchpoint que se crea. O comando que elimina os puntos de ruptura, delete, utiliza este identificador.

Comézase a execución do programa co comando run.

Unha vez detido o programa nun breakpoint pódese continuar a execución do mesmo de tres formas, executando continue, next ou step.

Elementos da Linguaxe C

Os Comentarios.

Os comentarios son anotacións que o programador fai no programa e que facilitan a comprensión do mesmo. En C, un comentario de varias liñas ponse entre os símbolos /* e */, e se queremos facer un comentario dunha soa liña temos que comenzala con //.

É moi importante comentar de modo axeitado os programas, sobre todo indicando cal é o cometido das funcións e qué valores aceptan e qué resultados producen.

Os Tipos de Datos Fundamentais.

Cando queremos almacenar información na memoria do ordenador recurrimos ó uso de variables. Unha variable é un nome que se lle da a un espacio de almacenamento na memoria, mediante ese nome poderemos almacenar información ou recuperala.

Na maior parte das linguaxes de alto nivel é necesario indicar qué tipo de información se vai a almacenar nas variables mediante unha declaración de variables, e dispoñen dunha serie de tipos de datos para definilas.

O C é unha linguaxe de tipado débil, os tipos de datos so indican o espacio de memoria dedicado para almacenar a información e non restrinxen o seu uso ó programador. Outras linguaxes, como PASCAL son de tipado fórte e os tipos de datos marcan o tipo de información que se vai a almacenar.

Os tipos básicos de datos en C son:

char (Caracter)

Defínese coa palabra chave char e utilízase para representar un caracter pertencente a un determinado código utilizado polo ordenador. Os códigos máis utilizados é o ASCII de 8 bits. Internamente o tipo de dato char é un número enteiro que se corresponde co código utilizado e que externamente ten unha representación en forma de caracter. Por exemplo a letra A almacénase internamente como o seu correspondente código ASCII, o número 65.

Pode levar asociados os modificadores de tipo signed ou unsigned. O rango de valores representables é de -128 a 127 para o signed char e de 0 a 255 para o unsigned char. Ó ser C unha linguaxe de tipado débil, corresponde o programador interpretar a información almacenada coma un número ou como o código ASCII dunha letra.

int (Enteiro)

Utilízase para almacenar números enteiros. Pode levar asociados os modificadores de tamaño short e long e de signo signed ou unsigned.

  • int : dependendo do compilador, si é de 16 bits ocupa 2 bytes e si é de 32, 4 bytes.
  • short int: ocupa sempre 2 bytes.
  • long int: ocupa sempre 4 bytes.
  • long long int: non existe en todos os compiladores, ocupa 8 bytes.

Estas declaracións pódense abreviar como int, short, long e long long e poden ser signed ou unsigned, como por exemplo unsigned int ou unsignend long.

float (Coma flotante)

En C existen dous formatos para almacenar números reais: float e double. Estes formatos diferéncianse entre si en que o segundo utiliza máis bits có primeiro para almacenar os datos. Isto fai que a precisión na representación sexa maior, ó poder almacenar un maior número de decimais. O rango de representación de float é de 3.4E+38 e o de double é 1.7E+308.

void (Vacío)

O tipo void utilízase para definir unha función que non devolve ningún valor ou para indicar que unha dirección de memoria non apunta a ningún tipo de dato en concreto.

Definición de Novos Tipos (typedef).

A linguaxe C dispón dunha declaración denominada typedef para a creación de novos nomes de tipos de datos partindo dos datos básicos. Así pode utilizarse o novo nome definido en lugar do tipo correspondente. typedef non crea un novo tipo, só lle asigna un sinónimo a un tipo xa existente. Por exemplo, a declaración:

  typedef long int EnteiroLongo;

Permite declarar variables da maneira: EnteiroLongo numero1;

Variables e Constantes.

As constantes son valores fixos que non cambian en todo o programa. Poden ser dun caracter ou de valores enteiros ou reais. Tamén as cadeas de caracteres poden ser constantes. Exemplos de constantes son: 24, 'A' ou "MUNDO". Unha variable é un sitio onde se pode almacenar un valor que é posible modificar en calqueira momento. Para representar as variables utilízanse nomes de variables.

En C é necesario declarar as variables antes de utilizalas. Ó declarar unha variable asóciaselle un identificador e un tipo de almacenamento. A declaración de variables ten o formato:

  <tipo de datos> <lista de identificadores>;

Por exemplo: int suma, resta, numero;

Ó declarar unha variable pódeselle asignar un valor inicial, independentemente de que o manteña ou non ó longo de todo o programa. Por exemplo:

<c>

unsigned int maximo = 65000;

</c>

As instruccións típicas con variables son :

Asignacións de valores

E o proceso de almacenar un valor a unha variable. Se realizará poñendo variable = valor ou variable = expresión matemática.

Construcción de expresións

Pódense utilizar variables en expresións matemáticas ou lóxicas. Nese caso utilizarase o valor que ten nese momento a variable para realizar o cálculo ou avaliar a expresión.

Identificadores.

Tódalas variables, constantes ou funcións que utilicemos nun programa deben ter un nome. Este nome denomínase identificador, e debe cumprir as seguintes normas:

  • Pode estar formado por calquera combinación de letras do alfabeto inglés (sen ñ e sen acentos), números e o caracter de subliñado (_), pero debe comezar obrigatoriamente por unha letra.
  • As letras maiúsculas e as minúsculas son interpretadas como diferentes.
  • O nome do identificador non pode coincidir con ningunha das palabras pertencentes á linguaxe de programación.

Unha recomendación xeral para a elección de nomes é que sexan significativos e dunha lonxitude non excesiva.

Operadores, Expresións Aritméticas e Lóxicas.

Unha expresión é unha combinación de constantes variables e operadores. Según o tipo de operadores utilizados poderemos clasificar as expresións como expresións aritméticas ou expresións lóxicas.

Operadores aritméticos

  • signo negativo: -
  • suma, resta e multiplicación: +, - e *
  • división e módulo: /, %. O módulo calcula o resto da división de números enteiros.
  • incremento e decremento: ++ e --. Diferencia entre ++a e a++.

Prioridade entre operadores aritméticos (Ante a dúbida usar parénteses.):

  1. signo menos, incremento, decremento
  2. multiplicación, división, módulo
  3. suma, resta

Operadores relacionais e lóxicos

Os operadores relacionais son:

<, <=, >, >=, == (igual a), != (distinto de).

e os operadores lóxicos

&& (e), || (ou), ! (non).

O C interpreta calquera operación con resultado distinto de 0 como certa, e calquera operación con resultado 0 como falsa. A evaluación de expresións lóxicas dará polo tanto como resultado 0 (falso) ou distinto de 0 (certo).

A prioridade entre operadores lóxicos e relacionais (Ante a dúbida usar parénteses.):

  1. !
  2. >, >=, <, <=
  3. ==, !=
  4. &&
  5. ||

Operadores para o tratamento de bits

& (e), | (ou), ~ (non, alt+126), ^ (xor), >> (desprazamento á dereita), << (desprazamento á esquerda)

Cada desprazamento dun bit á esquerda realiza unha multiplicación do operando por 2, e cada desprazamento á dereita realiza unha división do operando por 2.

Conversión de Tipos: Casting.

Formato: (Tipo) Operando

Utilízase para convertir o tipo de datos dun operando utilizado nunha expresión.

Por exemplo:

<c>

float valor = 8,45; 
int resultado; 
resultado= (int) valor/2;

</c>

Ámbito das Variables: Locais e Globais.

En C as instruccións agrúpanse en bloques que van encerrados entre os símbolos { e }. Pódense declarar variables ó comenzo de cada bloque de instruccións, ou fora de tódolos bloques:

Variables Locais

As variables que se declaran dentro dun bloque de sentencias denomínanse variables locais. Estas variables créanse ó comezo do bloque e destrúense ó saírse del. As variables que se declaran como argumentos nas funcións son tamén variables locais dentro de toda a función. A declaración das variables realízase ó principio de cada bloque, excepto os argumentos da función que van entre paréntese despois do nome da función.

Variables Globais

Unha variable chámase global cando se declara fóra de tódolos bloques. As variables globais coñécense ó longo de todo o programa e pódense utilizar desde calquera sitio. Tódalas funcións e bloques declarados despois dunha variable global, poderán acceder a ela.

Se dentro dun bloque se declara unha variable local co mesmo nome que unha definida nun bloque exterior, ó acceso referirase exclusivamente á variable dentro do bloque máis interno.

Recoméndase utilizar sempre variables locais, salvo en ocasións en que de maneira excepcional sexa imprescindible declarar variables globais.

Tipos de Almacenamento.

Especifica a forma en que se almacenará a variable. Existen catro especificadores para determinar a clase de almacenamento: auto, extern, static e register. Por defecto, se non se especifica a clase de almacenamento considérase auto (variable local).

As variables externas (extern) fan referencia a unha variable global definida noutro módulo, e empréganse polo tanto para pasar información de uns módulos do programa a outros.

Por exemplo:

 extern int numero;

Unha variable de clase estática (static) é en realidade unha variable global (existe durante toda a execución do programa) pero que pode ter so alcance local (so se pode utilizar dentro do bloque en que está declarada). Utilízase para conseguir que as variables das funcións conserven o valor entre varias chamadas.

Por exemplo:

 static int contador;

Unha variable é de clase rexistro (register) cando se desexa que resida nun dos rexistros da CPU. Emprégase normalmente para acelerar a execución dos programas. Só se pode utilizar a clase rexistro cos tipos enteiro e caracter e actualmente os compiladores ó facer a traducción as empregan automáticamente cando o consideran necesario.

Por exemplo:

 register int numero;

Modificadores de Acceso.

Os modificadores de acceso poden preceder á definición dunha variable e son dous, const e volatile.

  • A palabra reservada const indica que unha variable non se poderá modificar en todo o programa. Por exemplo:
const int maximo = 200;
  • O modificador volatile indica ó compilador que o valor dunha variable pode ser modificado de forma non especificada no programa. Isto pode suceder cando a dirección de memoria dunha variable é pasada a rutinas do sistema que poidan modificala. Utilízase para evitar que o optimizador do compilador convirta este tipo de variables a register, o que podería causar o funcionamento erróneo do programa.
 volatile int cuenta = 0;

Selección

As estructuras de selección permiten executar ou non executar seccións de código dependendo do valor dunha condición. En C existen dúas estructuras de selección:

Si (if)

<c> if (condición) {

  instruccións sección 1

} [else {

  instruccións sección 2

}];

</c> Esta instrucción executaría as instruccións sección 1 si a condición se cumple. No caso de que non se cumpla, e si se especifica a parte else se executarían as instruccións sección 2.

Según (switch)

<c> switch(variable) {

 case Valor: instruccións;
             break;
 case Valor: instruccións;
             break;
 ...
 [default: instruccións;
           break; ]

}

</c> Esta instrucción comenza a executar as instruccións indicadas no Valor que corresponda co valor de variable. As instruccións execútanse ata atopar a instrucción break, de xeito que si non se atopa a execución continúa no case seguinte. O default é opcional, e corresponde cos valores para a variable que non foron contemplados nos case con anterioridade.

Iteración

As instruccións de iteración permiten repetir a execución dun grupo de instruccións mentras se cumpla unha condición. En C existen as seguintes:

Mentras (while)

<c> while(condición) {

 instruccións;

}

</c> Repite as instruccións mentras se cumple a condición. En primeiro lugar evalúase condición, si é certa, se executan as instruccións e se volve a evaluar a condición, e así sucesivamente.

Facer ... Mentras (do {} while)

<c> do {

 instruccións;

} while (condición);

</c> Repite as instruccións mentras se cumple a condición. En primeiro lugar execútanse as instruccións, logo se evalúa a condición. Si é certa, vólvense a executar as instruccións e a evaluar a condición, e así sucesivamente.

Para (for)

<c> for (inicialización;condición;variación) {

 instruccións;

}

</c> A instrucción for é a máis versátil das que dispon a linguaxe C. Pódense especificar varias instruccións separadas por comas para a inicialización e para a variación. A instrucción executará en primeiro lugar e unha única vez as instruccións especificadas en inicialización, a continuación evalúase a condición. Si a condición é certa, execútanse as instruccións e logo as instruccións indicadas en variación, volvéndose a evaluar a condición, e así sucesivamente.

Vexamos un exemplo: <c> // Visualiza os números de 1 a 100 for (i=1;i<=100;i++) {

 printf("%d\n",i);

}

// Visualiza os 30 primeiros números pares e impares for (i=1,j=2,c=0;c<30;i=i+2,j=j+2,c=c+1) {

 printf("%d %d\n",i.j);

}

// O mesmo que o anterior, pero máis claro: i=1; j=2; for (c=0;c<30;c++) {

 printf("%d %d\n",i,j);
 i=i+2;
 j=j+2;

}

</c>

Creación de Funcións

Unha función é un conxunto de instruccións para facer unha tarefa concreta. En C todos os programas están compostos de funcións, sendo a función main a que se executa en primeiro lugar. Un programa fai o que indica a súa función main . A división dos problemas en outros máis simples é a base do deseño descendente (Top Down), e as función son a ferramenta principal.

Prototipos de Funcións

As funcións, do mesmo xeito que as variables, deben ser declaradas. É necesario indicar:

  • O tipo de datos que devolve a función. As funcións reciben información, realizan unha serie de operacións e devolven un resultado. E necesario indicar de qué tipo é o resultado que vai retornar a función.
Si a función non retorna ningún resultado indicarase co tipo void.
  • O nome da función.. Este nome é o que se utilizará para chamar a función.
  • Unha lista de declaración de variables, separadas por comas. Nestas variables recibirase a información necesaria para que a función poda realiza-lo seu traballo. Se non é necesaria ningunha información indicarase con void.
 tipo funcion(tipo variable, tipo variable, ...);

Creación de Funcións

Para programar a función, se pon o seu prototipo sen o punto e coma final, e o corpo da función (as instruccións para realiza-lo traballo) entre chaves.

tipo funcion(tipo variable, tipo variable, ...)
{
   instruccións;
}

Para que a función retorne o resultado, utilizarase a instrucción return, poñendo

  return variable;

Ou ben

  return valor;

Paso de Parámetros

O paso de parámetros refírese ó xeito de indicarlle a unha función a información coa que vai a traballar. Aínda que o paso de parámetros se realiza físicamente sempre do mesmo xeito, dende o punto de vista lóxico podemos distinguir dous tipos que ilustraremos con exemplos:

Por Valor

No paso por valor, o que se lle pasa á función e o valor que ten unha variable, ou directamente un valor:

<c> int potencia(int base, int exponente) {

 int tot=1;
 while(exponente>0) 
 {
    tot=tot*base;
    exponente--;
 }
 return tot;

}

void main(void) {

 int a,b,r;
 printf("Base? ");
 scanf("%d",&a);
 printf("Exponente? ");
 scanf("%d",&b);
 
 r=potencia(a,b);
 printf("%d elevado a %d = %d\n",a,b,c);
 r=potencia(2,5);
 printf("2 elevado a 5 = %d\n",c);

}

</c> Aquí o valor das variables a e b da función main cópianse nas variables base e exponente da función potencia. A función fai o seu traballo e retorna un resultado que se almacena na variable r da función main. Despois o valor 2 cópiase en base e o 5 en exponente, voltando logo a función un resultado que almacenamos de novo en r.

Por Referencia

<c> void potencia(int base, int exponente, int *tot) {

 *tot=1;
 while(exponente>0) 
 {
    *tot=(*tot)*base;
    exponente--;
 }

}

void main(void) {

 int a,b,r;
 printf("Base? ");
 scanf("%d",&a);
 printf("Exponente? ");
 scanf("%d",&b);
 
 potencia(a,b,&r);
 printf("%d elevado a %d = %d\n",a,b,c);
 potencia(2,5,&r);
 printf("2 elevado a 5 = %d\n",c);

}

</c> Neste outro caso, o valor de a cópiase en base, o valor de b en exponente, e a dirección de memoria da variable r cópiase en tot. Logo, na función traballamos co valor almacenado na dirección que ten tot (mediante o operador *) , co que estaremos a modificar o valor da variable r da función main.

Este é tamén o xeito de traballar da función estándar de C scanf, por eso pasamos a dirección (operador &) das variables a e b, para que a función scanf poda modificar o valor de a e b que son variables pertencentes á función main.

Estructura dun Programa en C.

Un programa debe ser claro, estar ben organizado e ser fácil de ler e entender.

Para aumentar a claridade non se deben escribir liñas moi longas nin funcións con moitas liñas de código. Unha función demasiado grande demostra, en xeral, unha programación descoidada e unha análise do problema pouco estudiada. Deberase, en tal caso dividir o bloque en varias chamadas a outras funcións máis simples, para que a súa lectura sexa máis fácil. En xeral débese modularizar sempre que se poida, de maneira que o programa principal chame ás funcións máis xerais, e estas vaian chamando a outras, hasta chegar ás funcións primitivas máis simples. Isto sigue o principio de divide e vencerás, mediante o cal é máis fácil solucionar un problema dividíndoo en subproblemas (funcións) máis simples.

Os comentarios dunha liña comezan cos caracteres // e os comentarios de varias liñas van encerrados entre /* como apertura de comentario e */ como peche de comentario.

Cada bloque de especial importancia ou significación, e cada función debe separarse da seguinte cunha liña en branco. Ás veces, entre cada función engádese unha liña de asteriscos ou guións, como comentario, para destacar que empeza a implementación doutra función.

E convinte poñer un comentario antes do comenzo de cada función indicando o seu nome, unha descripción do que fai, dos datos que toma como argumentos e dos datos que devolve como resultado.

A identación ou sangrado consiste en marxinar hacia a dereita tódalas sentencias dunha mesma función ou bloque, de xeito que se vexa rapidamente cales pertencen ó bloque e cales non. A identación é moi importante para que o lector/programador non perda a estructura do programa debido ós posibles anidamentos. Convén elexir un número de caracteres de identación razonable, de xeito que o código non quede escalonado en exceso e sexa fácil de imprimir. 3 ou 4 caracteres é un bo número.

A codificación debe ser uniforme respetando sempre as mesmas reglas de identado, nomeamento de variables e de funcións e posición da función principal.

Normalmente, un programa en C sóese estructurar da seguinte forma:

  • Primeiro, os comentarios de presentación (data, autor, descrición do programa, licencia, etc.).
  • Despois, as sentencias correspondentes ás directivas: "#include", "#define", etc.
  • Definicións de tipos, con "typedef".
  • Variables globais, usadas no módulo e declaradas noutro módulo distinto, coa palabra reservada "extern".
  • Declaración de variables globais do módulo.
  • Declaración de funcións do módulo: Escribirase só o prototipo da función, non a súa implementación. Desta forma, o programa (e o programador) saberá o número e o tipo de cada parámetro e cada función.
  • Implementación de funciones: Aquí programaranse as accións de cada función, incluída a función principal. Normalmente, se o módulo inclúe a función principal, a función main(), esta ponse a primeira. Recomendamos ordenar as funcións da mesma maneira cós seus prototipos, xa que así pode ser máis fácil localizalas.

Cando estemos a programar un módulo que consista nunha serie de funcións que van a ser chamadas dende outros módulos se fai un ficheiro con igual nome que o ficheiro fonte e extensión .h. Neste ficheiro chamado ficheiro de cabeceira irán as declaracións das funcións que se van a chamar dende outros módulos, as definicións de tipos (typedef) que queremos usar dende outros módulos e a declaración como extern das variables globais ás que queramos acceder desde outros módulos. Unha vez escrito, bastará con incluir este ficheiro de cabeceira con #include nos módulos apropiados. Para evitar erros tamén conven incluir ese ficheiro de cabeceira no propio módulo eliminando do módulo as declaracións postas no ficheiro .h excepto as declaracións das variables globais.