Programmering C: Lösningar till tentamen 2012-08-23

Observera att detta är förslag på lösningar. Det kan finnas andra lösningar som också är korrekta, och det kan hända att en del av lösningarna är mer omfattande än vad som krävs för full poäng på uppgiften. En del av lösningarna är kanske inte fullständiga, utan hänvisar bara till var man kan läsa svaret.

Uppgift 1 (1 p)

a) 4

b) 0

c) 1 (true är fel svar, men ger inget poängavdrag)

d) går inte att avgöra

Uppgift 2 (1 p)

x = 3, y = 2, z = 3

Uppgift 3 (4 p)

#include <stdio.h>

int main(void) {
    int antal, min, max;
    int hittat_utanfor = 0;

    printf("Ange antal: ");
    scanf("%d", &antal);
    printf("Ange min: ");
    scanf("%d", &min);
    printf("Ange max: ");
    scanf("%d", &max);

    for (int i = 0; i < antal; ++i) {
        int talet;
        printf("Ange tal nr %d:\n", i + 1);
        scanf("%d", &talet);
        if (talet < min || talet > max)
            hittat_utanfor = 1;
    }
    if (hittat_utanfor)
        printf("Inte Ok\n");
    else
        printf("Ok\n");
    
    return 0;
}

Uppgift 4 (4 p)

a)
int tre_lika(double x, double y, double z) {
    return x == y && y == z;
}

En kommentar: Observera att det inte fungerar att skriva x == y == z. Det här är ett exempel på att som programmerare behöver man veta vad man gör, för den konstruktionen går igenom kompilatorn utan fel, men betyder något helt annat! Det är inte heller säkert att man upptäcker felet vid testning, för med en del testfall ger båda konstruktionerna samma resultat.

b)

int tre_olika(double x, double y, double z) {
    return x != y && x != z && y != z;
}
c)
int main(void) {
    double x, y, z;

    printf("Skriv tre flyttal: ");
    scanf("%lf %lf %lf", &x, &y, &z);

    printf("tre_lika(%f, %f, %f) = %d\n", x, y, z, tre_lika(x, y, z));
    printf("tre_olika(%f, %f, %f) = %d\n", x, y, z, tre_olika(x, y, z));
    
    return 0;
}

En kommentar: Det kan vara lockande att göra en lösning som den här nedanför, med else-grenar, för om alla tre talen är lika så kan de inte vara olika, och så vidare. Men kom ihåg att meningen är att testa funktionerna, så vi kan inte förutsätta att de gör rätt!

    if (tre_lika(x, y, z))
        printf("Alla talen är lika.\n");
    else if (tre_olika(x, y, z))
        printf("Alla talen är olika.\n");
    else
        printf("Två av talen är lika.\n");

Uppgift 5 (1 p)

#define MAX_NAMNLANGD 30

struct Termos {
    char typnamn[MAX_NAMNLANGD + 1];
    double volym;
    char innehallets_namn[MAX_NAMNLANGD + 1];
    double innehallets_volym;
};

Uppgift 6 (1 p)

struct Termos termosen = { "Sahara MWS-A500", 0.5, "kaffe", 0.4 };

Uppgift 7 (2 p)

void visa_termos(struct Termos* tp) {
    printf("Typ: %s\n", tp->typnamn);
    printf("Volym: %.2f\n", tp->volym);
    printf("Innehåll: %s\n", tp->innehallets_namn);
    printf("Innehållets volym: %.2f\n", tp->innehallets_volym);
}

Uppgift 8 (3 p)

void las_termos(struct Termos* tp) {
    printf("Typ: ");
    gets(tp->typnamn);
    printf("Volym: ");
    scanf("%lf", &tp->volym); 
    while (getchar() != '\n')
        ;
    printf("Innehåll: ");
    gets(tp->innehallets_namn);
    printf("Innehållets volym: ");
    scanf("%lf", &tp->innehallets_volym);
    while (getchar() != '\n')
        ;
}

Uppgift 9 (1 p)

Funktionen gets kontrollerar inte att den inlästa strängen får plats i den angivna strängvariabeln. Om strängen är längre än vad som får plats, fortsätter gets helt enkelt att placera de inlästa tecknen i minnet efter slutet på strängvariabeln. Om det fanns något på den platsen i minnet, skrivs det över.

Beroende på vad som fanns på de platser som skrevs över, kan programmet antingen krascha, ge fel svar, eller fungera som förväntat. Även andra fel kan uppstå, till exempel att programmet gör något helt annat än vad som var tänkt. Det kan bli olika fel olika gånger man provkör. C-standarden kallar det för odefinierat beteende, och enligt standarden får vad som helst hända, till exempel att programmet raderar alla filer på hårddisken eller att datorn smälter och rinner ner på golvet i en pöl.

En kommentar: Det här är ett bra exempel på att som C-programmerare behöver man veta vad man gör. Det är inte säkert att man upptäcker felet vid testningen, för programmet kan mycket väl råka fungera som man tänkt sig för alla testfallen. Men sen när man installerat det i produktionsmiljön och kör det på riktigt, då ger programmet fel svar. Eller så smälter datorn.

Uppgift 10 (1 p)

int rimlig_termos(struct Termos* tp) {
    return tp->innehallets_volym <= tp->volym;
}

Uppgift 11 (2 p)

int main(void) {
    struct Termos termos;
    las_termos(&termos);
    visa_termos(&termos);
    if (!rimlig_termos(&termos))
        printf("*** Varning! Termosens data är orimliga!\n");
    return 0;
}

Uppgift 12 (2 p)

double volymsumma(struct Termos termosar[], int antal_termosar) {
    double summa = 0;
    for (int i = 0; i < antal_termosar; ++i)
        summa += termosar[i].volym;
    return summa;
}

Uppgift 13 (3 p)

int main(void) {
    struct Termos termosar[] = {
        { "Typ 1", 0.1, "slem", 0.1 },
        { "Typ 2", 10.0, "vakuum", 0.0 },
        { "Typ 3", 1.1, "klägg", 0.1 },
        { "Typ 4", 0.9, "kladd", 0.7 },
    };
    if (volymsumma(termosar, 4) != 12.1)
        printf("*** Varning! Oväntat svar från volymsumma!\n");
    return 0;
}

En kommentar: Man måste vara försiktig med likhetsjämförelser av flyttal, som den i programkoden ovan. Eftersom flyttal lagras med ett begränsat antal binära siffror (bitar), går det inte att representera alla tal exakt. Jämför med att man inte kan skriva exakt 1/3 som ett decimaltal med ändligt många siffror när man skriver med vanliga decimala siffror. Därför kan vi här mycket väl få en varning om ett oväntat svar, trots att funktionen "volymsumma" räknar (nästan) helt rätt.

Uppgift 14 (5 p)

#include <stdlib.h>
#include <stdio.h>

// Definitioner av struct Termos, las_termos och rimlig_termos

int main(void) {
    struct Termos termos;
    FILE* termosfilen;

    termosfilen = fopen("termosar.bin", "wb");
    if (termosfilen == NULL) {
        fprintf(stderr, "Kunde inte öppna filen 'termosar.bin'\n");
        exit(EXIT_FAILURE);
    }
    las_termos(&termos);
    while (rimlig_termos(&termos)) {
        fwrite(&termos, sizeof termos, 1, termosfilen);
        las_termos(&termos);
    }
    fclose(termosfilen);

    return 0;
}

Uppgift 15 (5 p)

#include <stdlib.h>
#include <stdio.h>

// Definitioner av struct Termos och visa_termos

int main(void) {
    struct Termos termos;
    FILE* termosfilen;
    double volymgrans;

    termosfilen = fopen("termosar.bin", "rb");
    if (termosfilen == NULL) {
        fprintf(stderr, "Kunde inte öppna filen 'termosar.bin'\n");
        exit(EXIT_FAILURE);
    }
    
    printf("Volymgräns: ");
    scanf("%lf", &volymgrans);

    printf("Termosar på filen som är minst %.2f stora:\n", volymgrans);
    while (fread(&termos, sizeof termos, 1, termosfilen) == 1) {
        if (termos.volym >= volymgrans)
            visa_termos(&termos);
    }
    fclose(termosfilen);

    return 0;
}


Thomas Padron-McCarthy (thomas.padron-mccarthy@oru.se), 28 augusti 2012