Pekare och arrayer En array är ett sammanhängande minnesområde rymmande ett antal element av en viss typ. Arraynamnet kan ses som adressen till arrayens början, dvs. dess första element. En pekare är en variabel innehållande adressen till ett minnesområde. Ett arraynamn kan alltså ses som ett konstant pekarvärde till arrayens första element. Antag deklarationerna: int vek[5], *pek=vek; vek pek Bild 71 Indexering och avreferering I C kan pekare och arrayer i de flesta situationer användas i samma betydelse och i samma syntaktiska konstruktioner, t.ex. kan arraynamn avrefereras och pekare indexeras. T.ex. har följande satser samma effekt: vek[0]=137; *vek=137; *pek=137; pek[0]=137; (och faktiskt 0[vek], men det låtsas vi inte om...) Obs dock att arraynamnet är ett konstant pekarvärde, som alltså inte kan ändras. Vidare finns det situationer då arrayer och pekare inte är utbytbara, t.ex. är extern int vektor[]; och extern int *pek; två helt olika saker. Bild 72 1
Pekararitmetik Vissa aritmetiska operationer är definierade för pekare av känd typ (alltså inte för void * ). Obs! att dessa operationer är typkänsliga och tar hänsyn till de utpekade elementens storlek! addition av pekare med heltal: pek++; - flyttar pek till nästa element pek2=pek+5; - pek2 pekar på femte elementet bortom pek pek+=5; - pek flyttas fem element längre bort Obs att heltalet tolkas som antalet element, inte antalet bytes! Obs vidare att dessa operationer garanteras fungera endast så länge resulterande pekarvärde ligger inom samma sammanhängande datastruktur, t.ex. inom en deklarerad array eller ett minnesutrymme allokerat med ett malloc()-anrop. Det resulterande pekarvärdet får dock peka just bortom sista elementet i strukturen. subtraktion av pekare med heltal: pek--; - flyttar pek till föregående element pek-=5; - pek flyttas fem element bakåt Bild 73 Pekararitmetik (forts.) subtraktion av pekare med pekare i samma datastruktur: count = pek2 - pek1; Ger antalet element mellan pekarna. jämförelse av pekare för likhet och olikhet: if (pek1 == pek2)... if (pek1!= pek2)... jämförelse av pekare i samma datastruktur med andra relationsoperatorer: if (pek1 < pek2)... Innebär alltså om pek1 pekar ut ett objekt som ligger före det objekt som pekas ut av pek2 i datastrukturen. Bild 74 2
Indexering och avreferering II Uttrycket vek[i] är per definition ekvivalent med *(vek+i). Indexering innebär alltså implicit avreferering. I vissa situationer kan man vilja undvika avreferering vid åtkomster till en array, säg t.ex. att man håller på att avtolka en sträng innehållande en kommandoradsflagga av typen -ofilnamn, och vill öppna filen med det givna namnet. Antag följande deklarationer: char str[20]; /*Innehåller flaggan */ char *filnamn; FILE *fil; Detta görs t.ex. så här: filnamn=str+2; if ((fil=fopen(filnamn, r ) == NULL){ fprintf(stderr, Can t open %s\n, filnamn); exit(-1); Bild 75 Ett kuriosum Eftersom vek[i] är ekvivalent med *(vek+i) och addition är kommutativ, dvs. *(vek+i) är ekvivalent med *(i+vek) som i sin tur är per definition ekvivalent med i[vek] så kan man byta plats mellan vektornamn och indexuttrycket... T.ex. är det tillåtet att skriva 5[ abcdefgh ], värdet av detta uttryck är f. Detta är dock inte den viktigaste detaljen på kursen och inte ett rekommenderat sätt att skriva sin kod. Bild 76 3
Vektorgenomgångar med pekararitmetik Vektorgenomgången for(i=0; i<10; i++) vek[i]=0; kan göras så här: for(pek=vek; pek < vek+10; pek++) *pek=0; För vektorer vilkas logiska slut markeras med ett noll-element, t.ex. strängar eller pekarvektorer kan noll-elementet användas som avbrottsvillkor. Exempel: En implementering av strcpy(): char *strcpy(char *str1, char *str2){ char *start=str1; while(*str1++=*str2++) ; return start; En implementering av strlen(): int strlen(const char *str){ char *start=str; while (*str) ++str; return str - start; Bild 77 Dynamiska arrayer Sambandet mellan pekare och arrayer gör att man kan implementera dynamiska arrayer, som alltså kan minskas eller utökas vid behov, genom att deklarera en pekare istället för en array och allokera utrymmet dynamiskt. Exempel (ineffektivt): int *arr=null; int count=0; void add(int val){ arr=realloc(arr, ++count*sizeof(int)); arr[count-1]=val; void del(void){ arr=realloc(arr, --count*sizeof(int)); void print(void){ int i; for(i=0; i<count; i++) printf( %d, arr[i]); printf( \n ); Bild 78 4
Dynamiska arrayer (forts.) Omallokering är en ganska krävande operation, varför man inte brukar göra det med ett element i taget. Istället allokerar man ett antal element åt gången. Om det är fråga om en enstaka buffertarea brukar man inte heller minska arean. Exempel: #define CHUNK 20 int count=0, siz=0; int *arr=null; void add(int val){ if (++count>siz) arr=realloc(arr, (siz+=chunk)*sizeof(int)); arr[count-1]=val; Bild 79 Pekararryer Samlingar av större objekt eller (framför allt) av objekt av varierande storlek hanteras lämpligen genom arrayer av pekare till objekten. Om antalet objekt varierar kan pekararrayen allokeras dynamiskt och omallokeras vid behov. En viktig användning av pekararrayer är arrayer av strängar. Exempel: enum errcodes {ERRNONE, ERROPEN, ERRMEM; const char *errmgs[]={ No error, Can t open file %s\n, Out of memory! ; void funk(char *fname){ FILE *f; if ((f=fopen(fname, r )==NULL){ fprintf(stderr, errmsg[erropen], fname); exit(erropen);... Bild 80 5
Kommandoradsargument Kommandoraden som ett program har startats med finns tillgänglig för programmet som en NULL-terminerad vektor av pekare till strängar som kommandoraden bestod utav. Kommandotolken delar upp kommandoraden i delsträngar avdelade med blanktecken (som alltså inte ingår i strängarna, såvida dessa inte var omgivna med citationstecken) efter eventuell avtolkning av specialtecken (såsom * - alla filnamn). Antalet strängar och en pekare till vektorn av strängpekarna blir tillgängliga för programmet genom deklaration av två argument till main(), kallade av konvention argc resp. argv:... Bild 81 argc och argv Om programmet prog har startats med kommandoraden prog hej hopp 123 så ger det följande situation: main argc 4 argv prog\0 hej\0 hopp\0 123\0 Bild 82 6
argv - exempel 1 Filkonkatenering - genomgång med indexering: #include <stdio.h> void copyfile(file *out, char *fname){... char *myself=argv[0]; FILE *out; int i; if(argc<3) { fprintf(stderr, %s: Usage:.., myself); exit(1); if ((out=fopen(argv[1], w ))==NULL){ fprintf(stderr, %s: Can t open %s!, myself, argv[1]); exit(2); for(i=2; i<argc; i++) copyfile(out, argv[i]); fclose(out); return 0; Bild 83 argv - exempel 2 Filkonkatenering - genomgång med pekararitmetik: #include <stdio.h> void copyfile(file *out, char *fname){... char *myself=*argv; FILE *out=null; while (*++argv) if(out==null) if((out=fopen(*argv, w ))==NULL){ fprintf(stderr, %s: Can t open %s!\n,myself,*argv); exit(2); else copyfile(out, *argv); fclose(out); return 0; Bild 84 7
argv - exempel 3 Ett program med flaggor, kommandoexempel: prog -a fil1 -ab fil2 fil3 #include <stdio.h> void doonefile(char *fname, int a, int b){... char *myself=*argv; int a=0, b=0; while (*++argv) if (**argv== - ) while (*++*argv) switch(**argv){ case a : a=1; break; case b : b=1; break; default:fprintf(stderr, Unknown %c\n,**argv); else doonefile(*argv, a, b); return 0; Bild 85 argv - exempel 4 Flaggor med argument, kommandoexempel: prog -hinledning -p40 fil1 -havhandling -p50 fil2 #include <stdio.h> void doonefile(char *fname, char *h, int p){... char *myself=*argv, *h=null; int p=60; while (*++argv) if (**argv== - ) switch(*++*argv){ case h : h=++*arg; break; case p : p=atoi(++*argv); break; default:fprintf(stderr, Unknown %c\n,**argv); else doonefile(*argv, h, p); return 0; Bild 86 8
Strängar - konverteringsfunktioner I stdio.h: int sprintf(char *str, const char *format,...); int sscanf(char *str, const char *format,...); I stdlib.h: int atoi(const char *str); double atof(const char *str); long atol(const char *str); long strtol(const char *str, char **endp, int base); double strtod(const char *str, char **endp); Bild 87 9