O Compilador de C: Empezando a Programar: Diferenzas entre revisións

De Wiki do Ciclo ASIR do IES de Rodeira
Saltar á navegación Saltar á procura
(Nova páxina: "== Metodoloxía da Programación. == A principal razón para que as persoas aprendan a programar e o obxectivo xeral das linguaxes de programación, é poder facer uso da computa...")
 
Sen resumo de edición
 
Liña 1: Liña 1:
== Ciclo de Creación dun Programa en ''"C"''. ==
== Metodoloxía da Programación. ==


=== O editor de Textos. ===
A principal razón para que as persoas aprendan a programar e o obxectivo xeral das linguaxes de programación, é poder facer uso da computadora como unha ferramenta para a resolución de problemas. Axudado por unha computadora, a resolución dun problema pódese dividir en tres fases importantes:


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.
# Análise do problema.
# Deseño ou desenvolvemento dun algoritmo.
# Resolución do algoritmo na computadora.


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.
==== Análise Do Problema. ====


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 '''[[gal:IDE|IDE]]''' ('''''I'''ntegrated '''D'''evelopment '''E'''nvironment'') 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 '''[[gal:RAD|RAD]]''' ('''''R'''apid '''A'''pplication '''D'''evelopment'') e programación visual. '''glade''' é un entorno de desenvolvemento visual baseado no sistema de escritorio ''Gnome''.
O propósito da análise dun problema é axudar ó programador para chegar a unha certa comprensión da natureza do problema. O problema debe estar ben definido si se desexa chegar a unha solución satisfactoria. A análise do problema esixe unha lectura previa co fin de obter unha idea xeral do que se solicita. A segunda lectura deberá servir para responder ás preguntas:


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.
*¿Que información debe proporcionar a resolución dun problema dado?
*¿Que datos se necesitan para resolve-lo?


==== Deseño do Algoritmo. ====
=== 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.
En xeral podemos definir un '''algoritmo''' como un método que permite resolver un problema dado, no cal se describen os pasos necesarios para conseguir o fin proposto. É importante ter en conta de que a descrición dun algoritmo é totalmente independente da linguaxe de programación que posteriormente se utilice para levar o algoritmo a un ordenador.


==== O Preprocesador. ====
As características dun bo algoritmo son:


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''.
# Preciso: Debe indicar sen ambigüidades a orde e os pasos necesarios para resolver o problema.
# Definido: Si se sigue o algoritmo dúas veces en idénticas condicións, débese obter o mesmo resultado.
# Finito: Debe chegar a un final nun número finito de pasos.


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).
Á súa vez, a definición dun algoritmo debe ter esencialmente tres partes:


As directivas na linguaxe C comezan polo símbolo "#". Por exemplo, a directiva:
# Entrada: Solicítanse os datos necesarios para o problema e para que o algoritmo se inicie.
# Proceso: Conxunto de operacións que se realizan sobre os datos de entrada e outros datos internos para dar lugar a un resultado.
# Saída: É a fase na que se proporcionan os resultados do algoritmo.


<c>
Un ordenador non ten capacidade para solucionar problemas por si mesmo. Só os soluciona cando se lle proporcionan os pasos sucesivos a realizar, ou sexa o algoritmo, traducido a código máquina.
#include <stdio.h>


</c>
Os problemas complexos se resolven máis eficazmente cando se dividen en sub-problemas (módulos) máis doados de solucionar que o orixinal. A descomposición do problema orixinal noutros máis simples e, a continuación, a división destes noutros aínda máis simples, e así sucesivamente ata chegar á solución completa do problema chámase '''deseño descendente''' ou '''''top-down'''''. Normalmente os pasos indicados no primeiro esbozo do algoritmo son xenéricos, incompletos e indicarán só un número reducido de tarefas. Tras esta primeira descrición, estes pasos amplíanse nunha descrición máis detallada con máis pasos máis específicos. Isto é o que se denomina ''refinamento do algoritmo''.


As vantaxes máis importantes do deseño descendente son:
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.
* Os problemas se entenden con máis facilidade ó dividilos en partes máis simples denominadas módulos.
* As modificacións e corrección dos módulos son máis sinxelas.
* A verificación do problema é máis fácil.
* Facilita a distribución do traballo para ser solucionado entre varios grupos de persoas.
* Reutilización do código. Algúns módulos poden ser útiles en outros programas


* '''directiva #define''': Serve para dar nomes a valores. O preprocesador substituirá os nomes polos valores antes de realizar a compilación. Por exemplo:
A preocupación do deseñador centrarase en resolver correctamente cada un dos subproblemas (tarefa máis sinxela), coa garantía de que a unión de tódalas solucións dos subproblemas resolverá adecuadamente o problema de partida.


<c>
Cando se diseñan os distintos módulos convén non perder de vista a posibilidade de reutilización que pode presentar o módulo e deseñalo tendo esto en conta. Deste xeito conseguiremos ir xuntando unha '''librería''' de módulos que nos axudarán nos novos traballos.
#define PI 3.141592
#define Media(a, b) ((a+b)/2)


</c>
Existen varias ferramentas que permiten o deseño e a representación dun algoritmo. Dúas delas son os diagramas de fluxo e o pseudocódigo.


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'''.
==== Resolución do Algoritmo na Computadora. ====


Unha vez finalizado o preproceso, o noso programa queda listo para que poida comenzarse a súa traducción a código executable.
Unha vez que o algoritmo está deseñado débese pasar á fase de resolución práctica do problema coa computadora. Esta fase descomponse á súa vez nas seguintes subfases:


==== Creación do Código Obxecto. ====
# Codificación do algoritmo nun programa.
# Compilación e execución.
# Verificación e depuración.


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.
===== Codificación =====


==== O Enlazado. ====
Consiste en transcribir o algoritmo a unha linguaxe de alto nivel. Dado que os algoritmos son deseñados independentemente das linguaxes de programación, o código pode ser escrito con igual facilidade nunha linguaxe ou outra.


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''.
===== Compilación =====


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:
Unha vez que o algoritmo se converteu nun programa fonte, mediante o proceso de codificación, é necesario traducilo a código ou linguaxe máquina, única que o computador é capaz de entender e executar. O encargado de realizar esta función é un programa traductor (compilador ou intérprete) que indicará se o programa fonte presenta erros de sintaxe, nese case deberanse corrixir e volvelo a compilar, ata que non existan erros.


* '''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.
===== Verificación e depuración =====


* '''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''' ('''''d'''inamic '''l'''ink '''l'''ibrary''). 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 verificación e depuración son procesos sucesivos mediante os que se proba un programa cunha ampla variedade de datos de entrada, chamados datos de proba, que determinan se o programa ten erros lóxicos.


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.
E imprescindible realizar as probas unha vez compilado o programa con éxito, xa que é moi difícil conseguir programas libres de erros de lóxica. As probas deben facerse sempre intentando que o programa falle, e e aconsellable que as faga unha persoa distinta á que realizou o programa.


==== O Compilador gcc. ====
A depuración consiste en, unha vez establecido que existe un erro de lóxica, localizalo e correxilo. Para axudar nesta tarefa existen utilidades chamadas '''depuradores''' (''debuggers'').


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''').
== Ferramentas e Notacións para o Deseño de Algoritmos. ==


O uso, dunha forma sinxela (xa que o gcc é un compilador moi complexo que admite moitos parámetros e compilación multiplataforma) é o seguinte:
Existen varias ferramentas que permiten o deseño e a representación dun algoritmo. Aquí destacamos dúas delas que son:


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


gcc miprog.c
Os '''Diagramas de fluxo''' foron unha das ferramentas máis usadas debido ó seu caracter gráfico, xa que tódolos algoritmos son representados a través de figuras ou símbolos estándar que teñen asociado un significado particular. Cada paso do algoritmo é representado a través dun símbolo adecuado e o orden en que estes pasos se executan indícase conectándoos con frechas chamadas liñas de fluxo. Este sistema proporciona unha perspectiva visual do fluxo de execución do programa.


Esta instrucción xenera un ficheiro executable, de nome a.out. Para executar este programa, basta con invocalo directamente:
Símbolos básicos dos diagramas de fluxo ou ordinogramas:


./a.out
[[Image:diafluxo.png]]


Se queremos dar un nome distinto de a.out ó ficheiro executable (o máis habitual) , podemos indicalo coa opción '''-o''':
Por exemplo o seguinte diagrama representa un algoritmo para visualizar os factores primos dun número solicitado ó usuario:


gcc miprog.c -o miprog
[[Image:fluxofacprimos.png]]


así o ficheiro xerado á saída chamarase miprog.
==== Pseudocódigo. ====


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''
O '''Pseudocódigo''' xurdiu como alternativa ós diagramas de fluxo. É unha linguaxe algorítmica similar ó idioma natural, pero máis conciso e que permite unha redacción rápida do algoritmo, carecendo, loxicamente, da precisión nas súas operacións que posúen as linguaxes de programación.


gcc miprog.c -o miprog -lm -lncurses
== Estructuras Básicas dos Programas. ==


Se queremos aplicar únicamente o preproceso ó noso programa, podemos conseguilo utilizando a opción '''-E'''.
Para implementar os programas utilízanse sempre as mesmas estructuras básicas, que nos proporcionan as ferramentas para almacenar valores, variar o fluxo de execución do algoritmo, ou agrupar un conxunto de instruccións que proporcionan unha funcionalidade de xeito que sexa posible reutilizalas cando sexa convinte: '''variables''', '''estructuras de control''' e as '''funcións'''.


Se executamos:
==== Variables e Constantes ====


gcc -E miprogr.c
Unha '''variable''' é un sitio donde se pode almacenar un valor. En calquera momento se pode modificar o valor da variable almacenando un novo valor.


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:
Unha '''constante''' é un nome que se lle da a un valor fixo. Por exemplo PI pra representar 3.14159, este valor polo tanto non varía.


gcc -E miprogr.c > saida.txt
As variables as identificaremos cun nome, que empezará por unha letra e ''non levará espacios en branco''. Convén elexir nomes para as variables que definan a información que van a almacenar, por exemplo: '''edade''' é un bo nome de variable, pero '''w''' non.


===== Compilación dun programa con varios módulos =====
As instruccións típicas con variables son :


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.
* ASIGNACIÓNS: E o proceso de almacenar un valor a unha varible. Se realizará poñendo '''variable = valor''' ou '''variable = expresión matemática'''.


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:
* EXPRESIÓNS: Pódense utilizar variables en expresións matemáticas ou lóxicas. Nese caso utilizarase o valor que ten en ese momento a variable para realizar o cálculo ou evaluar a expresión.


gcc -c prog1.c prog2.c prog3.c
En moitas linguaxes de programación as variables teñen asociado un '''tipo''' de datos que define o tipo de información que é capaz de almacenar.
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:
==== Estructuras de control ====


gcc -c prog1.c
Os programas normalmente se executan en secuencia, dende a primeira instrucción ata a última. Para variar o fluxo de execución do programa empréganse as estructuras de control.
gcc -c prog2.c
gcc prog3.c -o prog prog1.o prog2.o


===== Utilización de bibliotecas de funcións con gcc =====
Todas as linguaxes de programación proporcionan o mesmo tipo de estructuras de control básicas, o único que cambia e a forma de escribilas e as palabras empregadas. Aquí faremos unha posible descripción de modo que as podamos utilizar dun xeito independiente da linguaxe de programación que utilicemos posteriormente, facilitando así o deseño do seudocódigo dos programas.


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á:
==== Selección (Condicionais) ====


gcc prog.c -o prog -L./lib -lstat
As estructuras de selección permiten seleccionar que instruccións executar segun o resultado dunha condición. As habituais en case todas as linguaxes de programación son:


Si queremos crear as nosas propias librerías o procedimento a seguir é o seguinte:
<font color="#29531E"> </font>


===== Librerías Dinámicas =====
'''Si (condición) Si (condición) Según (variable)'''
<instruccións>; <instruccións>; '''No caso VALOR:''' <instruccións>;
'''Se non Fin-Si''' '''No caso VALOR:''' <instruccións>;
<instruccións> ......
'''Fin-Si''' '''Noutro caso:''' <instruccións>;
'''Fin-Según'''


* 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:
A sentencia '''Según''' permite seleccionar un grupo de instruccións dependendo do valor dunha variable. As sentencias '''Si''' permiten executar un grupo de instruccións so si se cumple a condición indicada. Tamén é posible decir que grupo de instruccións alternativa queremos executar si a condición non se cumple coa palabra '''Se non'''.


gcc -c -fPIC modulo.c
A sentencia '''Según''' en realidade e unha forma de abreviar varios '''Si''' anidados.


* Crear a librería
A representación en diagramas de fluxo sería:


gcc -shared -Wl,-soname,libnomelibreria.so.xxx -o libnomelibreria.so.xxx.yyy.zzz modulo.o modulo.o ...
[[Image:Símbolo_Si.png]]

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 '''-l'''nomelibreria 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 [[gal:shell|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:

# '''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.

# '''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 [[gal:Prompt|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 [[gal:Breakpoint|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'''<nowiki>: ocupa sempre 2 bytes. </nowiki>
* '''long int'''<nowiki>: ocupa sempre 4 bytes. </nowiki>
* '''long long int'''<nowiki>: non existe en todos os compiladores, ocupa 8 bytes. </nowiki>

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:


==== Iteracións (Bucles ou Ciclos) ====
<c>
<c>
unsigned int maximo = 65000;
Mentras (condición) Facer

<instruccións> <instruccións>
... ...
Fin-Mentras Mentras (condición);
</c>
</c>
A sentencia '''Mentras''' permite '''''repetir''''' a execución dun grupo de instruccións '''mentras''' se cumple a condición indicada. A diferencia con '''Facer''' é que esta última executa as instruccións antes de evaluar a condición a primeira vez.


As '''instruccións típicas con variables''' son :
A súa representación en diagramas de fluxo permite unha mellor comprensión:


==== Asignacións de valores ====
[[Image:Simbolo_Mentras.png]]


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


<font color="#0555A0"> </font>
As '''funcións''' consisten en agrupar unha serie de instruccións que proveen unha certa funcionalidade baixo un nome. Esto permite o '''deseño descendente''' dunha maneira efectiva e facilita a reutilización do código, xa que esa funcionalidade pode ser necesaria noutros programas. As funcións normalmente reciben os datos de entrada necesarios para realizar o seu traballo nunhas variables chamadas '''parámetros''' e producen un ''resultado de saída''. Unha forma de representar as funcións pode ser o seguinte:


==== Construcción de expresións ====
'''Función <nomedafunción>'''
'''RECIBE :''' variable: Descripción, variable: Descripción, variable: Descripción, ...
'''DEVOLVE:''' descripción
<instruccións>
...
'''Fin-Función <nomedafunción>'''


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.
Normalmente as variables utilizadas na función existen so dentro da función (locais), e non teñen nada que ver con outras variables que se utilicen fora aínda que se chamen igual. Para utilizar a función únicamente é necesario chamala polo seu nome e con a lista de parámetros entre paréntese e almacenar (si e necesario) o valor devolto nunha variable:


<font color="#0505A0"> </font>
variable = '''<nomedafunción> (dato1, dato2, dato3, ....)'''


=== 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:
== Técnicas de Programación. ==


* 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.
==== Programación 'Convencional'. ====
* 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.
A programación estructurada é unha metodoloxía de programación que fundamentalmente trata de construír programas que sexan facilmente comprensibles. Esta metodoloxía está baseada na técnica de desenvolvemento de programas por refinamentos sucesivos: inicialmente plantéxase a operación global a realizar polo programa, e descomponse noutras máis sinxelas. Á súa vez, estas últimas volven ser descompostas novamente noutras accións aínda máis elementais. Este proceso de descomposición continúa hasta que todo se pode escribir utilizando tres estructuras básicas de control (as estructuras de control determinan a orde na que deben realizarse as distintas accións):


<font color="#0505A0"> </font>
# Secuencia.
# Selección.
# Iteración.


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


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.
Cando se deben executar sucesivamente distintas accións, escribirase a lista destas accións na orde na que deban ser executadas.


===== Selección =====
==== Operadores aritméticos ====


* signo negativo: '''-'''
Son aquelas que controlan a execución ou a non execución dunha ou máis instruccións en función de que se cumpra ou non unha condición previamente establecida. a.- Alternativa simple. b.- Alternativa dobre. c.- Alternativa múltiple.
* suma, resta e multiplicación: '''+''', '''-''' e '''<nowiki>*</nowiki>'''
* 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.''):
===== Iteración =====


# signo menos, incremento, decremento
A estructura de control iterativa permite expresar que unha mesma acción ou composición de accións se debe executar unha ou máis veces e de forma consecutiva: a.- Estructura Mentres. b.- Estructura Repetir - Mentres c.- Estructura Para
# multiplicación, división, módulo
# suma, resta


==== Programación Orientada a Obxectos. ====
==== Operadores relacionais e lóxicos ====


Os operadores relacionais son:
A programación orientada a obxectos ('''OOP''') pretende solucionar os problemas enfocandoos dende o punto de vista dos obxectos que interveñen no problema e a forma de relacionarse entre eles. Baixo este punto de vista, un obxecto está composto de '''atributos ou propiedades''' (información que almacenan) e de '''métodos''' (accións que poden realizar sobre os seus atributos). Destes métodos, algúns son públicos (accesibles por outros obxectos) e outros privados (para uso interno do propio obxecto), os atributos públicos reciben o nome de ''interface'' do obxecto. Os obxectos se comunican entre eles invocando métodos doutros obxectos, no que se chama normalmente unha '''mensaxe'''.


'''<''', '''<=''', '''>''', '''>=''', '''<nowiki>==</nowiki>''' (igual a), '''<nowiki>!=</nowiki>''' (distinto de).
Os métodos en realidade son funcións que forman parte do obxecto. Os obxectos se deseñan pensando únicamente no obxecto e nas funcionalidades que debe proporcionar.


e os operadores lóxicos
Para crear un obxecto é necesario diseñar unha '''clase''' de obxecto, que é a que vai a definir qué atributos e métodos van a ter todos os obxectos que pertenezcan a esa clase. Por exemplo, si diseñamos a clase ''coche'', que ten como ''atributos'' a cor, a cilindrada, o número de portas e o tipo de combustible e como ''métodos'' arrancar, parar e cambiar de marcha poderemos crear obxectos de tipo coche, como poden ser ''wvpolo'', ''scenic'' ou ''golf''. Ó proceso de crear un obxecto coñécese como '''instanciación''' e dise que se crea unha '''''instancia''''' da clase á que pertence o obxecto.


'''&&''' (e), '''<nowiki>||</nowiki>''' (ou), '''<nowiki>!</nowiki>''' (non).
As características máis importantes da programación orientada a obxecto son a '''herdanza''' e o '''polimorfismo'''<nowiki>: </nowiki>


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 '''herdanza''' permite o deseño de novas clases a partir de outras existentes, herdando de modo automático toda a funcionalidade (métodos e atributos).


A prioridade entre operadores lóxicos e relacionais (''Ante a dúbida usar parénteses.''):
* O '''polimorfismo''' indica que un método pode ter distintas funcionalidades. Si definimos unha nova clase a partir de outra (herdanza) herdaremos os seus métodos. É posible reescribir a funcionalidade destes métodos herdados de modo que teremos novas versións dos mesmos métodos con comportamentos distintos. A posibilidade de ter distintas versións do mesmo método dentro dunha clase, diferenciándose únicamente no tipo de información que reciben coñécese como '''sobrecarga'''.


# !
Un ''exemplo de polimorfismo'' pode ser o seguinte: Temos a clase base ''Paxaro'', que ten un método chamado ''camiña'', que fai que o paxaro camiñe. Podemos crear novas clases derivadas de ''Paxaro'' (''Gorrión'', ''Paloma'' e ''Pingüín'') que heredan os métodos e atributos de ''Paxaro'' ('''son paxaros'''). Sen embargo o xeito de camiñar dos ''Pingüíns'' é distinto do dos demáis ''Paxaros'', co que é necesario reescribir a funcionalidade do método ''camiña'' dentro da clase ''Pingüín'', non así cos ''Gorrións'' e ''Palomas'' xa que camiñan como todos os ''Paxaros''. Desde o punto de vista do programa, todos os ''Paxaros'' poden camiñar (invocando ó metodo ''camiña'') o polimorfismo encárgase de que cada ''Paxaro'' poda facelo da maneira axeitada.
# >, >=, <, <=
# ==, !=
# &&
# ||


==== Operadores para o tratamento de bits ====
Este tipo de programación facilita moito a reutilización do código, xa que as clases deseñadas poden formar parte de varios problemas, e a herdanza proporciona unha forma de desenvolvemento moi rápida.

& (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''. <u>En primeiro lugar evalúase ''condición''</u>, 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''. <u>En primeiro lugar execútanse as ''instruccións''</u>, 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 <u>unha única vez</u> 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 <u>a dirección de memoria da variable '''r'''</u> 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''.
O obxectivo da programación orientada a obxectos é a creación de obxectos, mentres que na programación por procedementos é a xeración de fluxos de programa e liñas de código. Cando se pensa en orientación a obxectos estase facendo referencia a compoñentes software que posúen unha determinada estructura de datos e son capaces de responder a distintos estímulos.


== Estructura dun Programa en C. ==
Mentres que na programación clásica o importante son os procedementos, o que hai que facer, a funcionalidade do sistema e, pensando nela créanse as estructuras de datos. En cambio, na programación orientada a obxectos o importante son estes, a súa identificación e definición. Máis tarde tense en conta o código como un elemento que serve para facilitar a interacción entre obxectos.


Un programa debe ser claro, estar ben organizado e ser fácil de ler e entender.
Frente á programación por procedementos, as clases teñen as seguintes vantaxes: Facilítase a reutilización do código, a realización de modificacións e introducción de melloras na aplicación, o reparto das tarefas de desenvolvemento entre varios programadores e a corrección de erros.


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.
==== Programación Visual e Ferramentas Case. ====


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.
Coa chegada das interfaces gráficas de usuario ('''GUI''' ('''''G'''raphical '''U'''ser '''I'''terface'')) naceu o desenvolvemento visual e orientado a eventos. Neste tipo de desenvolvemento o usuario limítase a colocar na pantalla dun xeito visual os distintos compoñentes do programa programando so a resposta que debe dar o compoñente a certos eventos permitindo deste xeito un rápido desenvolvemento da interface do usuario. Por exemplo, podería colocar na pantalla un botón, e programar a acción a realizar cando se pulse ese botón (evento de botón pulsado). Este tipo de ferramentas para programar chámanse '''RAD''' ('''''R'''apid '''A'''pplication '''D'''evelopment'') e garda unha certa relación cos entornos de cuarta xeración e as ferramentas CASE.


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.
En principio calquera programa que se poida empregar nos procesos de análise, desenvolvemento, implantación e mantemento de aplicacións informáticas pode incluírse dentro da categoría de ferramenta CASE. Non obstante, existe todo un conxunto de utilidades, entornos e outras ferramentas especificamente CASE que proporcionan ó enxeñeiro de software a capacidade de automatizar as actividades manuais e de mellorar o seu enfoque de traballo.


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.
==== Entornos de cuarta xeración ====


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.
Un tipo de ferramentas CASE que se utilizan en tarefas de programación son os entornos de cuarta xeración.


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.
As técnicas de cuarta xeración son un conxunto de ferramentas que permiten a obtención de código fonte a partir de especificacións a alto nivel. Canto maior sexa o nivel de especificación maior será a velocidade de programación. As técnicas de cuarta xeración tenden a estar cada vez máis cerca da linguaxe natural.


Normalmente, un programa en C sóese estructurar da seguinte forma:
Dentro das técnicas de cuarta xeración pódense atopar: deseño visual de bases de datos, das súas táboas, de formularios e informes, xeración de código a partir de especificacións gráficas ou textuais, xeración automática da documentación dun proxecto, etc.


* Primeiro, os comentarios de presentación (data, autor, descrición do programa, licencia, etc.).
Entre as vantaxes do uso de técnicas de cuarta xeración está o incremento da productividade derivado dunha maior velocidade de desenvolvemento.
* 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.
Como inconveniente proponse que as técnicas de cuarta xeración non producen o código máis eficiente en cada caso, nin reducen o tempo de desenvolvemento, unicamente modifícase a duración de cada fase, aumenta o tempo dedicado á análise e disminúe o da programación. Deste xeito, en sistemas de pequeno ou mediano tamaño redúcese o tempo total, xa que non se necesita unha análise moi complexa, disminuíndo así o tempo dedicado á programación.
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.

Revisión actual feita o 26 de xuño de 2014 ás 10:10

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.