Kapitel 4
Funktioner

Kurt Nørmark
Institut for Datalogi, Aalborg Universitet


Sammendrag
Forrige lektion Næste lektion
Stikord Referencer Indhold
I denne lektion gennemgår vi funktions- og procedure begrebet, herunder lokale variable og parametre. Vi ser naturligvis også på detaljer omkring funktioner i C.


Procedurer og funktioner

Programmer og Algoritmer
Slide Indhold Stikord
Referencer 

Begrebet algoritme: En algoritme er endelig liste af instruktioner til løsning af problem, som karakteriseres af nul, en eller flere input og ét output. Hver instruktion skal være klar, utvetydig, og praktisk gennemførbar for en person som ønsker at følge algoritmen.

  • Algoritme

    • Benyttes meget i den formelle og matematiske del af faget

    • Et idealiseret program

  • Program

    • Knytter sig til den praktiske del af faget

    • Implementerer en algoritme i et bestemt programmeringssprog

    • Insisterer ikke på 'nul, en eller flere input, og ét output'

    • Giver ikke nødvendigvis anledning til et endeligt antal instruktioner

Procedurer og Funktioner
Slide Indhold Stikord
Referencer 

Vi starter med parameterløse procedurer og funktioner

Begrebet abstraktion: En abstraktion indkapsler, navngiver og parametriserer en samling af programdetaljer
Begrebet procedure: En procedure er en abstraktion over en sekvens af kommandoer
Begrebet funktion: En funktion er en abstraktion over et udtryk

  • Procedure

    • En proceduredefinition indkapsler et antal kommandoer

    • Kaldet af en procedure er selv en kommando

  • Funktion

    • En funktionsdefinition indkapsler netop ét udtryk

    • Kaldet af en funktion er selv et udtryk

Senere i lektionen vil vi generalisere procedurer og funktioner med brug af parametre

Funktionsdefinitioner uden parametre i C
Slide Indhold Stikord
Referencer 

C skelner ikke skarpt mellem procedurer og funktioner

I C kaldes begge abstraktionsformer blot for funktioner

Syntax: En funktionsdefinition i C

type function_name(void) {
  declarations
  commands
}

  • Karakteristika:

    • Procedurer angiver type som void

    • Funktioner angiver type som en egentlig datatype, f.eks. int, char, eller double

    • I procedurer og funktioner med parametre erstattes void med de formelle parametre

I mange tilfælde er en C funktion en blanding af en procedure og en funktion

Man kan tænke på mange C funktioner som værdi-returnerende procedurer

Eksempel: Rødder i en andengradsligning
Slide Indhold Stikord
Referencer 

En simpel parameterløs funktion der finder rødderne i andengradsligning

Henvisning

Program: En funktion der finder rødder i en andengradsligning.
#include <stdio.h>
#include <math.h>

/* Find the roots in a * x*x + b * x + c = 0 */
void solveQuadraticEquation(void){
  double a, b, c, discriminant;

  printf("Enter coeficients a, b, and c: ");
  scanf("%lf %lf %lf", &a, &b, &c);

  discriminant = b * b - 4 * a * c;

  if (discriminant < 0)
    printf("No roots\n");
  else if (discriminant == 0)
    printf("One root: %f\n", -b/(2*a));
  else 
    printf("Two roots: %f and %f\n",
           (-b + sqrt(discriminant))/(2*a), 
           (-b - sqrt(discriminant))/(2*a) );
}   

int main(void) {
  int i = 3;

  // ...

  // Solve three equations:
  for (i = 1; i <= 3; i++)
    solveQuadraticEquation();  

  // ...

  return 0;
}

Henvisning

  • Observationer:

    • Indkapsling af formlen for problemløsningen er bekvem i forhold til gentagen brug.

    • Det er primitivt og ufleksibelt at indlæse koeficienterne med scanf.

    • Det er tilsvarende primtivt at udskrive røddernes værdier med printf.

I de fleste C funktioner ønsker vi at undgå direkte dialog

Kald af funktioner uden parametre - lokale variable
Slide Indhold Stikord
Referencer 

Syntax: Et funktionskald i C

function_name()

  • Lokale variable opstår når funktionen kaldes, og nedlægges når funktionen returnerer

  • Værdien af en lokal variabel er udefineret med mindre den initialiseres via et assignment

    • En udefineret værdi er den tilfældige værdi, som befinder sig i lageret på variablens sted

  • En funktion kan ikke kaldes før den er erklæret

Program: Illustration af ulovlig anvendelse af en lokal variabel.
#include <stdio.h>
#include <math.h>

/* Find the roots in a * x*x + b * x + c = 0 */
void solveQuadraticEquation(void){
  double a, b, c, discriminant;

  printf("Enter coeficients a and b - c is not read: ");
  scanf("%lf %lf %lf", &a, &b, &c);
  discriminant = b * b - 4 * a * c;

  if (discriminant < 0)
    printf("No roots\n");
  else if (discriminant == 0)
    printf("One root: %f\n", -b/(2*a));
  else 
    printf("Two roots: %f and %f\n",
           (-b + sqrt(discriminant))/(2*a),
           (-b - sqrt(discriminant))/(2*a) );

  printf("c = %f", c);
}   

int main(void) {
  solveQuadraticEquation();  
  printf("Diskriminanten var: %f",  discriminant  );
                           /* WRONG: discriminant does not exist here */
  return 0;
}

Program: Illustration af uinitialiseret lokal variabel.
#include <stdio.h>
#include <math.h>

/* Find the roots a * x*x + b * x + c = 0 */
void solveQuadraticEquation(void){
  double a, b, c, discriminant;

  printf("Enter coeficients a and b - c is not read: ");
  scanf("%lf %lf", &a, &b);
  discriminant = b * b - 4 * a *  c  ;   // c is not initialized

  if (discriminant < 0)
    printf("No roots\n");
  else if (discriminant == 0)
    printf("One root: %f\n", -b/(2*a));
  else 
    printf("Two roots: %f and %f\n",
           (-b + sqrt(discriminant))/(2*a),
           (-b - sqrt(discriminant))/(2*a));

  printf("c = %f", c);
}   

int main(void) {
  solveQuadraticEquation();  
  return 0;
}

Program: Illustration af funktionsdefinition efter funktionskald.
#include <stdio.h>
#include <math.h>

int main(void) {
  solveQuadraticEquation();   
  /* WRONG:  solveQuadraticEquation not yet defined */
  return 0;
}

/* Find the roots a * x*x + b * x + c = 0 */
void solveQuadraticEquation(void){
  double a, b, c, discriminant;

  printf("Enter coeficients a, b, and c: ");
  scanf("%lf %lf %lf", &a, &b, &c);
  discriminant = b * b - 4 * a * c;

  if (discriminant < 0)
    printf("No roots\n");
  else if (discriminant == 0)
    printf("One root: %f\n", -b/(2*a));
  else 
    printf("Two roots: %f and %f\n",
           (-b + sqrt(discriminant))/(2*a),
           (-b - sqrt(discriminant))/(2*a));
}   

Program: Løsning af 'definition efter kald' med brug af en prototype.
#include <stdio.h>
#include <math.h>

void solveQuadraticEquation(void);  /* this is a prototype */

int main(void) {
  solveQuadraticEquation();  
  return 0;
}

/* Find the roots in a * x*x + b * x + c = 0 */
void solveQuadraticEquation(void){
  double a, b, c, discriminant;

  printf("Enter coeficients a, b, and c: ");
  scanf("%lf %lf %lf", &a, &b, &c);
  discriminant = b * b - 4 * a * c;

  if (discriminant < 0)
    printf("No roots\n");
  else if (discriminant == 0)
    printf("One root: %f\n", -b/(2*a));
  else 
    printf("Two roots: %f and %f\n",
           (-b + sqrt(discriminant))/(2*a),
           (-b - sqrt(discriminant))/(2*a) );
}   


Top-down programmering ved trinvis forfinelse

Lasagne al forno
Slide Indhold Stikord
Referencer 

  • Bland og ælt 500 g durum hvedemel, 5 æg, lidt salt og 2 spsk olie.

  • Kør pastaen gemmen en pastamaskine i tynde baner.

  • Smelt 3 spsk smør, og rør det sammen med 3 spsk mel.

  • Spæd med med 5 dl mælk og kog sovsen under svag varme.

  • Svits 2 hakkede løg med 500 g hakket oksekød, samt salt og pebber.

  • Tilsæt 3 spsk tomatpuré, en ds. flåede tomater og 3 fed knust hvidløg.

  • Kog kødsovsen i 10 minutter.

  • Bland nu lagvis pastabaner, kødsovs og hvid sovs. Drys med paramesanost.

  • Gratiner retten i ovnen 15 minutter ved 225 grader.

En god lasagne - en ustruktureret opskrift

Struktureret lasagne
Slide Indhold Stikord
Referencer 

I en mere struktureret opskrift indgår delopskrifter på lasagneplader, hvid sovs og kødsovs

Program: Ustruktureret lasagne.
Bland og ælt 500 g durum hvedemel, 5 æg, lidt salt og 2 spsk olie;

Kør pastaen gemmen en pastamaskine i tynde baner;

Smelt 3 spsk smør, og rør det sammen med 3 spsk mel;

Spæd med med 5 dl mælk og kog sovsen under svag varme;

Svits 2 hakkede løg med 500 g hakket oksekød, samt salt og pebber;

Tilsæt 3 spsk tomatpuré, en ds. flåede tomater og 3 fed knust hvidløg;

Kog kødsovsen i 10 minutter;

Bland nu lagvis pastabaner, kødsovs og hvid sovs. Drys med paramesanost;

Gratiner retten i ovnen 15 minutter ved 225 grader;

Program: Struktureret Lasagne.
Lasagne:

  Lav en portion Lasagneplader;

  Lav en portion Hvid sovs;

  Lav en portion Kødsovs;
 
  Bland nu lagvis pastabaner, kødsovs og hvid sovs. Drys med paramesanost;

  Gratiner retten i ovnen 15 minutter ved 225 grader;

Program: Lav en portion lasagneplader.
Lasagneplader:

  Bland og ælt 500 g durum hvedemel, 5 æg, lidt salt og 2 spsk olie;

  Kør pastaen gemmen en pastamaskine i tynde baner;

Program: Lav en portion hvid sovs.
Hvid sovs:

  Smelt 3 spsk smør, og rør det sammen med 3 spsk mel;

  Spæd med 5 dl mælk og kog sovsen under svag varme;

Program: Lav en portion kødsovs.
Kødsovs:

  Svits 2 hakkede løg med 500 g hakket oksekød, 
  samt salt og pebber;

  Tilsæt 3 spsk tomatpuré, en ds. flåede tomater
  og 3 fed knust hvidløg;

  Kog kødsovsen 10 minutter;

Anvendelser af delopskrifter højner abstraktionsniveauet og muliggør genbrug af basale opskrifter.

Delopskrifter svarer til procedurer i programmeringssprog.

Brug af parametre ville generalisere opskriften så den kan anvendes på flere portionsstørrelser.

Lasagne ala C
Slide Indhold Stikord
Referencer 

Vi skriver nu et par versioner af et C program der bager Lasagne

Program: Et pseudo C program som laver lasagne.
#include <stdio.h>
#include <stdlib.h>

void make_lasagne(void);
void make_lasagne_plates(void);
void make_white_sauce(void);
void make_meat_sauce(void);

int main(void) {
  make_lasagne();
  return 0;
}

void make_lasagne(void) {
  make_lasagne_plates();
  make_white_sauce();
  make_meat_sauce();
  
  mix plates, meat sauce, and white sauce;
  sprinkle with paramesan cheese;
  bake 15 minutes at 225 degrees;  
}

void make_lasagne_plates(void) {
  mix flour, egs, salt and oil;
  process the pasta in the pasta machine;
}

void make_white_sauce(void) {
  melt butter and stir in some flour;
  add milk and boil the sauce;
}

void make_meat_sauce(void){
  chop the onion, and add meat, salt and pebber;
  add tomatos and garlic;
  boil the sauce 10 minutes;
}

Program: Et pseudo C program som laver lasagne - funktioner med parametre.
#include <stdio.h>
#include <stdlib.h>

void make_lasagne(int portion);
void make_lasagne_plates(int portion);
void make_white_sauce(int portion);
void make_meat_sauce(int portion);

int main(void) {
  int size;
 
  // Promt for size:
  printf("How large a portion do you want? ");
  scanf("%d", &size);

  // Make some food:
  make_lasagne(size);

  return 0;
}

void make_lasagne(int portion) {
  make_lasagne_plates(portion);
  make_white_sauce(portion);
  make_meat_sauce(portion);
  
  mix plates, meat sauce, and white sauce;

  sprinkle with paramesan cheese;
  bake 15 minutes at 225 degrees;  
}

void make_lasagne_plates(int portion) {
  get appropriate amounts of ingredients according to portion;
  mix flour, egs, salt and oil;
  process the pasta in the pasta machine;
}

void make_white_sauce(int portion) {
  get appropriate amounts of ingredients according to portion;
  melt butter and stir in some flour;
  add milk and boil the sauce;
}

void make_meat_sauce(int portion){
  get appropriate amounts of ingredients according to portion;
  chop the onion, and add meat, salt and pebber;
  add tomatos and garlic;
  boil the sauce 10 minutes;
}

En tændstikpige (1)
Slide Indhold Stikord
Referencer 

Vi ønsker at tegne en tændstikpige ved brug af simpel tegngrafik

Matchgirl - Stickman ...

Program: Det ønskede output - tændstikpigen.
      *          
    *   *        
  *       *      
  *       *      
    *   *        
      *          
-------------    
     /\         
    /  \        
   /    \       
  /      \      
 /        \     
/          \    
-------------    
     /\         
    /  \        
   /    \       
  /      \      
 /        \     
/          \    

Program: Udskrivning af tændstikpige i termer af udskrivning af hoved, arme, krop og ben.
int main(void){
  prn_match_girl();
  return 0;
}

void prn_match_girl(void){
  prn_head();
  prn_arms();
  prn_body();
  prn_legs();
}

Processen udføres top-down.

Programmet forfines trin for trin.

I næste trin må vi definere, hvordan kropsdelene tegnes.

En tændstikpige (2)
Slide Indhold Stikord
Referencer 

Pigens hoved, arme, krop og ben tegnes ved kald af generelle geometriske tegne procedurer

Program: Udskrivning af hoved, arme, krop og ben i termer af generelle geometriske figurer.
void prn_head(void){
  prn_circle();
}

void prn_arms(void){
  prn_horizontal_line();
}

void prn_body(void){
  prn_reverse_v();
  prn_horizontal_line();
}

void prn_legs(void){
  prn_reverse_v();
}

I næste trin må vi definere hvordan vi tegner cirkler, linier og andre geometriske former

En tændstikpige (3)
Slide Indhold Stikord
Referencer 

Cirkler, linier og andre former tegnes ved brug af primitiv tegngrafik

Program: Udskrivning af cirkler, linier og andre geometriske figurer.
/* C file of the simple graphical library */

#include <stdio.h>

void prn_circle(void){
  printf("      *          \n");
  printf("    *   *        \n");
  printf("  *       *      \n");
  printf("  *       *      \n");
  printf("    *   *        \n");
  printf("      *          \n");
}

void prn_horizontal_line(void){
  printf("-------------    \n");
}

void prn_reverse_v(void){
  printf("     /\\         \n");
  printf("    /  \\        \n");
  printf("   /    \\       \n");
  printf("  /      \\      \n");
  printf(" /        \\     \n");
  printf("/          \\    \n");
}

En tændstikpige (4)
Slide Indhold Stikord
Referencer 

Programmet er lavet top-down.

Programmering ved trinvis forfinelse.

Programmet designes og implementeres i et antal niveauer med postulering og efterfølgende realisering af et antal procedurer

Figur. En illustration of problemer og delproblemer i forbindelse med tegning af en tændstikpige

En tændstikpige (5)
Slide Indhold Stikord
Referencer 

Den samlede organisering af C programmet som tegner tændstikpigen

Program: Tændstikpige programmet.
#include "char-graphics.h"

void prn_match_girl(void);
void prn_head(void);
void prn_arms(void);
void prn_body(void);
void prn_legs(void);

int main(void){
  prn_match_girl();
  return 0;
}

void prn_match_girl(void){
  prn_head();
  prn_arms();
  prn_body();
  prn_legs();
}

void prn_head(void){
  prn_circle();
}

void prn_arms(void){
  prn_horizontal_line();
}

void prn_body(void){
  prn_reverse_v();
  prn_horizontal_line();
}

void prn_legs(void){
  prn_reverse_v();
}

Program: Filen char-graphics.h - header fil med prototyper af de grafiske funktioner.
/* Header file of very simple character-based graphical library  */


/* Print a circle */
void prn_circle(void);

/* Print a horizontal line */
void prn_horizontal_line(void);

/* Print a reverse v shape */
void prn_reverse_v(void);

Program: Filen char-graphics.c - implementationen af de grafiske funktioner.
/* C file of the simple graphical library */

#include <stdio.h>

void prn_circle(void){
  printf("      *          \n");
  printf("    *   *        \n");
  printf("  *       *      \n");
  printf("  *       *      \n");
  printf("    *   *        \n");
  printf("      *          \n");
}

void prn_horizontal_line(void){
  printf("-------------    \n");
}

void prn_reverse_v(void){
  printf("     /\\         \n");
  printf("    /  \\        \n");
  printf("   /    \\       \n");
  printf("  /      \\      \n");
  printf(" /        \\     \n");
  printf("/          \\    \n");
}

Program: Oversættelse af programmerne.
* Compilation of the char-graphics.c library:

   gcc -c char-graphics.c

* Compilation of the match-girl.c program:

   gcc match-girl.c char-graphics.o  -o match-girl

Program: Oversættelse af programmerne - med mange warnings.
* Compilation of the char-graphics.c library:

   gcc -ansi -pedantic -Wall -O -c char-graphics.c

* Compilation of the match-girl.c program:

   gcc -ansi -pedantic -Wall -O match-girl.c char-graphics.o  -o match-girl

Program: Makefile - som automatiserer oversættelsen.
match-girl: match-girl.c char-graphics.o char-graphics.h
	gcc match-girl.c char-graphics.o -o match-girl

char-graphics.o: char-graphics.c char-graphics.h
	gcc -c char-graphics.c

Program: Makefile med makroer.
CC = gcc
CFLAGS = -ansi -pedantic -Wall -O

match-girl: match-girl.c char-graphics.o char-graphics.h
	$(CC) $(CFLAGS) match-girl.c char-graphics.o -o match-girl

char-graphics.o: char-graphics.c char-graphics.h
	$(CC) $(CFLAGS) -c char-graphics.c

Henvisning

Funktioner skal erklæres før brug

Genbrugelige funktioner kan organiseres i separat oversatte C filer

Prototyper af sådanne funktioner skrives i såkaldte header files

Del og hersk - del og kombiner
Slide Indhold Stikord
Referencer 

Komplekse problemer opdeles i et antal simplere delproblemer.

Delproblemernes løsninger kombineres til en løsning på det oprindelige problem.

Figur. Opdelning af et kompleks problem i delproblemer

  • Del og hersk som top down programmering ved trinvis forfinelse:

    • Hvert delproblem P løses i en procedure

    • Procedurer, som er løsninger på delproblemer af P, placeres efter proceduren som løser P

      • Kræver erklæring af funktionsprototyper i starten af programmet

    • I kroppen af proceduren, som løser problemet P, programmeres en kombinering af delproblemernes løsning


C funktioner med input parametre

Formelle og aktuelle parametre
Slide Indhold Stikord
Referencer 

Den primære anvendelse af parametre er at overbringe input til en funktion

Syntax: En funktionsdefinition i C

type function_name (formal_parameters) {
  declarations
  commands
}

  • De formelle parametre er navne som optræder i funktionsdefinitionen, i parentes efter funktionsnavnet

  • De aktuel parametre er udtryk som optræder i funktionskaldet, i parentesen efter funktionsnavnet

  • Antallet af aktuelle parametre i et kald af en funktion skal være det samme som antallet af formelle parametre i funktionens definition

  • Rækkefølgen af aktuelle parametre og formelle parametres skal også svare til hinanden

  • Typen af en aktuel parameter skal svare til den angivne type af den tilsvarende formelle parameter

Syntax: Et funktionskald i C

function_name (actual_parameters)

Program: Illustration af formelle og aktuelle parametre.
double f(int a, char b, double c){
  ...
}


void g(void){
   int i = 6;
   char ch = 'x';
   double c = 5.6;

   ...
   ...  3. 4 + f(i + 7, ch, c)  ...
   ...

   f(i * i, 'y' + 1, (double)i);
   ...
}

Henvisning

I lærebogen kaldes de aktuelle parametre for (aktuelle) argumenter

Eksempel: Rødder i en andengradsligning
Slide Indhold Stikord
Referencer 

Koeficienterne til polynomiet i en andgradsligning bør være parametre til funktionen

Henvisning

Program: En funktion der finder rødder i en andengradsligning - nu med input parametre.
#include <stdio.h>
#include <math.h>

/* Prints roots of the quadratic equation a * x*x + b * x + c = 0 */
void solveQuadraticEquation(double a, double b, double c){
  double discriminant, root1, root2;

  discriminant = b * b - 4 * a * c;

  if (discriminant < 0)
    printf("No roots\n");
  else if (discriminant == 0){
    root1 = -b/(2*a);
    printf("One root: %f\n", root1);
  }
  else {
    root1 = (-b + sqrt(discriminant))/(2*a);
    root2 = (-b - sqrt(discriminant))/(2*a);
    printf("Two roots: %f and %f\n", root1, root2);
  }

}   

int main(void) {
  double a = 1.0, b = -8.0, c = 15.0,
         d = 2.0, e =  8.0, f =  2.0,
         g, h, i;

  /* First call - coefficents are values of variables */
  solveQuadraticEquation(a, b, c);  

  /* Second call - coefficents are values of expressions */
  solveQuadraticEquation(d - 1, -e, 7 * f + 1); 

  /* Third call - coefficents are entered by user */
  printf("Enter coeficients a, b, and c: ");
  scanf("%lf %lf %lf", &g, &h, &i);
  solveQuadraticEquation(g, h, i);  

  return 0;
}

Henvisning

  • Parameteroverførslen i eksemplet - værdiparametre - bringer input til funktionen:

    • Værdien af første aktuelle parameter kopieres over i første formelle parameter

    • Værdien af anden aktuelle parameter kopieres over i anden formelle parameter

    • Værdien af tredie aktuelle parameter kopieres over i tredie formelle parameter

Værdiparametrene har løst input problemet

Output problemet er stadig uløst!

Det er meget ufleksibelt blot at udskrive resultaterne med printf

Opgave 4.2. Trinvis forfinelse af solveQuadraticEquation

På mange måder er funktionen solveQuadraticEquation fra programmet på den tilhørende slide en god løsning på at finde rødderne i den generelle andengradsligning a x2 + b x + c = 0. Dog udestår, som omtalt på tilhørende slide en tilfredsstillende løsning på aflevering af outputtet: Antallet af rødder og rødderne selv. Men det kommer lidt senere i kurset.

I denne opgave vil vi nedbryde problemløsningen endnu mere, og vi vil på systematisk vis programmere C funktioner som løser disse delproblemer. Opgaven går altså ud på at anvende ideerne om trinvis forfinelse og top-down programmering.

Programmer hvert af følgende delproblemer som en separat funktion:

  • Beregning af diskriminanten.
  • Beregningen af første rod, under forudsætning af at den findes.
  • Beregningen af anden rod, under forudsætning af at den findes.

Hvis der kun er én rod, anses første og anden rod for at være ens. Hvis der ikke findes rødder må de to sidstenævnte funktioner ikke kaldes.

Find gode navne til funktionerne, der løser ovenstående tre delproblemer, og forsyn funktionerne med relevante input parametre. Funktionernes output skal formidles via funktionernes returværdi.

Omskriv programmet så det kalder de tre funktioner.

Programmer endvidere main således at solveQuadraticEquation kaldes i en løkke. Afslut løkken når alle de tre koeficienter a, b og c er lig med 0

I denne opgave bedes du programmere det hele i én C fil. Der skal således ikke laves en header fil med prototyper.

Eksempel: Temperaturomregninger
Slide Indhold Stikord
Referencer 

Eksempler på ægte matematisk funktioner, som udelukkende karakteriseres af deres input og output

Program: Et program som omregner frysepunkt og kogepunkt til Fahrenheit grader.
#include <stdio.h>

double fahrenheit_temperature(double celcius_temp){
  return (9.0 / 5.0) * celcius_temp + 32.0;
}

int main(void){

  printf("Freezing point: %6.2f F.\n", 
         fahrenheit_temperature(0.0));

  printf("Boiling point: %6.2f F.\n",
         fahrenheit_temperature(100.0));
  
  return 0;
}

Program: Et program som omregner frysepunkt og kogepunkt til Fahrenheit grader - uden abstraktion.
#include <stdio.h>

int main(void){

  printf("Freezing point: %6.2f F.\n", 
         9.0 / 5.0 * 0 + 32.0);

  printf("Boiling point: %6.2f F.\n",
         9.0 / 5.0 * 100 + 32.0);
  
  return 0;
}

Figur. To kald af fahrenheit_temperature funktionen

Program: Et program som udskriver en celcius fahrenheit tabel.
#include <stdio.h>

double fahrenheit_temperature(double celcius_temp){
  return (9.0 / 5.0) * celcius_temp + 32.0;
}

int main(void){
  double c;  

  printf("%-20s %-20s\n", "Celcius degrees", "Fahrenheit degrees");

  for (c = -30; c <= 45; c++)
    printf("%-20.2f %-20.2f\n", c, fahrenheit_temperature(c));
  
  return 0;
}

Program: Output fra ovenstående program.
Celcius degrees      Fahrenheit degrees  
-30.00               -22.00              
-29.00               -20.20              
-28.00               -18.40              
-27.00               -16.60              
-26.00               -14.80              
-25.00               -13.00              
-24.00               -11.20              
-23.00               -9.40               
-22.00               -7.60               
-21.00               -5.80               
-20.00               -4.00               
-19.00               -2.20               
-18.00               -0.40               
-17.00               1.40                
-16.00               3.20                
-15.00               5.00                
-14.00               6.80                
-13.00               8.60                
-12.00               10.40               
-11.00               12.20               
-10.00               14.00               
-9.00                15.80               
-8.00                17.60               
-7.00                19.40               
-6.00                21.20               
-5.00                23.00               
-4.00                24.80               
-3.00                26.60               
-2.00                28.40               
-1.00                30.20               
0.00                 32.00               
1.00                 33.80               
2.00                 35.60               
3.00                 37.40               
4.00                 39.20               
5.00                 41.00               
6.00                 42.80               
7.00                 44.60               
8.00                 46.40               
9.00                 48.20               
10.00                50.00               
11.00                51.80               
12.00                53.60               
13.00                55.40               
14.00                57.20               
15.00                59.00               
16.00                60.80               
17.00                62.60               
18.00                64.40               
19.00                66.20               
20.00                68.00               
21.00                69.80               
22.00                71.60               
23.00                73.40               
24.00                75.20               
25.00                77.00               
26.00                78.80               
27.00                80.60               
28.00                82.40               
29.00                84.20               
30.00                86.00               
31.00                87.80               
32.00                89.60               
33.00                91.40               
34.00                93.20               
35.00                95.00               
36.00                96.80               
37.00                98.60               
38.00                100.40              
39.00                102.20              
40.00                104.00              
41.00                105.80              
42.00                107.60              
43.00                109.40              
44.00                111.20              
45.00                113.00              

Eksempel: Antal dage i en måned
Slide Indhold Stikord
Referencer 

Vi introducerer en funktion i programmet der beregner antal dage i en måned

Vi bruger eksemplet til at diskutere overførsel af parametre til funktioner

Program: Et program der beregner antal dage i en måned med en funktion.
#include <stdio.h>
#include <stdlib.h>

int daysInMonth(int mth, int yr);
int isLeapYear(int yr);

int main(void) {

  int mth, yr;

  do{
    printf("Enter a month - a number between 1 and 12: ");
    scanf("%d", &mth);
    printf("Enter a year: ");
    scanf("%d", &yr);

    if (yr != 0)
       printf("There are %d days in month %d in year %d\n",
               daysInMonth(mth, yr), mth, yr);
  } while (yr != 0);

  return 0;
}

int daysInMonth(int month, int year){
  int numberOfDays;
  switch(month){
    case 1: case 3: case 5: case 7: case 8: case 10: case 12: 
      numberOfDays = 31; break;
    case 4: case 6: case 9: case 11: 
      numberOfDays = 30; break;
    case 2:
      if (isLeapYear(year)) numberOfDays = 29; 
      else numberOfDays = 28; break;
    default: exit(-1);  break;
  }
  return numberOfDays;
}   

int isLeapYear(int year){
  int res;
  if (year % 400 == 0)
    res = 1;
  else if (year % 100 == 0)
    res = 0;
  else if (year % 4 == 0)
    res = 1;
  else res = 0;
  return res;
}

Henvisning

Program: Et program der beregner antal dage i en måned med en funktion - uden IsLeapYear funktionen.
#include <stdio.h>
#include <stdlib.h>

int daysInMonth(int mth, int yr);
int isLeapYear(int yr);

int main(void) {

  int mth, yr;

  do{
    printf("Enter a month - a number between 1 and 12: ");
    scanf("%d", &mth);
    printf("Enter a year: ");
    scanf("%d", &yr);

    if (yr != 0)
       printf("There are %d days in month %d in year %d\n",
               daysInMonth(mth, yr), mth, yr);
  } while (yr != 0);

  return 0;
}

int daysInMonth(int month, int year){
  int numberOfDays, leapYear;
  switch(month){
    case 1: case 3: case 5: case 7: case 8: case 10: case 12: 
      numberOfDays = 31; break;
    case 4: case 6: case 9: case 11: 
      numberOfDays = 30; break;
    case 2:
      if (year % 400 == 0)
        leapYear = 1;
      else if (year % 100 == 0)
        leapYear = 0;
      else if (year % 4 == 0)
        leapYear = 1;
      else leapYear = 0;

      if (leapYear) numberOfDays = 29; 
      else numberOfDays = 28; break;
    default: exit(-1);  break;
  }
  return numberOfDays;
}   

Vi har øget genbrugeligheden - og dermed værdien af vort program

Vi ønsker som regel at undgå brug af scanf og printf i genbrugelige procedurer og funktioner

Eksempel: GCD
Slide Indhold Stikord
Referencer 

Vi introducerer ligeledes en funktion i programmet der beregner den største fælles divisor

Vi vil igen diskutere overførsel af parametre

Program: Et program der beregner største fælles divisor med en funktion.
#include <stdio.h>

int gcd(int, int);

int main(void) {
  int i, j, small, large;
 
  printf("Enter two positive integers: ");
  scanf("%d %d", &i, &j);

  small = i <= j ? i : j;
  large = i <= j ? j : i;
  
  printf("GCD of %d and %d is %d\n\n", i, j, gcd(large, small));
  
  return 0;
}

int gcd(int large, int small){
  int remainder; 
  while (small > 0){
    remainder = large % small;
    large = small;
    small = remainder;
  }
  return large;
}   

Henvisning

Opgave 4.5. Find de første n primtal

Denne opgave giver dig blandt andet træning i programmering af et C program, der anvender en header file (.h fil) og en tilhørende .c fil.

Du skal skrive et program med en main funktion der udskriver de første n primtal. Skriv dit program i en fil der hedder test-primes.c. Der ønskes følgende output hvis n er 100:

  prime 1: 2
  prime 2: 3
  prime 3: 5
  prime 4: 7
  prime 5: 11
  prime 6: 13
  prime 7: 17
  prime 8: 19
  prime 9: 23
  prime 10: 29
  ...
  prime 99: 523
  prime 100: 541

Du kan bruge følgende funktion, der placeres i filen primes.h, og som fortæller om et tal n er et primtal:

/* Return if n is a prime number */
int is_prime(int n);

I din main funktion skal du - ganske enkelt - gennemløbe så mange positive heltal, som det er nødvendigt, for at finde de første n primtal.

For at få alt dette til at virke skal du placere følgende programlinier i filen primes.c, og oversætte denne c fil separat.

#include "primes.h"
#include <math.h>
#include <assert.h>

/* Return if i is a prime number. It is assumed as a precondition that
   the parameter i is non-negative */
int is_prime(int i)
{
   assert(i >= 0);

   if (i == 1) 
     return 0;
   else if (i == 2) 
     return 1;
   else if (i % 2 == 0)
     return 0;
   else{
     int k, limit;
     limit = (int)(ceil(sqrt(i)));
     for (k = 3; k <= limit; k += 2)
        if (i % k == 0)
           return 0;
     return 1;
   }
}

Compilering af programmet: Følg mønstret fra vores gennemgang af oversættelse af tændstikpige programmet.

Inspirationen til denne opgave er fra bogen C by Dissection - anvendt med tilladelse fra forlaget.

Opgave 4.5. Goldbachs Formodning

Goldbachs formodning udtrykker en påstand om at alle lige heltal større end to er summen af to primtal. Denne formodning er hverken bevist eller modbevist. I denne opgave vil vi beskæftige os med følgende variation af påstanden:

  • Ethvert lige heltal større end 6 kan udtrykkes som summen af to ulige primtal.

Skriv et program der beviser denne formodning for alle lige heltal mellem to givne grænser. Eksempelvis for alle lige heltal mellem 7 og 2.000.000. Hvis du er i stand til at finde et modeksempel, er berømmelsen måske lige om hjørnet...

Det foreslås at funktionen is_prime fra en tidligere opgave bruges ved løsningen af denne opgave.

Det er for stor en mundfuld at løse dette problem uden opdeling i mindre delproblemer. Det foreslås derfor at I skriver en funktion, som undersøger påstanden for et bestemt lige heltal, n. Denne funktion kan så kaldes for alle lige heltal n mellem f.eks. 7 og 2.000.000.

Hint: Når I skal bevise påstanden for et tal, n, anbefales det at gennemløbe alle mulige summer (n-i) + i, og dermed undersøge om I kan finde et i så både n-i og i er ulige primtal.

Hvis I bliver hurtigt færdige med denne opgave bedes I se på den variant, der på Wikipedia beskrives som Goldbach's weak conjecture.

Som en anden udfordring, kan det være interessant at se alle mulige summer- ikke kun den første. Eller denne variant: Hvilket af de testede tal har det største antal opløsninger?

Denne opgave stammer fra bogen C by Dissection - anvendt med tilladelse fra forlaget.

Opgave 4.5. RGB Pixels

I en tidligere opgave har vi arbejdet med PPM grafik, hvor vi med tre kald af fputc skrev én pixel bestående af tre bytes (rød, grøn, blå) på en fil. Denne opgave forbereder vores fremtidige programmering af PPM grafik med en eksplict og relativ kompakt repræsentation af farverne i én pixel.

I denne opgave vil vi repræsentere en pixel i en unsigned int, som antages at fylde 4 bytes, på følgende måde:

  • Den mest betydende byte er i realiteten ubrugt - reserveret til fremtidige behov (til f.eks. at skelne mellem forskellige måder at repræsentere farver på). Vi vælger dog at skrive bitmønstret 00000001 i dette felt.
  • De efterfølgende tre bytes er hhv. den røde, grønne, og blå komponent (hver positive heltal mellem 0 og 255). Den blå komponent bliver således den mindst-betydende byte.

Skriv en funktion

  unsigned int make_pixel(int red, int green, int blue);

som indsætter tre heltal (som hver forudsættes at være mellem 0 og 255) i en unsigned int, og returnerer den resulterende pixels (som en unsigned int).

Skriv endvidere tre funktioner, som udtrækker hhv. den røde grønne og blå komponent af en pixel:

  int get_red(unsigned int p);
  int get_green(unsigned int p);
  int get_blue(unsigned int p);

Det skal naturligvis være således at

  • get_red(make_pixel(r, g, b)) = r
  • get_green(make_pixel(r, g, b)) = g
  • get_blue(make_pixel(r, g, b)) = b

Check at dette er tilfældet gennem en række eksempler.

Organiser de fire funktioner ovenfor i filen pixels.c, og lav en tilsvarende header fil pixels.h. Vær sikker på at du kan oversætte (compilere) filen pixels.c separat.

Det er attraktivt - men ikke strengt nødvendigt - at anvende de bitvise operatorer (eksempelvis <<, >> og &). Disse er beskrevet i appendix C, side C-3 i lærebogen.

Regler for overførsel af værdiparametre i C
Slide Indhold Stikord
Referencer 

Parametre til en funktion bruges til at gøre funktionen mere generelt anvendelig

Henvisning

Begrebet formel parameter: En parameter, som optræder i en funktionsdefinition, kaldes en formel parameter. En formel parameter er et navn.
Begrebet aktuel parameter: En parameter, som optræder i et kald af en funktion, kaldes en aktuel parameter. En aktuel parameter er et udtryk, som beregnes inden det overføres.

  • Antallet af aktuelle parametre i kaldet skal modsvare antallet af formelle parametre i definitionen

  • Typen af en aktuel parameter skal modsvare den angivne type af hver formel parameter

    • Dog muligheder for visse implicitte typekonverteringer

  • Parametre til funktioner i C overføres som værdiparametre - call-by-value

    • Der overføres en kopi af den aktuelle parameters værdi

    • Kopien bindes til (assignes til) det formelle parameternavn

    • Når funktionen returnerer ophører eksistensen af de formelle parametre, på samme måde som lokale variable i funktionen.

Eksempel: Rodsøgning (1)
Slide Indhold Stikord
Referencer 

Enhver kontinuert funktion f, som har en negativ værdi i l og en positiv værdi i u (eller omvendt) vil have mindst én rod r mellem l og u.

Figur. En kontinuert funktion f med en rod mellem l og u

Ved at indsnævre intervallet [l, u], og ved hele tiden at holde roden mellem l og u, kan vi hurtigt finde frem til en værdi r for hvilken f(r) = 0.

Eksempel: Rodsøgning (2)
Slide Indhold Stikord
Referencer 

Program: Rodsøgningsfunktionen.
/* Precondition: The sign of f(a) and f(b) are different */
double findRootBetween(double a, double b){
 double l = a, u = b;
 while (!isSmallNumber(f(middleOf(l,u)))){ 
   if(sameSign(f(middleOf(l,u)), f(u)))
     u = middleOf(l,u);
   else 
     l = middleOf(l,u);
 }
 return middleOf(l,u);
}  

Program: Funktionerne sameSign, middleOf og isSmallNumber.
int sameSign(double x, double y){
  return (x > 0 && y > 0) || (x < 0 && y < 0);
}

double middleOf(double x, double y){
  return x + (y - x)/2;
}

int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

Program: Hele rodsøgningsprogrammet.
#include <stdio.h>
#include <math.h>

int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);
double findRootBetween(double a, double b);
 
double f (double x){
  /* (x - 5.0) * (x - 3.0) * (x + 7.0) */
  return (x*x*x - x*x - 41.0 * x + 105.0);
}

int main (void){
    double x, y;
    int numbers; 

    do{
      printf("%s","Find a ROOT between two number: ");
      numbers = scanf("%lf%lf", &x, &y);

      if (numbers == 2 && !sameSign(f(x),f(y))){
          double solution = findRootBetween(x,y);
          printf("\nThere is a root in %lf\n", solution);
        }
      else if (numbers == 2 && sameSign(f(x),f(y)))
          printf("\nf must have different signs in %lf and %lf\n",
                  x, y);
      else if (numbers != 2)
          printf("\nBye\n\n");
    }
    while (numbers == 2);

    return 0;
}

/* Precondition: The sign of f(a) and f(b) are different */
double findRootBetween(double a, double b){
 double l = a, u = b;
 while (!isSmallNumber(f(middleOf(l,u)))){ 
   if(sameSign(f(middleOf(l,u)), f(u)))
     u = middleOf(l,u);
   else 
     l = middleOf(l,u);
 }
 return middleOf(l,u);
}  

int sameSign(double x, double y){
  return (x > 0 && y > 0) || (x < 0 && y < 0);
}

double middleOf(double x, double y){
  return x + (y - x)/2;
}

int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

Forskellige problemer med input parametre og returværdi
Slide Indhold Stikord
Referencer 

Vi viser forskellige elementære problemer med input parametre og returværdi fra en funktion

Program: Det gode program.
/* OK */

#include <stdio.h>
#include <math.h>

double hypotenuse (double katete1, double katete2){
  return sqrt(katete1 * katete1 + katete2 * katete2);
}

int main(void) {
  double k1, k2;
  printf("Indlaes to kateter: ");
  scanf("%lf %lf", &k1, &k2);

  printf("Katete1: %f, katete2: %f, hypotenuse: %f\n",
         k1, k2, hypotenuse(k1, k2));
}

Program: En double funktion som glemmer at bruge return.
/* Forget return */

#include <stdio.h>
#include <math.h>

double hypotenuse (double katete1, double katete2){
  sqrt(katete1 * katete1 + katete2 * katete2);
}

int main(void) {
  double k1, k2;
  printf("Indlaes to kateter: ");
  scanf("%lf %lf", &k1, &k2);

  printf("Katete1: %f, katete2: %f, hypotenuse: %f\n",
         k1, k2, hypotenuse(k1, k2));
}

Program: En void funktion som printer.
/* Void function som printer */

#include <stdio.h>
#include <math.h>

void hypotenuse (double katete1, double katete2){
  printf("Hypotenuse: %f", sqrt(katete1 * katete1 + katete2 * katete2));
}

int main(void) {
  double k1, k2;
  printf("Indlaes to kateter: ");
  scanf("%lf %lf", &k1, &k2);

  printf("Katete1: %f, katete2: %f, hypotenuse: %f\n",
         k1, k2, hypotenuse(k1, k2));        /* Problem with call of hypotenuse */
}

Program: En funktion hypotenuse kan ikke se en anden funktions lokale variable.
/* hypotenuse cannot see local variables in main */

#include <stdio.h>
#include <math.h>

double hypotenuse (void){
  return sqrt(katete1 * katete1 + katete2 * katete2);    /* katete1 and katete2 undeclared */
}

int main(void) {
  double katete1, katete2;
  printf("Indlaes to kateter: ");
  scanf("%lf %lf", &katete1, &katete2);

  printf("Katete1: %f, katete2: %f, hypotenuse: %f\n",
         katete1, katete2, hypotenuse());
}

Program: Brug af globale variable til input i stedet for parametre.
/* Communication via global variables - Works - NOT GOOD */

#include <stdio.h>
#include <math.h>

double katete1, katete2;

double hypotenuse (void){
  return sqrt(katete1 * katete1 + katete2 * katete2);  
}

int main(void) {
  printf("Indlaes to kateter: ");
  scanf("%lf %lf", &katete1, &katete2);

  printf("Katete1: %f, katete2: %f, hypotenuse: %f\n",
         katete1, katete2, hypotenuse());
}

Program: Brug af globale variable til input og output.
/* Communication via global variables - works - EVEN WORSE */

#include <stdio.h>
#include <math.h>

double katete1, katete2, result;

void hypotenuse (void){
  result = sqrt(katete1 * katete1 + katete2 * katete2); 
}

int main(void) {
  printf("Indlaes to kateter: ");
  scanf("%lf %lf", &katete1, &katete2);

  hypotenuse();

  printf("Katete1: %f, katete2: %f, hypotenuse: %f\n",
         katete1, katete2, result);
}


C funktioner med output parametre

Output fra funktioner
Slide Indhold Stikord
Referencer 

Funktionens grænseflade til resten af programmet udgøres af parametrene

Dette gælder input såvel som output

Output parametre kan i C håndteres med brug af pointere - call-by-reference

  • Output fra en funktion:

    • Via funktionens returværdi - return something;

      • En funktions-orienteret løsning

      • Problematisk hvis der er mere end ét output

    • Via indkaspling af flere stykker output i én helhed som kan returneres

      • Bliver muligt når vi introducere struct senere i kurset

    • Via et antal parametre af pointer typer

      • En imperativ løsning

      • Call-by-reference

Single return: Det er ofte mest gennemskueligt kun at have én return 'i bunden' af hver funktion

Introduktion til Pointere (1)
Slide Indhold Stikord
Referencer 

Normalt tilgås pladser i lageret ved brug af faste navne

Alternativt kan pladser i lageret tilgås indirekte via adresser, som kaldes pointere

Figur. En variabel v og en pointer til v

Introduktion til Pointere (2)
Slide Indhold Stikord
Referencer 

Fortsættelse af eksemplet

Figur. En variabel v og en pointer til v

Program: Det tilsvarende C program.
#include <stdio.h>

int main(void) {
  
  int v = 7;
  int *pv = &v;

  *pv = 8;

  printf("v = %d", v);

  return 0;
}

Program: Program output.
v = 8

To operatorer knyttet til pointere
Slide Indhold Stikord
Referencer 

Adresse operatoren &

Dereferencing operatoren operatoren *

  • Address operator

    • Udtrykket &v returnerer adressen hvori variablen v er lagret

    • Hvis typen af v er t, er typen af &v t*

    • t*   læses som   en pointer til t

  • Dereferencing operator - eller indirection operator

    • Udtrykket *pv returnerer den værdi, som pv peger på.

    • *pv følger pointeren i pv

    • *pv kan både optræde på venstre og højre side af et assignment

Henvisning

Både adresse operatoren og dereferencing operatoren er unære, præfix operatorer.

Begge operatorer & og * har også en anden betydning - hhv. bitvis and og multiplikation.

Output parametre - ift. input parametre
Slide Indhold Stikord
Referencer 

Input parametre sammenlignet med output parametre

Figur. Værdi parametre (til input) og reference parametre (til output)

Program: Funktionen f modtager værdierne af to variable som input - figur til venstre.
#include <stdio.h>

/* Input: We pass the values of two variables to f */
 
void f(int fp1, int fp2){
  printf("fp1 + fp2 = %d + %d = %d\n", fp1, fp2, fp1 + fp2);
}

int main(void) {
  int i = 3,
      j = 4;
  
  f(i, j);

  printf("i + j = %d + %d = %d\n", i, j, i + j);
  
  return 0;
}

Program: Program output.
fp1 + fp2 = 3 + 4 = 7
i + j = 3 + 4 = 7

Program: Funktionen f modtager værdierne af to udtryk som input.
#include <stdio.h>

/* Input: We pass the values of two expressions to f */
 
void f(int fp1, int fp2){
  printf("fp1 + fp2 = %d + %d = %d\n", fp1, fp2, fp1 + fp2);
}

int main(void) {
  int i = 3,
      j = 4;
  
  f(i + 1, j + 2);

  printf("i + j = %d + %d = %d\n", i, j, i + j);
  
  return 0;
}

Program: Program output.
fp1 + fp2 = 4 + 6 = 10
i + j = 3 + 4 = 7

Program: Funktionen f ændrer på værdierne af de to formelle parametre.
#include <stdio.h>

/* Input: We pass the values of two variables to f.
   We change the values of the formal parameters in f. */
 
void f(int fp1, int fp2){
  fp1 += 1; fp2 += 2;
  printf("fp1 + fp2 = %d + %d = %d\n", fp1, fp2, fp1 + fp2);
}

int main(void) {
  int i = 3,
      j = 4;
  
  f(i, j);

  printf("i + j = %d + %d = %d\n", i, j, i + j);
  
  return 0;
}

Program: Program output.
fp1 + fp2 = 4 + 6 = 10
i + j = 3 + 4 = 7

Program: En funktion g der modtager to pointere til variable som input - med henblik på tilbageføring af output - figur til højre.
#include <stdio.h>

/* We pass pointers as parameters to g*/

void g(int *fp1, int *fp2){
  *fp1 = *fp1 + 1;
  *fp2 = 6;    
  printf("*fp1 + *fp2 = %d + %d = %d\n", *fp1, *fp2, *fp1 + *fp2);
}

int main(void) {

  int i = 3,
      j = 4;
  
  g(&i, &j);  
  printf("i + j = %d + %d = %d\n", i, j, i + j);
  
  return 0;
}

Program: Program output.
*fp1 + *fp2 = 4 + 6 = 10
i + j = 4 + 6 = 10

Program: En funktion g der modtager to pointere til udtryk som input - Giver ikke mening.
#include <stdio.h>

/* We attempt to pass two pointers to g - not successful - the compiler finds out */

void g(int *fp1, int *fp2){
  *fp1 = *fp1 + 1;
  *fp2 = 6;    
  printf("*fp1 + *fp2 = %d + %d = %d\n", *fp1, *fp2, *fp1 + *fp2);
}

int main(void) {

  int i = 3,
      j = 4;
  
  g(&(i + 1), &(j + 2));                            /* Invalid lvalues */
  printf("i + j = %d + %d = %d\n", i, j, i + j);  
  
  return 0;
}

Program: Tilbageføring af output fra g med scanf.
#include <stdio.h>

/* We pass pointers to two variables to g */

void g(int *fp1, int *fp2){
  printf("Enter two integers: ");
  scanf("%d %d", fp1, fp2);
}

int main(void) {

  int i, j;   /* Not initialized */
  
  g(&i, &j);  
  printf("i + j = %d + %d = %d\n", i, j, i + j);
  
  return 0;
}

Program: Program input og output.
Enter two integers: 3 4
i + j = 3 + 4 = 7

Eksempel: Rødder i en andengradsligning
Slide Indhold Stikord
Referencer 

Koeficienterne til polynomiet i andengradsligningen er input parametre til funktionen

Antallet af rødder og selve rødderne er output parametre til funktionen

Henvisning

Program: En funktion der finder rødder i en andengradsligning - både input og output parametre.
#include <stdio.h>
#include <math.h>


/* Find roots in the quadratic equation a * x*x + b * x + c = 0.
   Assume as a precondition that a is not zero                    */
void solveQuadraticEquation(double a, double b, double c, 
                            int *numberOfRoots, double *root1, double *root2){
  double discriminant;

  discriminant = b * b - 4 * a * c;

  if (discriminant < 0){
    *numberOfRoots = 0;
  }
  else if (discriminant == 0){
    *numberOfRoots = 1;
    *root1 = -b/(2*a);
  }
  else{
    *numberOfRoots = 2;
    *root1 = (-b + sqrt(discriminant))/(2*a);
    *root2 = (-b - sqrt(discriminant))/(2*a);
  }
}   

int main(void) {
  double a, b, c, firstRoot, secondRoot;
  int numberOfRoots;
  printf("Enter coeficients a, b, and c: ");
  scanf("%lf %lf %lf", &a, &b, &c);

  if (a != 0){
    solveQuadraticEquation(a, b, c, &numberOfRoots, &firstRoot, &secondRoot);  
    if (numberOfRoots == 0)
      printf("No roots\n");
    else if (numberOfRoots == 1)
      printf("One root: %f\n", firstRoot);
    else 
      printf("Two roots: %f and %f\n", firstRoot, secondRoot);
  }
  else
    printf("The coeficient a must be non-zero");

  return 0;
}

  • Output parametre i eksemplet - call-by-reference parametre:

    • Der overføres en pointer til en int og two pointere til doubles

    • Output fra rod-beregningen formidles tilbage til kaldstedet gennem disse tre pointere

Denne udgave af funktionen er langt bedre end udgaven der blot printer rødderne!

Opgave 4.8. Celcius til fahrenheit med output parameter

Vi har tidligere programmeret en simpel funktion, der omregner fra en celcius temperatur til fahrenheit temperatur. Det er helt naturligt at celcius temperaturen er en call by value input parameter. Ligeledes er det naturligt at fahrenheit temperaturen returneres med return fra funktionen.

Omskriv nu funktionen således at fahrenheit temperaturen returneres gennem en output parameter - en pointer. Omskriv også main, således at kaldet ændres til denne nye parameterprofil.

Hvilken version foretrækker du?

Opgave 4.8. Timer, minutter og sekunder - igen, igen

Vi har på et tidligt tidspunkt i kurset skrevet et program, som omregner et antal sekunder til timer, minutter og sekunder efter de sædvanlige principper.

Skriv nu en funktion, hours_minutes_seconds, som tager antal af sekunder som en input parameter, og som returnerer de tre outputs (timer, minutter og sekunder) som output parametre (pointere, call-by-reference).

Opgave 4.8. Seddeludlevering i pengeautomat

Dette program handler om seddeludlevering fra en amerikansk pengeautomat, hvor der kun anvendes 100, 50, 20 og 10 dollar sedler.

Programmet skal som input acceptere et dollar beløb, der er dividerbart med 10. Programmet skal beregne antallet af udleverede 100, 50, 20 og 10 dollar sedler svarende til beløbet. Der skal udleveres så få sedler som muligt.

Problemet skal løses med en funktion der tager både input og output parametre. Beløbet, der skal veksles, skal være en input parameter. Antallet af udleverede 100, 50, 20 og 10 dollar sedler skal være output parametre.


Rekursive funktioner

Introduktion til rekursive funktioner
Slide Indhold Stikord
Referencer 

Rekursive funktioner er nyttige når et problem kan opdeles i delproblemer, hvoraf nogle har samme natur som problemet selv

En rekursiv funktion kalder sig selv

Henvisning

Program: Et program med en rekursivt defineret fakultetsfunktion.
#include <stdio.h>

unsigned long int factorial(unsigned int n){
  if (n == 0)
    return 1;
  else 
    return n * factorial(n - 1);
}

int main(void) {
  unsigned int k;

  /* Upper limits of k:  12  (if long is 4 bytes) */
  /* Upper limits of k:  20  (if long is 8 bytes) */

  for (k = 1; k <= 12; k++)                     
    printf("%-20u %20lu\n", k, factorial(k));
  
  return 0;
}

Program: Et tilsvarende program med en iterativ fakultetsfunktion.
#include <stdio.h>

unsigned long int factorial(unsigned int n){
  unsigned int i; 
  unsigned long result = 1;
  
  for(i = 1; i <= n; i++)
    result *= i;

  return result;
}

int main(void) {
  unsigned int k;

  for (k = 1; k <= 12; k++)
    printf("%-20u %20lu\n", k, factorial(k));
  
  return 0;
}

Program: Output fra ovenstående programmer.
1                                       1
2                                       2
3                                       6
4                                      24
5                                     120
6                                     720
7                                    5040
8                                   40320
9                                  362880
10                                3628800
11                               39916800
12                              479001600

Henvisning

Program: En rekursiv version af funktionen findRootBetween.
double findRootBetween(double l, double u){
  if (isSmallNumber(f(middleOf(l,u))))
     return middleOf(l,u);
  else if (sameSign(f(middleOf(l,u)), f(u)))
     return findRootBetween(l, middleOf(l,u));
  else if (!sameSign(f(middleOf(l,u)), f(u)))
     return findRootBetween(middleOf(l,u),u);
  else exit(-1);
}  

Program: Hele rodsøgningsprogrammet.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
 
double f (double x){
  /* (x - 5.0) * (x - 3.0) * (x + 7.0) */
  return (x*x*x - x*x - 41.0 * x + 105.0);
}

int sameSign(double x, double y){
  return x * y >= 0.0;
}

double middleOf(double x, double y){
  return x + (y - x)/2;
}

int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

double findRootBetween(double l, double u){
  if (isSmallNumber(f(middleOf(l,u))))
     return middleOf(l,u);
  else if (sameSign(f(middleOf(l,u)), f(u)))
     return findRootBetween(l, middleOf(l,u));
  else if (!sameSign(f(middleOf(l,u)), f(u)))
     return findRootBetween(middleOf(l,u),u);
  else exit(-1);
}  

int main (void){
    double x, y;
    int numbers; 

    printf("RECURSIVE ROOT FINDING\n\n");

    do{
      printf("%s","Find a ROOT between which numbers: ");
      numbers = scanf("%lf%lf", &x, &y);

      if (numbers == 2 && !sameSign(f(x),f(y))){
          double solution = findRootBetween(x,y);
          printf("\nThere is a root in %lf\n", solution);
        }
      else if (numbers == 2 && sameSign(f(x),f(y)))
          printf("\nf must have different signs in %lf and %lf\n",
                  x, y);
      else if (numbers != 2)
          printf("\nBye\n\n");
    }
    while (numbers == 2);

    return 0;
}

I hvert rekursivt kald afsættes der plads til nye parametre og nye lokale variable.

Vi vender tilbage til rekursion i en senere lektion


Pointere til funktioner

Pointere til funktioner
Slide Indhold Stikord
Referencer 

Det er ofte nyttigt at overføre funktioner som parametre til andre funktioner

I C er dette muligt ved brug af pointere til funktioner

Henvisning

Program: Rodsøgning i en funktionen, som overføres som parameter til findRootBetween.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
 
double pol1 (double x){
  /* (x - 5.0) * (x - 3.0) * (x + 7.0) */
  return (x*x*x - x*x - 41.0 * x + 105.0);
}

double pol2 (double x){
  return (x*x - 2.0);
}


int sameSign(double x, double y){
  return (x > 0 && y > 0) || (x < 0 && y < 0);
}

double middleOf(double x, double y){
  return x + (y - x)/2;
}

int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

/* Precondition: The sign of f(l) and f(u) are different */
double findRootBetween(double (*f)(double), double l, double u){
 while (!isSmallNumber((*f)(middleOf(l,u)))){ 
   if(sameSign((*f)(middleOf(l,u)), (*f)(u)))
     u = middleOf(l,u);
   else 
     l = middleOf(l,u);
 }
 return middleOf(l,u);
}  

int main (void){
    double x, y;
    int numbers; 
    int function_selection;
    double (*fn)(double);  // fn is a pointer to a function:   double -> double
    int done = 0;

    do{
      /* Select function: */
      printf("Select function:\n");
      printf("1: x*x*x - x*x - 41.0 * x + 105.0\n");
      printf("2: x*x - 2.0\n");
      printf("3: sin\n");
      printf("4: EXIT\n");

      scanf("%d", &function_selection);

      switch(function_selection){
        case 1:
          fn = &pol1; break;
        case 2:
          fn = &pol2; break;
        case 3:
          fn = &sin; break;
        default:
          done = 1;
      }


      /* Find a root in selected function: */
      if (!done){
        printf("%s","Find a ROOT between which numbers: ");
        numbers = scanf("%lf%lf", &x, &y);
  
        if (numbers == 2 && !sameSign((*fn)(x),(*fn)(y))){
            double solution = findRootBetween((*fn), x ,y);
            printf("\nThere is a root in %lf\n\n", solution);
          }
        else if (numbers == 2 && sameSign((*fn)(x),(*fn)(y)))
            printf("\nf must have different signs in %lf and %lf\n\n",
                    x, y);
        else if (numbers != 2)
            done = 1;
      }
    }
    while (!done);


    return 0;
}

Program: En version uden dereferencing og address operator på funktionerne.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
 
double pol1 (double x){
  /* (x - 5.0) * (x - 3.0) * (x + 7.0) */
  return (x*x*x - x*x - 41.0 * x + 105.0);
}

double pol2 (double x){
  return (x*x - 2.0);
}


int sameSign(double x, double y){
  return (x > 0 && y > 0) || (x < 0 && y < 0);
}

double middleOf(double x, double y){
  return x + (y - x)/2;
}

int isSmallNumber(double x){
  return (fabs(x) < 0.0000001);
}   

/* Precondition: The sign of f(l) and f(u) are different */
double findRootBetween(double (*f)(double), double l, double u){
 while (!isSmallNumber((*f)(middleOf(l,u)))){ 
   if(sameSign((*f)(middleOf(l,u)), (*f)(u)))
     u = middleOf(l,u);
   else 
     l = middleOf(l,u);
 }
 return middleOf(l,u);
}  

int main (void){
    double x, y;
    int numbers; 
    int function_selection;
    double (*fn)(double);  //fn is a pointer to a function:   double -> double
    int done = 0;

    do{
      /* Select function: */
      printf("Select function:\n");
      printf("1: x*x*x - x*x - 41.0 * x + 105.0\n");
      printf("2: x*x - 2.0\n");
      printf("3: sin\n");
      printf("4: EXIT\n");

      scanf("%d", &function_selection);

      switch(function_selection){
        case 1:
          fn = pol1; break;
        case 2:
          fn = pol2; break;
        case 3:
          fn = sin; break;
        default:
          done = 1;
      }


      /* Find a root in selected function: */
      if (!done){
        printf("%s","Find a ROOT between which numbers: ");
        numbers = scanf("%lf%lf", &x, &y);
  
        if (numbers == 2 && !sameSign(fn(x),fn(y))){
            double solution = findRootBetween( fn , x ,y);
            printf("\nThere is a root in %lf\n\n", solution);
          }
        else if (numbers == 2 && sameSign(fn(x),fn(y)))
            printf("\nf must have different signs in %lf and %lf\n\n",
                    x, y);
        else if (numbers != 2)
            done = 1;
      }
    }
    while (!done);


    return 0;
}

Henvisninger

Pointere til funktioner anvendes bl.a. i sorteringsfunktionen qsort fra stdlib.h


Coding Style

Indrykning
Slide Indhold Stikord
Referencer 

Eksempler på gode og anerkendte indrykningskonventioner, og eksempler på dårlig indrykning

Program: K & R Style.
#include <stdio.h>
 
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

/* K & R style */
double findRootBetween(double a, double b)
{
    double l = a, u = b;
    while (!isSmallNumber(f(middleOf(l, u)))) { 
        if(sameSign(f(middleOf(l, u)), f(u)))
            u = middleOf(l, u);
        else 
            l = middleOf(l, u);
    }
    return middleOf(l, u);
}  

int main (void)
{
    findRootBetween(-3.0, 5.0);
}

Program: K & R Style, med curly braces om enkelt sætninger.
#include <stdio.h>
 
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

/* K & R style, variant. Curly braces also used around single statements */
double findRootBetween(double a, double b)
{
    double l = a, u = b;
    while (!isSmallNumber(f(middleOf(l, u)))) { 
        if(sameSign(f(middleOf(l, u)), f(u))) {
            u = middleOf(l, u);
        }
        else {
            l = middleOf(l, u);
        }
    }
    return middleOf(l, u);
}  

int main (void)
{
    findRootBetween(-3.0, 5.0);
}

Program: Mindre variant af K & R style. Alternativ opsætning af else.
#include <stdio.h>
 
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

/* K & R style, minor variant. More compact setup of else */
double findRootBetween(double a, double b)
{
    double l = a, u = b;
    while (!isSmallNumber(f(middleOf(l, u)))) { 
        if(sameSign(f(middleOf(l, u)), f(u))) {
            u = middleOf(l, u);
        } else {
            l = middleOf(l, u);
        }
    }
    return middleOf(l, u);
}  

int main (void)
{
    findRootBetween(-3.0, 5.0);
}

Program: Meget kompakt formatering - ofte anvendt i disse noter.
#include <stdio.h>
 
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

/* A compact formatting - used for many programs in these notes */
double findRootBetween(double a, double b){
  double l = a, u = b;
  while (!isSmallNumber(f(middleOf(l, u)))) { 
    if(sameSign(f(middleOf(l, u)), f(u)))
      u = middleOf(l, u);
    else 
      l = middleOf(l, u);
  }
  return middleOf(l, u);
}  

int main (void){
  findRootBetween(-3.0, 5.0);
}

Program: Allman style.
#include <stdio.h>
 
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

/* Allman style */
double findRootBetween(double a, double b)
{
    double l = a, u = b;
    while (!isSmallNumber(f(middleOf(l, u)))) 
    { 
        if(sameSign(f(middleOf(l,u)), f(u))) 
        {
            u = middleOf(l, u);
        } 
        else
        {
            l = middleOf(l, u);
        }
    }
    return middleOf(l, u);
}  

int main (void)
{
    findRootBetween(-3.0, 5.0);
}

Program: Whitesmiths style.
#include <stdio.h>
 
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

/* Whitesmiths style */
double findRootBetween(double a, double b)
{
    double l = a, u = b;
    while (!isSmallNumber(f(middleOf(l, u)))) 
        { 
        if(sameSign(f(middleOf(l,u)), f(u))) 
            {
            u = middleOf(l, u);
            } 
        else
            {
            l = middleOf(l, u);
            }
        }
    return middleOf(l, u);
}  

int main (void)
{
    findRootBetween(-3.0, 5.0);
}

Program: Inkonsistent indrykning - blandet anvendelse af indrykningsreglerne.
#include <stdio.h>
 
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

/* Inkonsistent indrykning - blandet anvendelse af konventionerne */
double findRootBetween(double a, double b){
    double l = a, u = b;
    while (!isSmallNumber(f(middleOf(l, u)))) 
    { 
        if(sameSign(f(middleOf(l, u)), f(u)))
            u = middleOf(l, u);
        else 
        {
            l = middleOf(l, u);
        }
    }
    return middleOf(l, u);
}  

int main (void)
{
    findRootBetween(-3.0, 5.0);
}

Program: Inkonsistent indrykning - ujævn mængde af indrykning.
#include <stdio.h>
 
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

/* En anden variant af inkonsistent indrykning */
double findRootBetween(double a, double b)
{
  double l = a, u = b;
    while (!isSmallNumber(f(middleOf(l, u)))) { 
      if(sameSign(f(middleOf(l, u)), f(u)))
            u = middleOf(l, u);
      else 
        l = middleOf(l, u);
    }
      return middleOf(l, u);
}  

int main (void)
{
findRootBetween(-3.0, 5.0);
}

Program: Udrykning - ikke indrykning.
#include <stdio.h>
 
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

/* Udrykning - ikke indrykning */
double findRootBetween(double a, double b)
{
    double l = a, u = b;
    while (!isSmallNumber(f(middleOf(l, u)))) { 
  if(sameSign(f(middleOf(l, u)), f(u)))
      u = middleOf(l, u);
  else 
      l = middleOf(l, u);
  }
        return middleOf(l, u);
}  

int main (void)
{
    findRootBetween(-3.0, 5.0);
}

Program: Så bliver det ikke meget værre...
#include <stdio.h>
 
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

/* Just ugly */
double findRootBetween(double a, double b)    {
    double l = a,
  u =   b   ;
    while    (!isSmallNumber(f(middleOf(l, u)))) { 
  if(sameSign(f(middleOf(l, u)), f(u)))
              u = middleOf(l,u);
      else 
            l = middleOf(l,   u);
    }
    return
        middleOf(l,
 u);
}  

int main (void)
{
    findRootBetween(-3.0, 
                    5.0);}

Variabelnavne
Slide Indhold Stikord
Referencer 

Konventioner for brug af sammensatte navne

Program: Lower camel case.
#include <stdio.h>

/* Lower camel case */
double f (double x);
int sameSign(double x, double y);
double middleOf(double x, double y);
int isSmallNumber(double x);

double findRootBetween(double a, double b)
{
    double l = a, u = b;
    while (!isSmallNumber(f(middleOf(l, u)))) { 
        if(sameSign(f(middleOf(l, u)), f(u)))
            u = middleOf(l, u);
        else 
            l = middleOf(l, u);
    }
    return middleOf(l, u);
}  

int main (void)
{
    findRootBetween(-3.0, 5.0);
}

Program: Upper camel case.
#include <stdio.h>

/* Upper camel case */
double f (double x);
int SameSign(double x, double y);
double MiddleOf(double x, double y);
int IsSmallNumber(double x);

double FindRootBetween(double a, double b)
{
    double l = a, u = b;
    while (!IsSmallNumber(f(middleOf(l, u)))) { 
        if(SameSign(f(middleOf(l, u)), f(u)))
            u = middleOf(l, u);
        else 
            l = MiddleOf(l, u);
    }
    return MiddleOf(l, u);
}  

int main (void)
{
    FindRootBetween(-3.0, 5.0);
}

Program: Underscore.
#include <stdio.h>

/* Underscores */
double f (double x);
int same_sign(double x, double y);
double middle_of(double x, double y);
int is_small_number(double x);

double find_root_between(double a, double b)
{
    double l = a, u = b;
    while (!is_small_numerb(f(middle_of(l, u)))) { 
        if(same_sign(f(middle_of(l, u)), f(u)))
            u = middle_of(l, u);
        else 
            l = middle_of(l, u);
    }
    return middle_of(l, u);
}  

int main (void)
{
    find_root_between(-3.0, 5.0);
}

Korte og lange navne

Tilknytning af andre egenskaber til navnet

Program: Brug af meget korte navne.
#include <stdio.h>

/* Meget korte navne */
double f (double x);
int ss(double x, double y);
double mo(double x, double y);
int isn(double x);

double frb(double a, double b)
{
    double l = a, u = b;
    while (!isn(f(mo(l, u)))) { 
        if(ss(f(mo(l, u)), f(u)))
            u = mo(l, u);
        else 
            l = mo(l, u);
    }
    return mo(l, u);
}  

int main (void)
{
    frb(-3.0, 5.0);
}

Program: Hungarian notation - forkortede typenavne anvendt som prefix på alle variable.
#include <stdio.h>
#include <math.h>

/* Hungarian notation - abbreviated type names attached to all variables */
void solve_quadratic_equation(double d_a, double d_b, double d_c, 
                              int *pi_number_of_roots, double *pd_root1, 
                              double *pd_root2){
  double d_discriminant;

  d_discriminant = d_b * d_b - 4 * d_a * d_c;

  if (d_discriminant < 0){
    *pi_number_of_roots = 0;
  }
  else if (d_discriminant == 0){
    *pi_number_of_roots = 1;
    *pd_root1 = -d_b/(2*d_a);
  }
  else{
    *pi_number_of_roots = 2;
    *pd_root1 = (-d_b + sqrt(d_discriminant))/(2*d_a);
    *pd_root2 = (-d_b - sqrt(d_discriminant))/(2*d_a);
  }
}   

int main(void) {
  double d_a, d_b, d_c, d_firstRoot, d_secondRoot;
  int i_number_of_roots;
  printf("Enter coeficients a, b, and c: ");
  scanf("%lf %lf %lf", &d_a, &d_b, &d_c);

  if (d_a != 0){
    solve_quadratic_equation(d_a, d_b, d_c, 
             &i_number_of_roots, &d_firstRoot, &d_secondRoot);  
    if (i_number_of_roots == 0)
      printf("No roots\n");
    else if (i_number_of_roots == 1)
      printf("One root: %f\n", d_firstRoot);
    else 
      printf("Two roots: %f and %f\n", d_firstRoot, d_secondRoot);
  }
  else
    printf("The coeficient a must be non-zero");

  return 0;
}

Program: All caps - som et eksempel på at små/store bogstaver kan bruges til at signalere rollen af navne.
#include <stdio.h>

/* Names of constants defined as macroes are ALL CAPS */

#define TABLE_SIZE 11

int main(void) {

  /* Declaration: */
  double table[TABLE_SIZE];
  int i;

  /* Initialization: */
  for(i = 0; i < TABLE_SIZE; i++)
    table[i] = (double)(2 * i);

  /* Printing: */
  for(i = 0; i < TABLE_SIZE; i++)
    printf("%f\n", table[i]);

  printf("\n");
  
  return 0;
}


Ekstra Opgaver

Ekstra Opgaver
Slide Indhold Stikord
Referencer 

Opgave 4.10. Skudårsfunktionen

Vi har tidligere i denne lektion mødt skudårsfunktionen

int isLeapYear(int year){
  int res;
  if (year % 400 == 0)
    res = 1;
  else if (year % 100 == 0)
    res = 0;
  else if (year % 4 == 0)
    res = 1;
  else res = 0;
  return res;
}

Programmer en ny skudårsfunktion med brug af && og ||, og uden brug af if-else og uden brug af betingede udtryk.

Kald begge skudårsfunktioner for alle årstal mellem år 1900 og år 2100. Giver de to funktioner samme resultat på alle årstal?

Opgave 4.10. En simpel lommeregner

Skriv et program der modellerer en simpel lommeregner. Hver input-line skal bestå af den næste operation, som skal udføres, efterfulgt af den højre operand. Antag at den venstre operand er akkumulatoren (den beregnede værdi, i starten 0).

Du skal have en funktion, scan_data, med to output parametre som returnerer en operator og den højre operand fra en data linje. Du skal også have en funktion, do_next_op, som udfører den påkrævede operation. do_next_op skal have to input parametre (operator og operand) og en input/output parameter (akkumulatoren).

Her er de gyldige operationer:

  +  addition
  -  subtraktion
  *  multiplikation
  /  division
  ^  potensopløftning
  q  quit

Din lommeregner skal vise den akkumulerede værdi efter hver operation.

Her er et eksempel:

  + 5.0
  result so far is 5.0
  ^ 2
  result so far is 25.0
  / 2.0
  result so far is 12.5
  q 0
  final result is 12.0

(Denne opgave svarer til opgave 10 side 360 i 6. udgave af lærebogen, og den minder om opgave 10 side 391 i 7. udgave).


PPM Grafik

PPM grafik funktioner
Slide Indhold Stikord
Referencer 

Vi introducerer et lille og simpelt bibliotek til produktion af PPM grafik

Funktionerne beror på pixel typen, som vi har arbejdet med i en tidligere opgave, og på typen ppm, som repræsenterer et grafisk billede.

PPM billeder refereres gennem pointere

  • De vigtigste PPM funktioner

    • ppm *make_image(unsigned int width, unsigned int height, pixel background_pixel);

    • void set_pixel(ppm *image, unsigned int x, unsigned int y, pixel p);

    • pixel get_pixel(ppm *image, unsigned int x, unsigned int y);

    • void write_image(ppm *image, char *file_name);

Program: Header filen pixel.h.
/* PIXELS - A pixel represents a RGB color. */

/** A new type that represents a single RGB pixel */
typedef unsigned int pixel;

/** The constructor of a pixel in terms of red, green and blue (between 0 and 255) */
pixel make_pixel(unsigned int red, unsigned int green, unsigned int blue);

/** Access and return the red component of the pixel p */
unsigned int get_red(pixel p);

/** Access and return the green component of the pixel p */
unsigned int get_green(pixel p);

/** Access and return the blue component of the pixel p */
unsigned int get_blue(pixel p);

Program: Den tilsvarende fil pixel.c - en mulig implementation.
#include "pixel.h"

pixel make_pixel(unsigned int red, unsigned int green, unsigned int blue){
  return (1 << 24) | (red << 16) | (green << 8) | blue; 
}

unsigned int get_red(pixel p){
  return (p >> 16) & 0xff;
}

unsigned int get_green(pixel p){
  return (p >> 8) & 0xff;
}
unsigned int get_blue(pixel p){
  return p & 0xff;
}

Program: Header filen ppm.h.
#include "pixel.h"

/* PPM IMAGES */

/* A new type that represents a PPM image */
typedef struct ppm{
   unsigned int width;
   unsigned int height;
   unsigned int **pixels;
   } ppm;

/* The constructor of a PPM image. Returns a pointer to a PPM image given the width,
   height and a background pixel (used throughout the entire image).  */
ppm *make_image(unsigned int width, unsigned int height, pixel background_pixel);

/* Set a single pixel in image at (x, y) to p.
   Drawing area: x in [0 .. width-1], y in [0 .. height-1].
   If (x,y) is outside the drawing area, the image is not affected. */
void set_pixel(ppm *image, unsigned int x, unsigned int y,  pixel p);

/* Return the pixel at position (x, y) in image.
   x and y must be within the drawing area:  x in [0 .. width-1], y in [0 .. height-1].*/
pixel get_pixel(ppm *image, unsigned int x, unsigned int y); 

/* Return the width of the image */
unsigned int image_width(ppm *img);

/* Return the height of the image */
unsigned int image_height(ppm *img);

/* Write the PPM image to a file named file_name.*/
void write_image(ppm *image, char *file_name);

/* Read an existing PPM image (P6) from a file named file_name and return it*/
ppm *read_image(char *file_name);

Program: Den tilsvarende fil ppm.c - en mulig implementation.
#include <stdlib.h>
#include <stdio.h>
#include <math.h>
#include "ppm.h"

/* IT IS NOT NECESSARY TO UNDERSTAND THE DETAILS OF THIS PROGRAM.  
   IT IS SUFFICIENT TO UNDERSTAND ppm.h  */

unsigned int **alloc_image(int m, int n){
  int i;
  unsigned int **dpp = (unsigned int **)malloc(m * sizeof(unsigned int *));
  if(dpp == NULL) {
    fprintf(stderr, "out of memory\n");
    exit(EXIT_FAILURE);
  }
  for(i = 0; i < m; i++) {
    if((dpp[i] = (unsigned int *)malloc(n * sizeof(unsigned int))) == NULL) {
        fprintf(stderr, "out of memory\n");
        exit(EXIT_FAILURE);
    }
  }
  return dpp;
}


void init_image(unsigned int **image, unsigned int width, unsigned int height,
                unsigned int red, unsigned int green, unsigned int blue){
  int x,y ;

  for(y = 0; y < height; y++)
    for (x = 0; x < width ; x++){
      image[x][y] = (red << 16) | (green << 8) | blue;
    }
}

ppm *make_image(unsigned int width, unsigned int height, pixel background_pixel){
  /* Allocate the struct: */
  ppm *the_image = malloc(sizeof(ppm));   

  /* Initialize the fields: */
  the_image->width = width;
  the_image->height = height;
  the_image->pixels = alloc_image(width,height);

  /* Initialize all pixels of image: */
  init_image(the_image->pixels, width, height, 
             get_red(background_pixel),
             get_green(background_pixel),
             get_blue(background_pixel));

  /* Return the pointer to the image: */
  return the_image;  
}

void set_pixel(ppm *image, unsigned int x, unsigned int y, pixel p){
  unsigned int **pixel_table = image->pixels;
  if (x >= 0 && x < image->width && y >= 0 && y < image->height)
     pixel_table[x][y] = (unsigned int)p;
}

pixel get_pixel(ppm *image, unsigned int x, unsigned int y){
  unsigned int **pixel_table = image->pixels;
  return (pixel)(pixel_table[x][y]);
}

/* Return the width of the image */
unsigned int image_width(ppm *img){
  return img->width;
}

/* Return the height of the image */
unsigned int image_height(ppm *img){
  return img->height;
}

void write_image(ppm *image, char *file_name){
  FILE *image_file;
  unsigned int **pixel_table = image->pixels;
  unsigned int r, g, b;
  int x, y;
  char width_height_str[50];

  image_file = fopen(file_name, "wb");  

  /* Write PPM header: */
  fputs("P6\n", image_file); 
  sprintf(width_height_str, "%d %d\n", image->width, image->height);
  fputs(width_height_str, image_file);
  fputs("255\n", image_file);

  /* Write pixels: */
  for(y = 0; y < image->height; y++)
    for (x = 0; x < image->width; x++){
         r = (pixel_table[x][y] >> 16) & 0xff;
         g = (pixel_table[x][y] >> 8) & 0xff;
         b = pixel_table[x][y] & 0xff;
         fputc(r, image_file);  fputc(g, image_file); fputc(b, image_file);
    }

  fclose(image_file);
}

int blank_char(int ch){
  return (ch == 32 || ch == 10 || ch == 12);
}

ppm *read_image(char *file_name){
  ppm *the_image = malloc(sizeof(ppm));     /* Allocate the ppm struct: */
  FILE *image_file;
  unsigned int **image;

  int ch, ch1, ch2, red, green, blue,
      width, height, pixel_depth,  x, y;

  image_file = fopen(file_name, "rb");  

  /* Get two first chars - expected 'P6': */
  ch1 = fgetc(image_file); ch2 = fgetc(image_file); 

  if (ch1 == 'P' && ch2 == '6'){
    fscanf(image_file, " %d", &width);  fscanf(image_file, " %d", &height);
    fscanf(image_file, " %d", &pixel_depth);

    if (pixel_depth == 255){
      the_image->width = width;
      the_image->height = height;
      the_image->pixels = alloc_image(width,height);      
      image = the_image->pixels;

      /* Read blank stuff before image: */
      while (blank_char(ch = fgetc(image_file))); 
      ungetc(ch, image_file);

      /* Read the image bytes */
      for(y = 0; y < height; y++)
        for (x = 0; x < width ; x++){
          red = fgetc(image_file); green = fgetc(image_file); blue = fgetc(image_file);
          image[x][y] = (red << 16) | (green << 8) | blue;
        }

      return the_image;
     }
     else {
       printf("Unsupported pixel depth. Bye.");
       exit(EXIT_FAILURE);
    }
  }
  else {
    printf("The image file does not seem to be PPM, P6 file. Bye.");
    exit(EXIT_FAILURE);
  }
}

Henvisning

Program: Et simpelt program der genererer og udskriver et PPM billede.
// Diagonal lines

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

int main(void) {

  ppm *img = make_image(500, 500,  make_pixel(255U, 255U, 255U));  // white
  int i;

  pixel black_pixel = make_pixel(0U, 0U, 0U);

  for(i = 0; i < 500; i++){
    set_pixel(img, i, i, black_pixel);
    set_pixel(img, 500 - i - 1, i, black_pixel);
  }

  write_image(img, "img2.pnm");

  return 0;
}

Program: Makefilen som styrer oversættelsen af programmerne.
CC = gcc

prog: ppm.o pixel.o
	$(CC) -lm prog.c pixel.o ppm.o -o prog

ppm.o: ppm.h ppm.c
	$(CC) -c ppm.c

pixel.o: pixel.c pixel.c
	$(CC) -c pixel.c

Henvisning

Opgave 4.11. Flere PPM funktioner

I denne opgave vil vi - trin for trin - tilføje nye nyttige funktioner, som arbejder på PPM billeder. De første er forholdsvis simple. De senere er lidt mere udfordrende. I en tidligere opgave genererede vi PPM billeder i en dobbelt for-løkke. I denne opgave er billedet tilgængelig i en passende datastruktur, som vi dog ikke har behov for at gå i dybden med på nuværende tidspunkt.

Arbejdet vil basere sig på nogle allerede programmerede funktioner, som kan aflæse og sætte én pixel i et PPM billede. Du kan også tænke på en pixel som en farve.

Der findes en zip-fil, som indeholder alt det nødvendige for at komme igang. Du kan også tilgå de enkelte filer. README filen giver anvisninger på hvordan du kan compilere programmerne.

Nogle af jer har allerede lavet lidt forarbejde, nemlig funktioner som arbejder med RGB pixels.

Det anbefales først at læse ppm.h og det simple eksempel på anvendelse af nogle af funktionerne fra ppm.h. Denne anvendelse genererer dette billede.

Programmer nu følgende:

  • En funktion der tegner en vandret linie med en bestemt farve i et ppm billede.
  • En funktion der tegner en lodret linie med en bestemt farve.
  • Lav varianter af ovenstående som også tilbyder tegning af vandrette/lodrette linier med en bestemt tykkelse.
  • Lav evt. varianter af ovenstående som tilbyder tegning af vandrette/lodrette stiplede linier.
  • En funktion der tegner en ikke-udfyldt rektangel (parallel med akserne) med en bestemt farve.
  • En funktion der tegner en udfyldt rektangel (parallel med akserne) med en bestemt farve.
  • En funktion der tegner en vilkårlig linie med en bestemt farve. Eksperimenter med denne funktion - og find selv frem til udfordringen!
  • En funktion der tegner en cirkel med en bestemt radius, og med en bestemt farve - ikke-udfyldt, dernæst udfyldt.

Arbejdet kan fortsættes med endnu flere primitiver, i forskellige retninger. Tegning af stiplede linier er en mulighed. Eller indnu mere ambitiøst, indsættelse af tegn (fra en bestemt font) i et ppm billede.


Samlede referencer
Indhold Stikord
Tidligere udgave med problemløsning i main
Senere udgave med input parametre
Notes about GCC
Regler for parameteroverførsel
Tidligere udgave uden parametre
Senere udgave med output parametre
Programmet der beregner antal dage i en måned
Programmet der beregner største fælles divisor
Aktuelle og formelle parametre
Operator tabellen for C
Tidligere version med input parametre
Rekursive delproblemer
Den iterative funktion findRootBetween
Det oprindelige rodsøgningsprogram
Læs om pointere til funktioner
Doxygen dokumentation af ppm.h
Billedet genereret af programmet

 

Kapitel 4: Funktioner
Kursets hjemmeside     Forfatteren's hjemmeside     Om frembringelsen af disse sider     Forrige lektion (top)     Næste lektion (top)     Forrige lektion (bund)     Næste lektion (bund)     
Genereret: 6. november 2012, 16:28:07