Presentation laddar. Vänta.

Presentation laddar. Vänta.

Datavetenskap för teknisk kemi 10p, moment 1

Liknande presentationer


En presentation över ämnet: "Datavetenskap för teknisk kemi 10p, moment 1"— Presentationens avskrift:

1 Datavetenskap för teknisk kemi 10p, moment 1
Funktioner Istället för att försöka lösa ett stort problem på en gång, kan man dela upp det i mindre delproblem. ”top-down” metod för att lösa problemen Försök dela upp programmet i mindre och mindre delproblem. När delproblemen är enkla nog, använd funktioner för att lösa delproblemen (och därmed hela problemet). Program är ofta uppdelade på flera källkodsfiler där varje fil kan innehålla noll eller flera funktioner. En funktion måste dock alltid finnas (main()) Datavetenskap för teknisk kemi 10p, moment 1

2 Datavetenskap för teknisk kemi 10p, moment 1
Funktionsdefinition C-källkoden, som beskriver vad en funktion gör, kallas funktionsdefinition. Den ska inte misstas för funktionsdeklarationen (också kallad funktionsprototyp). Funktionsdefinition ser ut så här i den generella formen type functionname( parameter list ) { declarations statements } Allting innan första krullparentesen kallas för funktionshuvud. Allting mellan krullparenteserna kallas för funktionskropp. Parameterlistan är en komma-separerad lista med deklarationer. Datavetenskap för teknisk kemi 10p, moment 1

3 Datavetenskap för teknisk kemi 10p, moment 1
Exempel 1 int factorial (int n) { int i, product = 1; for (i = 2; i <= n; ++i) product *= i; return product; } Den första int säger att värdet som funktionen returnerar kommer att konverteras (om det behövs) till int. Parameterlistan består av deklarationen int n. Detta säger åt kompilatorn att funktionen tar ett argument av typen int. Ett uttryck som factorial(7) gör att funktionen anropas med argumentet 7. Den lokala variabeln n sätts till värdet 7 innan funktionen börjar. Datavetenskap för teknisk kemi 10p, moment 1

4 Datavetenskap för teknisk kemi 10p, moment 1
Exempel 2 void wrt_adress(void) { printf(”%s\n%s\n%s\n%s\n\n”, ” **********************”, ” ** SANTA CLAUS **”, ” ** NORTH POLE **”, ” **********************”); } Denna funktion returnerar ingenting (void). Den tar inte heller några parametrar. Den anropas med uttrycket wrt_adress(). För att anropa den tre ggr: for (i = 0; i < 3; i++) wrt_adress(); Datavetenskap för teknisk kemi 10p, moment 1

5 Datavetenskap för teknisk kemi 10p, moment 1
Exempel 3 void nothing(void) { } /* does nothing */ double twice(double x) { return (2.0 * x); } int all_add(int a, int b) { int c; ..... return (a + b + c); Datavetenskap för teknisk kemi 10p, moment 1

6 Datavetenskap för teknisk kemi 10p, moment 1
Variabler Variabler i en funktion är lokala för den funktionen. Ändringar av de variablerna kommer inte att påverka andra variabler utanför funktionen som har samma namn. En variabel kan också deklareras utanför en funktionskropp och blir då global. Normalt är globala variabler inte att rekommendera. #include <stdio.h> int a = 33; int main(void) { int b = 77; printf(”%d\n”, a + b); } Datavetenskap för teknisk kemi 10p, moment 1

7 Datavetenskap för teknisk kemi 10p, moment 1
return return satsen kan ha ett uttryck eller vara utan. return_statement ::= return; | return expression; return; return ++a; return (a * b); Uttrycket som returneras kan omges av parenteser, men detta krävs ej. När en return sats påträffas så avbryts exekveringen (körningen) av funktionen och kontrollen ges åter till den anropande miljön. Om return satsen innehåller ett uttryck, så skickas värdet av uttrycket (efter eventuell konvertering) tillbaks till den anropande miljön. Exempel på s. 201 Datavetenskap för teknisk kemi 10p, moment 1

8 Datavetenskap för teknisk kemi 10p, moment 1
Funktionsprototyper En funktion bör deklareras innan den används. ANSI C tillhandahåller en deklarationssyntax som kallas funktions-prototyp. En funktionsprototyp berättar för kompilatorn antalet argument och av vilken typ argumenten är. Den berättar dessutom vilken typ av värde (om något) som funktionen returnerar. double sqrt(double); Detta berättar för kompilatorn att sqrt() är en funktion som tar ett enda argument av typen double och returnerar double. Datavetenskap för teknisk kemi 10p, moment 1

9 Datavetenskap för teknisk kemi 10p, moment 1
Exempel void f(char c, int i); är samma sak som void f(char, int); Identifierarna c och i används inte av kompilatorn. Deras enda syfte är att ge dokumentation till programmeraren. Funktionsprototyper låter kompilatorn kontrollera källkoden bättre och lättare hitta fel. Dessutom kommer värden till funktioner att bli korrekt omvandlade. T.ex. om funktionen sqrt() har definerats, så kommer ett anrop sqrt(4) att fungera. Kompilatorn kommer att omvandla 4 till 4.0 (double) Datavetenskap för teknisk kemi 10p, moment 1

10 Funktions- deklarationer
Om ett funktionsanrop f(x) påträffas innan någon deklaration eller prototyp gjorts, så förutsätter kompilatorn en standard deklaration av formen int f(); Inget förutsätts om parameterlistan till funktionen. Funktionsdefinitioner och prototyper har vissa begränsningar. Lagringsklassen kan vara endera extern eller static men inte båda. Om inget anges, så är lagringsklassen extern Typerna ”array of ...” och ”function returning ...” kan ej returneras av en funktion. Däremot kan en pekare till en array eller en funktion returneras. Datavetenskap för teknisk kemi 10p, moment 1

11 Alternativ funktionsdefinition
Antag att vi vill skriva ett stort program i en enda källkodsfil. Normalt så lägger vi en #include i början av filen där vi har våra funktionsprototyper. Ett alternativt sätt att lägga funktionsdeklarationerna överst i filen innan funktionen anropas. main()-funktionen läggs sist av alla. Detta fungerar eftersom att en funktionsdeklaration också fungerar som en funktionsprototyp. Det är att föredra att använda det traditionella sättet med funktionsprototyper, main() först och sedan funktionsdeklarationer. Datavetenskap för teknisk kemi 10p, moment 1

12 Datavetenskap för teknisk kemi 10p, moment 1
Exempel #include <stdio.h> #define N 7 void prn_heading(void) { .... } void power(int m, int n) { int main(void) { prn_heading(); printf(”%d\n”, power(7,3)); Datavetenskap för teknisk kemi 10p, moment 1

13 Datavetenskap för teknisk kemi 10p, moment 1
Funktionsanrop Programmet börjar alltid med ett anrop till main(). När en funktion anropas, ges kontroll till den funktionen. Efter att funktionen är klar med sitt jobb, ges kontroll tillbaka till den anropande funktionen. Om en funktionsprototyp har getts, kontrollerar kompilatorn att typerna på argumenten är kompatibla. Argumenten skickas ”call-by-value”. Detta betyder att varje argument utvärderas och värdet av argumentet skickas till funktionen. Värdena lagras i den korresponderande lokala parametern. Alltså, om en variabel skickas till en funktion så ändras inte värdet på variabeln i den anropande miljön. Datavetenskap för teknisk kemi 10p, moment 1

14 Datavetenskap för teknisk kemi 10p, moment 1
Exempel #include <stdio.h> int compute_sum(int n); int main(void) { int n = 3, sum; printf(”%d\n”, n); sum = compute_sum(n); printf(”%d\n”, sum); } /* forts. nästa slide */ Datavetenskap för teknisk kemi 10p, moment 1

15 Datavetenskap för teknisk kemi 10p, moment 1
Exempel (forts) /* forts. från förra slide */ int compute_sum(int n) { int sum = 0; for (; n > 0; --n) sum += n; return sum; } Datavetenskap för teknisk kemi 10p, moment 1

16 Datavetenskap för teknisk kemi 10p, moment 1
Funktionsanrop Funktionsanrop innebär: Varje uttryck i argumentlistan utvärderas Värdet på uttrycket konverteras, om nödvändigt, till typen på den formella parametern och det värdet ges till den formella parametern i början av funktionskroppen. Funktionskroppen exekveras (körs). Om en return-sats påträffas, så ges kontrollen tillbaka till den anropande miljön Om return-satsen innehåller ett uttryck, utvärderas uttrycket och konverteras, om nödvändigt, till returtypen och det värdet skickas till den anropande miljön. Om en return-sats inte innehåller ett uttryck, skickas inget användbart värde tillbaka till den anropande miljön. Datavetenskap för teknisk kemi 10p, moment 1

17 Datavetenskap för teknisk kemi 10p, moment 1
Funktionsanrop Funktionsanrop innebär: (forts.) Om ingen return-sats påträffas, ges kontrollen tillbaka till den anropande miljön när slutet på funktionskroppen påträffas. Inget användbart värde returneras. Alla argument skickas ”call-by-value” Datavetenskap för teknisk kemi 10p, moment 1

18 Datavetenskap för teknisk kemi 10p, moment 1
Programutveckling Normalt skrivs ett stort program i ett separat bibliotek som en samling .h och .c filer, där varje .c-fil innehåller en eller flera funktionsdefinitioner. Varje .c-fil kan kompileras igen endast när det är nödvändigt och därmed sparas tid både för kompileraren och programmeraren (se användningen av make, s. 532) Antag att vi utvecklar ett stort program ”pgm”. I början av varje .c-fil så lägger vi raden Rita bild på s.209 på tavlan!!! #include ”pgm.h” När preprocessorn påträffar detta direktiv, söker den först i det aktuella biblioteket efter filen ”pgm.h”. Om den finns, så läggs den in där #include påträffades. Om den inte finns, söker preprocessorn i system-biblioteken. Datavetenskap för teknisk kemi 10p, moment 1

19 Datavetenskap för teknisk kemi 10p, moment 1
pgm.h #include <stdio.h> #include <stdlib.h> #define N 3 /* funktionsprototyper */ void fct1(int k); void fct2(void); void wrt_info(char *); Datavetenskap för teknisk kemi 10p, moment 1

20 Datavetenskap för teknisk kemi 10p, moment 1
main.c #include ”pgm.h” int main(void) { char ans; int i, n = N; printf(”%s”, ”This program does not do very much.\n” ”Do you want more information? ”); scanf(”%c”, &ans); if (ans == ’y’ || ans == ’Y’) wrt_info(”pgm”); for (i=0; i < n; ++i) fct1(i); printf(”Bye!\n”); return 0; } Datavetenskap för teknisk kemi 10p, moment 1

21 Datavetenskap för teknisk kemi 10p, moment 1
fct.c #include ”pgm.h” void fct1(int n) { int i; printf(”Hello from fct1()\n”); for (i=0; i < n; ++i) fct2(); } void fct2(void) { printf(”Hello from fct2()\n”); Datavetenskap för teknisk kemi 10p, moment 1

22 Datavetenskap för teknisk kemi 10p, moment 1
wrt.c #include ”pgm.h” void mrt_info(char *pgm_name) { printf(”Usage: %s\n\n”, pgm_name); printf(”%s\n”, ”This program illustrates how one can write a program\n” ”in more than one file. In this example, we have a\n” ”single .h file that gets included at the top of our\n” ”three .c files. Thus, the .h file acts as the \”glue\”\n” ”that binds the program together.\n\n” ”Note that the functions fct1() and fct2() when called\n” ”first says \”hello\”. When writing a serious program, the\n” ”programmer sometimes does this in a first working version\n” ”the code.\n”); } /* } on this line to save space only! */ Datavetenskap för teknisk kemi 10p, moment 1

23 Vad utgör ett stort program?
För en vanlig programmerare så kan ett par hundra rader utgöra ett stort program. Vilka rader räknas? READ_ME .c och .h filer I kommersiella applikationer (program) skrivs program av flera programmerare och ett stort program kan bestå av flera hundra tusen rader. På 70-talet hos IBM fick programmerarna betalt efter antalet ”k-lines” de skrev (1000 rader). Självklart blev programmen större än nödvändigt... Datavetenskap för teknisk kemi 10p, moment 1

24 Datavetenskap för teknisk kemi 10p, moment 1
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> int f(int a, int b) { assert(a!=4 && b!=0); return a/b; } int main() { f(2, 0); /* assertion will fail! */ assert = försäkra, bedyra ”se till att” Datavetenskap för teknisk kemi 10p, moment 1

25 Datavetenskap för teknisk kemi 10p, moment 1
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()!!! Datavetenskap för teknisk kemi 10p, moment 1

26 Datavetenskap för teknisk kemi 10p, moment 1
Räckvidd (scope) Grundregeln är att identifierare är bara tillgängliga inne i det block där de deklarerades. De finns inte utanför blocket. Detta låter som en enkel regel, men programmerare väljer ofta att använda samma identifierare på olika variabler. Frågan uppstår då vilken objekt identifieraren refererar till... Datavetenskap för teknisk kemi 10p, moment 1

27 Datavetenskap för teknisk kemi 10p, moment 1
Exempel { int a = 2; /* outer block a */ printf(”%d\n”, a); int a = 5; /* inner block a */ } printf(”%d\n”, ++a); /* back to the outer block a */ Skriv ekvivalenta exempelt på s. 214 på tavlan!!! Vad skrivs ut? Datavetenskap för teknisk kemi 10p, moment 1

28 Datavetenskap för teknisk kemi 10p, moment 1
Exempel { int a = 1, b = 2, c = 3; printf(”%3d%3d%3d\n”, a, b, c); int b = 4; float c = 5.0; printf(”%3d%3d%5.1f\n”, a, b, c); a = b; int c; c = b; } Skriv på OH! 1 2 3 4 4 4 4 2 3 Datavetenskap för teknisk kemi 10p, moment 1

29 Parallella och nästlade block
{ int a, b; { /* inner block 1 */ float b; /* int a is known, but not int b */ } { /* inner block 2 */ float a; /* int b is known but not int a.*/ /* nothing in inner block 1 is known */ Huvudsyftet med att använda block är att spara minne. Minne reserveras bara när variabeln behövs. Vid andra tillfällen kan minnet användas till annat. Datavetenskap för teknisk kemi 10p, moment 1

30 Datavetenskap för teknisk kemi 10p, moment 1
Debugging Ett annat vanligt användningsområde för block är debugging (avlusning) Antag att variabeln v missköter sig { static int cnt = 0; printf(”*** debug: cnt = %d v = %d\n”, ++cnt, v); } Blocket plockas bort sedan när programmet fungerar... cnt är nu lokal till blocket och kommer inte att störa resten av programmet. Den är dessutom deklarerad som static, vilket betyder att den initialiseras (i detta fall till noll) och behåller sedan sitt gamla värde. Datavetenskap för teknisk kemi 10p, moment 1

31 Datavetenskap för teknisk kemi 10p, moment 1
Lagringsklasser Varje variabel och funktion i C har två attribut; typ och lagringsklass. De fyra lagringsklasserna är auto, extern, register och static. Den vanligaste lagringklassen är auto, men en programmerare måste känna till de andra lagringsklasserna. Alla fyra lagringsklasser har viktiga användningsområden. Datavetenskap för teknisk kemi 10p, moment 1

32 Datavetenskap för teknisk kemi 10p, moment 1
Lagringklassen auto Om man deklarerar en variabel som vanligt, så blir den auto. En sammansatt sats med variabel deklarationer kallas för ett block. Nyckelordet auto kan användas för att explicit specifiera lagringklassen auto int a, b, c; auto float f; Eftersom en variabel automatiskt blir auto om ingen annan lagringsklass anges, så är det sällan lagringsklassen anges explicit som ovan. Datavetenskap för teknisk kemi 10p, moment 1

33 Lagringsklassen extern
Ett sätt att överföra information mellan block och funktioner är att använda externa variabler. När en variabel deklareras utanför en funktion så är lagringen permanent och dess lagringsklass blir extern. En sådan variabel är global för alla funktioner som deklareras efter variabeldeklarationen. Normalt är globala (eller externa) variabler inte bra för programmets korrekthet. Även funktioner kan deklareras till att vara externa. Datavetenskap för teknisk kemi 10p, moment 1

34 Datavetenskap för teknisk kemi 10p, moment 1
Exempel #include <stdio.h> int a = 1, b = 2, c = 3; int f(void); int main(void) { printf(”%3d\n”, f()); printf(”%3d%3d%3d\n”, a, b, c); return 0; } int f(void) { int b, c; a = b = c = 4; return (a + b + c); Datavetenskap för teknisk kemi 10p, moment 1

35 Datavetenskap för teknisk kemi 10p, moment 1
extern Nyckelordet extern kan användas vid deklarationen av externa variabler, men krävs ej. En extern variabel kan ej ges lagringsklasserna auto eller register. Den kan ges static, men då blir användningen speciell (se senare OH). extern kan också användas för att säga åt kompilatorn att söka någon annanstans efter värdet på variabeln. Datavetenskap för teknisk kemi 10p, moment 1

36 Datavetenskap för teknisk kemi 10p, moment 1
file1.c #include <stdio.h> int a = 1, b = 2, c = 3; int f(void); int main(void) { printf(”%3d\n”, f()); printf(”%3d%3d%3d\n”, a, b, c); return 0; } Datavetenskap för teknisk kemi 10p, moment 1

37 Datavetenskap för teknisk kemi 10p, moment 1
file2.c int f(void) { extern int a; /* look for it elsewhere */ int b, c; a = b = c = 4; return (a + b + c); } Detta ger samma effekt som det tidigare program-exemplet. Externa variabler försvinner aldrig. De kan användas för att överföra information mellan funktioner. Ofta är detta inte bra. Externa variabler ”försvinner” (tillfälligt) när identifieraren omdefineras. Datavetenskap för teknisk kemi 10p, moment 1

38 Lagringklassen register
Denna lagringsklass säger åt kompilatorn att variablerna ska lagras i processorns interna register. Dessa register är mycket snabbare än det vanliga minnet. När man deklarerar en variabel som register, så ger man en rekommendation till kompilatorn. Den kan välja att ignorera din begäran. Många register i processorn är dessutom reserverade för system-ändamål och kan inte användas för vanliga program. Men om det finns lediga register, kan programmet snabbas upp betydligt. Datavetenskap för teknisk kemi 10p, moment 1

39 Datavetenskap för teknisk kemi 10p, moment 1
Exempel { register int i; for (i = 0; i < LIMIT; ++i) { ... } } /* block exit will free register */ En vanlig variabel att deklarera som register är loopvariabler. Dessa används ofta och tjänar mest på att bli register-variabler. Om ingen typ anges, antas typen vara int register i; är samma sak som register int i; Datavetenskap för teknisk kemi 10p, moment 1

40 Lagringklassen static
Statiska deklarationer har två viktiga användningsområden. De låter en lokal variabel behålla sitt gamla värde De kan också användas vid externa deklarationer (se senare OH) void f(void) { static int cnt = 0; ++cnt; if (cnt % 2 == 0) /* do something */ else /* do something different */ } Datavetenskap för teknisk kemi 10p, moment 1

41 Statiska externa variabler
Varför ska man använda statiska externa variabler? Externa variabler behåller ju redan sitt gamla värde. En viktig princip inom programmering är modularitet. Genom att separera olika delar av ett program så underlättar man debugging. Om en extern variabel deklareras som static, kommer dess räckvidd att begränsas till filen som den deklarerades i och bara vara tillgänglig i funktioner som deklareras nedanför i filen. Även om en annan fil försöker komma åt den externa variabeln med nyckelordet extern så kommer detta att misslyckas. Det fungerar alltså som en ”privacy-mechanism”. Datavetenskap för teknisk kemi 10p, moment 1

42 Datavetenskap för teknisk kemi 10p, moment 1
Exempel #define INITIAL_SEED #define MULTIPLIER #define INCREMENT #define MODULUS #define FLOATING_MODULUS static unsigned seed = INITIAL_SEED; /* external but private for this file */ unsigned random(void) { seed = (MULTIPLIER * seed + INCREMENT) % MODULUS; return seed; } double probability(void) { return (seed / FLOATING_MODULUS); Datavetenskap för teknisk kemi 10p, moment 1

43 Datavetenskap för teknisk kemi 10p, moment 1
Exempel En funktion kan också deklareras som static och blir därmed ”privat” för den filen. static int g(void); /* funktionsprototyp */ void f(int a) { /* funktionsdefinition */ /* g() är tillgängligt här, */ } /* men inte i andra filer */ static int g(void) { /* funktionsdefinition */ .... } Datavetenskap för teknisk kemi 10p, moment 1

44 Datavetenskap för teknisk kemi 10p, moment 1
Initialisering Normala variabler (auto och register) innehåller skräpvärden om de inte har initialiserats. Däremot så initialiseras externa och statiska variabler till noll om inte programmeraren anger ett värde. Vissa C-kompilerare sätter dock värdet på auto-variabler till noll om programmeraren inte anger något annat. Detta kan dock skilja sig åt och som programmerare bör man inte lita på detta utan istället explicit sätta värden till noll. Datavetenskap för teknisk kemi 10p, moment 1

45 Datavetenskap för teknisk kemi 10p, moment 1
Rekursion En funktion är rekursiv om den anropas sig själv, direkt eller indirekt. Alla funktioner kan anropas rekursivt. I sin enklaste form är rekursion enkel att förstå. #include <stdio.h> int main(void) { printf(” The universe is never ending! ”); main(); return 0; /* kommer aldrig att nås! */ } Datavetenskap för teknisk kemi 10p, moment 1

46 Datavetenskap för teknisk kemi 10p, moment 1
Exempel För att summera de fem första heltalen kan man skriva int sum(int n) { if (n <= 1) return n; else return (n + sum(n-1)); } sum(1) 1 sum(2) sum(1) eller 2 + 1 sum(3) sum(2) eller sum(4) sum(3) eller Datavetenskap för teknisk kemi 10p, moment 1

47 Datavetenskap för teknisk kemi 10p, moment 1
fakultet.c Matematiskt så lyder definitionen av fakultet som följer 0! = 1, n! = n(n-1) ... 3*2*1 för n > 0 eller ekvivalent 0! = 1 n! = n((n-1)!) för n > 0 I C kan det skrivas rekursivt som int factorial(int n) { if (n <= 1) /* antar att n inte är negativt */ return 1; else return (n* factorial(n-1)); } Datavetenskap för teknisk kemi 10p, moment 1

48 Iterativ motsvarighet
Funktionen kan också skrivas iterativt int factorial(int n) { int product = 1; for (; n > 1; --n) product *= n; return product; } Datavetenskap för teknisk kemi 10p, moment 1

49 Datavetenskap för teknisk kemi 10p, moment 1
wrt_backwards.c #include <stdio.h> void wrt_it(void); int main(void) { printf(”Input a line: ”); wrt_it(); printf(”\n\n”); return 0; } void wrt_it(void) { int c; if ((c = getchar()) != ’\n’) putchar(c); Datavetenskap för teknisk kemi 10p, moment 1

50 Datavetenskap för teknisk kemi 10p, moment 1
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 dom. Ofta kan rekursion orsaka många funktionsanrop. Fibonacci-sekvensen defineras som följer f0 = 0, f1 = 1, fi+1 = fi + fi-1 för i = 1, 2, ... Datavetenskap för teknisk kemi 10p, moment 1

51 Datavetenskap för teknisk kemi 10p, moment 1
fibonacci.c int fibonacci(int n) { if (n <= 1) return n; else return (fibonacci(n-1) + fibonacci(n-2)); } /* n fibonacci(n) antalet funktionsanrop som läggs på stacken .... .... */ Datavetenskap för teknisk kemi 10p, moment 1

52 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 deess nackdelar. Datavetenskap för teknisk kemi 10p, moment 1


Ladda ner ppt "Datavetenskap för teknisk kemi 10p, moment 1"

Liknande presentationer


Google-annonser