Jacek Matulewski
Programowanie I - Q&A
(wykład dla kierunku kognitywistyka)

  1. Organizacyjne
  2. Visual Studio
    • Pyt.: W Visual Studio 2022 po utworzeniu projektu pojawia się tylko jedna linia z poleceniem Console.WriteLine("Hello, World") zamiast widocznej na filmach klasy Program z metodą Main.
      Odp.: W nowych wersjach języka C# jest możliwe używanie tzw. poleceń najwyższego poziomu, które nie wymagają klasy lub metody i są wykonywane zamiast zawartości statycznej metody Main w klasie Program. Aby uniknąć ich użycia w nowym projekcie i zachować zgodność z tym, co pokazuję w filmach, należy w kreatorze projektu aplikacji konsolowej zaznaczyć opcję "Nie używaj instrukcji najwyższego poziomu" (zob. rysunek poniżej).
    • Pyt.: Faktycznie pojawiło się więcej linii z kodem, ale dalej nie są identyczne z tymi na wideo. Nie ma using System, zamiast class jest internal class.
      Odp.: Pierwsza różnica wynika ze zmiany szablonu projektu aplikacji konsolowej w nowszych wersjach Visual Studio. Różnica między internal class a class nie będzie ważna w opisywanych projektach (znaczenie modyfikatorów zakresu dostępu zostanie wyjaśniona w dalszej części kursu). Modyfikator internal można po prostu usunąć lub zmienić na public. Natomiast brak polecenia using związany jest z tym, że Visual Studio 2022 przejmuje na siebie część obowiązków związanych z zarządzaniem dostępem do przestrzeni nazw. Można się brakiem tej linii po prostu nie przejmować, ale można ją również po prostu samodzielnie dopisać.
  3. Typy i zmienne
    • zad. 1, "W zadaniu dotyczącym równania kwadratowego zignorowaliśmy sytuację, w której współczynnik a równy jest zeru..."
      Pyt.: Po uruchomieniu programu, do współczynnika a przypisałem wartość 0, jednak nie wyświetlił się żaden błąd. Nie jestem pewien, czy to, że nie pojawiła się informacja o błędzie w konsoli to informacja dobra, bo nie zmieniałem nic z wyróżnikiem, natomiast przepisałem wszystko tak jak było w przykładzie podanym na ćwiczeniach.

      Odp.: Rzeczywiście nie pojawi się wyjątek, który przerwałby działanie programu (to dlatego, że liczby typu double można dzielić przez zero - wynikiem jest Infinity), jednak wyświetlane wyniki nie są poprawne (zamiast liczby pojawia się znak zapytania lub NaN). Błąd więc jest. Krótko mówiąc, Pana kod działa tak, jak należy się spodziewać tj. w sytuacji, gdy a = 0 nie oblicza poprawnych wyników.
    • fragmentu filmu "Typy i zmienne" od 42:52 do 43:37
      Pyt.: W tym fragmencie filmu przedstawiona jest odpowiedź na pytanie o możliwość przypisania do zmiennej o zadeklarowanym typie double liczbę typu int oraz czy zmienna ta zmieni wówczas swój typ. Odpowiadając na pierwszą część pytania wytłumaczył Pan, że jest możliwe takie przypisanie dzięki rzutowaniu niejawnemu. W drugiej zaś części podane jest, iż w przypadku
      (sytuacji odwrotnej tj. rzutowania int na double, choć w filmie nie było to jasno powiedziane - dop. JM):
      double d = 2.0;
      int i = (int)d;
      
      zmienna d nie zmieni swojego typu, jednak wartość tej zmiennej jest rzutowana na typ całkowity i przypisywana do zmiennej i. Podkreślił Pan również, że takie przypisanie jest możliwe tylko w przypadku rzutowania jawnego. Bardzo długo zastanawiałam się nad tym wyjaśnieniem i nie jestem pewna czy rozumiem dlaczego w pierwszej części pytania mówimy o rzutowaniu niejawnym, a w drugim o rzutowaniu jawnym. Oczywiście wiem, w jaki sposób obie konwersje wyglądają i kiedy należy je zastosować, jednak tutaj naszły mnie wątpliwości. Czy biorąc pod uwagę (zgodnie z pierwszą częścią odpowiedzi), iż do zmiennej typu double można przypisać liczbę typu int w sposób niejawny, czyli np.: double x = 5; to czy nie oznacza to, że zmienna ta nie zmienia swojego typu przez wzgląd na to, iż jest rzutowana? Mam tu na myśli, czy rzutowanie niejawne sprawia, że typ zmiennej zostaje zachowany, a jedynie jej wartość jest konwertowana na typ całkowity i przypisana do zmiennej int? Gdyby tak było, to skąd wynika pojawienie się konwersji jawnej w drugiej części Pana odpowiedzi na pytanie w wykładzie?
      Odp.: W tym kawałku filmu niewyraźnie rozdzieliłem dwie sytuacje: rzutowania double->int i int->double. Pierwsze rzutowanie musi być jawne, podczas gdy drugie może być niejawne. Ponieważ te typy są strukturami, przy przypisaniu zawsze następuje klonowanie - tylko wartość jest kopiowana z oryginalnej zmiennej do zmiennej, do której przypisujemy tę wartość. A tu dodatkowo następuje zmiana typu kopiowanej wartości. Oryginalna zmienna w żaden sposób się nie zmienia, w szczególności nie zmienia się także jej typ, bez względu na to, czy rzutowanie jest jawne, czy nie. Nieco inna sytuacja jest w przypadku typów referencyjnych (klas), bo w ich przypadku nie ma klonowania, ale o tym będzie na kolejnych filmach.
  4. Instrukcje sterujące
    • zad. 1 "Korzystając z wielokrotnej pętli for i instrukcji Console.Write, wyświetl w konsoli poniższe cztery wzory..."
      Kod do przykładu drugiego:
      for (int j = 5; j <= 8; j++)
      {
          for (int i = j; i > j-5; i--)
          {
              Console.Write(i);
          }
          Console.WriteLine();
      }
      
  5. Tablice
    • zad. 6, Wykorzystując powyższą metodę obliczającą histogram zbioru...
      Pyt.: Czy tablica ma być zainicjowana dla konkretnej czy dla dowolnej liczby elementów?

      Odp.: Tablica może być zainicjowana tylko dla z góry określonej liczby elementów, która nie może zmieniać się po jej utworzeniu. Natomiast w przypadku listy, liczba elementów nie musi być określana w momencie tworzenia i może zmieniać się już po jej utworzeniu. Metoda histogram (sygnatura: static int[] histogram(double[] wartości, int liczbaPrzedziałów)), której należy użyć w tym zadaniu, zwraca tablicę o liczbie elementów równej liczbie przedziałów zadeklarowanych w drugim argumencie. Ten wynik należy zapisać do zmiennej typu int[] (tablica).
    • zad. 7, "Przygotuj metodę ciągFibonacciego, która zapełni..."
      Pyt.: Mam problem ze zrozumieniem ostatniego zdania, czy mogłabym prosić o dokładniejsze wyjaśnienie?

      Odp.: Zdanie, o które chodzi to "Zwróć uwagę, że metoda nie ma zwracać tablicy o wskazanej w argumencie długości, a zapełnić istniejącą tablicę, do której referencja przekazana jest w argumencie.". Proponuję, żeby metoda miała sygnaturę static void ciągFibonacciego(int[] wartości);, czyli żeby przyjmowała jako argument tablicę liczb całkowitych i obliczała tylko tyle wyrazów ciągu Fibonacciego, ile potrzeba do zapełnienia przesłanej tablicy. Ponieważ tablica int[] jest typu referencyjnego, zmienione elementy będą widoczne także po zakończeniu metody, w miejscu jej wywołania.
    • Opis w pliku Histogram.pdf, pierwszy listing.
      Pyt.: Odrabiając zadania z programowania wydaje mi się, że natrafiłam na błąd lub niedopatrzenie w kodzie w ćwiczeniu dotyczącym histogramu. Mianowicie jedna z wartości, którą przyjmuje metoda histogram z góry przyjmuje wartość 11. Odnosi się ona do ilości przedziałów w histogramie. Jednak biorąc pod uwagę, że wartości w tablicy, z której sporządzamy histogram są generowane losowo, nie zawsze liczba przedziałów będzie tyle wynosić. Widać to dobrze gdy generujemy mniejszą liczbę wartości. W środku histogramu pojawiają się zera, tam gdzie ich być nie powinno.

      Odp.: Domyślam się, że chodzi o trzecią linię w metodzie Main z instrukcją int[] histogram = Program.histogram(tablica, 11);? Wartość 11 została tu użyta w wywołaniu metody histogram, a nie w jej definicji. Tym samym, ogólność tej metody nie została ograniczona. Liczba przedziałów w tym miejscu dopasowana jest do problemu dwóch kostek. Możliwe wartości w tym problemie są z zakresu od 2 do 12. To oznacza, że jest 11 a priori możliwych wartości. Jeżeli rzutów jest niewiele, niektóre wartości (szczególnie te mniej prawdopodobne na skrajach przedziału) mogą się nie pojawiać. Zera w histogramie są jak najbardziej akceptowalnymi wartościami - mówią o tym, że pewne wartości nie zostały ani razu wylosowane.
      Ale oczywiście można podejść do tematu bardziej elastycznie i przed utworzeniem histogramu sprawdzić ile rzeczywiście jest wartości w tablicy ("dziur" w rozkładzie bym jednak nie usuwał). W tym celu można uruchomić metodę zakres i użyć uzyskanej z niej wartości, uruchamiając metodę histogram:

      int liczbaWartości = (int)zakres(tablica) + 1; 
      int[] histogram = Program.histogram(tablica, liczbaWartości);
      

      Pyt.: (kontynuacja) Wpisanie wartości 11 w wywołaniu metody, w przypadku gdy ani razu nie wylosuje się liczba 2, powoduje, że wartość 0 w histogramie nie pojawia się na pierwszym miejscu tylko gdzieś w środku. Utrudnia to odczytanie histogramu. Nie wiadomo wtedy, która wartość odpowiada danej liczbie. Czy można temu zapobiec zmieniając coś w metodzie histogram, czy to kwestia tylko podania odpowiedniej liczby w wywołaniu metody?
      Odp.: Sprawdziłem metodę histogram m.in. na zbiorze int[] wartości = new int[]{ 3, 6, 6, 6, 4, 8, 8 };. Metoda histogram sprawdza sama zakres danych (wywołuje metodę zakres) i dlatego w przypadku braku wartości 2, zero na początku będzie pominięte (a nie wstawione w środek). Metoda histogram przygotowana jest dla danych ciągłych (liczb typu double) - my używamy jej z danymi dyskretnymi (liczby typu int). Dlatego, jeżeli zakres jest mniejszy od liczby przedziałów, to przedziały nie będą pokrywały się z liczbami całkowitymi i "w środku" mogą pojawić się dodatkowe zera. Histogram dla liczb całkowitych powinien być inaczej napisany: powinien przyjmować tablicę liczb int[] oraz opcjonalnie zakres histogramu zamiast liczby przedziałów (zakres pozwoli na uwzględnienie zer na skrajach przedziału możliwych wartości).

  6. Przykład: Maszyna Turinga
    • zad. 3, "Zmodyfikuj projekt tak, żeby taśma była nieskończona..."
      Pyt.: Czy spacja ma być rozumiana jako litera alfabetu? Jeżeli nie, to czy napotkanie spacji przez głowicę ma spowodować zakończenie programu (na przykład jako zgłoszony wyjątek)? Czy program do Maszyny Turinga (plik Program.txt) może zawierać komendę "q Bb", która wykona się, gdy program napotka na spację?

      Odp.: W oryginalnym sformułowaniu maszyna Turinga ma nieskończenie długą taśmę. W propozycji z zadania 3, spacja ma być literą alfabetu, a więc może być uwzględniana w rozkazach programu przygotowanego dla maszyny. Specjalne znaczenie spacji polega na tym, że wszystkie spacje z lewego i prawego krańca taśmy są pomijane przy zapisywaniu taśmy do pliku, jak również na tym, że, jeżeli głowica przechodzi poza wczytane z pliku znaki taśmy, program powinien zakładać, że są tam właśnie spacje. Takie użycie spacji nie wyklucza jednak użycia jej w obrębie znaków odczytanych z w pliku.
  7. Testy jednostkowe
      zad. 1, "Zmodyfikuj strukturę Ulamek omawianą na ćwiczeniach..."
      Czy testy jednostkowe mają być przygotowane dla każdej z tych metod, czy tylko dla tych, które są "użyteczne" w strukturze Ułamek?

      Odp.: Myślałem o wszystkich metodach, ale testy dla kolejnych typów liczbowych będą niemal takie same, więc można się ograniczyć do połowy. W przypadku konwersji na typy, które nie są liczbami (np. DateTime) można zgłosić wyjątek i wówczas test powinien sprawdzać, czy wyjątek się pojawia (atrybut ExpectedException).
  8. Wersjonowanie i backup kodu
    • Korekta do tabeli C.1: od marca 2020 usługa GitHub w ramach planu GitHub Free umożliwia hostowanie kodu źródłowego dla zespołów o nieograniczonej liczbie osób (pozostaje ograniczenie 500MB)