Współczesne komputery są niewiarygodnie szybko i cały czas coraz szybciej. Jednakże komputery mają również pewne istotne ograniczenia: natywnie rozumieją jedynie ograniczony zestaw instrukcji i należy im dokładnie powiedzieć, co mają robić.
A program komputerowy to sekwencja instrukcji, która nakazuje komputerowi wykonanie określonych działań w określonej kolejności. Programy komputerowe są zazwyczaj pisane w języku programowania, który jest językiem zaprojektowanym w celu ułatwienia pisania instrukcji dla komputerów. Dostępnych jest wiele różnych języków programowania, z których każdy zaspokaja inny zestaw potrzeb. Akt (i sztuka) pisania programu nazywa się programowaniem. O tym, jak tworzyć programy w C++, porozmawiamy bardziej szczegółowo w nadchodzących lekcjach tego rozdziału.
Kiedy komputer wykonuje czynności opisane w instrukcjach programu komputerowego, mówimy, że uruchamia lub wykonuje program. Komputer nie rozpocznie wykonywania programu, dopóki nie otrzyma takiego polecenia. Zwykle wymaga to od użytkownika uruchomienia (lub uruchomienia lub wykonania) programu, chociaż programy mogą być również uruchamiane przez inne programy.
Programy są wykonywane na sprzęcie komputera, który składa się z fizycznych elementów tworzących komputer. Godny uwagi sprzęt znajdujący się na typowym urządzeniu komputerowym obejmuje:
- CPU (jednostka centralna, często nazywana „mózgiem” komputera), który faktycznie wykonuje instrukcje.
- Pamięć, do której programy komputerowe są ładowane przed wykonaniem.
- Urządzenia interaktywne (np. monitor, ekran dotykowy, klawiatura lub mysz), które umożliwiają osobie interakcję z komputerem komputer.
- Urządzenia pamięci masowej (np. dysk twardy, dysk SSD lub pamięć flash), które przechowują informacje (w tym zainstalowane programy) nawet po wyłączeniu komputera.
Z kolei termin oprogramowanie w szerokim zakresie odnosi się do programów w systemie, które są przeznaczone do wykonywania na sprzęcie.
W nowoczesnych komputerach programy często wchodzą w interakcję z więcej niż tylko sprzęt — współdziałają także z innym oprogramowaniem w systemie (w szczególności z systemem operacyjnym). Termin platforma odnosi się do kompatybilnego zestawu sprzętu i oprogramowania (system operacyjny, przeglądarka itp.), który zapewnia środowisko do uruchamiania oprogramowania. Na przykład termin „PC” jest używany potocznie do określenia platformy składającej się z systemu operacyjnego Windows działającego na procesorze z rodziny x86.
Platformy często zapewniają przydatne usługi dla uruchomionych na nich programów. Na przykład aplikacja komputerowa może poprosić system operacyjny o udostępnienie jej fragmentu wolnej pamięci, utworzenie tam pliku lub odtworzenie dźwięku. Działający program nie musi wiedzieć, w jaki sposób jest to faktycznie ułatwione. Jeśli program korzysta z możliwości lub usług udostępnianych przez platformę, staje się zależny od tej platformy i nie można go uruchomić na innych platformach bez modyfikacji. O programie, który można łatwo przenieść z jednej platformy na drugą, mówi się, że jest przenośny. Czynność modyfikowania programu tak, aby działał na innej platformie, nazywa się portowaniem.
Teraz, gdy rozmawialiśmy o programach, porozmawiajmy o językach programowania. To nie jest tylko lekcja historii, będziemy także wprowadzać terminologię, która pojawi się na przyszłych lekcjach.
Język maszynowy
Procesor komputera nie jest w stanie zrozumieć C++. Zamiast tego procesory są w stanie przetwarzać tylko instrukcje zapisane w język maszynowy (lub kod maszynowy). Zbiór wszystkich możliwych instrukcji języka maszynowego, które może zrozumieć dany procesor, nazywany jest zestawem instrukcji.
Oto przykładowa instrukcja języka maszynowego: 10110000 01100001.
Każda instrukcja jest rozumiana przez procesor jako polecenie wykonania bardzo specyficznego zadania, takiego jak „porównaj te dwie liczby” lub „skopiuj ten numer do tego miejsca w pamięci”. Kiedy wynaleziono komputery, programiści musieli pisać programy bezpośrednio w języku maszynowym, co było bardzo trudne i czasochłonne.
Sposób organizacji i interpretacji tych instrukcji wykracza poza zakres tej serii tutoriali, ale warto zwrócić uwagę na kilka rzeczy.
Po pierwsze, każda instrukcja składa się z sekwencji jedynek i zer. Każde indywidualne 0 lub 1 nazywane jest w skrócie a cyfra binarna lub bit . Liczba bitów w instrukcji języka maszynowego jest różna — na przykład niektóre procesory przetwarzają instrukcje, które zawsze mają długość 32 bitów, podczas gdy inne procesory (takie jak te z rodziny x86, których możesz używać) mają instrukcje o zmiennej długości.
Po drugie, każda rodzina kompatybilnych procesorów (np. x86, Arm64) ma swój własny język maszynowy, a ten język maszynowy nie jest kompatybilny z językiem maszynowym innych rodzin procesorów. Oznacza to, że programy w języku maszynowym napisane dla jednej rodziny procesorów nie mogą być uruchamiane na procesorach z innej rodziny!
Powiązana treść
„Rodzina procesorów” jest formalnie nazywana „architekturą zestawu instrukcji” (w skrócie „ISA”). Wikipedia zawiera listę różnych rodzin procesorów tutaj.
Języki asemblera
Instrukcje języka maszynowego (takie jak 10110000 01100001) są idealne dla procesora, ale są trudne do zrozumienia dla człowieka. Ponieważ programy (przynajmniej w przeszłości) były pisane i utrzymywane przez ludzi, ma sens, że języki programowania powinny być projektowane z myślą o potrzebach ludzi.
An język asemblera (często nazywana w skrócie asemblacja w skrócie) to język programowania, który zasadniczo funkcjonuje jako język maszynowy bardziej czytelny dla człowieka. Oto ta sama instrukcja, co powyżej w języku asemblera x86: mov al, 0x61.
Odczyt opcjonalny
Ta instrukcja ilustruje wiele możliwości, które czynią asembler bardziej czytelnym niż język maszynowy.
- Operacja (co robi instrukcja) jest identyfikowana za pomocą krótkiego mnemonika (zwykle 3-5 literowa nazwa).
movmożna łatwo zrozumieć jako mnemonik „przenieś”, który jest operacją kopiującą bity z jednego miejsca do innego inny. - Do rejestrów (lokalizacji szybkiej pamięci, które są częścią samego procesora) można uzyskać dostęp poprzez nazwę.
alto nazwa konkretnego rejestru w procesorze x86. - Liczby można określić w wygodniejszym formacie. Języki asemblera zazwyczaj obsługują zarówno liczby dziesiętne (np.
97), jak i liczby szesnastkowe (np.0x61).
Dość łatwo zrozumieć, że instrukcja asemblera mov al, 0x61 kopiuje liczbę szesnastkową 0x61 do al rejestru procesora.
Ponieważ procesory nie rozumieją asemblacji na język maszynowy, programy w asemblerze muszą zostać przetłumaczone na język maszynowy, zanim będą mogły zostać wykonane. To tłumaczenie jest wykonywane przez program zwany asemblerem. Ponieważ każda instrukcja języka asemblera jest zazwyczaj zaprojektowana tak, aby odzwierciedlała równoważną instrukcję języka maszynowego, proces tłumaczenia jest zwykle prosty.
Tak jak każda rodzina procesorów ma swój własny język maszynowy, każda rodzina procesorów ma również swój własny język asemblera (który jest przeznaczony do składania w język maszynowy dla tej samej rodziny procesorów). jest wiele różnych języków asemblera Chociaż koncepcyjnie są podobne, różne języki asemblera obsługują różne instrukcje, używają różnych konwencji nazewnictwa itp....
Wprowadzenie do języków niskiego poziomu
Języki maszynowe i języki asemblera są uważane za języki niskiego poziomu, ponieważ języki te zapewniają minimalną abstrakcję od architektury maszyny. Innymi słowy, sam język programowania jest dostosowany do konkretnej architektury zestawu instrukcji, na której będzie uruchamiany.
Języki niskiego poziomu mają wiele znaczących wad:
- Programy napisane w języku niskiego poziomu nie są przenośne. Ponieważ język niskiego poziomu jest dostosowany do określonej architektury zestawu instrukcji, programy napisane w tym języku również. Przenoszenie takich programów na inne architektury zazwyczaj nie jest trywialne.
- Napisanie programu w języku niskiego poziomu wymaga szczegółowej wiedzy o samej architekturze. Przykładowo instrukcja
mov al, 061hwymaga wiedzy, żealodnosi się do rejestru procesora dostępnego na tej konkretnej platformie i zrozumienia sposobu pracy z tym rejestrem. W innej architekturze rejestr ten może mieć inną nazwę, mieć inne ograniczenia lub w ogóle nie istnieć. - Programy niskiego poziomu są trudne do zrozumienia. Chociaż poszczególne instrukcje asemblera mogą być całkiem zrozumiałe, nadal może być trudno wydedukować, co faktycznie robi sekcja kodu asemblera. A ponieważ programy asemblera wymagają wielu instrukcji do wykonania nawet prostych zadań, są one zwykle dość długie
- Trudno jest pisać programy asemblera o znacznej złożoności, ponieważ język zapewnia jedynie prymitywne możliwości. Programista musi sam wdrożyć wszystko, czego potrzebuje.
Podstawową zaletą języków niskiego poziomu jest to, że są szybkie. Asembler jest nadal używany, gdy istnieją sekcje kodu, które mają krytyczne znaczenie dla wydajności. Jest on również używany w kilku innych przypadkach, z których jeden omówimy za chwilę.
Wprowadzenie do języków wysokiego poziomu
Aby zaradzić wielu powyższym wadom, opracowano nowe języki programowania „wysokiego poziomu”, takie jak C, C++, Pascal (a później języki takie jak Java, JavaScript i Perl).
Oto ta sama instrukcja, co powyżej w C/C++: a = 97;.
Podobnie jak programy w asemblerze (które muszą być złożone w języku maszynowym), programy napisane w języku wysokiego poziomu muszą zostać przetłumaczone na język maszynowy, zanim będą mogły zostać uruchomione. Można to zrobić na dwa podstawowe sposoby: kompilacja i interpretacja.
Programy w języku C++ są zwykle kompilowane. A kompilator to program (lub zbiór programów), który odczytuje kod źródłowy jednego języka (zwykle języka wysokiego poziomu) i tłumaczy go na inny język (zwykle język niskiego poziomu). Na przykład kompilator C++ tłumaczy kod źródłowy C++ na kod maszynowy.
Odczyt opcjonalny
Większość kompilatorów C++ można również skonfigurować do generowania kodu asemblera. Jest to przydatne, gdy programista chce zobaczyć, jakie konkretne instrukcje generuje kompilator dla sekcji programu.
Kod maszynowy wyjściowy kompilatora można następnie spakować do pliku wykonywalnego (zawierającego instrukcje w języku maszynowym), który można rozesłać innym osobom i uruchomić przez system operacyjny. Warto zauważyć, że uruchomienie pliku wykonywalnego nie wymaga instalacji kompilatora.
Na początku kompilatory były prymitywne i tworzyły powolny, niezoptymalizowany asembler lub kod maszynowy. Jednak z biegiem lat kompilatory stały się bardzo dobre w tworzeniu szybkiego, zoptymalizowanego kodu, a w niektórych przypadkach mogą wykonać pracę lepiej niż ludzie!
Oto uproszczona reprezentacja procesu kompilacji:

Alternatywnie interpreter to program, który bezpośrednio wykonuje instrukcje zawarte w kodzie źródłowym, bez konieczności ich wcześniejszej kompilacji. Interpretatory są zwykle bardziej elastyczne niż kompilatory, ale są mniej wydajne podczas uruchamiania programów, ponieważ proces interpretacji musi być wykonywany za każdym razem, gdy program jest uruchamiany. Oznacza to również, że interpreter musi być zainstalowany na każdej maszynie, na której będzie uruchamiany interpretowany program.
Oto uproszczona reprezentacja procesu interpretacji:

Odczyt opcjonalny
Można znaleźć dobre porównanie zalet kompilatorów i interpreterów tutaj.
Kolejną zaletą skompilowanych programów jest to, że dystrybucja skompilowanego programu nie wymaga dystrybucji kodu źródłowego. W środowisku innym niż open source jest to ważne ze względu na ochronę własności intelektualnej (IP).
Większość języków wysokiego poziomu można kompilować lub interpretować. Tradycyjnie kompilowane są języki wysokiego poziomu, takie jak C, C++ i Pascal, podczas gdy języki „skryptowe”, takie jak Perl i JavaScript, są zwykle interpretowane. Niektóre języki, takie jak Java, używają kombinacji tych dwóch. Wkrótce bliżej przyjrzymy się kompilatorom C++.
Zalety języków wysokiego poziomu
Języki wysokiego poziomu są tak nazywane, ponieważ zapewniają wysoki poziom abstrakcji z podstawowej architektury.
Rozważ instrukcję a = 97;. Ta instrukcja pozwala nam przechowywać wartość 97 gdzieś w pamięci, bez konieczności dokładnej wiedzy, gdzie ta wartość zostanie umieszczona lub jaka konkretna instrukcja kodu maszynowego jest potrzebna procesorowi do przechowywania tej wartości. W rzeczywistości ta instrukcja nie ma w ogóle nic specyficznego dla platformy. Kompilator wykonuje całą pracę, aby dowiedzieć się, jak instrukcja C++ przekłada się na kod maszynowy specyficzny dla platformy.
Języki wysokiego poziomu umożliwiają programistom pisanie programów bez dużej wiedzy o platformie, na której będą uruchamiane. To nie tylko ułatwia pisanie programów, ale także czyni je znacznie bardziej przenośnymi. Jeśli będziemy ostrożni, możemy napisać pojedynczy język C++, który będzie się kompilował na każdej platformie wyposażonej w kompilator C++! O programie zaprojektowanym do działania na wielu platformach mówi się, że jest wieloplatformowy.

Dla zaawansowanych czytelników
Poniżej znajduje się częściowa lista rzeczy, które mogą utrudniać przenośność kodu C++:
- Wiele systemów operacyjnych, takich jak Microsoft Windows, oferuje funkcje specyficzne dla platformy, których można użyć w swoim kodzie. Mogą one znacznie ułatwić pisanie programu dla określonego systemu operacyjnego lub zapewnić głębszą integrację z tym systemem operacyjnym, niż byłoby to możliwe w innym przypadku.
- Wiele bibliotek innych firm jest dostępnych tylko na niektórych platformach. Jeśli użyjesz jednego z nich, będziesz ograniczony do platform, dla których obsługiwana jest ta biblioteka.
- Niektóre kompilatory obsługują rozszerzenia specyficzne dla kompilatora, które są możliwościami dostępnymi tylko w tym kompilatorze. Jeśli ich użyjesz, Twoje programy nie będą mogły być kompilowane bez modyfikacji przez inne kompilatory, które nie obsługują tych samych rozszerzeń. Porozmawiamy o tym więcej później, gdy już zainstalujesz kompilator.
- W niektórych przypadkach język C++ pozwala kompilatorowi określić, jak coś powinno się zachować. Omawiamy to szerzej podczas lekcji 1.6 — Niezainicjowane zmienne i niezdefiniowane zachowanie w części „Zachowanie zdefiniowane w ramach implementacji”.
Jeśli kierujesz reklamy tylko na jedną platformę, przenośność może nie mieć większego znaczenia. Jednak obecnie wiele aplikacji jest skierowanych na wiele platform, aby poszerzyć ich zasięg. Na przykład aplikacja mobilna będzie prawdopodobnie skierowana zarówno na system iOS, jak i Android.
Nawet jeśli początkowo przenośność nie wydaje się przydatna, wiele aplikacji, które początkowo były przeznaczone na jedną platformę (np. PC), zdecydowało się na przeniesienie na inne platformy (np. Mac i różne konsole) po zobaczeniu pewnego poziomu sukcesu i zainteresowania. Jeśli nie zaczniesz od przenośności, późniejsze przeniesienie aplikacji będzie wymagało więcej pracy.
W tych samouczkach będziemy w jak największym stopniu unikać kodu specyficznego dla platformy, dzięki czemu nasze programy będą działać na dowolnej platformie z nowoczesnym kompilatorem C++.
Języki wysokiego poziomu mają również inne zalety:
- Programy napisane w języku wysokiego poziomu są łatwiejsze do czytania, pisania i uczenia się, ponieważ ich instrukcje bardziej przypominają język język naturalny i matematyka, których używamy na co dzień. W wielu przypadkach języki wysokiego poziomu wymagają mniej instrukcji do wykonania tych samych zadań, co języki niskiego poziomu. Na przykład w C++ możesz napisać
a = b * 2 + 5;w jednym wierszu. W języku asemblera wymagałoby to od 4 do 6 różnych instrukcji. Dzięki temu programy napisane przy użyciu języków wysokiego poziomu są bardziej zwięzłe, co ułatwia ich zrozumienie. - Języki wysokiego poziomu zazwyczaj zawierają dodatkowe możliwości, które ułatwiają wykonywanie typowych zadań programistycznych, takich jak żądanie bloku pamięci lub manipulowanie tekstem. Na przykład wystarczy jedna instrukcja, aby określić, czy znaki „abc” występują w dużym bloku tekstu (a jeśli tak, ile znaków należy sprawdzić, zanim znaleziono „abc”). Może to radykalnie zmniejszyć złożoność i czas programowania.
Nomenklatura
Chociaż C++ jest technicznie uważany za język wysokiego poziomu, nowsze języki programowania (np. języki skryptowe) zapewniają jeszcze wyższy poziom abstrakcji. W związku z tym C++ jest czasami błędnie nazywany w porównaniu z nim „językiem niskiego poziomu”.
Nota autora
Dzisiaj C++ prawdopodobnie byłoby dokładniej określane jako język średniego poziomu. Jednak podkreśla to również jedną z kluczowych zalet języka C++: często zapewnia on możliwość pracy na różnych poziomach abstrakcji. Możesz wybrać działanie na niższym poziomie, aby uzyskać lepszą wydajność i precyzję, lub na wyższym poziomie, aby zapewnić większą wygodę i prostotę.
Zasady, najlepsze praktyki i ostrzeżenia
W trakcie przeglądania tych samouczków wyróżnimy wiele ważnych punktów w następujących trzech kategoriach:
Reguła
Reguły to instrukcje, które należy muszą wykonać zgodnie z wymaganiami języka. Nieprzestrzeganie reguły zazwyczaj spowoduje, że Twój program nie będzie działał.
Najlepsza praktyka
Najlepsze praktyki to rzeczy, które powinieneś robisz, ponieważ taki sposób działania jest albo konwencjonalny (idiomatyczny), albo zalecany. Oznacza to, że albo wszyscy tak robią (a jeśli postąpisz inaczej, zrobisz coś, czego ludzie się nie spodziewają), albo jest to ogólnie lepsze rozwiązanie od alternatyw.
Ostrzeżenie
Ostrzeżenia to rzeczy, które nie powinny robisz, ponieważ zazwyczaj prowadzą one do nieoczekiwanych rezultatów.

