1Forklar, hvordan Gos nulværdier fungerer for indbyggede og reference-lignende typer, og hvorfor de er vigtige, når man erklærer variabler uden eksplicit initialisering.
I Go vil en variabel, der er erklæret uden en eksplicit initialiseringsværdi, automatisk blive initialiseret til nulværdien for dens type. Numeriske typer bliver `0`, `bool` bliver `false`, `string` bliver `""`, og arrays eller `struct`-typer nulstilles element for element eller felt for felt. Pointer-lignende eller reference-lignende typer såsom pointere, `slice`-værdier, `map`-typer, `channel`-typer, funktioner og interfaces har `nil` som deres nulværdi. Dette er vigtigt, fordi Go-variabler og udeladte `struct`-felter starter i en deterministisk tilstand i stedet for at indeholde vilkårlige hukommelsesdata, og mange API'er (Application Programming Interfaces) er designet, så nulværdien er en brugbar standardværdi, selvom visse `nil`-værdier stadig kræver initialisering før bestemte operationer.
2Hvordan håndterer Go lighed for `struct`-typer, og hvad sker der, når en `struct` indeholder felter, der ikke kan sammenlignes?
Værdier af en `struct` i Go kan kun sammenlignes med `==` og `!=`, når hvert eneste felt i den pågældende `struct` er sammenligneligt. Lighed sammenligner de tilsvarende felter ved hjælp af hvert felts egen regel for lighed. Hvis en `struct` indeholder et felt, der ikke er sammenligneligt, såsom en `slice`, `map` eller funktion, er selve `struct`-typen ikke sammenlignelig, og en sammenligning af to værdier af denne `struct`-type med `==` vil resultere i en kompilatorfejl. For sådanne `struct`-typer bør man bruge tilpasset sammenligningslogik eller en passende hjælpefunktion til dyb sammenligning (deep equality), især i test.
3Forklar uforanderlighed for strenge (string immutability) i Go samt relationen mellem `string`, `[]byte`, `bytes.Buffer` og `strings.Builder`.
En Go-`string` er en uforanderlig sekvens af bytes, ofte UTF-8-tekst, men den behøver ikke at være gyldig UTF-8. Du kan ikke ændre en streng på stedet (in place); for at ændre indholdet konverterer du typisk til `[]byte` for redigeringer på byteniveau, eller til `[]rune` for redigeringer på kodepunktsniveau (code points), hvorefter du konverterer tilbage. Almindelige konverteringer mellem `string` og `[]byte` kopierer data og kan allokere hukommelse, så gentagne konverteringer eller gentagen sammensætning af strenge i løkker kan være omkostningstungt. `strings.Builder` er optimeret til effektivt at opbygge strenge, mens `bytes.Buffer` er en foranderlig byte-buffer, der er velegnet til byteorienterede data og I/O, og som også kan producere en streng.
4Hvordan fungerer pointere i Go, og hvilke operationer er bevidst ikke tilladt sammenlignet med C?
En pointer i Go er en typebestemt værdi, der indeholder adressen på en anden værdi, f.eks. `*int`, der peger på en `int`. Brug `&x` for at hente en værdis adresse og `*p` til at dereferere en pointer for at læse eller skrive den værdi, der peges på. Ved at overføre eller gemme en pointer kan flere steder observere eller ændre den samme underliggende værdi, og en pointer kan være `nil`. I modsætning til C tillader sikker Go bevidst ikke pointer-aritmetik og vilkårlig manipulation af rå adresser; undtagelser på lavt niveau kræver `unsafe`-pakken.
5Forklar, hvordan konstanter og `iota` fungerer i Go, herunder typiserede kontra utypiserede konstanter.
Go-konstanter er værdier, der evalueres på kompileringstidspunktet (compile-time): booleske, strenge eller numeriske. En typiseret konstant har en specifik type. En utypiseret konstant har ingen fast, konkret type, før den bruges i en typiseret kontekst, og numeriske utypiserede konstanter repræsenteres eksakt med høj præcision indtil da; værdien skal dog kunne repræsenteres i den valgte type. `iota` er en foruddeklareret identifikator, der bruges i `const`-deklarationer: den starter på 0 i hver `const`-blok og tælles op for hver konstantspecifikation, hvilket gør den nyttig til enum-lignende konstanter og bit-flag.
6Hvordan fungerer eksplicitte konverteringer i Go, og hvorfor undgår sproget mange implicitte konverteringer?
Go kræver generelt eksplicitte konverteringer ved hjælp af `T(x)`, når en værdis type ændres, såsom `int64(i)` eller `MyID(n)`. Dette holder typeændringer synlige og undgår overraskende implicitte numeriske, booleske eller streng-konverteringer. Konverteringer er kun tilladt ifølge Gos konverteringsregler og kan ændre værdien, for eksempel gennem heltalsoverløb/afkortning (overflow/truncation), kommatal-til-heltal-afkortning mod nul eller tab af præcision. Utypede konstanter er mere fleksible: de kan bruges i en typet kontekst, hvis konstantens værdi kan repræsenteres i den pågældende type.
7Beskriv forskellen mellem arrays og slices i Go, herunder hvordan længde, kapacitet og den underliggende lagring fungerer.
Et array i Go har en fast længde, som er en del af dets type, f.eks. `[3]int`; det gemmer sine elementer direkte, og hvis man tildeler eller videregiver et array, kopieres hele array-værdien. En slice, f.eks. `[]int`, er en lille deskriptor over et underliggende array: konceptuelt indeholder den en pointer til elementer, en længde og en kapacitet. Længden på en slice er antallet af synlige elementer; dens kapacitet er, hvor mange elementer der kan bruges fra startpunktet af en slice, før slutningen af det bagvedliggende array nås. Slices er fleksible: reslicing ændrer deskriptoren, og `append` kan genbruge det samme underliggende array, hvis kapaciteten tillader det, eller allokere et nyt, hvis ikke.
8Hvordan opfører Gos `map`-type sig med hensyn til nøgletyper, manglende nøgler, `nil`-maps og iterationsrækkefølge?
Nøgletyper i et Go `map` skal være sammenlignelige; `slice`-værdier, `map`-typer og funktioner kan ikke bruges direkte som nøgler. Hvis man slår en nøgle op, der ikke er til stede, returneres elementtypens nulværdi, så comma-ok-formen (`v, ok := m[k]`) bruges til at skelne mellem manglende tilstedeværelse og en tilstedeværende nulværdi. Et `nil` `map` kan læses fra og itereres over med `range`, men tildeling til det forårsager en panic; det skal initialiseres før skrivning. Iterationsrækkefølgen i et `map` er uspecificeret, og koden må ikke afhænge af den.
9Hvornår bør du videregive en `struct` eller en anden værdi som værdi (by value) frem for via en pointer i Go?
Videregiv som værdi, når værdien er lille, skrivebeskyttet (read-only) for kaldet, eller når du ønsker uafhængig kopisemantik. Videregiv via pointer, når funktionen skal ændre på kalderens værdi (mutate), når kopiering vil være dyr, når typen ikke bør kopieres, eller når `nil` eller delt identitet (shared identity) er en del af API'et (Application Programming Interface). Antag ikke, at pointere altid er hurtigere: de kan tilføje aliasing, heap-escapes, arbejde til GC (Garbage Collector) og resultere i en mindre cache-venlig tilgang. Foretræk klare semantikker først, og udfør benchmarking på valg, der er kritiske for ydeevnen.
10Hvordan fungerer tildeling og kopiering for `struct`-typer, arrays, `map`-typer og `slice`-værdier i Go?
Tildeling i Go kopierer den værdi, der tildeles, men effekten afhænger af typen. Tildeling af en `struct` kopierer dens `struct`-felter, og tildeling af et array kopierer hvert element. Tildeling af en `slice` kopierer dens `slice`-header — pointer, længde og kapacitet — så `slice`-værdier deler som regel det samme underliggende array. Tildeling af et `map` kopierer en reference-lignende `map`-deskriptor, så begge variabler refererer til de samme `map`-data. For at opnå uafhængigt ejerskab skal man lave en defensiv kopiering, f.eks. ved at bruge `copy` eller `slices.Clone` til `slice`-værdier, og ved at oprette et nyt `map` og kopiere indgangene for `map`-typer.
11Hvordan indfanger closures variabler i Go, og hvilke fejl kan opstå i forbindelse med løkker og goroutines?
En closure i Go er en funktionsværdi, der refererer til variabler fra dens omgivende leksikalske synlighedsområde (scope). Den indfanger selve variablerne, så mutationer kan observeres af den pågældende closure, og de indfangede variabler kan leve længere end den funktion, der skabte dem, hvis den pågældende closure overlever funktionen og allokeres på heapen (escapes). Fejl i forbindelse med løkker og goroutines opstår, når closures deler eller observerer en variabel, efter at den er ændret. Det fører ofte til, at goroutines eller forsinkede callbacks ser en utilsigtet værdi, eller at der opstår data races på delt tilstand. Den sædvanlige løsning er at overføre den tilsigtede værdi som et argument til funktionen eller at oprette en ny lokal kopi pr. iteration, samt at synkronisere samtidig adgang, når det er nødvendigt. Siden Go 1.22 gælder løkkevariabler, der er deklareret med `for`/`range`, pr. iteration, hvilket fjerner mange klassiske fejl ved indfangning af løkkevariabler. Genbrugte variabler uden for løkken og anden delt, foranderlig tilstand kan dog stadig forårsage problemer.
12Beskriv semantikken for `range` over arrays, `slice`-værdier, `map`-typer, strenge og kanaler (channels).
`range` itererer afhængigt af operandens type. Over et array eller en `slice` returneres et indeks og en elementværdi; elementværdien er en kopi, så en tildeling til denne vil ikke ændre samlingen. Når man bruger `range` over en array-værdi, kopieres hele arrayet til iterationen, hvorimod `range` over en `slice` bruger slice-headeren og indekserer det underliggende array. Over et `map` returneres nøgle og værdi i en uspecificeret rækkefølge, og værdien er en kopi. Over en streng returneres byte-indekset og det afkodede Unicode-kodepunkt (`rune`), ikke et indeks for runens position. Over en kanal modtager den værdier, indtil kanalen er lukket og tømt; brug af `range` på en `nil`-kanal blokerer for evigt.
13Hvad er forskellen på værdi-receivers og pointer-receivers i Go, og hvordan påvirker valget af receiver foranderlighed, kopiering og opfyldelse af interfaces?
En metode med en værdi-receiver modtager en kopi af receiveren, så den er velegnet til skrivebeskyttet adfærd og små, uforanderlige typer; ændringer i kopien af receiveren er ikke synlige for kalderen. En metode med en pointer-receiver modtager en kopi af en pointer til receiveren, så den kan ændre originalen og undgå at kopiere store værdier eller værdier, der ikke bør kopieres. Valget af receiver påvirker metodesættet: Metoder med værdi-receivers er i metodesættet for både `T` og `*T`, mens metoder med pointer-receivers kun er i metodesættet for `*T`. Derfor opfyldes et interface, der kræver en metode med en pointer-receiver, af `*T` og ikke af `T`, selvom en adresserbar `T`-værdi kan kalde metoden ved hjælp af almindelig metodekalds-syntaks.
14Beskriv, hvordan interfaces fungerer i Go, og hvad det vil sige, at en type opfylder et interface implicit.
Et interface i Go definerer et sæt af krævede metoder. En konkret type opfylder et interface implicit, når dens metodesæt indeholder disse metoder; der er ingen eksplicit "implements"-deklaration. En interface-variabel kan indeholde en konkret dynamisk værdi, hvis type opfylder interfacet, og kald af interface-metoder viderestilles (dispatches) til den dynamiske værdis implementering. Det tomme interface, der skrives som `interface{}` eller oftest `any`, har ingen krævede metoder, så enhver type opfylder det. Hvorvidt `T`, `*T` eller begge opfylder et interface, afhænger af deres metodesæt og valget af modtager (receiver).
15Hvordan fungerer type-assertion og type-switch med interface-værdier?
En type-assertion kontrollerer den dynamiske værdi, der er gemt i et interface: `v := x.(T)` lykkes, hvis interface-værdiens dynamiske type er `T`, eller hvis `T` er et interface, der implementeres af den dynamiske værdi. Formen med ét resultat udløser en `panic`, hvis den fejler. 'Comma-ok'-formen, `v, ok := x.(T)`, rapporterer succes uden at udløse en `panic`. En type-switch, `switch v := x.(type)`, forgrener sig baseret på interface-værdiens dynamiske type, hvor `v` får typen i overensstemmelse med den matchede case.