Rekursion En funktion är rekursiv om den anropar sig själv, direkt eller indirekt. Alla funktioner kan anropas rekursivt. I sin enklaste form är rekursion enkel att förstå. printf("the universe is never ending\n"); main(); return 0; /* Kommer aldrig att nås */ Exempel För att summera de fyra första heltalen kan man skriva sum(4) där sum är funktionen nedan int sum(int n) { if (n == 1) return n; else return n + sum(n-1); sum(1) 1 sum(2) 2 + sum(1) eller 2 + 1 sum(3) 3 + sum(2) eller 3 + 2 + 1 sum(4) 4 + sum(3) eller 4 + 3 + 2 + 1 202 203 fakultet.c Matematiskt så lyder definitionen av fakultet som följer 0!=1, n!=n (n-1) 2 1 n>0 eller ekvivalent 0!=1, n!=n (n-1)! n>0 I C kan detta skrivas rekursivt som Iterativ motsvarighet Funktionen kan också skrivas iterativt int factorial (int n) { int product = 1; for ( ; n > 1; n-- ) product *= n; return product; int factorial (int n) { return (n == 0? 1 : n * factorial(n-1) ); 204 205 Effektivitet Många algoritmer har både rekursiva och iterativa lösningar. Normalt så är den rekursiva formuleringen vackrare och använder färre variabler för att göra samma beräkning. Vissa datorspråk använder bara rekursion för att åstadkomma upprepning (dvs. de har inte t.ex. while eller for). Rekursion orsakar att datorn lägger gamla värden av variabler i en stack för att komma ihåg dem. Ofta kan rekursion orsaka många funktionsanrop. fibonacci.c Fibonaccisekvensen definieras som följer ƒ 0 =0, ƒ 1 =1, ƒ i+1 =ƒ i +ƒ i-1, i=1,2, int fibonacci (int n) { return (n <= 1? n : fibonacci(n-1) + fibonacci(n-2); N fibonacci(n) Antalet funktionsanrop 0 0 1 1 1 1 2 1 3 37 24157817 78176337 38 39088169 126491971 206 207 1
Rekursion vs. iteration Vissa programmerare anser att rekursion inte ska användas p.g.a. de många funktionsanropen ineffektivitet. Rekursion har dock många fördelar de är ofta enklare att skriva lättare att förstå lättare att underhålla Därför kan det vara motiverat att använda rekursion trots dess nackdelar. Preprocessorn Preprocessorn evaluerar speciella direktiv i koden innan koden kompileras. Rader med preprocessordirektiv börjar med en #. Preprocessorn utför inkludering av filer, villkorlig kompilering och expansion av makron. Rader med preprocessordirektiv kan förekomma var som helst i koden. 208 209 Filinkludering Inkludering av filer utförs med #include. Standardfiler Standardfiler anges inom < och > varefter preprocessorn söker på ett antal fördefinierade ställen. #include <tcp.h> Användarfiler Användarfiler anges inom " varefter preprocessorn letar i den aktuella katalogen. #include "list.h" #include "matrix.h" Filinkludering Raden med preprocessordirektivet byts ut mot en kopia av filen vilket kan vara av intresse om det finns beroenden mellan filer som inkluderas. Filinkluderingar kan vara nästlade, d.v.s filer som inkluderas kan själv inkludera filer. 210 211 Makron och konstanter Makron kan användas till att definiera konstanter och öka tydligheten och portabiliteten av kod. Ett makro definieras med #define. #define identifier token_string Preprocessorn byter ut alla förekomster av identifier mot token_string i koden. #define MAX_STR_LEN 50 #define EPS 2.2204e-16 #define EQ == Det finns inga begränsningar i vad som kan definieras som makron. Makron med argument #define identifier(identifier,, identifier) token_string Efter en deklaration #define max(x,y) ((x)>(y)? (x) : (y)) blir uttrycket c = max(a+1,b-1); expanderat av preprocessorn till c = ((a+1)>(b-1)? (a+1) : (b-1)); 212 213 2
Vanliga fel Utan parenteser #define SQ(x) x*x c = SQ(5 + 1); blir expanderat av preprocessorn till c = 5 + 1 * 5 + 1 ; /* == 11 och ej 36 */ Semikolon och lika med #define add = + #define A 3; c = b add A-5; blir expanderat av preprocessorn till c = b = + 3; -5; Fler vanliga fel Funktionsargument Vad blir nämligen egentligen detta? #define MAX(x,y) ((x) > (y)? (x) : (y)) int give_next() { static int d = 2; d++; return d;.. MAX(3, give_next()) 214 215 Makron i stdio.h int getchar(); int putchar(int c); Returnerar: c vid OK, annars EOF getchar läser ett tecken från tangentbordet. putchar skriver ett tecken till skärmen. getchar() och putchar() getchar() returnerar en int och putchar() tar en int som argument. I minnet så lagras en char som 1 byte och en int lagras oftast som 4 byte. Därför kan en int lagra alla värden som en char kan. Vi kan tänka oss char som en liten heltalstyp och int som en stor teckentyp. Vi kan alltså lagra tecken i int lika gärna som i char. 216 217 double_out.c /* i stdio.h finns raden #define EOF (-1) */ int c; while ((c = getchar())!= EOF) { return 0; capitalize.c int c; while ((c = getchar())!= EOF) { if (c >= a && c <= z ) putchar( A + c - a ); else return 0; 218 219 3
Makron i ctype.h Makron för teckenhantering. #include <ctype.h> int isalpha(int c); 0 om c är en bokstav int isupper(int c); 0 om c är en stor bokstav int islower(int c); 0 om c är en liten bokstav int isdigit(int c); 0 om c är en siffra int isspace(int c); 0 om c är ett vitt tecken int toupper(int c); motsvarande stora bokstav eller c int tolower(int c); motsvarande lilla bokstav eller c Skriva eller läsa enskilda tecken Motsvarigheten till getchar och putchar heter för filer getc och putc. int putc(int c, FILE *stream); Returnerar: värdet av c. EOF vid filslut eller fel. int getc(file *stream); Returnerar: Nästa tecken från strömmen. EOF vid filslut eller fel. Dessa anrop sker genom makron. Det finns också litet långsammare funktionsvarianter av dessa som heter fputc och fgetc. 220 221 Exempel Observera att dessa två satser är ekvivalenta och att man inte kan öppna eller stänga stdout. fprintf(stdout, "Detta skrivs till skärm\n"): printf("detta skrivs till skärm\n"): Liksom att dessa tre satser är ekvivalenta. putc( a, stdout); fputc( a, stdout); putchar( a ); Villkorlig kompilering #if constant_integer_expression #ifdef identifier #ifndef identifier #elif #else Portabel kod Debug utskrifter Undvika att inkludera filer flera gånger 222 223 Villkorlig kompilering Debugutskrifter: #define DEBUG 1 #if DEBUG printf("debug: a = %d\n", a); eller #define DEBUG #ifdef DEBUG printf("debug: a = %d\n", a); Bryta upp kod i moduler Hålla samman relaterade funktioner Lättare felsökning Lättare att bygga ut. 224 225 4
Headerfiler: Filinkluderingar Typdeklarationer Definitioner, makron Funktionsprototyper Källkodsfil: Filinkluderingar Funktioner 226 temp.h #ifndef TEMP_H #define TEMP_H #include <stdlib.h> #define CK_ADJUST 273.15 #define CF_SCALE 1.8 #define CF_ADJUST 32 typedef double celsius; typedef double kelvin; typedef double farenheit; kelvin celsius_to_kelvin (celsius); farenheit celsius_to_farenheit (celsius); celsius farenheit_to_celsius (farenheit); kelvin farenheit_to_kelvin (farenheit); celsius kelvin_to_celsius (kelvin); farenheit kelvin_to_farenheit (kelvin); 227 temp.c #include temp.h kelvin celsius_to_kelvin(celsius c) { return (kelvin)(c - CK_ADJUST); farenheit celsius_to_farenheit(celsius c) { return (farenheit)((c * CF_SCALE) + CF_ADJUST); celsius farenheit_to_celsius(farenheit f) { return (celsius)((f - CF_ADJUST)/CF_SCALE); 228 Assertions Header-filen assert.h innehåller makrot assert(). Det är bra programmeringsvana att använda sig av assert() för att kontrollera att argumenten till en funktion är korrekta. #include <assert.h> double div (double a, double b) { assert(b!= 0); return a/b; div(2, 0); /* assertion will fail */ 229 Assertions Om ett argument till assert() är falskt, kommer programmet att skriva ut ett felmeddelande, t.ex. Assertion failed on line 23 och programmet kommer att avslutas. Assertions är enkla att skriva och gör källkoden robustare. Andra läsare av källkoden kan dessutom lättare förstå innehållet. Använd assert()!!! 230 5