Punti in un piano cartesiano

Supponiamo di voler rappresentare dei punti di un piano cartesiano. Come è noto, ogni punto si può rappresentare come una coppia di numeri reali, che danno le due coordinate che individuano un punto. Per ogni punto ci servono quindi due variabili. Dal momento che si tratta di due dati che riguardano lo stesso oggetto, è ragionevole usare una struttura:

struct Punto {
  float x;
  float y;
};

typedef struct Punto TipoPunto;

Queste due definizioni fanno sí che si possa dichiarare una variabile di tipo TipoPunto. Queta variabile risulta composta da due reali, che usiamo per memorizzare le coordinate x e y del punto. Il vantaggio è che è ora possibile rappresentare un punto come una singola variabile di tipo struttura, invece di avere due variabili indipendenti reali.

Per dichiarare una variabile come un punto, usiamo la solita sintassi della dichiarazione di variabile, mettendo TipoPunto come tipo, ossia:

TipoPunto e;

Questa dicharazione dice che la variabile e rappresenta un punto. In particolare, le coordinate sono memorizzate nei due campi e.x ed e.y. Quindi, per memorizzare il punto che ha coordinate 1.2 e -2.2 in questa variabile, l'istruzione da usare è:

e.x=1.2;
e.y=-2.2;

Vediamo ora un piccolo esempio. Supponiamo di volere una funzione che calcola la distanza fra due punti. Questa funzione ritorna un numero reale. Per quello che riguarda i suoi parametri, abbiamo due alternative: o passiamo quattro parametri (due coordinate per ogni punto), oppure passiamo solo due parametri, ognuno dei quali è la struttura che contiene uno dei due punti.

La prima soluzione è:

float distanza(int x1, int y1, int x2, int y2) {
  float d;

  d=sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));

  return d;
}

La seconda soluzione consiste nel passare due strutture. Le coordinate dei due punti si ottengono semplicemente prendendo i due campi di ognuna di queste strutture.

float distanza(TipoPunto a, TipoPunto b) {
  float d;

  d = sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));

  return d;
}

Il vantaggio di questa seconda soluzione è evidente: ogni volta che si deve calcolare la distanza fra due punti, basta passare alla funzione i due punti, ossia le due variabili struttura che rappresentano i punti. Questo semplifica il codice e riduce la probabilità di commettere errori.

Il seguente programma punti.c è un esempio di quello che si è detto sopra: si definisce la struttura per rappresentare i punti, si definisce una funzione che calcola la distanza, e la si usa per calcolare la distanza fra due punti.

/*
  Definizione del tipo punto,
funzione che calcola la distanza.
*/

#include<stdlib.h>
#include<math.h>


/* definizione di struttura e tipo */

struct Punto {
  float x;
  float y;
};

typedef struct Punto TipoPunto;


/* distanza fra due punti */

float distanza(TipoPunto a, TipoPunto b) {
  float d;

  d = sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));

  return d;
}


/* main */

int main() {
  TipoPunto a, b;

		/* assegna valori al primo punto */
  a.x=0.324;
  a.y=1.34;

		/* assegna valori al secondo punto */
  b.x=0.0;
  b.y=0.0;

  printf("Distanza=%f\n", distanza(a, b));

  return 0;
}


Perimetro

Vediamo ora un altro esempio di uso della struttura punto. Supponiamo di avere un file di testo in cui sono memorizzate le coordinate di un insieme di punti, che possono essere al massimo cento. In particolare, su ogni linea ci sono due numeri reali, che rappresentano le coordinate di un punto. Questa sequenza di punti rappresenta una figura piana chiusa. Quello che vogliamo calcolare è il suo perimetro. Si noti che l'ultimo punto del file è da considerarsi unito al primo da un segmento.

Per prima cosa, analizziamo i dati del problema: abbiamo una sequenza di punti, per cui ci può sicuramente essere utile la definizione della struttura punto. Dal momento che il file contiene una intera sequenza di punti, possiamo leggere tutta la sequenza in memoria. Una sequenza di elementi si può rappresentare come un vettore. Dal momento che ogni elemento è un punto, usiamo un vettore di punti, ossia un vettore di strutture. La dichiarazione del vettore che usiamo per rappresentare l'insieme dei punti è:

  TipoPunto vett[100];

Nel dare questa definizione usiamo l'ipotesi che nel file ci siano al massimo cento punti. Se cosí non fosse, sarebbe stato necessario usare un array dinamico.

La prima cosa da fare è aprire il file, e da questo leggere i punti e memorizzarli nell'array di strutture. Questa prima parte non presenta difficoltà: si tratta di leggere un array da file, e quindi è analogo al programma di lettura di un array di interi da file. L'unica differenza è che il file contiene i dati di strutture invece che interi. Quindi, la lettura del singolo elemento non è più res=fscanf(fd, "%d", &vett[n]); (che legge un intero da file). Dal momento che la struttura è composta da due reali, possiamo usare, per leggere il singolo elemento del vettore:

    res=fscanf(fd, "%f %f", &vett[i].x, &vett[i].y);

Il resto del codice di lettura è uguale a quello del programma di lettura di array di interi da file. È possibile quindi usare un ciclo for per scandire gli elementi dell'array, oppure usare un ciclo while. Il valore di ritorno della funzione fscanf viene usato per capire quando il file è finito, ma può anche venire usato per capire se ci sono stati errori nella lettura (la cosa può avvenire per esempio se il file non è composto da coppie di reali, ma contiene per esempio dei caratteri che non si possono interpretare come numeri).

La parte più interessante del programma è il calcolo del perimetro. Dal momento che dobbiamo calcolare, per ogni coppia di punti consecutivi, la loro distanza, usiamo la funzione distanza definita nel programma precedente punti.c.

L'algoritmo che usiamo è il seguente: per ogni punto del vettore, calcoliamo la distanza fra questo punto e il successivo. Tutte queste distanze vengono sommate via via a una variabile che usiamo per memorizzare la somma parziale.

Ci sono ovviamente due cose da tenere in consderazione: la prima è che l'ultimo punto non ha un punto successivo nell'array. In altre parole, se n è la dimensione dell'array, allora non possiamo calcolare la distanza fra l'ultimo punto vett[n-1] e il successivo vett[n], semplicemente perchè quest'ultimo non è definito (non è stato letto da file).

Usiamo quindi il seguente procedimento: per tutti i punti tranne l'ultimo, sommiamo la distanza fra il punto e il successivo. Alla fine del ciclo di somma, calcoliamo la distanza fra l'ultimo punto e il primo, e la aggiungiamo alla somma. Questo perchè abbiamo detto che la figura memorizzata su file va considerata chiusa, ossia l'ultimo punto è collegato al primo.

Il risultato di questo ragionamento è che il ciclo sull'array va fatto non su tutti gli indici che contengono un elemento significativo (che sono quelli da 0 a n-1) ma su tutti tranne l'ultimo elemento significativo, ossia si deve fare un ciclo in cui l'indice va da 0 a n-2 (penultimo elemento significativo). Ad ogni passo si somma la distanza fra un punto e il successivo.

Il programma completo perimetro.c è qui sotto.

/*
  Definizione del tipo punto, lettura di
una sequenza di punti da file e memorizzazione
in un array, calcolo del perimetro della
figura chiusa definita dalla sequenza di punti.
*/

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


/* definizione di struttura e tipo */

struct Punto {
  float x;
  float y;
};

typedef struct Punto TipoPunto;


/* funzione distanza */

float distanza(TipoPunto a, TipoPunto b) {
  float d;

  d = sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));

  return d;
}


/* main */

int main() {
  FILE *fd;
  int res;

  TipoPunto vett[100];
  int n;
  int i;
  float p;


			/* apre il file */
  fd=fopen("punti.txt", "r");
  if( fd==NULL ) {
    perror("Errore in apertura del file");
    exit(1);
  }


			/* lettura dei punti */
  for(i=0; i<=99; i++) {
    res=fscanf(fd, "%f %f", &vett[i].x, &vett[i].y);
    if( res!=2 )
      break;
  }
  n=i;


			/* calcolo del perimentro */
  p=0;
  for(i=0; i<=n-1-1; i++) 
    p+=distanza(vett[i], vett[i+1]);

  p+=distanza(vett[n-1], vett[0]);


			/* stampa il perimetro */
  printf("Perimetro=%f\n", p);

  return 0;
}


Punti allineati

Un altro esercizio che può essere interessante risolvere è quello di scrivere una funzione che dice se tre punti su un piano cartesiano sono allineati.


Punto mediano

Il punto mediano fra due altri punti è quello che si trova esattamente in mezzo. Il calcolo delle coordinate del punto mediano si ottiene semplicemente facendo il calcolo della media fra le coordinate dei due punti.

La cosa interessante del programma risolutivo mediano.c è il fatto che una funzione può ritornare come risultato una variabile che è definita come una struttura. Questo si ottiene semplicemente usando il tipo struttura come tipo di ritorno della funzione, ossia si dichiara la funzione come: TipoPunto mediano(TipoPunto a, TipoPunto b) {...

/*
  Trova il punto mediano fra altri
due punti. La funzione che effettua
questo calcolo deve ritornare una
struttura punto.
*/

#include<stdlib.h>
#include<math.h>


/* definizione di struttura e tipo */

struct Punto {
  float x;
  float y;
};

typedef struct Punto TipoPunto;


/* punto mediano */

TipoPunto mediano(TipoPunto a, TipoPunto b) {
  TipoPunto m;

  m.x=(a.x+b.x)/2;
  m.y=(a.y+b.y)/2;

  return m;
}


/* main */

int main() {
  TipoPunto a, b, c;

		/* assegna valori al primo punto */
  a.x=0.324;
  a.y=1.34;

		/* assegna valori al secondo punto */
  b.x=0.0;
  b.y=0.0;


		/* trova il punto mediano */
  c=mediano(a, b);


		/* stampa le sue coordinate */
  printf("Punto mediano: (%f,%f)\n", c.x, c.y);

  return 0;
}