• No results found

6.1 Kompilering och lite grundläggande information

N/A
N/A
Protected

Academic year: 2022

Share "6.1 Kompilering och lite grundläggande information"

Copied!
9
0
0

Loading.... (view fulltext now)

Full text

(1)

6 Något om C

Förhoppningsvis ska de C-konstruktioner som gås igenom här tillsammans med de exempelpro- gram som ges här och i andra delar av lab-PM vara tillräckliga för att ni ska kunna klara av labo- rationerna. Syftet är inte på något sätt att ge en heltäckande beskrivning av C eller ens en syste- matisk introduktion till språket.

Den version av C som beskrivs här är traditionell Kernighan & Ritchie C. Det finns också en ANSI-standard för C där man har snyggat upp språket en del och lagt till några nya konstruk- tioner, men i detta lab-PM används traditionell C.

6.1 Kompilering och lite grundläggande information

Kompilatorn startas med kommandotcc. För att t.ex. kompilera programmetfoo.coch skapa den exekverbara filenfoo skriver man:

cc -o foo foo.c

Alla C-program måste innehålla en funktionmaindär exekveringen startar. Vidare måste man se till att inkludera inkluderingsfiler för de bibliotek som man använder sig av. Följande är ett komplett C-program som kan kompileras och köras:

#include <stdio.h>

main() {

printf("Concurrent Programming is fun!\n");

}

Programmet skriver ut ett meddelande på skärmen följt av ett radslutstecken. Funktionen printf, som används för utskriften, definieras i biblioteketstdiovarför motsvarande inklu- deringsfilstdio.h måste inkluderas.

Parenteserna ”{” och ”}” som avgänsar funktionens kropp motsvarar begin respektive endi många andra språk. Sådana block kan nästlas och innehålla lokala variabeldeklarationer.

Observera dock att funktioner inte får deklareras lokalt, vilket leder till att C-program får en flat struktur.

Ytterligare en sak som bör påpekas är att C skiljer mellan stora och små bokstäver i nyck- elord och identifierare.

6.2 Grundläggande variabeldeklarationer och konstantdefinitioner

Variabler kan deklareras antingen lokalt i block eller globalt på toppnivån. Deklarationer skrivs i C med typen först och de deklarerade objekten efteråt. En deklaration av två heltalsvariabler respektive en teckenvariabel ser alltså ut som följer:

int i, j;

char ch;

Det går också att initialisera variabler i samband med deklarationen:

int i = 10, j = 20;

char ch = ’a’;

(2)

I (traditionell) C finns inte någon möjlighet att deklarera konstanter. Istället använder man makron vilkas förekomster i programtexten vid kompilering textuellt kommer att bytas ut mot makrots värde av en preprocessor innan själva C-kompilatorn tar vid. Nedanstående visar typisk definition och användning. Notera att man brukar skriva makron med stora bokstäver:

#define SIZE 10

#define SPECIALCHAR ’a’

:

if (i < SIZE && ch == SPECIALCHAR) ...

6.3 Pekare

Pekare är mycket viktiga i C och betydligt flexiblare än i många andra språk. C-pekare är t.ex.

inte begränsade till att enbart referera till dynamiskt allokerade objekt, utan det är lätt skapa en pekare till en godtycklig variabel eller del av strukturerad variabel. Vidare kan vissa aritmetiska operationer utföras på pekare i C; en vanlig konstruktion är t.ex. att stega igenom en vektor med hjälp av en pekare som successivt får peka ut elementen i stället för att använda en indexv- ariabel.

Två pekarvariabler, ip och cp, som kan lagra en pekare till ett heltal respektive till ett tecken, deklareras så här:

int *ip;

char *cp;

Notera att variablerna ännu är oinitialiserade (d.v.s. inte pekar någonstans) i likhet med andra variabler som inte explicit initialiseras.

Det går också bra att blanda deklarationer av variabler av bastypen och pekarvariabler.

Observera placeringen av ”*”:

int i, j, *ip;

char ch, *cp;

De viktigaste operatorerna som hör ihop med pekare är ”&” och ”*”. ”&” används för att plocka fram adressen till en variabel, d.v.s.&irepresenterar en pekare till innehållet i variabelni. ”*”

gör tvärt om och utnyttjas för att komma åt det som en pekare pekar på. Följande är t.ex. ett omständligt sätt att tilldela3 tilli, givet deklarationerna ovan:

ip = &i;

*ip = 2;

*ip = *ip + 1;

Idén med den ”bakvända” typnotationen vid deklaration av pekarvariabler ska nu förhopp- ningsvis vara klar: ”När *appliceras på ipså fås int. Alltså måste ipvara en pekare till heltal”. Man får så att säga arbeta sig inifrån och ut för att kunna sluta sig till vilken typ varia- beln har. Deklarationer fungerar genomgående på detta sätt i C.

Som framgått ur det föregående är pekare i C typade och man kan därför inte blanda pekare av olika typer hur som helst. Ovan representerar t.ex. &i en heltalspekare eftersom i är ett heltal, så om man försöker tilldela&itillcpkommer kompilatorn att varna eftersomcpär en teckenpekarvariabel.

C-kompilatorn håller också ordning på storleken av de element som en pekare av viss typ pekar på. Detta används för att göra aritmetik på pekare på ett vettigt sätt. Med deklarationerna ovan gäller t.ex. att(ip + 3)betecknar en heltalspekare som refererar till ett heltal som ligger

(3)

tre heltal bortom det heltal somiprefererar till. Sådan pekararitmetik används ofta för att stega igenom vektorer.

Det speciella pekarvärdetNULLbetecknar en pekare som inte pekar någonstans alls.NULL är tilldelningskompatibel med alla pekartyper.

6.4 Vektorer och strängar

Deklarationer av vektorer av heltal respektive tecken ser ut så här:

int a[10];

char b[80], c[80];

Identifieraren aär nu namnet på ett utrymme som kan lagra tio stycken heltal, men i de allra flesta fall behandlar Casom en pekare till första elementet i utrymmet ifråga. Elementen adres- seras som a[0],a[1], ...,a[9](undre indexgränsen är alltid 0 i C). Men eftersomaär en pekare, så hade man lika gärna kunnat adressera elementeta[3]som*(a+3)i enlighet med vad som sagts om pekararitmetik ovan. På samma sätt är b en pekare till ett utrymme som rymmer 80 tecken,b[0],b[1], ...,b[79].

Detta har också konsekvenser när man tilldelar en vektor till en annan. Följande försök att tilldela innehållet i den vektor somb refererar till den vektor somc refererar till är felaktigt:

c = b; /* FEL */

Jämför med att försöka tilldela ett heltal till ett annat. Vill man kopiera innehållet ibtillcmåste detta göras elementvis. Däremot går det bra att tilldela t.ex.btill en pekarvariabel cpdekla- rerad enligt ovan, vilket innebär attb ochcp kommer att referera till samma minnesutrymme:

b[2] = ’z’;

cp = b; /* cp now points to array b.*/

a[9] = cp[2]; /* a[9] now contains ’z’. */

*(cp+3) = ’x’; /* b[3] now contains ’x’. */

När man skickar vektorer som parametrar till funktioner är det följaktligen bara adressen till (första elementet i) vektorn som skickas. Effekten blir alltså att en vektor alltid skickas som referensparameter, även om C ser det som att det är en pekare till vektorn som skickas över som en värdeparameter.

Strängar representeras i C som vektorer av tecken. Variabelnbovan kan alltså ses som en strängvariabel. Strängar i C avslutas alltid av tecken nummer noll, varför b rymmer strängar med en effektiv längd av upp till 79 tecken eftersom sluttecknet också måste få plats.

6.5 Poster

Poster i C fungerar ungefär som poster i Pascal. Till skillnad från fallet med vektorer ovan är alltså variabler av posttyp ”riktiga” variabler som verkligen innehåller posten, och inte bara en referens. Följande definierar en posttypbook, två variabler av denna typ,b1ochb2, samt en pekarvariabelbp som kan peka på poster av typenbook:

struct book {

char title[50], author[50];

int pages, price;

float weight;

} b1, b2, *bp;

(4)

Eftersomb1ochb2är riktiga variabler, så är tilldelningenb1 = b2i detta fall helt legal och kommer att kopiera innehållet i b2till b1. När poster skickas som parametrar till (och retur- neras som resultat från) funktioner sker också kopiering, d.v.s. de skickas som värdeparametrar.

Fälten i posten adresseras med punktnotation, vilket illustreras i följande exempel:

b1.pages = 345;

b1.price = 255;

printf("Title: %s.\n", b1.title);

printf("Price: %d.\n", b1.price);

I enlighet med vad som sagts om vektorer ovan ärb1.title och b2.author fortfarande bara pekare till respektive fält.

Exemplet nedan visar hur man skapar en pekare till postenb1 och tilldelar denna tillbp, samt hur adressering görs:

bp = &b1;

b2.price = (*bp).price;

(*bp).weight = 123.4;

Eftersom pekare till poster är vanligt förekommande, så finns ett förkortat skrivsätt för att komma åt fält i poster via en pekare:

bp->weight = 123.4;

printf("%d\n", pb->pages);

6.6 Funktionsdefinitioner

Funktioner är de enda programenheter som finns i C; egentliga procedurer saknas. Man kan dock alltid behandla en funktion som om den vore en procedur genom att helt enkelt låta bli att ta hand om returvärdet. Man kan också explicit deklarera att en funktion är tänkt att användas som en procedur genom att låta den ha typenvoid, se nedan. Följande är en definition av en heltalsfunktion med tre parametrar:

int foo(a, b, s) int a, b;

char s[];

{

printf("%s\n", s);

return 10 * a + b;

}

Notera hur parametern sdeklareras för att kunna ta emot en referens till en vektor. För funk- tionsargument är detta skrivsätt ekvivalent med att explicit deklarera parametern till att vara en pekare, d.v.s. i detta fall hadeslika gärna kunnat deklareras genomchar *s. Funktionfoo skulle kunna anropas enligt följande:

x = foo(1, i, "Hej!");

därxochiär heltalsvariabler. I C, som vi redan har sett exempel på, skickas alla argument som värdeparametrar. Det är alltså värdet1och innehållet iisom skickas över och kopieras in i de formella parametrarnaaochb. Strängar ses som teckenvektorer och representeras av en pekare till första tecknet. Det är denna pekare som kopieras in i s, i enlighet med hur C behandlar vektorer i allmänhet.

(5)

Vill man inte returnera något värde från en funktion (d.v.s. om man vill ha en procedur) låter man funktionen ha typenvoid:

void fie() {

/* Do something. */

}

Denna funktion tar alltså inga parametrar, returnerar inget resultat och anropas enligt:

fie();

Observera att parenteserna måste vara med vid alla funktionsanrop, även om funktionen inte tar några parametrar! Det är ett mycket vanligt fel att glömma detta och kompilatorn kommer då inte att varna eller att ge något felmeddelande eftersomfie i sig själv är en pekare till funk- tionen, varför ”fie;” är en laglig sats även om den inte utför något (ett godtyckligt uttryck blir en sats bara det följs av semikolon).

Eftersom funktionsnamn ses som pekare till funktioner är det mycket enkelt att skicka funk- tioner som parametrar. Funktionenapplynedan tar en pekare till en heltalsfunktion som första argument och applicerar den utpekade funktionen på det andra argumentet. Notera likheten mellan hur funktionsparametern deklareras och dess användning:

int foo(x) int x;

{

return 2*x;

}

int apply(f, x) int (*f)(), x;

{

return (*f)(x);

} main() {

printf("%d\n", apply(foo, 10));

}

Slutligen ett exempel på hur man kan ordna så att man får utparametrar. Som tidigare nämnts passas ju alla parametrar som värdeparametrar i C, varför vi explicit måste skicka en pekare till den variabel i vilken vi vill ha resultatet, om vi vill ha en utparameter:

void fum(p) int *p;

{

*p = 10;

}

fum anropas sedan enligt:

fum(&i);

där i är en heltalsvariabel. Detta är en mycket vanlig användning för operatorn ”&”. Efter anropet hari värdet10.

(6)

6.7 Typkonverteringar

Typkonverteringar används ganska flitigt i C, i synnerhet i samband med pekare. Typkonverte- ring indikeras genom att namnet på den typ som man vill konvertera ett uttryck till skrivs inom parentes framför uttrycket. Konvertering av heltal till ett dubbelprecisionsflyttal ser t.ex. ut så här (x antas vara av typendouble):

x = (double)(1+3);

I många fall sker dessutom implicita typkonverteringar enligt ganska komplicerade regler.

Funktionsargument av flyttalstyp konverteras t.ex. till dubbelprecisionsflyttal vid funktions- anrop.

6.8 Operatorer

I denna sektion beskrivs några operatorer som kan vara bra att känna till. C är mycket rikt på operatorer varför endast de viktigaste tas med här.

I C ser man tilldelning som en operator. Tilldelningsoperatorn skrivs ”=” och värdet av ett tilldelningsuttryck är värdet på uttrycket till höger om likhetstecknet. Observera att likhetsope- ratorn skrivs ”==”. Ett av de allra vanligaste misstagen när man börjar att programmera i C (om man t.ex. är van vid Pascal) är att man gör en tilldelning fast man tänkte testa likhet.

Eftersom tilldelning är ett uttryck, kan man göra tilldelningar inne i andra uttryck för att få kompaktare källkod. Till exempel kan satserna:

x = foo(7);

if (x == 0) {

/* Do something */

}

kompaktare formuleras som:

if ((x = foo(7)) == 0) { /* Do something */

}

vilket är ett mycket vanligt sätt att uttrycka sig på i C.

Nedan följer en lista över några C-operatorer i avtagande prioritetsording (varje rad motsvarar dock inte nödvändigtvis en egen prioritetsnivå). Några av dem har redan behandlats ovan.

() [] . -> Funktionsanrop, vektor- och postadressering.

++ -- Öka/minska variabel med 1, t.ex. i++ är detsamma somi=i+1;.

& * Adressoperatorn och derefereringsoperatorn.

- Unärt minus.

! Logisk negation (trueochfalserepresenteras av1respektive0 i C, men godtyckligt tal skiljt från0 ses somtrue av de logiska operatorerna).

* / % Multiplikation, division och modulo.

+ - Addition och subtraktion.

(7)

< <= >= > Relationsoperatorerna.

== != Lika med respektive skiljt ifrån.

&& Logiskt OCH, kortsluten evaluering.

|| Logiskt ELLER, kortsluten evaluering.

= += Tilldelningsoperatorn och ökningsoperatorn.

6.9 Satser och styrstrukturer

Nedan följer ett antal exempel på satser i C. Syntaxen ska förhoppningsvis klart framgå av dessa exempel. Det första exemplet är den sammansatta satsen:

{

/* Satser */

}

Det går också bra att ha variabeldeklarationer i en sammansatt sats, som då kallas för block.

Detta kan vara bekvämt om man behöver en temporär variabel endast lokalt i ett block.

{

/* Variabeldeklarationer */

/* Satser */

}

Sammansatta satser och block kan nästlas efter behag.

If-satsen och switch-satsen exemplifieras i det följande. Notera att bådeelse-grenen ochdefault-klausulen kan uteslutas:

if (x == 5) {

/* Do something. */

} else {

/* Do something else. */

}

switch (i) { case 0:

/* Do something. */

break;/* Must have break here or will fall through to next case. */

case 1:

/* Do something else. */

break;

case 5:

case 6:

case 7:

/* Do something different if i = 5, 6, or 7. */

break;

default:

/* Do this if nothing else apply. */

break;

}

Det följande är ett exempel på enwhile-loop:

(8)

i = 0;

while (i < 10) {

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

i++;

}

Ovanstående är precis ekvivalent med följandefor-loop:

for (i = 0; i < 10; i++) { printf("i = %d\n", i);

}

Man kan även ha testvillkoret sist så att loopen alltid utförs minst en gång:

do {

/* Do something. */

} while (i <= 10);

6.10 In- och utmatning

Vi har redan sett exempel på utmatning m.h.a. funktionenprintffrån biblioteketstdioett antal gånger. Som första argument tar printfen formatsträng som innehåller den text som ska skrivas ut samt eventuellt ett antal omvandlingsspecifikationer som indikerar var i strängen och i vilket format resterande parametrar ska skrivas ut.

Funktionen printf tar alltså ett variabelt antal argument och man måste själv se till att argumenten till typ och antal stämmer överens med vad som indikeras i formatsträngen. Annars så kan intressanta fenomen uppstå.

Funktionen är mycket flexibel och ett otal olika effekter kan åstadkommas med olika typer av omvandlingsspecifikationer. De viktigaste är dock:

%d Motsvarande argument är ett heltal.

%c Argumentet är ett tecken.

%f Argumentet är ett dubbelprecisionsflyttal.

%s Argumentet är en sträng, d.v.s. pekare till en teckenvektor terminerad med tecknet’\0’.

%% Skriv ut ett %-tecken.

Ett exempel kan kanske vara klargörande:

printf("Integer %d, float %f, string %s.\n", 42, 3.14, "Hello");

När denna sats exekveras kommer följande att skrivas ut, följt av ett radslutstecken:

Integer 42, float 3.140000, string Hello.

Funktionen scanf från stdio.h kan användas för inläsning. Den påminner en del om printfoch är i likhet med denna mycket flexibel. Två kompletta exempelprogram ges nedan för att illustrera hur scanftypiskt kan användas. Observera att förutomstdio.hså inklu- deras filen stddef.h. Denna innehåller ett antal standarddefinitioner som ofta är bra att ha, t.ex. definitionen av NULL, men i programmen nedan hade den egentligen inte behövt inklu- deras.

(9)

Följande program läser ord separerade med en eller flera blanka och radframmatningar från standard input och skriver dem på standard output tills filslut signaleras (d.v.s. tills ctrl-D trycks först på en rad om inmatning sker från terminalen):

#include <stddef.h>

#include <stdio.h>

main() {

char s[80];

while (scanf("%s", s) != EOF) {

printf("Word read: %s\n", s); /* Note: s is pointer to char array. */

} }

Följande program läser heltal från standard input och skriver dem på standard output tills -1 har lästs. Notera hur en pekare till en heltalsvariabel skickas tillscanf:

#include <stddef.h>

#include <stdio.h>

main() {

int i;

do {

scanf("%d", &i); /* Note: Must pass pointer to i, hence &i. */

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

} while (i >= 0);

}

References

Related documents

Den aktuella studien syftar till att ta reda på hur polisen arbetar proaktivt mot ungdomskriminalitet och hur de upplever sitt arbete med kriminella ungdomar.. Studien

Även Wal- demarsson (2009) menar att som ledare i en miljö som hanterar tillfälliga arbetsgrupper finns ett ännu större behov av att arbeta med bekräftelse och återkoppling

Förekomsten av mycket hygroskopiska föreningar i aerosoler kan påskynda processen för bildandet molndroppar, medan närvaron av mindre hygroskopiska ämnen kan förlänga den tid som

Linnéuniversitetet är resultatet av en vilja att öka kvalitet, attraktionskraft och utvecklingspotential för utbildning och forskning, och spela en framträdande roll i samverkan

Med hjälp av tekniken kunde de individanpassa inlärningen för eleverna, vilket de gjorde när de letade material på Internet som de senare skulle använda i undervisningen och det kan

Den kategoriseringsprocess som kommer till uttryck för människor med hög ålder inbegriper således ett ansvar att åldras på ”rätt” eller ”nor- malt” sätt, i handling

Det framkommer också att en högre balans i förmågor, både när det gäller samtliga förmågor och enbart kognitiva, ökar sannolikheten att vara egenföretagare.. Individer som har

1(1) Remissvar 2021-01-22 Kommunledning Nykvarns kommun Christer Ekenstedt Utredare Telefon 08 555 010 97 christer.ekenstedt.lejon@nykvarn.se Justitiedepartementet