[WikiDyd] [TitleIndex] [WordIndex

Materiały zostały opracowane w ramach realizacji Programu Rozwojowego Politechniki Warszawskiej.

http://prpw.iem.pw.edu.pl/images/KAPITAL_LUDZKI.gif http://prpw.iem.pw.edu.pl/images/EU+EFS_P-kolor.gif

http://www.pr.pw.edu.pl/ jest projektem współfinansowanym przez Unię Europejską w ramach Europejskiego Funduszu społecznego (działanie 4.1.1 Programu Operacyjnego Kapitał Ludzki) i ma na celu poprawę jakości kształcenia oraz dostosowanie oferty dydaktycznej Politechniki Warszawskiej do potrzeb rynku pracy. Będzie on realizowany przez Uczelnię w latach 2008-2015.


Laboratorium metodyki programowania

Ćwiczenie 3: Wskaźniki

Scenariusz

  1. Student loguje się do systemu LOP (Laboratorium Otwartego Programowania).
  2. Student przechodzi do katalogu roboczego dla zajęć LMP.
  3. Student pobiera z repozytorium kod programów.
  4. Po wyjaśnieniach prowadzącego student przystępuje do kompilacji i uruchomienia programów. Porównuje wynik działania z kodem źródłowym.
  5. Student modyfikuje programy według wskazówek prowadzącego (patrz opis szczegółowy). Powtarza próbę kompilacji i uruchomienia.
  6. Ćwiczenie zaliczeniowe: student rozszerza funkcjonalność programu według zamówienia prowadzącego.

Opis szczegółowy

W trakcie zajęć nauczymy się podstaw posługiwania się obiektami typu wskaźnik.

Rozpoczęcie pracy

Zajęcia będą prowadzone na komputerze stud.iem.pw.edu.pl. W celu rozpoczęcia pracy należy zalogować się na tym komputerze za pomocą usługi ssh. Można w tym celu wykorzystać albo dostępny w każdym praktycznie systemie Unix/Linux program ssh albo klienta SSH dla systemu Windows - PuTTY. Na maszynach studenckich w IETiSIP PW można podnieść różne dystrybucje Uniksa/Linuksa lub system Windows, ale w każdej z nich jest zainstalowane oprogramowanie ssh.

Do logowania na maszynie stud.iem.pw.edu.pl należy użyć takiego samego loginu i hasła jakie wykorzystywane sa do dostępu do usług wydziałowych (poczta, e-dziekanat, itd.).

Po zalogowaniu się należy przejść do utworzonego na pierwszych zajęciach katalogu lmp:

 cd lmp

Kod do zajęć

lmp3.tgz

Praca nad kodem

Pierwszy program

Pierwszy program (p1.c) to w przede wszystkim prościutka funkcja main demonstrująca podstawowe operacje na wskaźnikach: inicjalizacja adresem już istniejącego obiektu, inkrementacja, dodawanie liczny całkowitej i odejmowanie wskaźników. Oprócz niej w programie umieszczono zmienioną implementację znanej z poprzednich zajęć funkcji pwekt (wypisującej wektor liczb double na stdout). Tym razem funkcja traktuje swój pierwszy argument jako wskaźnik na obiekt double i wypisuje n kolejnych obiektów (rozpoczynając od wskazywanego).

Oto program p1.c:

   1 #include <stdio.h>
   2 
   3 void pwekt(double *v, int n)
   4 {
   5         printf("[ ");
   6         while (n--) {
   7                 printf("%g ", *v);
   8                 v++;
   9         }
  10         printf("]");
  11 }
  12 
  13 int main()
  14 {
  15         double          v[] = {10, 20, 30, 1.1, 2.2, 3.3};
  16         double         *pv;
  17 
  18                                 printf( "Wektor: " );
  19                                 pwekt( v, sizeof v / sizeof v[0] );
  20                                 printf( "\n" );
  21 
  22         pv = &v[0];
  23         printf("Pierwszy element: %g\n", *pv);
  24 
  25         pv = v;
  26         printf("Pierwszy element: %g\n", *pv);
  27 
  28         pv++;
  29         printf("Drugi element: %g\n", *pv);
  30 
  31         pv = v + 4;
  32         printf("Piaty element: %g\n", *pv);
  33 
  34         printf("Odleglosc poimiedzy piatym a pierwszym elementem: %ld\n", pv - v);
  35 
  36         return 0;
  37 }

Oto wynik kompilacji i uruchomienia:

volt:~/lmp/lmp3/gr1> cc -Wall -ansi -pedantic p1.c
volt:~/lmp/lmp3/gr1> ./a.out
Wektor: [ 10 20 30 1.1 2.2 3.3 ]
Pierwszy element: 10
Pierwszy element: 10
Drugi element: 20
Piaty element: 2.2
Odleglosc poimiedzy piatym a pierwszym elementem: 4
volt:~/lmp/lmp3/gr1>

Ćwiczenia:

Drugi program

Drugi prosty program pokazuje, że zastosowanie wskaźników pozwala nam modyfikować wartości obiektów zewnętrznych - przekazanie jako argumentu wskaźnika na zmienną, zamiast samej zmiennej, pozwala funkcji zmodyfikować wartość zmiennej (p2.c):

   1 #include <stdio.h>
   2 
   3 void usun_ujemne(double *v, int *n)
   4 {                                  
   5         /* Usuwa ujemne elementy z wektora v */
   6         double         *u = v;
   7         int             l = *n;
   8         while (l--) {
   9                 if (*v >= 0)    /* jeśli element jest dodatni */
  10                         *u++ = *v;      /* to zapamiętaj go w "nowym"
  11                                          * wektorze */
  12                 else
  13                         (*n)--; /* w przeciwnym razie zmniejsz 
  14                                  * wartosc wskazywana przez n */
  15                 v++;
  16         }
  17 }
  18 
  19 void pwekt(double *v, int n)
  20 {
  21         printf("[ ");
  22         while (n--) {
  23                 printf("%g ", *v);
  24                 v++;
  25         }
  26         printf("]");
  27 }
  28 
  29 int main()
  30 {
  31         double          v[] = {-10, 0, 10, -20, 30, 1.1, -2.2, -3.3, 300.003, 1024.5};
  32         int             n = sizeof v / sizeof v[0];
  33 
  34         printf("Wektor: ");
  35         pwekt(v, n);
  36         printf(" (%d elementow)\n", n);
  37 
  38         usun_ujemne(v, &n);
  39 
  40         printf("Wektor po usunieciu ujemnych elementow: ");
  41         pwekt(v, n);
  42         printf(" (%d elementow)\n", n);
  43 
  44         return 0;
  45 }

Ćwiczenie:

Trzeci program

Trzeci program pokazuje wykorzystanie wskaźników na funkcje. Po wywołaniu tworzy tablicę wartości wybranej przez użytkownika funkcji w zadanym przedziale z zadaną gęstością. Zasadnicze zadanie wykonywane jest przez funkcję tabelka, która jako pierwszy argument (f) otrzymuje wskaźnik do funkcji, którą ma "tablicować". Określenie argumentów aktualnych dla funkcji tabelka ma miejsce wewnątrz main, gdzie wykorzystywane są dwie powiązane tablice: nazwy - zawierająca nazwy funkcji i funkcje - zawierająca wskaźniki do funkcji. Związek pomiędzy tablicami nie jest w żaden sposób sformalizowany - twórca programu musi pilnować, aby poszczególne elementy obu wektorów były ze sobą zgodne.

Funkcja jest wybierana przez użytkownika programu, który w momencie wywołania podaje jako pierwszy argument nazwę funkcji. Ten pierwszy argument wywołania programu jest obligatoryjny; jako argumenty opcjonalne można jeszcze podać początek i koniec przedziału, w którym należy obliczyć wartości funkcji, liczbę wartości do obliczenia oraz nazwę pliku, do którego mają być zapisane wyniki. Wywołany bez argumentów program "przedstawi się", podając krótką instrukcję użycia.

Oto kod (p3.c):

   1 #include <stdio.h>
   2 #include <stdlib.h>
   3 #include <math.h>
   4 #include <string.h>
   5 
   6 void tabelka(double (*f) (double), double a, double b, int n, FILE * out){
   7         int             i;
   8         double          dx = (b - a) / (n - 1);
   9 
  10         for (i = 0; i < n; i++)
  11                 fprintf(out, "%g %g\n", a + i * dx, f(a + i * dx));
  12 }
  13 
  14 double moja(double x)
  15 {
  16         return 3 * x * x + 2 * x + 1;
  17 }
  18 
  19 int main(int argc, char **argv)
  20 {
  21         char           *fun_name = argc > 1 ? argv[1] : "";
  22         double          a = argc > 2 ? atof(argv[2]) : 0.;
  23         double          b = argc > 3 ? atof(argv[3]) : 1.;
  24         int             n = argc > 4 ? atoi(argv[4]) : 50;
  25 
  26         int             i;
  27 
  28         FILE           *out = argc > 5 ? fopen(argv[5], "w") : stdout;
  29 
  30         double          (*wybrana_fun) (double)= NULL;
  31 
  32         double          (*funkcje[5]) (double)= {cos, sin, tan, moja, atan};
  33         char           *nazwy[5] = {"cos", "sin", "tan", "moja", "atan"};
  34 
  35         if (argc < 2) {
  36                 printf("\n%s: tablicuje wybrana funkcje w zadanym przedziale.\n", argv[0]);
  37                 printf("\n\tWywolanie:\n"
  38                        "\t\t%s <nazwa-funkcji> [ <start> [ <stop> [ <n> [ <plik> ] ] ] ]\n\n", argv[0]);
  39                 printf("\tGdzie:\n"
  40                        "\t\t<nazwa-funkcji>:");
  41                 for (i = 0; i < sizeof nazwy / sizeof nazwy[0]; i++)
  42                         printf("\t%s", nazwy[i]);
  43                 printf("\n");
  44                 printf("\t\t<start>-<stop>:  \tprzedzial - domyslnie <0,1>\n"
  45                      "\t\t<n>:             \tliczba krokow - domyslnie 50\n"
  46                  "\t\t<plik>:          \twyniki - domyslnie do stdout\n\n");
  47                 return 0;
  48         }
  49         for (i = 0; i < sizeof nazwy / sizeof nazwy[0]; i++)
  50                 if (strcmp(nazwy[i], fun_name) == 0)
  51                         wybrana_fun = funkcje[i];
  52 
  53         if (wybrana_fun != NULL)
  54                 tabelka(wybrana_fun, a, b, n, out);
  55         else
  56                 fprintf(stderr, "Nie znam funkcji \"%s\"\n", fun_name);
  57 
  58         return 0;
  59 }

A tu kompilacja i przykłady wywołania:

volt:~/lmp/lmp3/gr1> cc p3.c -lm -o tab
volt:~/lmp/lmp3/gr1> ./tab

./tab: tablicuje wybrana funkcje w zadanym przedziale.

        Wywolanie:
                ./tab <nazwa-funkcji> [ <start> [ <stop> [ <n> [ <plik> ] ] ] ]

        Gdzie:
                <nazwa-funkcji>:        cos     sin     tan     moja    atan
                <start>-<stop>:         przedzial - domyslnie <0,1>
                <n>:                    liczba krokow - domyslnie 50
                <plik>:                 wyniki - domyslnie do stdout

volt:~/lmp/lmp3/gr1> ./tab sin 0 3.14159265 5
0 0
0.785398 0.707107
1.5708 1
2.35619 0.707107
3.14159 3.58979e-09
volt:~/lmp/lmp3/gr1> ./tab cos 0 3.1415926 5
0 1
0.785398 0.707107
1.5708 2.67949e-08
2.35619 -0.707107
3.14159 -1
volt:~/lmp/lmp3/gr1> ./tab moja 0 10 11
0 1
1 6
2 17
3 34
4 57
5 86
6 121
7 162
8 209
9 262
10 321
volt:~/lmp/lmp3/gr1>

Ćwiczenie:

Program na zaliczenie

Teoria:

Uwaga: poniższy opis jest może troszkę trudny, bo brak w nim rysunków. Zachęcamy do obejrzenia ich w dowolnej książce dotyczącej metod numerycznych, albo w wikipedii, albo w witrynie www.algorytmy.org.

Całkowanie numeryczne (proste metody Newtona-Cotesa):

Zasada ogólna: Dana jest funkcja f(x) i przedział <a,b>. Przedział całkowania dzielimy równomiernie na n pod-przedziałów o długości d każdy: d = (b-a) /n. Otrzymujemy ciąg punktów {x_0, x_1, .... x_n }, gdzie x_0 = a, x_1 = a+d, ... x_n = b.

Metoda prostokątów:

Całka oznaczona z f(x) w przedziale <a,b> obliczana jest w sposób przybliżony jako suma pól prostokątów o szerokości d każdy (poza pierwszym i ostatnim, które mają szerokość d/2) i wysokościach wyznaczanych przez kolejne wartości funkcji:

f(x_0)*d/2 + f(x_1)*d + f(x_2)*d + .... + f(x_(n-1))*d + f(x_n)*d/2

Błąd w tej metodzie szacuje się jako: |R| = | -(b-a)/24*d^2*f"(x) | gdzie f"(x) oznacza maksymalną wartość drugiej pochodnej funkcji podcałkowej f w przedziale <a,b>.

Metoda trapezów:

Całka obliczana jest w sposób przybliżony jako suma pól trapezów wyznaczanych przez kolejne pary punktów:

( f(x_0) + f(x_1) ) /2 * d + ( f(x_1) + f(x_2) ) /2 * d + .....

Błąd w tej metodzie szacuje się jako: |R| = | -(b-a)/12*d^2*f"(x) | gdzie f"(x) oznacza maksymalną wartość drugiej pochodnej funkcji podcałkowej f w przedziale <a,b>.

Metoda Simpsona (parabol):

Całka obliczana jest w sposób przybliżony jako suma pól pod parabolami określanymi przez kolejne trójki punktów:

( f(x_0) + 4*f(x_1) + f(x_2)) /3 * d + ( f(x_2) + 4*f(x_3) + f(x_4) ) /3 * d + .....

Błąd w tej metodzie szacuje się jako: |R| = | -(b-a)/180*d^4*f""(x) | gdzie f""(x) oznacza maksymalną wartość czwartej pochodnej funkcji podcałkowej f w przedziale <a,b>.

Zadanie:

Oto program testujący napisaną funkcję (test.c):

   1 /** Program testujacy dla funkcji calkujacej met. trapezow
   2     jstar@iem.pw.edu.pl 15.02.2003-10.01.2010
   3 */
   4 
   5 #include <stdio.h>
   6 #include <stdlib.h>
   7 #include <math.h>
   8 
   9 #include "calnum.h"   /* naglowek funkcji calkujacej */
  10 
  11 double f( double x ) { /* przykladowa funkcja testowa */
  12   return x*sin(x);
  13 }
  14 
  15 int main( int argc, char **argv ) {
  16   double a= argc > 1 ? atof( argv[1] ) : 0;    /* pocz. przedzialu calkowania */
  17   double b= argc > 2 ? atof( argv[2] ) : M_PI; /* koniec przedzialu calkowania, M_PI jest zdef. w math.h */
  18   int n   = argc > 3 ? atoi( argv[3] ) : 1000; /* liczba krokow calkowania */
  19 
  20    printf( "Int <%g,%g>[%i] ( x*sin(x) ) = %g\n", a, b, n, calnum( f, a, b, n ) );
  21 
  22   return 0;
  23 }

Plik nagłówkowy (calnum.h) z prototypem funkcji calnum:

   1 /** Prototyp funcji calkujacej numerycznie f. jednej zmiennej.
   2  *  f to funkcja podcalkowa
   3  *  <a,b> to przedzial calkowania
   4  *  n to liczba podprzedzialow (posrednio okresla dokladnosc) */
   5 double calnum ( double (*f)( double ), double a, double b, int n );

Celem będzie napisanie pliku calnum.c zawierającego funkcję calnum implementującą jedną z opisanych wyżej metod (wybraną przez prowadzącego), a następnie przetestowanie programu na zadanej przez prowadzącego funkcji podcałkowej.


To już prawie wszystko, ale nie wolno nam zapomnieć, że

materiały zostały opracowane w ramach realizacji Programu Rozwojowego Politechniki Warszawskiej.

http://prpw.iem.pw.edu.pl/images/KAPITAL_LUDZKI.gif http://prpw.iem.pw.edu.pl/images/EU+EFS_P-kolor.gif

http://www.pr.pw.edu.pl/ jest projektem współfinansowanym przez Unię Europejską w ramach Europejskiego Funduszu społecznego (działanie 4.1.1 Programu Operacyjnego Kapitał Ludzki) i ma na celu poprawę jakości kształcenia oraz dostosowanie oferty dydaktycznej Politechniki Warszawskiej do potrzeb rynku pracy. Będzie on realizowany przez Uczelnię w latach 2008-2015.


2015-09-23 06:44