Głębokość C++

Samouczek języka C++

Początkowe sekcje tego samouczka obejmują podstawowe materiały przedstawione już w 2 ostatnich modułach i dodatkowe informacje o zaawansowanych zagadnieniach. W tym module skupiliśmy się na pamięci dynamicznej oraz szczegółowych informacjach o obiektach i klasach. Omówiono także niektóre zagadnienia zaawansowane, takie jak dziedziczenie, polimorfizm, szablony, wyjątki i przestrzenie nazw. Omówimy je później w ramach kursu C++ dla zaawansowanych.

Projektowanie obiektów

To doskonały samouczek poświęcony projektowaniu zorientowanemu na obiekt. Zastosujemy metodologię opisaną w tym module.

Wykorzystaj przykład nr 3

W tym module skupimy się na opanowaniu wskaźników, projektowania zorientowanego na obiekty, wielowymiarowych tablic oraz klas/obiektów. Zapoznaj się z tymi przykładami. Nie możemy dostatecznie podkreślić, że podstawą bycia dobrym programistą jest praktyka, ćwiczenie, praktyka.

Ćwiczenie nr 1: Więcej ćwiczeń z wykorzystaniem wskaźników

Jeśli potrzebujesz dodatkowej praktyki ze wskazówkami, przeczytaj ten zasób, w którym omówiono wszystkie aspekty wskaźników i znajdziesz wiele przykładów programów.

Jaki jest wynik tego programu? Nie uruchamiaj programu, ale narysuj obraz pamięci, aby określić dane wyjściowe.

void Unknown(int *p, int num);
void HardToFollow(int *p, int q, int *num);

void Unknown(int *p, int num) {
  int *q;

  q = #
  *p = *q + 2;
  num = 7;
}

void HardToFollow(int *p, int q, int *num) {
  *p = q + *num;
  *num = q;
  num = p;
  p = &q;
  Unknown(num, *p);
}

main() {
  int *q;
  int trouble[3];

  trouble[0] = 1;
  q = &trouble[1];
  *q = 2;
  trouble[2] = 3;

  HardToFollow(q, trouble[0], &trouble[2]);
  Unknown(&trouble[0], *q);

  cout << *q << " " << trouble[0] << " " << trouble[2];
}

Po ręcznym ustaleniu wyników uruchom program, aby sprawdzić, czy są one poprawne.

Ćwiczenie nr 2: Więcej ćwiczeń z wykorzystaniem klas i obiektów

Jeśli potrzebujesz więcej ćwiczeń z klasami i obiektami, tutaj znajdziesz zasób omawiający implementację 2 małych klas. Poświęć trochę czasu na wykonanie ćwiczeń.

Ćwiczenie nr 3: Tablice wielowymiarowe

Rozważ skorzystanie z tego programu: 

const int kStudents = 25;
const int kProblemSets = 10;

// This function returns the highest grade in the Problem Set array.
int get_high_grade(int *a, int cols, int row, int col) {
  int i, j;
  int highgrade = *a;

  for (i = 0; i < row; i++)
    for (j = 0; j < col; j++)
      if (*(a + i * cols + j) > highgrade)  // How does this line work?
        highgrade = *(a + i*cols + j);
  return highgrade;
}

int main() {
 int grades[kStudents][kProblemSets] = {
   {75, 70, 85, 72, 84},
   {85, 92, 93, 96, 86},
   {95, 90, 83, 76, 97},
   {65, 62, 73, 84, 73}
 };
 int std_num = 4;
 int ps_num = 5;
 int highest;

 highest = get_high_grade((int *)grades, kProblemSets, std_num, ps_num);
 cout << "The highest problem set score in the class is " << highest << endl;

 return 0;
}

W programie znajduje się wiersz o nazwie „Jak działa ten wiersz?”. Możesz to rozgryźć? Tutaj znajdziesz wyjaśnienie.

Napisz program, który inicjuje tablicę 3-Dim i wypełnia wartość trzeciego wymiaru sumą wszystkich 3 indeksów. Tutaj znajdziesz nasze rozwiązanie.

Ćwiczenie nr 4: rozbudowany przykład projektowania OO

Oto szczegółowy przykład projektowania zorientowanego na obiekt, który przedstawia cały proces od początku do końca. Ostatni kod jest napisany w języku programowania Java, ale da się go przeczytać, dopóki nie zaszkodzisz.

Poświęć chwilę na zapoznanie się z całym przykładem. Stanowi świetną ilustrację procesu i używających go narzędzi do projektowania.

Testowanie jednostkowe

Wstęp

Testowanie jest kluczowym elementem procesu inżynierii oprogramowania. Test jednostkowy to specjalny rodzaj testu, który sprawdza funkcjonalność pojedynczego, małego modułu kodu źródłowego.Testowanie jednostkowe zawsze wykonuje inżynier i zwykle w tym samym czasie koduje moduł. Sterowniki testowe użyte do przetestowania klas Composer i Database to przykłady testów jednostkowych.

Testy jednostkowe mają następujące cechy: Oni...

  • testowanie komponentu w izolacji;
  • są deterministyczne
  • zazwyczaj mapują się na jedne zajęcia
  • unikaj zależności od zasobów zewnętrznych, np. baz danych, plików, sieci;
  • szybkie wykonywanie
  • można uruchamiać w dowolnej kolejności

Istnieją zautomatyzowane platformy i metodologie, które zapewniają wsparcie i spójność podczas testowania jednostkowego w dużych organizacjach zajmujących się inżynierią oprogramowania. Istnieją zaawansowane metody testowania jednostek typu open source. Omówimy je w dalszej części tej lekcji.

Poniżej znajdziesz testy wykonywane w ramach testowania jednostkowego.

W idealnym świecie sprawdzamy:

  1. Interfejs modułu jest testowany pod kątem prawidłowego przepływu informacji.
  2. Lokalne struktury danych są sprawdzane pod kątem poprawnego przechowywania danych.
  3. Warunki graniczne są testowane, aby upewnić się, że moduł działa prawidłowo na granicach, które ograniczają lub ograniczają przetwarzanie.
  4. Testujemy niezależne ścieżki w module, by mieć pewność, że każda ścieżka, a tym samym każda instrukcja w module, zostanie wykonana co najmniej raz. 
  5. Na koniec musimy sprawdzić, czy błędy są obsługiwane we właściwy sposób.

Pokrycie kodu

Testy nie zapewniają pełnego „zasięgu kodu”. Pokrycie kodu to metoda analizy określająca, które części systemu oprogramowania zostały wykonane (uwzględnione) przez zestaw przypadków testowych, a które nie. Jeśli osiągniemy 100% pokrycia, poświęcimy więcej czasu na pisanie testów jednostkowych niż na pisanie samego kodu. Rozważ przygotowanie testów jednostkowych dla wszystkich niezależnych ścieżek wymienionych poniżej. Szybko może to stać się problemem wykładniczym.

Na tym diagramie czerwone linie nie są testowane, a linie bezkolorowe – testowane.

Zamiast skupiać się na testowaniu 100%, skupiamy się na testach, które zwiększają naszą pewność, że moduł działa prawidłowo. Sprawdzamy m.in.:

  • Liczba przypadków null
  • testy zakresów, np. testy wartości dodatnich lub ujemnych;
  • Skrajne przypadki
  • Przypadki niepowodzenia
  • Testowanie ścieżek, które są najprawdopodobniej wykonywane przez większość czasu

Platformy testów jednostkowych

Większość platform testów jednostkowych używa asercji do testowania wartości podczas wykonywania ścieżki. Asercje to stwierdzenia, które sprawdzają, czy warunek jest spełniony. Rezultatem potwierdzenia może być powodzenie, niekrytyczna niepowodzenie lub błąd krytyczny. Po wykonaniu asercji program będzie kontynuował normalnie, jeśli zakończy się to sukcesem lub niepowodzeniem. Jeśli wystąpi błąd krytyczny, bieżąca funkcja zostanie przerwana.

Testy składają się z kodu, który konfiguruje stan lub manipuluje modułem, oraz szereg asercji weryfikujących oczekiwane wyniki. Jeśli wszystkie asercje w teście się udają, czyli zwrócą wartość „true” (prawda), test zakończy się powodzeniem. W przeciwnym razie zakończy się niepowodzeniem.

Przypadek testowy zawiera co najmniej 1 test. Testy dzielimy na przypadki testowe, które odzwierciedlają strukturę testowanego kodu. W tym kursie będziemy używać CPPUnit jako platformy testowania jednostkowego. Dzięki niej możemy pisać testy jednostkowe w C++ i uruchamiać je automatycznie, przekazując raport o udanych lub porażkach testów.

Instalacja CPPUnit

Pobierz kod CPPUnit z SourceForge. Znajdź odpowiedni katalog i umieść w nim plik tar.gz. Następnie wpisz następujące polecenia (w systemach Linux i Unix), zastępując odpowiednią nazwę pliku cppunit:

gunzip filename.tar.gz
tar -xvf filename.tar

Jeśli korzystasz z systemu Windows, możesz potrzebować narzędzia do wyodrębniania plików tar.gz. Następnym krokiem jest skompilowanie bibliotek. Przejdź do katalogu cppunit. Znajdziesz tam plik INSTALL, który zawiera szczegółowe instrukcje. Zwykle trzeba uruchomić następujące polecenie:

./configure
make install

W razie problemów zapoznaj się z plikiem INSTALL. Biblioteki znajdują się zwykle w katalogu cppunit/src/cppunit. Aby sprawdzić, czy kompilacja się powiodła, przejdź do katalogu cppunit/examples/simple i wpisz „make”. Jeśli wszystko się zgadza, nie musisz nic więcej robić.

Tutaj znajdziesz doskonały samouczek. Zapoznaj się z tym samouczkiem i utwórz klasę liczb zespolonych oraz powiązane z nią testy jednostkowe. W katalogu cppunit/examples znajduje się kilka dodatkowych przykładów.

Dlaczego muszę to zrobić?

Testowanie jednostkowe jest niezwykle ważne w branży z kilku powodów. Znasz już jeden powód: podczas tworzenia kodu musimy w jakiś sposób sprawdzić naszą pracę. Nawet gdy opracowujemy bardzo mały program, instynktownie piszemy jakiś element sterujący lub sterownik, aby sprawdzić, czy nasz program działa zgodnie z oczekiwaniami.

Z bogatego doświadczenia inżynierowie wiedzą, że szanse na to, że program sprawdzi się za pierwszym razem, są bardzo małe. Testy jednostkowe bazują na tej koncepcji, ponieważ programy do testowania automatycznie się sprawdzają i powtarzają. Potwierdzenia zastępują one ręczne sprawdzanie danych wyjściowych. Łatwość analizowania wyników (zakończenie lub niezaliczenie testów) sprawia, że testy można przeprowadzać wiele razy, co zwiększa odporność na zmiany kodu.

Ujmijmy to w konkretny sposób: gdy po raz pierwszy prześlesz gotowy kod do pliku CVS, działa on doskonale. Przez jakiś czas działa idealnie. Pewnego dnia ktoś inny zmienia Twój kod. Prędzej lub później ktoś złama Twój kod. Myślisz, że zauważą je same? Nieprawdopodobne. Testy jednostkowe mogą jednak być uruchamiane automatycznie codziennie przez inne systemy. Są to tzw. systemy ciągłej integracji. Jeśli więc ten inżynier X uszkodzi Twój kod, system będzie wysyłać im paskudne e-maile, dopóki tego nie naprawią. Nawet jeśli inżynierem X to TY!

Testy jednostkowe to nie tylko pomoc w tworzeniu oprogramowania, ale też jego zabezpieczaniu przed zmianami.

  • Tworzy specyfikację pliku wykonywalnego i dokumentację, która jest synchronizowana z kodem. Inaczej mówiąc, możesz poczytać test jednostkowy, aby dowiedzieć się, jakie zachowanie obsługuje dany moduł.
  • Pomaga oddzielić wymagania od ich implementacji. Ponieważ oświadczasz, że działasz w sposób widoczny na zewnątrz, masz szansę dokładnie je rozważyć, zamiast łączyć różne pomysły na jego wdrożenie.
  • Wspomaga eksperymentowanie. Jeśli masz zabezpieczenia, które ostrzegają Cię o problemach z działaniem modułu, łatwiej Ci będzie wypróbować rozwiązania i ponownie skonfigurować projekty.
  • Ulepsza projekty. Pisanie gruntownych testów jednostkowych często wymaga usprawnienia kodu. Testowalny kod jest często bardziej modułowy niż nietestowalny.
  • Utrzymuje wysoką jakość. Drobny błąd w krytycznym systemie może spowodować utratę milionów dolarów, a nawet szczęścia czy zaufanie użytkownika, Poziom bezpieczeństwa zapewniany przez testy jednostkowe ogranicza to takie ryzyko. Dzięki temu, że wykrywają błędy na wczesnym etapie, zespoły kontroli jakości mogą poświęcić czas na bardziej złożone i trudne scenariusze niepowodzenia zamiast zgłaszać oczywiste błędy.

Poświęć trochę czasu na napisanie testów jednostkowych przy użyciu jednostki CPPUnit dla aplikacji bazy danych Composer. Więcej informacji znajdziesz w katalogu cppunit/examples/.

Jak działa Google

Wprowadzenie

Wyobraź sobie średniowiecznego mnicha patrzącego na tysiące rękopisów znajdujących się w archiwum swojego klasztoru.„Gdzie jest to Arystoteles...”

biblioteka klasztorna

Na szczęście rękopisy są uporządkowane według treści i opatrzone są specjalnymi symbolami, które ułatwiają pobieranie informacji zawartych w każdym z nich. Bez takiej organizacji znalezienie odpowiedniego rękopisu byłoby bardzo trudne.

Czynności polegające na przechowywaniu i pobieraniu informacji zapisanych w dużych zbiorach nazywamy pobieraniem informacji. Przez wieki te działania nabierały coraz większego znaczenia, zwłaszcza w wyniku wynalazków takich jak papier czy prasa drukarska. Kiedyś mieszkało w nim niewiele osób. Obecnie jednak setki milionów osób pozyskują informacje każdego dnia, korzystając z wyszukiwarki czy komputera.

Pierwsze kroki z pobieraniem informacji

kot w kapeluszu

W ciągu 30 lat dr Seuss napisał 46 książek dla dzieci. Jego książki opowiadały o kotach, krowach i słoniach, o kotach, o kotach, zachwytach i loraksach. Pamiętasz, jakie stworzenia były przedstawione w danej historii? Jeśli nie jesteś rodzicem, tylko dzieci mogą powiedzieć, w którym zestawie opowieści doktora Seussa występują stworzenia:

(COW i BEE) lub CROWS

Aby rozwiązać ten problem, zastosujemy kilka klasycznych modeli pobierania informacji.

Oczywistym podejściem jest stosowanie brute-force: pobierz wszystkie 46 historii Dr. Seussa i zacznij je czytać. W przypadku poszczególnych książek zwróć uwagę, która z nich zawiera słowa „COW” i „BEE”, a zarazem szukaj książek zawierających słowo CROWS. Komputery są przy tym znacznie szybsze od nas. Jeśli mamy cały tekst z książek doktora Seussa w postaci cyfrowej, na przykład w plikach tekstowych, możemy je po prostu odczytać. Ta technika sprawdza się w przypadku niewielkiej kolekcji, takiej jak książki doktora Seussa.

Jest jednak wiele sytuacji, w których potrzebujemy więcej. Na przykład zbieranie wszystkich danych znajdujących się obecnie online w internecie jest zbyt duże dla narzędzia grep. Nie chcemy tylko dokumentów spełniających nasze warunki. Przyzwyczailiśmy się też do ich porządkowania według trafności.

Inną metodą oprócz grep jest utworzenie indeksu dokumentów w kolekcji przed rozpoczęciem wyszukiwania. Indeks w IR jest podobny do indeksu na końcu podręcznika. W każdej historii Dr. Seussa tworzymy listę wszystkich słów (lub terminów), pomijając takie słowa jak „i”, „i” oraz inne elementy łączące, przyimki itp. (tzw. odrzucane słowa). Następnie przedstawiamy te informacje w sposób, który ułatwia znalezienie haseł i określenie tematów, w których się znajdują.

Jednym z możliwych sposobów jest tablica z artykułami u góry i hasłami wymienionymi w każdym wierszu. „1” w kolumnie oznacza, że hasło pojawia się w artykule w danej kolumnie.

tabela książek i słów

Każdy wiersz lub każdą kolumnę można wyświetlić jako wektor bitowy. Wektor bitowy wiersza wskazuje, w jakich artykułach pojawia się dane hasło. Wektor bitowy kolumny wskazuje, jakie hasła występują w artykule.

Wróćmy do pierwotnego problemu:

(COW i BEE) lub CROWS

Obliczamy wektory bitowe tych terminów i rozliczamy operatory bitowe ORAZ, a następnie do wyniku dodaj operator LUB.

(100001 i 010011) lub 000010 = 000011

Odpowiedź: „Pan Brown Can Muo! Can You?” i „Lorax”. Ten przykład pokazuje model pobierania wartości logicznej, który jest modelem z dopasowaniem ścisłym.

Załóżmy, że mamy rozszerzyć macierz, aby uwzględnić w niej wszystkie historie dr. Seussa i ich istotne terminy. Macierz znacznie się powiększy, a ważną obserwacją jest to, że większość wpisów będzie miała wartość 0. Macierz nie jest prawdopodobnie najlepszą reprezentacją indeksu. Musimy znaleźć sposób na przechowywanie tylko 1.

Niektóre ulepszenia

Struktura używana w infrastrukturze IR do rozwiązania tego zadania jest nazywana odwróconym indeksem. Prowadzimy słownik dotyczący terminów. Dla każdego hasła znajduje się lista dokumentów, w których występuje dane hasło. Jest to tzw. lista postów. Tę strukturę można przedstawić w formie pojedynczej połączonej listy, tak jak to pokazano poniżej.

Jeśli nie potrafisz korzystać z połączonych list, wyszukaj w Google „listę połączonych list w C++”, a znajdziesz wiele zasobów z opisem, jak utworzyć taką listę i jak będzie ona wykorzystywana. Szczegółowo omówimy to w dalszym module.

Zwróć uwagę, że zamiast nazwy artykułu używamy identyfikatorów dokumentów (DocIDs). Te identyfikatory DocID sortujemy również, ponieważ ułatwia to przetwarzanie zapytań.

Jak przetwarzamy zapytanie? W przypadku pierwotnego problemu najpierw znajdujemy listę postów COW, a następnie listę postów BEE. Następnie je „scalamy” ze sobą:

  1. Umieść znaczniki na obu listach i przeglądaj je jednocześnie.
  2. W każdym kroku porównaj identyfikator DocID wskazywany przez oba wskaźniki.
  3. Jeśli są takie same, umieść ten identyfikator DocID na liście wyników. W przeciwnym razie przesuń wskaźnik do mniejszego identyfikatora.

Oto jak utworzyć odwrócony indeks:

  1. Przypisz identyfikator DocID do każdego interesującego Cię dokumentu.
  2. Dla każdego dokumentu określ odpowiednie hasła (tokenizuj je).
  3. Dla każdego hasła utwórz rekord składający się z tego hasła, identyfikatora DocID miejsca, w którym się znajduje, oraz częstotliwości występowania w tym dokumencie. Pamiętaj, że jeśli hasło może występować w więcej niż jednym dokumencie, może istnieć wiele rekordów.
  4. Posortuj rekordy według hasła.
  5. Słownik i listę postów możesz utworzyć, przetwarzając pojedyncze rekordy dla hasła, a także łącząc wiele rekordów haseł występujących w więcej niż jednym dokumencie. Utwórz połączoną listę identyfikatorów DocID (w kolejności posortowanej). Każdy element ma też częstotliwość, która jest sumą częstotliwości we wszystkich rekordach dla tego hasła.

Projekt

Znajdź kilka długich dokumentów zwykłego tekstu do eksperymentowania. Projekt ma na celu utworzenie odwróconego indeksu na podstawie dokumentów przy użyciu algorytmów opisanych powyżej. Musisz też utworzyć interfejs do wprowadzania zapytań i mechanizm ich przetwarzania. Partnera projektu możesz znaleźć na forum.

Oto możliwy proces ukończenia tego projektu:

  1. Pierwszą czynnością do wykonania jest zdefiniowanie strategii identyfikacji haseł w dokumentach. Sporządź listę wszystkich pomijanych słów, jakie przychodzi Ci do głowy, i napisz funkcję, która będzie odczytywać słowa zawarte w plikach, zapisywać hasła i eliminować pomijane słowa. Podczas przeglądania listy haseł z iteracji może być konieczne dodanie do listy większej liczby pomijanych słów.
  2. Utwórz przypadki testowe CPPUnit do przetestowania funkcji oraz plik Makefile, który połączy wszystkie elementy kompilacji. Sprawdź pliki w systemie CVS, szczególnie jeśli współpracujesz z partnerami. Możesz się dowiedzieć, jak udostępnić instancję CVS inżynierom zdalnym.
  3. Dodaj przetwarzanie, aby uwzględnić dane o lokalizacji, czyli który plik i gdzie znajduje się w nim hasło. Być może warto wykonać obliczenia, aby określić numer strony lub akapitu.
  4. Utwórz przypadki testowe CPPUnit w celu przetestowania tej dodatkowej funkcji.
  5. Utwórz odwrócony indeks i zapisz dane o lokalizacji w rekordzie każdego hasła.
  6. Napisz więcej przypadków testowych.
  7. Zaprojektować interfejs umożliwiający użytkownikom wpisywanie zapytań.
  8. Za pomocą algorytmu wyszukiwania opisanego powyżej przetwórz odwrócony indeks i zwróć użytkownikowi dane o lokalizacji.
  9. Pamiętaj, aby w ostatniej części uwzględnić też przypadki testowe.

We wszystkich projektach możesz korzystać z forum i czatu, aby znaleźć partnerów projektowych i dzielić się pomysłami.

Dodatkowa funkcja

Częstym etapem przetwarzania w wielu systemach IR jest proces ten. Głównym założeniem tej koncepcji jest to, że użytkownicy szukający informacji na temat „pobierania” będą również zainteresowani dokumentami zawierającymi informacje zawierające takie słowa jak „odbieranie”, „odzyskanie”, „pobieranie” itd. Systemy mogą być narażone na błędy z powodu słabego rdzenia, więc nie jest to łatwe. Na przykład użytkownik zainteresowany „pobieraniem informacji” może zobaczyć dokument zatytułowany „Informacje o grupach golden retriever” ze względu na ich źródło. Przydatnym algorytmem podstaw jest algorytm Portera.

Aplikacja: gdziekolwiek jesteś!

Zerknij na przykład na zastosowanie tych koncepcji na stronie Panoramas.dk.