F5 Introduktion till C
Table of Contents
1 Introduktion till C
1.1 Datorns minne
För att lära sig programmera i C måste man ha en uppfattning om hur en dator fungerar på låg nivå. En dators minne kan man se som en stor vektor av bytes. En byte är 8 bitar. Dagens dator har mycket minne t.ex. 16Gb = 16 000 000 000 bytes.
Ett program består dels av olika maskinkodsinstruktioner i en del av minnet. Dels av data som lagras i en annan del av minnet. Data kan allokeras statiskt eller dynamiskt. Det brukar implementeras med en stack och en heap. Ofta används ena änden av dataminnet till en stack och andra änden till en heap. Exempel:
Programkod | Hårdkodat data | Heap | Stack |
På stacken allokeras lokala variabler i varje funktionsanrop. Ofta läggs också annat data på stacken som t.ex. returadressen vid funktionsanrop. Data som läggs på stacken städas upp automatiskt.
1.2 Maskinkod
De kodrader du skriver översätts av en kompilator till maskinkodsinstruktioner. Om kompilatorn inte förstår en kodrad så får du kompileringsfel.
Översättningen sker i flera steg men nuförtiden görs allting med ett enda terminalkommando
gcc program.c
Om man inte specifierar vad programmet ska heta (med flaggan -o) så kommer programmet att heta a.out (eller a.exe på windows) och du kör programmet genom att skriva programnamnet
./a.out
1.3 typat språk
När man deklarerar (inför) en variabel måste man tala om hur vad det är slags variabel. Variabler allokeras på stacken och tar olika mycket plats.
C är ett litet språk med få typer, flera av dem återfinns i java. T.ex. finns heltal int. Det är skillnad på heltal i java och C. T.ex. är det inte säkert hur stor en int är det beror på vilken dator man kör på. En annan skillnad är att man kan välja att deklarera den unsigned vilket betyder att heltalet inte kan anta negativa värden. I tidigare versioner av C var man tvungen att deklarera lokala variabler i början av funktionen.
Typade språk kontrollerar tilldelningar under kompileringen och ger kompileringsfel om typerna inte stämmer. Kompileringsfel är att föredra framför logiska fel eller exekveringsfel (runtime errors).
Följande program i python ger en semantiskt konstig utskrift eftersom man kastat om anropsparametrarna
def foo(name, age): print("Hej", name, "du är", age, "år gammal") foo(18, "Linda")
I C skulle typkontrollen gett kompilatorfel vid parameteröverföringen
void foo(char * name, int age) { //... foo(18, "Linda")
Kompilatorn släpper dock igenom att man tilldelar ett väldigt stort tal till ett för litet tal. Då kan det gå väldigt fel. Links to an external site.
1.4 strängar och arrayer
Det finns inga strängar i C utan man använder sig av teckenvektorer. Dessa vektorer kan man allokera statiskt med fix storlek eller dynamiskt. Dynamiskt minne allokeras med malloc (står för allocate memory) och sizeof. Det finns ingen skräphanterare i C utan minne måste frigöras av programmeraren med free.
char a; char b = 'q'; char v1[5]; // en array av tecken int v2[] = {1, 5, 7}; // en array med tre siffror char * s1 = "Linda"; // En pekare till en hårdkodad teckensträng char * s2 = malloc(sizeof(char) * 8); // En pekare till en 8 stor tecken array på heapen free(s2);
1.5 pekare
Allting i ett program ligger på en minnesadress. I C kan få en pekare till adressen med adress-of-operatorn &. En pekare deklareras men en * och den typ av variabel som pekas på. Med pekaren kan man skriva i minnet den pekar på.
int a = 3; int * p = &a; *p = 5; // a är nu 5
Det går även att få adressen till var en funktion ligger i minnet.
1.6 pekararitmetik
Man når en vektors element med en pekare till elementets minnesadress. Om man stegar en vektorpekare med ett så kommer pekaren att peka på nästa element även om elementet ligger några bytes bort. Indexoperatorn är syntaktiskt socker för pekararitmetik.
char * s = "alba"; char c = *s; // 'a' c = *(s + 2); // 'b' c = s[2] // 'b'
Det är väldigt viktigt att man tänker sig för med pekare så att man inte råkar skriva i fel del av minnet. Pekararitmetik är bara definierat för pekare till een array.
int a = 1; int b = 2; int c = 3; int * p = &b; p = p + 1; *p = 7;
1.7 struct
Det finns inga klasser i C men det finns struct. Även matlab har struct. En stuct är en sammansatt datatyp där men grupperat flera medlemsvariabler samman.
struct koordinat { int x; int y; int z; }; typedef struct Point; // gör att man skriva _Point_ istället för _struct koordinat_
1.8 loopar
Loopar görs med while- eller for-satser. Om man loopen ska göra mer än en sak måste den inneslutas i ett block med måsvingar {}.
Exempel
int i = 0; int sum = 0; while (i < 10) { sum += i; i++; } // Samma loop med for-sats sum = 0; for (int j = 0; j < 10; j++) sum += j; // endast en sats i loopen
1.9 villkor
Villkor görs med if-satser eller switch-satser. Varje case måste avbrytas med ett brake såvida man inte vill utföra nästföljande case.
// ... switch (val) { case 1 : //.. do stuff and continue to case 2 case 2 : // .. do stuff and break out break; case 3 : // do some other stuff }
Ett vanligt fel är att råka göra en tilldelning i if-satser. Det är fullt legalt att skriva:
a = 1; if (a = 5) printf(”a = %d”, a);
1.10 funktioner
Funktioner deklareras med en returtyp, namn och parameterlista.
float add (float a, float b) { return a+b; }; float mult (float a, float b) { return a*b; };
Även funktioner kan pekas på med funktionspekare. Ibland kan det användas för att ersätta stora if/switch-satser
float doCalc1(float a, float b, float (*pt2Func)(float, float)) { return pt2Func(a, b); // anrop med funktionspekare } float doCalc2(float a, float b, char opCode) { switch(opCode) { case '+' : return add (a, b); break; case '*' : return mult(a, b); break; //… doCalc1 (1, 2, &add); // Skicka med adressen till funktionen add doCalc2 (1, 2, ‘+’); // Testa på ‘+’ för att veta vad som ska göras
1.11 värdeparametrar (by value)
I C liksom i java och python finns enbart värdeparametrar. Det innebär att skickade parametrar kopieras till funktionen. Ändrar man på parameterns värde i funktionen påverkas inte variabeln som skickades. Vill man ändra på ett parametervärde kan man som i Java eller python kapsla in parametern i en struct och skicka structen som ett värde. Då kan man ändra structens medlemsvariabler (fält). Detta är inte så vanligt i C utan istället kan man skicka adressen till parametern man vill ändra på.
Flera inbyggda funktioner förutsätter att man skickar en pekare till den variabel man vill ändra på t.ex. scanf som tar in värden som matas in via stdin (terminalen).
float lengd; scanf(stdin, "%f", &lengd);
Vill man ändra på en pekares värde kan man på samma sätt skicka adressen till pekaren. I funktionens parameterlista tar man då emot en pekare till en pekare. Det går att nästla pekare till pekare hur många nivåer som helst.
int a = 3; int * p = &a; int ** pp = &p; int *** ppp = &pp;
I C++, C# och pascal finns referensparametrar, då kopieras inte värdet utan ändringarna ändrar också anropsparametern.
1.12 utksrift, importera kod
Utskrift ingår inte i C utan man måste importera ett biliotek med I/O-rutiner. Utskriftsrutiner använder platshållare Links to an external site. för att skriva ut variabler i meningar.
#include <stdio.h> int main() { int a = 3; printf("a = %d", a); }
include-satser med <> säger åt kompilatorn att leta där C är installerat (via en miljövariabel i operativsystemet) och include-satser med "" letar explicit i filsystemet.
## Ett programexempel
#include <stdlib.h> #include <stdio.h> struct post { char name[29]; float bmi; struct post * next; }; typedef struct post Post; void writePost(Post * p) { printf("Namn: %s\nbmi: %f\n", p->name, p->bmi); } int vikt; /* global variabel */ static float lengd; /* filglobal variabel */ int main(int argc, char * argv[]) { Post * p = (Post *) malloc(sizeof(Post)); p -> next = (Post *) malloc(sizeof(Post)); printf("Vad heter du? "); fscanf(stdin, "%s", p->name); printf("Hur lång är du (m)? "); fscanf(stdin, "%f", &lengd); printf("Vad väger du (kg)? "); fscanf(stdin, "%d", &vikt); p -> bmi = vikt / (lengd * lengd); writePost(p); return 0; }