1) W C++ istnieje dokładniejsza kontrola typów wyliczeniowych. Jeżeli definiujemy np. "enum typ" to w języku C jest możliwy zapis "typ++" natomiast w C++ nie.
2) W języku C definicja zmiennych musi nastąpić na początku wywołania funkcji main.
3) W języku C++ nie można wywołać niezadeklarowanej uprzednio funkcji.
4) C dopuszcza przypisanie wskaźnikowi do void każdego innego typu. W C i C++ możliwa jest następująca instrukcja (przy założeniu, że "integer" jest zmienną typu int równą 10): "void * wskaznik_do_void = & integer". Natomiast "int * wskaznik_do_integer = wskaznik_do_void" dopuszczalne jest tylko w C.
5) C nie pozwala aby struktura nie zawierała danych składowych.
6) W C dozwolony jest zapis np. "const int zmienna" natomiast w C++ nie. Kompilator języka C przyjmuje taki zapis jako deklarację obszaru pamięci przydzielonego w jakimś innym miejscu programu. Jest to dopuszczalne z uwagi na to, iż w języku C stałe są domyślnie łączone zewnętrznie. W C++ domyślnie w stosunku do stałych jest stosowane łączenie wewnętrzne. W C++ stałe nie zawsze zajmują pamięć (w przeciwieństwie do C).
7) Język C wymagał podczas deklaracji zmiennych strukturalnych poprzedzanie nazwy struktury łańcuchem "struct". W C++ jest to zbędne.
8) Struktury języka C nie mogą posiadać funkcji składowych.
Przeczytaj część pierwszą.
Źródło: Eckel B., Thinking in C++. Edycja polska, Helion SA, 2002
poniedziałek, 5 maja 2014
C++11
W stosunku do poprzedniej wersji języka C++ wprowadzono m.in. następujące nowości:
- typ long long,
- typy char16_t i char32_t dla zmiennych bez znaku odpowiednio o szerokości 16 i 32 bitów (literały znakowe i napisowe dla typu char16_t są poprzedzone literą u natomiast dla char32_t są poprzedzone literą U),
- obsługa dla wariantu kodowania znaków UTF-8 (literał poprzedzamy przedrostkiem "u8"),
- łańcuch literalny, który sprawia, że znaki nie są interpretowane przez kompilator czyli można je wpisywać wprost w łańcuchu jak np. znaki specjalne łącznie ze znakami cudzysłowu (przed literałem dodajemy tutaj "R"),
- inicjalizacja zmiennych: "int zmienna{1};" oraz instrukcja "int zmienna{}", która wstawia do zmiennej wartość 0,
- podobnie inicjalizacja tablic: "double tablica[2] {1.26e4, 1.11e-8};" i wypełnianie wartościami zerowymi w postaci "double tablica[10] {};",
- nadawanie wartości początkowych łańcuchom znaków: "char znaki[] = {"Znaki"};" lub "char znaki[] {"Znaki"};", a także "string signs = {"Znaki"};" lub "string signs {"Znaki"};",
- podobnie sytuacja ma miejsce w przypadku struktur: "struct struktura {"Pole 1", 2, 3.0}" i dla inicjalizacji wartościami zerowymi "struct struktura {}",
- przyrostki "LL" i "ULL" dla literałów odpowiednio typu long long i unsigned long,
- dedukowanie typu zmiennej na podstawie typu wartości inicjalizującej gdzie np. instrukcja "auto zmienna = 0.0" określa zmienną jako double, a instrukcja "auto zmienna = 0" jako int,
- klasa szablonowa array będąca skuteczną i bezpieczną alternatywą dla tablic wbudowanych o stałym rozmiarze,
- pętla zakresowa for postaci "for (typ zmienna : tablica)" gdzie "typ" jest typem danych przechowywanych w "tablica", a "zmienna" przyjmuje wartość pierwszego elementu z danego zestawu danych (pętla przechodzi po wszystkich elementach tablicy),
- referencja do r-wartości deklarowana jest np. jako "int && r_value = (2 * zmienna + 20);" co niedozwolone jest dla zwykłej referencji,
- wyrażenie "decltype" umożliwia określenie tego samego typu zmiennej, np. wyrażenie "decltype(zmienna_1) zmienna_2;" przypisuje "zmienna_2" typ "zmienna_1", a z kolei wyrażenie "decltype(zmienna_1 + zmienna_2) zmienna_3;" przypisuje "zmienna_3" typ wyrażenia ujętego w nawiasy,
- opóźniona deklaracja typu zwracanego: "auto funkcja(typ zmienna_1, typ zmienna_2) -> double;" czy też "auto funkcja(typ zmienna_1, typ zmienna_2) -> decltype(zmienna_1 + zmienna_2);",
- specyfikator thread_local wskazujący, że czas życia zmiennej jest równy czasowi życia wątku zawierającego zmienną (zmienna z tym przydomkiem jest dla wątku tym samym co zmienna statyczna dla całego programu),
- inicjalizacja operatorem new: możliwe są np. takie instrukcje jak "int * tablica = new int[2] {1, 2};" czy też "double * wskaznik = new int {1};",
- inicjalizacja obiektu klasy za pomocą listy: "klasa obiekt = {"Argument", 1, 2.0};",
- wyliczenia z własnym zasięgiem: deklarując typ wyliczeniowy jako "enum nazwa_klasy nazwa_typu_wyliczeniowego {Element_1, Element_2}" możemy odwoływać się do elementów wyliczenia np. jako "nazwa_typu_wyliczeniowego nazwa_odniesienia = nazwa_typu_wyliczeniowego::Element_1" co pozwala na eliminację problemu kolizji nazw elementów wyliczeń poprzez nadanie im zasięgu klasy,
- w C++11 domyślnym typem wewnętrznym wyliczenia jest int jednak można ręcznie określić pożądanym typ wewnętrzny za pomocą wyrażenia o postaci np. "enum nazwa_klasy : short nazwa_typu_wyliczeniowego {Element1, Element2}" (typ wewnętrzny musi być typem całkowitoliczbowym,
- w C++98 słowo "explicit" nie działa z funkcjami konwersji w przeciwieństwie do C++11,
- notacja "nullptr" określająca wskaźnik pusty,
- inicjalizacja składowych klas: zapis "class klasa { int zmienna = 1; };" równoważny jest zapisowi "klasa::klasa() : zmienna(1) { }" jako liście inicjalizacyjnej,
- szablon będący argumentem typu dla innego szablonu: w poprzedniej wersji C++ w obawie przed mylną interpretacją operatora przesunięcia bitowego należało pisać deklarację w postaci np. "szablon < klasa<int> > obiekt;" natomiast w C++ ten problem został już zażegnany,
- szablonowe aliasy typów: zamiast stosowania deklaracji np. "typedef std::klasa<int, 1> aliast" możemy wykorzystać instrukcję "template<typename type> using alias = std::klasa<type, 1>" co sprawia, że pisząc np. "alias<double> obiekt" określamy, iż "obiekt" jest typu "std::klasa<double, 1>",
- klasa kontenerowa forward_list, która implementuje zwyczajną listę jednokierunkową,
- szablon klasy array,
- nieuporządkowane kontenery asocjacyjne typu unordered_set, unordered_multiset, unordered_map i unordered_multimap,
- szablon initializer_list, który pozwala na stosowanie składni listy inicjalizującej do inicjalizowania kontenerów STL listami wartości,
poniedziałek, 28 kwietnia 2014
Systemy liczbowe
W kolumnowym obrazie systemu liczbowego podstawą systemu jest mnożnik, przez który należy pomnożyć wartość znajdująca się w kolumnie z prawej strony. W naszym systemie o podstawie 10 wartość każdej kolejnej kolumny jest dziesięciokrotnie większa od wartości kolumny z prawej strony. Każdy system liczbowy ma zestaw własnych symboli cyfrowych. Liczba tych symboli równa jest wartości podstawy systemu. W żadnym systemie cyfrowym liczba równa podstawie systemu nie może być wyrażona jedną cyfrą. Każda kolumna ma wartość określoną przez liczbę stanowiącą podstawę systemu podniesioną do potęgi równej porządkowemu numerowi kolumny minus jeden. Wartość w pierwszej kolumnie otrzymujemy podnosząc podstawę systemu do potęgi 1 - 1 (kolumna jednostkowa ponieważ każda liczba podniesiona do potęgi zerowej ma wartość 1). Druga kolumna ma wartość podstawy systemu podniesioną do potęgi 2 - 1 (kolumna ma wartość wielkości podstawy systemu) itd. Cyfra znajdująca się w każdej kolumnie wskazuje ile razy wartość danej kolumny jest zawarta w całej liczbie. Np. jeżeli, biorąc pod uwagę system ósemkowy, w kolumnie czwartej znajduje się wartość 6 to jest to informacja, że wartość kolumny jest zawarta sześciokrotnie w wartości ósemkowej całej liczby, a jej wartość do 8 do potęgi 4 - 1 czyli 512 (512 jest zawarta sześciokrotnie w całej liczbie).
Potęgi liczby 16:
Przykłady:
Potęgi liczby 16:
- 16^2 = 256,
- 16^3 = 4096,
- 16^4 = 65536,
- 16^5 = 1048576,
- 16^6 = 16777216.
- 000 dwójkowo to 0 ósemkowo,
- 001 dwójkowo to 1 ósemkowo,
- 010 dwójkowo to 2 ósemkowo,
- 011 dwójkowo to 3 ósemkowo,
- 100 dwójkowo to 4 ósemkowo,
- 101 dwójkowo to 5 ósemkowo,
- 110 dwójkowo to 6 ósemkowo,
- 111 dwójkowo to 7 ósemkowo.
- 0000 dwójkowo to 0 szesnastkowo,
- 0001 dwójkowo to 1 szesnastkowo,
- 0010 dwójkowo to 2 szesnastkowo,
- 0011 dwójkowo to 3 szesnastkowo,
- 0100 dwójkowo to 4 szesnastkowo,
- 0101 dwójkowo to 5 szesnastkowo,
- 0110 dwójkowo to 6 szesnastkowo,
- 0111 dwójkowo to 7 szesnastkowo,
- 1000 dwójkowo to 8 szesnastkowo,
- 1001 dwójkowo to 9 szesnastkowo,
- 1010 dwójkowo to a szesnastkowo,
- 1011 dwójkowo to b szesnastkowo,
- 1100 dwójkowo to c szesnastkowo,
- 1101 dwójkowo to d szesnastkowo,
- 1110 dwójkowo to e szesnastkowo,
- 1111 dwójkowo to f szesnastkowo.
- system dwójkowy:
- dwójkowy na dziesiętny: 10101101 dwójkowo to 173 dziesiętnie bo (1 * 2^7) + (0 * 2^6) + (1 * 2^5) + (0 * 2^4) + (1 * 2^3) + (1 * 2^2) + (0 * 2^1) + (1 * 2^0) = 173,
- dwójkowy na ósemkowy: liczbę dwójkową dzielimy grupy po 3 bity idąc od prawej do lewej (jednak kolejność w jakiej zapisujemy jest taka jak kolejność bitów w liczbie dwójkowej), a brakujące bity uzupełniamy zerami i odczytujemy z tabeli wartości odpowiadające, przykładowo: liczba dwójkowa 10101101 ma 3 grupy tj. 010, 101 i 101 co odpowiada liczbie ósemkowej 255,
- dwójkowy na szesnastkowy: liczbę dwójkową dzielimy na grupy po 4 bity idąc od prawej do lewej (jednak kolejność w jakiej zapisujemy jest taka jak kolejność bitów w liczbie dwójkowej), a brakujące bity uzupełniamy zerami i odczytujemy z tabeli wartości odpowiadające, przykładowo: 10101101 ma 2 grupy tj. 1101 i 1010 co odpowiada liczbie szesnastkowej da,
- system ósemkowy:
- ósemkowy na dwójkowy: każdą cyfrę z liczby ósemkowej zamieniamy według na tabeli na odpowiadającą jej wartość dwójkową i tak liczba ósemkowa 255 to 010101101 dwójkowo,
- ósemkowy na dziesiętny: 1105 ósemkowo to 581 dziesiętnie bo (1 * 8^3) + (1 * 8^2) + (0 * 8^1) + (5 * 8^0) = 581,
- ósemkowy na szesnastkowy: zamieniamy na dziesiętny i następnie na szesnastkowy,
- system dziesiętny:
- dziesiętny na dwójkowy: 173 % 2 = 1, 86 % 2 = 0, 43 % 2 = 1, 21 % 2 = 1, 10 % 2 = 0, 5 % 2 = 1, 2 % 2 = 0, 1 % 2 = 1, a więc 173 dziesiętnie to 10101101 (od tyłu),
- dziesiętny na ósemkowy: 100 / 8 = 12 (reszta 4), 12 / 8 = 1 (reszta 4), 1 / 8 = 0 (reszta 1), a więc 100 dziesiętnie to 144 ósemkowo (odczytujemy reszty od tyłu),
- dziesiętny na szesnastkowy: 39345 / 4096 (największa potęga liczby 16 mniejsza niż liczba, z której przeliczamy) = 9 reszta 2571, 2571 / 256 (kolejna potęga liczby 16) = 10 reszta 11, 11 / 16 = 0, 11 / 1 = 11, a więc 39345 dziesiętnie to 9a0b szesnastkowo,
- system szesnastkowy:
- szesnastkowy na dwójkowy: każdą cyfrę z liczby szesnastkowej zamieniamy według tabeli na odpowiadającą jej wartość dwójkową i tak liczba szesnastkowa 9a0b to 1001101000001011 dwójkowo,
- szesnastkowy na ósemkowy: zamieniamy na dziesiętny i następnie na ósemkowy,
- szesnastkowy na dziesiętny: 9a0b szesnastkowo to 39435 dziesiętnie bo (9 * 16^3) + (a * 16^2) + (0 * 16^1) + (b * 16^0) = (9 * 4096) + (10 * 256) + (0 * 16) + (11 * 1) = 39545.
Literatura: Duntemann J., Zrozumieć Asembler, Translator S.C., 1994
sobota, 26 kwietnia 2014
Operacje wejścia i wyjścia w C++
Wyświetlanie wartości w innym systemie liczbowym:
Dostosowywanie szerokości pól:
Ustawianie sposobu wyświetlania liczb:
Plik nagłówkowy iomanip:
Podstawowa instrukcją służącą w C++ do wprowadzania danych jest cin, np.:
Tak jak C++ traktuje dane wynikowe jako strumień znakowy tak samo strumień wejściowy jest strumieniem znakowym wpływającym do programu. Obiekt cin potrafi konwertować dane wejściowe będące łańcuchem znaków z klawiatury na postać odpowiednią dla zmiennej. Nie można z klawiatury wprowadzić znaku końca łańcucha więc obiekt cin musi mieć inną metodę znajdowania jego końca. Uznaje się, że łańcuch kończy się wraz z napotkaniem białego znaku. W związku z tym cin odczytuje tylko jedno słowo. Po jego odczytaniu automatycznie dodawany jest znak końca łańcucha jeżeli wczytywanie następuje do tablicy znakowej. Obiekt cin może być również wywoływany w postaci "cin.get(zmienna_char);", która wczytuje następny znak nawet jeżeli jest to spacja i przypisuje go zmiennej.
Przykład tego jak widoczne są dane wejściowe przy zastosowaniu cin:
Jednoznakowe funkcje wejścia:
Łańcuchowe funkcje wejścia:
Funkcja getline (klasa istream) wczytuje całe wiersze uznając za koniec danych znak nowego wiersza, a na końcu tablicy dodaje znak końca łańcucha, np.:
Kolejną funkcją wczytującą całe wiersze jest get (również klasa istream), np.:
Różnicą w stosunku do getline jest to, iż zamiast wczytać i odrzucić znak końca wiersza, zostawia go w kolejce wejściowej. W związku z tym kolejne wywołanie w postaci "cin.get(tablica, ilość_znaków);" sprawi, że zostanie wczytany pusty łańcuch. Rozwiązaniem jest tutaj wywołanie w postaci "cin.get();" poprzedzające chęć pobrania danych z klawiatury, które "zje" znak nowego wiersza. Bezargumentowa wersja get zwraca następny znak pozyskany z wejścia programu, który można przypisać zmiennej (zwrca kod znaku, który można przypisać do typu int). Rozwiązanie problemu może mieć również postać "cin.get(tablica, ilość_znaków).get();" po czym następuje kolejne wywołanie get z argumentami.
Różnicą między getline a get jest to, że get pozwala na zachowanie większej ostrożności. Przykładowo jeżeli użyliśmy get do wczytania tablicy to skąd wiadomo, iż odczytany został cały wiersz czy też przerwano go z powodu przepełnienia tablicy. Jeżeli kolejnym znakiem wejściowym jest znak nowego wiersza to wczytano cały wiersz, a w przeciwnym wypadku nadal mamy dane do odczytania. getline jest prostsza w użyciu jednak w starszych implementacjach może w ogóle nie występować.
Jednym z problemów jakie towarzyszą wywołaniom powyższych funkcji jest to, że w funkcje getline i get pozostawiają niewczytane znaki w kolejce wejściowej jeżeli łańcuch wejściowy jest dłuższy od miejsca na niego przeznaczonego. Poza tym jeżeli get odczyta pusty wiersz to ustawia bit błędu (co nie tyczy się getline), a dalsze wczytywanie danych jest wstrzymane aż do wywołania "cin.clear();". Problem pojawia się też kiedy wczytujemy liczbę za pomocą obiektu cin, a w kolejce wejściowej pozostaje nam znak nowego wiersza. Wyjściem z sytuacji jest oczywiście puste wywołanie "cin.get()" tudzież "(cin >> zmienna).get()".
Wejście i wyjście plikowe:
Stałe trybu otwarcia pliku:
Źródło: Prata S., Język C++. Szkoła programowania, Wydanie VI, Helion SA, 2012
#include <iostream>
int main() {
int number;
std::cout << "Podaj liczbe calkowita: ";
std::cin >> number;
std::cout << "Osemkowo: " << std::oct << number << std::endl;
std::cout << "Szesnastkowo: " << std::hex << number << std::endl;
std::cout << "Dziesietnie: " << std::dec << number << std::endl;
}
int main() {
int number;
std::cout << "Podaj liczbe calkowita: ";
std::cin >> number;
std::cout << "Osemkowo: " << std::oct << number << std::endl;
std::cout << "Szesnastkowo: " << std::hex << number << std::endl;
std::cout << "Dziesietnie: " << std::dec << number << std::endl;
}
Dostosowywanie szerokości pól:
#include <iostream>
int main() {
std::cout.width(20); // Następny łańcuch danych wyświetlony na standardowym wyjściu zostanie umieszczony w polu o szerokości 20 znaków i dosunięty do prawej.
std::cout << "String.";
std::cout << "String.\n"; // Domyślnie (dosunięte do lewej krawędzi).
}
int main() {
std::cout.width(20); // Następny łańcuch danych wyświetlony na standardowym wyjściu zostanie umieszczony w polu o szerokości 20 znaków i dosunięty do prawej.
std::cout << "String.";
std::cout << "String.\n"; // Domyślnie (dosunięte do lewej krawędzi).
}
Ustawianie sposobu wyświetlania liczb:
#include <iostream>
int main() {
float number_float = 20.1000;
std::cout << "Domyslnie: " << number_float << std::endl; // Wyświetla "20.1".
std::cout.precision(2);
std::cout << "Po ustawieniu precyzji na wartosc 2 (domyslnie precyzja jest ustawiona na 6): " << number_float << std::endl; // Wyświetla "20".
std::cout.setf(std::ios_base::showpoint);
std::cout.precision(6);
std::cout << "Po ustawieniu flagi \"showpoint\" i prezycji na domyslna wartosc: " << number_float << std::endl; // Wyświetla "20.1000".
std::cout.unsetf(std::ios_base::showpoint);
std::cout << "Po usunieciu flagi \"showpoint\" (precyzja nadal na taka wartosc jak poprzednio): " << number_float << std::endl; // Wyświetla "20.1".
}
int main() {
float number_float = 20.1000;
std::cout << "Domyslnie: " << number_float << std::endl; // Wyświetla "20.1".
std::cout.precision(2);
std::cout << "Po ustawieniu precyzji na wartosc 2 (domyslnie precyzja jest ustawiona na 6): " << number_float << std::endl; // Wyświetla "20".
std::cout.setf(std::ios_base::showpoint);
std::cout.precision(6);
std::cout << "Po ustawieniu flagi \"showpoint\" i prezycji na domyslna wartosc: " << number_float << std::endl; // Wyświetla "20.1000".
std::cout.unsetf(std::ios_base::showpoint);
std::cout << "Po usunieciu flagi \"showpoint\" (precyzja nadal na taka wartosc jak poprzednio): " << number_float << std::endl; // Wyświetla "20.1".
}
Plik nagłówkowy iomanip:
#include <iostream>
#include <iomanip>
int main() {
float number_float = 20.1001;
std::cout << std::setw(15); // Umieść dane wysłane na standardowe wyjście w polu o szerokości 15 znaków i wyrównaj do prawej.
std::cout << number_float << std::endl;
// Aktualnie powyższe wywołanie funkcji setw już nie obowiązuje.
std::cout << std::setprecision(6); // Precyzja na 6.
std::cout << number_float << std::endl; // Wyświetla "20.1001".
std::cout << std::setw(15);
std::cout << std::setfill('.'); // Wypełnia pustą przestrzeń aktualnego pola określonymi znakami.
std::cout << number_float << std::endl;
}
#include <iomanip>
int main() {
float number_float = 20.1001;
std::cout << std::setw(15); // Umieść dane wysłane na standardowe wyjście w polu o szerokości 15 znaków i wyrównaj do prawej.
std::cout << number_float << std::endl;
// Aktualnie powyższe wywołanie funkcji setw już nie obowiązuje.
std::cout << std::setprecision(6); // Precyzja na 6.
std::cout << number_float << std::endl; // Wyświetla "20.1001".
std::cout << std::setw(15);
std::cout << std::setfill('.'); // Wypełnia pustą przestrzeń aktualnego pola określonymi znakami.
std::cout << number_float << std::endl;
}
Podstawowa instrukcją służącą w C++ do wprowadzania danych jest cin, np.:
cin >> zmienna;
Przykład tego jak widoczne są dane wejściowe przy zastosowaniu cin:
#include <iostream>
#include <string>
int main() {
std::string input;
int integer;
char sign;
std::cout << "Podaj dane (tablica char, int, char oddzielone spacjami): ";
std::cin >> input >> integer >> sign;
// Jeżeli podamy dane wejściowe tak jak nas prosi program to oczywiście jest to poprawne ale jeżeli trzecia dana będzie stringiem to zostanie przeczytany tylko jeden znak, a reszta zostanie w buforze.
std::cin.clear(); // Ta funkcja zeruje stan strumienia.
std::cout << "Podaj liczby: ";
while (std::cin >> integer) {
std::cout << "Podales: " << integer << std::endl;
// Jeżeli jednak zamiast liczby całkowitej podamy np. "123Z" to program wczyta do zmiennej wartość 123 i zakończy działanie.
}
}
#include <string>
int main() {
std::string input;
int integer;
char sign;
std::cout << "Podaj dane (tablica char, int, char oddzielone spacjami): ";
std::cin >> input >> integer >> sign;
// Jeżeli podamy dane wejściowe tak jak nas prosi program to oczywiście jest to poprawne ale jeżeli trzecia dana będzie stringiem to zostanie przeczytany tylko jeden znak, a reszta zostanie w buforze.
std::cin.clear(); // Ta funkcja zeruje stan strumienia.
std::cout << "Podaj liczby: ";
while (std::cin >> integer) {
std::cout << "Podales: " << integer << std::endl;
// Jeżeli jednak zamiast liczby całkowitej podamy np. "123Z" to program wczyta do zmiennej wartość 123 i zakończy działanie.
}
}
Jednoznakowe funkcje wejścia:
#include <iostream>
int main() {
char character;
std::cout << "Podaj dane wejsciowe (funkcja get):\n";
std::cin.get(character); // Ze względu na zastosowanie funkcji get program będzie wyświetlał zarówno znaki białe jak i drukowalne.
while (character != '\n') {
std::cout << "Podales: " << character << std::endl;
std::cout << "Podaj dane wejsciowe (funkcja get):\n";
std::cin.get(character);
}
std::cout << std::endl;
std::cout << "Podaj dane wejsciowe (obiekt cin) i zakoncz znakiem \"q\":\n";
std::cin >> character; // Użycie tej instrukcji spowoduje, iż program pominie białe znaki, a wykonywanie poniższej pętli nie zostanie nigdy przerwane jeżeli warunkiem jest przypisanie do zmiennej białego znaku.
while (character != 'q') {
std::cout << "Podales: " << character << std::endl;
std::cout << "Podaj dane wejsciowe (obiekt cin):\n";
std::cin >> character;
}
// Poniższy kod pokazuje można łączyć wywołanie funkcji get i obiektu cin:
// char character_one, character_two, character_three;
//
// std::cin.get(character_one).cin.get(character_two) >> character_three;
}
int main() {
char character;
std::cout << "Podaj dane wejsciowe (funkcja get):\n";
std::cin.get(character); // Ze względu na zastosowanie funkcji get program będzie wyświetlał zarówno znaki białe jak i drukowalne.
while (character != '\n') {
std::cout << "Podales: " << character << std::endl;
std::cout << "Podaj dane wejsciowe (funkcja get):\n";
std::cin.get(character);
}
std::cout << std::endl;
std::cout << "Podaj dane wejsciowe (obiekt cin) i zakoncz znakiem \"q\":\n";
std::cin >> character; // Użycie tej instrukcji spowoduje, iż program pominie białe znaki, a wykonywanie poniższej pętli nie zostanie nigdy przerwane jeżeli warunkiem jest przypisanie do zmiennej białego znaku.
while (character != 'q') {
std::cout << "Podales: " << character << std::endl;
std::cout << "Podaj dane wejsciowe (obiekt cin):\n";
std::cin >> character;
}
// Poniższy kod pokazuje można łączyć wywołanie funkcji get i obiektu cin:
// char character_one, character_two, character_three;
//
// std::cin.get(character_one).cin.get(character_two) >> character_three;
}
Łańcuchowe funkcje wejścia:
#include <iostream>
const int limit = 256;
int main() {
char array[limit];
char character;
std::cout << "Wprowadz lancuch znakow (funkcja get): ";
std::cin.get(array, limit, '\n'); // Ta funkcja pozostawia znak separatora w kolejce wejściowej.
std::cout << "Oto Twoje dane: " << array << std::endl;
std::cin.get(character);
std::cout << "Nastepny znak wejsciowy to: " << character << std::endl;
std::cout << "Wprowadz lancuch znakow (funkcja getline): ";
std::cin.getline(array, limit, '\n'); // Ta funkcja nie pozostawia znaku separatora w kolejce wejściowej.
std::cout << "Oto Twoje dane: " << array << std::endl;
std::cin.get(character);
std::cout << "Nastepny znak wejsciowy to: " << character << std::endl;
}
const int limit = 256;
int main() {
char array[limit];
char character;
std::cout << "Wprowadz lancuch znakow (funkcja get): ";
std::cin.get(array, limit, '\n'); // Ta funkcja pozostawia znak separatora w kolejce wejściowej.
std::cout << "Oto Twoje dane: " << array << std::endl;
std::cin.get(character);
std::cout << "Nastepny znak wejsciowy to: " << character << std::endl;
std::cout << "Wprowadz lancuch znakow (funkcja getline): ";
std::cin.getline(array, limit, '\n'); // Ta funkcja nie pozostawia znaku separatora w kolejce wejściowej.
std::cout << "Oto Twoje dane: " << array << std::endl;
std::cin.get(character);
std::cout << "Nastepny znak wejsciowy to: " << character << std::endl;
}
Funkcja getline (klasa istream) wczytuje całe wiersze uznając za koniec danych znak nowego wiersza, a na końcu tablicy dodaje znak końca łańcucha, np.:
cin.getline(tablica, ilość_znaków);
Kolejną funkcją wczytującą całe wiersze jest get (również klasa istream), np.:
cin.get(tablica, ilość_znaków);
Różnicą między getline a get jest to, że get pozwala na zachowanie większej ostrożności. Przykładowo jeżeli użyliśmy get do wczytania tablicy to skąd wiadomo, iż odczytany został cały wiersz czy też przerwano go z powodu przepełnienia tablicy. Jeżeli kolejnym znakiem wejściowym jest znak nowego wiersza to wczytano cały wiersz, a w przeciwnym wypadku nadal mamy dane do odczytania. getline jest prostsza w użyciu jednak w starszych implementacjach może w ogóle nie występować.
Jednym z problemów jakie towarzyszą wywołaniom powyższych funkcji jest to, że w funkcje getline i get pozostawiają niewczytane znaki w kolejce wejściowej jeżeli łańcuch wejściowy jest dłuższy od miejsca na niego przeznaczonego. Poza tym jeżeli get odczyta pusty wiersz to ustawia bit błędu (co nie tyczy się getline), a dalsze wczytywanie danych jest wstrzymane aż do wywołania "cin.clear();". Problem pojawia się też kiedy wczytujemy liczbę za pomocą obiektu cin, a w kolejce wejściowej pozostaje nam znak nowego wiersza. Wyjściem z sytuacji jest oczywiście puste wywołanie "cin.get()" tudzież "(cin >> zmienna).get()".
Wejście i wyjście plikowe:
#include <iostream>
#include <fstream> // Dla klas ofstream (zapis) i ifstream (odczyt).
#include <string>
#include <exception>
int main() {
std::string filename;
std::cout << "Podaj sciezke i nazwe nowego pliku: ";
std::cin >> filename;
std::ofstream out(filename.c_str(), std::ios_base::out|std::ios_base::app); // Odczyt i dołączanie na końcu pliku.
// std::ofstream out(filename.c_str()); // W tej postaci wywołania wszystkie dane, które są w podanym pliku zostaną uprzednio wykasowane.
// Można również powyższa instrukcję zastąpić poniższymi:
// std::ofstream out;
//
// out.open(filename.c_str());
out << "Pierwsza linijka.\n";
out << "Druga linijka.\n";
out.close();
std::ifstream in(filename.c_str());
std::cout << "Oto zawartosc pliku:\n";
char character;
try {
while (in.get(character))
std::cout << character;
}
catch (std::ios::failure const & exception) {
std::cout << "Nie udalo sie przeczytac z pliku poniewaz: " << exception.what() << std::endl;
}
// Do sprawdzania możliwości otwarcia pliku można również użyć wywołania "in.is_open()", które zwraca false jeżeli nie udało się otworzyć do odczytu.
in.close();
}
#include <fstream> // Dla klas ofstream (zapis) i ifstream (odczyt).
#include <string>
#include <exception>
int main() {
std::string filename;
std::cout << "Podaj sciezke i nazwe nowego pliku: ";
std::cin >> filename;
std::ofstream out(filename.c_str(), std::ios_base::out|std::ios_base::app); // Odczyt i dołączanie na końcu pliku.
// std::ofstream out(filename.c_str()); // W tej postaci wywołania wszystkie dane, które są w podanym pliku zostaną uprzednio wykasowane.
// Można również powyższa instrukcję zastąpić poniższymi:
// std::ofstream out;
//
// out.open(filename.c_str());
out << "Pierwsza linijka.\n";
out << "Druga linijka.\n";
out.close();
std::ifstream in(filename.c_str());
std::cout << "Oto zawartosc pliku:\n";
char character;
try {
while (in.get(character))
std::cout << character;
}
catch (std::ios::failure const & exception) {
std::cout << "Nie udalo sie przeczytac z pliku poniewaz: " << exception.what() << std::endl;
}
// Do sprawdzania możliwości otwarcia pliku można również użyć wywołania "in.is_open()", które zwraca false jeżeli nie udało się otworzyć do odczytu.
in.close();
}
Stałe trybu otwarcia pliku:
- "ios_base::in": odczyt,
- "ios_base::out": zapis,
- "ios_base::ate": ustaw się na końcu,
- "ios_base::app": dołącz na koniec pliku,
- "ios_base::trunc": usuń zawartość pliku,
- "ios_base::binary": plik binarny.
Źródło: Prata S., Język C++. Szkoła programowania, Wydanie VI, Helion SA, 2012
piątek, 4 kwietnia 2014
Operatory rzutowania w C++
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
class object_one { };
class object_two : public object_one { };
class object_three {
public:
short variable_1;
short variable_2;
};
int main() {
// Operator dynamic_cast. Jego zadaniem jest umożliwienie (lub nie) rzutowania w górę wewnątrz hierarchii klas.
object_one first_object_one;
object_two first_object_two;
object_one * pointer_object_one;
object_two * pointer_object_two = & first_object_two;
pointer_object_one = dynamic_cast<object_one *>(pointer_object_two);
if (pointer_object_one != nullptr)
cout << "Udalo sie rzutowac za pomoca dynamic_cast.\n";
else
cout << "Nie udalo sie rzutowac za pomoca dynamic_cast.\n";
// Operator const_cast. Służy do rzutowania typu, którego jedynym celem jest zmiana etykiety wartości między "const" a "volatile". Przydomek "volatile" mowi kompilatorowi, że ma być ostrożony w kontaktach z danym obiektem. Słowo to ostrzega, że obiekt może się w jakiś niezauważalny dla kompilatora sposób zmieniać. Kompilator nie powinien sobie upraszczać sprawy tylko za każdym razem zwracać się do przydzielonych temu obiektowi komórek pamięci.
const object_two * const_pointer_object_two = & first_object_two;
pointer_object_two = const_cast<object_two *>(const_pointer_object_two); // Wskaźnik "pointer_object_two" może teraz zostać użyty do zmiany wartości obiektu "first_object_two" (usunięcie etykiety "const").
// "const object_one * const_pointer_object_one = const_cast<const object_one *>(const_pointer_object_two);" - to wyrażenie jest niepoprawne gdyż próbuje zmienić z typu "const object_two *" na "const object_one *".
// Operator static_cast.
pointer_object_one = static_cast<object_one *>(& first_object_two); // Rzutowanie w górę.
pointer_object_two = static_cast<object_two *>(& first_object_one); // Rzutowanie w dół.
if (pointer_object_one != nullptr)
cout << "Udalo sie rzutowac w gore za pomoca static_cast.\n";
else
cout << "Nie udalo sie rzutowac w gore za pomoca static_cast.\n";
// object_three * pointer_object_three = static_cast<object_three *>(& first_object_two); // Niepoprawne gdyż klasa object_three jest poza hierarchią.
// Operator reinterpret_cast. Służy do ryzykownych operacji jak np. rzutowanie typu wskaźnikowego na typ całkowity, który jest wystarczająco pojemny aby pomieścić reprezentację wskaźnika. Nie możemy jednak rzutować wskaźnika na mniej pojemny typ liczby całkowitej lub na typ zmiennoprzecinkowy czy też rzutować wskaźnik na funkcję na wskaźnik na dane i odwrotnie.
long value = 0xa224b118;
object_three * pointer_object_three = reinterpret_cast<object_three *>(&value);
cout << "Dwa pierwsze bajty wartosci: " << std::hex << pointer_object_three->variable_1 << endl;
}
using std::cout;
using std::cin;
using std::endl;
class object_one { };
class object_two : public object_one { };
class object_three {
public:
short variable_1;
short variable_2;
};
int main() {
// Operator dynamic_cast. Jego zadaniem jest umożliwienie (lub nie) rzutowania w górę wewnątrz hierarchii klas.
object_one first_object_one;
object_two first_object_two;
object_one * pointer_object_one;
object_two * pointer_object_two = & first_object_two;
pointer_object_one = dynamic_cast<object_one *>(pointer_object_two);
if (pointer_object_one != nullptr)
cout << "Udalo sie rzutowac za pomoca dynamic_cast.\n";
else
cout << "Nie udalo sie rzutowac za pomoca dynamic_cast.\n";
// Operator const_cast. Służy do rzutowania typu, którego jedynym celem jest zmiana etykiety wartości między "const" a "volatile". Przydomek "volatile" mowi kompilatorowi, że ma być ostrożony w kontaktach z danym obiektem. Słowo to ostrzega, że obiekt może się w jakiś niezauważalny dla kompilatora sposób zmieniać. Kompilator nie powinien sobie upraszczać sprawy tylko za każdym razem zwracać się do przydzielonych temu obiektowi komórek pamięci.
const object_two * const_pointer_object_two = & first_object_two;
pointer_object_two = const_cast<object_two *>(const_pointer_object_two); // Wskaźnik "pointer_object_two" może teraz zostać użyty do zmiany wartości obiektu "first_object_two" (usunięcie etykiety "const").
// "const object_one * const_pointer_object_one = const_cast<const object_one *>(const_pointer_object_two);" - to wyrażenie jest niepoprawne gdyż próbuje zmienić z typu "const object_two *" na "const object_one *".
// Operator static_cast.
pointer_object_one = static_cast<object_one *>(& first_object_two); // Rzutowanie w górę.
pointer_object_two = static_cast<object_two *>(& first_object_one); // Rzutowanie w dół.
if (pointer_object_one != nullptr)
cout << "Udalo sie rzutowac w gore za pomoca static_cast.\n";
else
cout << "Nie udalo sie rzutowac w gore za pomoca static_cast.\n";
// object_three * pointer_object_three = static_cast<object_three *>(& first_object_two); // Niepoprawne gdyż klasa object_three jest poza hierarchią.
// Operator reinterpret_cast. Służy do ryzykownych operacji jak np. rzutowanie typu wskaźnikowego na typ całkowity, który jest wystarczająco pojemny aby pomieścić reprezentację wskaźnika. Nie możemy jednak rzutować wskaźnika na mniej pojemny typ liczby całkowitej lub na typ zmiennoprzecinkowy czy też rzutować wskaźnik na funkcję na wskaźnik na dane i odwrotnie.
long value = 0xa224b118;
object_three * pointer_object_three = reinterpret_cast<object_three *>(&value);
cout << "Dwa pierwsze bajty wartosci: " << std::hex << pointer_object_three->variable_1 << endl;
}
Źródło: Prata S., Język C++. Szkoła programowania. Wydanie VI, Helion SA, 2012
wtorek, 1 kwietnia 2014
Architektura procesorów rodziny IA-32 (x86-32) oraz Intel 64 (x86-32e)
Do wymienionej w temacie dużej rodziny procesorów zaliczamy wszystkie procesory rodziny Celeron, Pentium i Core. Współczesne procesory x86-32 począwszy od roku 2003 (niektóre model Pentium 4 oraz wszystkie następne takie jak Core 2 czy Core i7) posiadają dodatkowy 64-bitowy tryb pracy. Architekturę 64-bitową wprowadziła po raz pierwszy AMD pod nazwą x86-64. 64-bitowe rozszerzenie architektury procesorów Intel oznaczane jest jako x86-32e tudzież EM64T. Obecnie oficjalnie jest używana nazwa Intel 64, której nie należy mylić z IA-64, w oparciu o którą zbudowana jest zupełnie inna linia procesorów opracowanych przez firmy Hewlett-Packard oraz Intel o nazwie Itanum.
Każy kolejny procesor firmy Intel z linii x86-32 był tak projektowany aby oprogramowanie działające na starszym modelu mogło być w dalszym ciągu używane.
Jeżeli chodzi o procesory AMD to zbiór instrukcji jednostki stałoprzecinkowej oraz zmiennoprzecinkowej (procesora głównego oraz koprocesora arytmetycznego) jest zgodny z firmą Intel. Podobnie jest z instrukcjami MMX poczynając od procesora AMD K6 2. W kolejnych procesorach K6 firma wprowadziła swoje rozszerzenie technologii MMX o nazwie 3DNow! bazujące na 64-bitowych rejestrach MMX. Można powiedzieć, że była to odpowiedź na SSE Intela. Instrukcje 3DNow! pozwalają wykonywać operacje na dwóch spakowanych wartościach zmiennoprzecinkowych pojedynczej precyzji. Kolejnym rozszerzeniem 3DNow!, czasem nazywanym 3DNow+, jest DSP. Intelowskie rozszerzenie SSE posiada AMD Athlon 4 i nowsze, a SSE2 pojawiło się w AMD Athlon 64.
Procesor 8086 Intela był pierwszym, w którym rejestry dostępne programowo są 16-bitowe. Z niewielkim uproszczeniem można przyjąć, że procesor o architekturze x86-32 widziany jest przez programistę jako szybki procesor 8086. Można stwierdzić, iż procesor pracuje wtedy trybie adresacji rzeczywistej bądź w trybie 16 bitów (chodź programowo są dostępne rejestry 32-bitowe wprowadzone w procesorze 80386). Instrukcje procesora 8086 stanowią podzbiór zbioru instrukcji np. procesora Intel Core i7. Programy mogą operować na ośmiu podstawowych rejestrach:
Należy wymienić jeszcze dwa inne rejestry procesora 8086:
Rejestry AX, BX, CX, DX, SI, DI, BP oraz SP w czasie wykonania programu mogą zawierać odpowiednio:
W procesorze 80386 w miejsce wymienionych wyżej rejestrów 16-bitowych wprowadzono rejestry 32-bitowe (i tak pozostało do procesorów o architekturze x86-32 pracujących w trybie 32-bitowym):
Począwszy od procesora 80386 mamy też do dyspozycji 32-bitowy rejestr wskaźników rozkazów EIP, a także 32-bitowy rejestr znaczników EFLAGS. W rejestrze EFLAGS oprócz znanych z procesora 8086 znaczników stanu oraz znaczników sterujących mamy również znaczniki systemowe.
Równolegle do procesora 8086 powstał koprocesor arytmetyczny 8087, a później dla kolejnych procesorów 80286 i 80386 koprocesory 80287 i 80387. Koprocesor arytmetyczny posiada 80-bitowe rejestry oraz listę instrukcji pozwalającą na stosunkowo proste wykonywanie nawet bardzo złożonych operacji matematycznych na liczbach zmiennoprzecinkowych. Począwszy od procesora i486 (80486) koprocesor arytmetyczny włączony został do procesora głównego przy zachowaniu wszystkich swoich funkcji. W procesorach o architekturze x86-32 występuje w postaci tzw. jednostki zmiennoprzecinkowej. Liczby zmiennoprzecinkowe są tam składowane w rejestrach od R0 do R7. Programista nie ma bezpośredniego dostępu do tych rejestrów lecz może się do nich odwoływać poprzez aliasy jakimi są rejestry od ST(0) do ST(7).
Kolejne rejestry zostały udostępnione w procesorze Pentium MMX, który był przejściowym modelem i dlatego mówi się raczej, że procesorze Pentium 2 zostało wprowadzone rozszerzenie MMX. Rozszerzenie to wprowadza osiem 64-bitowych rejestrów oznaczonych od MM0 do MM7.
W procesorze Pentium 3 wprowadzono tym razem osiem rejestrów o rozmiarze 128 bitów, które stanowią podstawowe zasoby rozszerzenia procesora o architekturze x86-32 o nazwie SSE. W procesorze Pentium 4 nie przybyło już żadnych dostępnych programowo rejestrów, a została jedynie rozbudowana lista instrukcji. Rozszerzenie procesora Pentium 4 nazwano zatem SSE2. Rejestry rozszerzeń SSE i SSE2 nazywamy XMM (oznaczane od XMM0 do XMM7). Później Intel wprowadził na rynek wersję procesora Pentium 4 z jądrem o nazwie Prescott, w którym wprowadzono rozszerzenie SSE3. W następnych wersjach procesorów pojawiły się jeszcze rozszerzenia SSE3 i SSE4.
Dane przetwarzane w programie mogą znajdować się w opisanych wyżej rejestrach tudzież w pamięci operacyjnej. Pamięć operacyjna ma organizację bajtową czyli każdy bajt w pamięci ma swój własny fizyczny adres. Podzielona jest ona na części zwane segmentami. Maksymalna wielkość pamięci operacyjnej zależy od wielkości magistrali adresowej. Każdy bajt w pamięci adresować będziemy przez adres logiczny (a nie fizyczny), który składa się z dwóch części:
Do określenia offsetu służą rejestry segmentowe. Procesor 8086 ma ich cztery, a procesor 80386 i kolejne ma ich sześć:
Jeżeli chodzi o tryb 64-bitowy procesorów to programista otrzymuje:
Źródło: Wróbel E., Praktyczny kurs asemblera. Wydanie II, Helion SA, 2011
Każy kolejny procesor firmy Intel z linii x86-32 był tak projektowany aby oprogramowanie działające na starszym modelu mogło być w dalszym ciągu używane.
Jeżeli chodzi o procesory AMD to zbiór instrukcji jednostki stałoprzecinkowej oraz zmiennoprzecinkowej (procesora głównego oraz koprocesora arytmetycznego) jest zgodny z firmą Intel. Podobnie jest z instrukcjami MMX poczynając od procesora AMD K6 2. W kolejnych procesorach K6 firma wprowadziła swoje rozszerzenie technologii MMX o nazwie 3DNow! bazujące na 64-bitowych rejestrach MMX. Można powiedzieć, że była to odpowiedź na SSE Intela. Instrukcje 3DNow! pozwalają wykonywać operacje na dwóch spakowanych wartościach zmiennoprzecinkowych pojedynczej precyzji. Kolejnym rozszerzeniem 3DNow!, czasem nazywanym 3DNow+, jest DSP. Intelowskie rozszerzenie SSE posiada AMD Athlon 4 i nowsze, a SSE2 pojawiło się w AMD Athlon 64.
Procesor 8086 Intela był pierwszym, w którym rejestry dostępne programowo są 16-bitowe. Z niewielkim uproszczeniem można przyjąć, że procesor o architekturze x86-32 widziany jest przez programistę jako szybki procesor 8086. Można stwierdzić, iż procesor pracuje wtedy trybie adresacji rzeczywistej bądź w trybie 16 bitów (chodź programowo są dostępne rejestry 32-bitowe wprowadzone w procesorze 80386). Instrukcje procesora 8086 stanowią podzbiór zbioru instrukcji np. procesora Intel Core i7. Programy mogą operować na ośmiu podstawowych rejestrach:
- AX (16 bitów): główny rejestr jednostki stałoprzecinkowej procesora, który służy jako akumulator dla argumentów instrukcji procesora oraz zapamiętania wyników:
- AH (8 bitów),
- AL (8 bitów),
- BX (16 bitów): rejestr bazowy będący wskaźnikiem do danych w pamięci (w segmencie danych):
- BH (8 bitów),
- BL (8 bitów),
- CX (16 bitów): licznik w operacjach na łańcuchach oraz w pętlach programowych:
- CH (8 bitów),
- CL (8 bitów),
- DX (16 bitów): rejestr danych będący rejestrem adresowym układów wejścia i wyjścia:
- DH (8 bitów),
- DL (8 bitów),
- SI (16 bitów): rejestr indeksowy stanowiący wskaźnik do danych (w operacjach na łańcuchach wskaźnik dla łańcucha źródłowego),
- DI (16 bitów): rejestr indeksowy stanowiący wskaźnik do danych (w operacjach na łańcuchach wskaźnik dla łańcucha przeznaczenia),
- BP (16 bitów): rejestr bazowy jako wskaźnik do danych w segmencie stosu,
- SP (16 bitów): wskaźnik szczytu stosu.
Należy wymienić jeszcze dwa inne rejestry procesora 8086:
- IP (16 bitów): wskaźnik instrukcji wskazujący adres w segmencie kodu, z którego kolejno pobierane będą instrukcje programu do wykonania,,
- FLAGS (16 bitów): rejestr znaczników:
- znaczniki stanu:
- CF (1 bit): znacznik przeniesienia (przyjmuje 1 gdy w wyniku wykonanej operacji nastąpiło przeniesienie z bitu najstarszego na zewnątrz lub też nastąpiła pożyczka z zewnątrz do bitu najstarszego,
- PF (1 bit): znacznik parzystości (ustawiany na 1 gdy w wyniku realizowanej operacji liczba bitów o wartości 1 w młodszym bajcie wyniku jest parzysta,
- AF (1 bit): znacznik przeniesienia pomocniczego (przyjmuje wartość 1 gdy nastąpiło przeniesienie z bitu 3 na 4 lub pożyczka z bitu 4 na 3),
- ZF (1 bit): znacznik zera (przyjmuje wartość 1 gdy wynik operacji jest równy zeru),
- SF: znacznik znaku (1 gdy najbardziej znaczący bit czyli bit znaku w otrzymywanym wyniku jest równy 1),
- OF: znacznik przepełnienia (przyjmuje wartość 1 gdy przy realizowaniu określonej operacji wystąpiło przeniesienie na bit znaku lub też z tego bitu pobrana została pożyczka ale nie wystąpiło przeniesienie lub pożyczka z bitu znaku),
- znaczniki sterujące:
- TF: znacznik pracy krokowej (stan równy 1 powoduje wprowadzenie procesora w tryb pracy umożliwiający wygenerowanie przerwania i przejście do specjalnych procedur obsługi po każdym wykonanym rozkazie),
- IF: znacznik zezwolenia na przerwanie (ustawiony w stan 1 powoduje odblokowanie systemu przerwań procesora),
- DF: znacznik kierunku (jeżeli ma wartość 0 to przetwarzanie łańcuchów odbywa się z inkrementacją adresów).
Rejestry AX, BX, CX, DX, SI, DI, BP oraz SP w czasie wykonania programu mogą zawierać odpowiednio:
- argumenty dla wykonywanych w programie operacji arytmetycznych i logicznych,
- argumenty służące do obliczania adresu w pamięci operacyjnej,
- wskaźniki do pamięci operacyjnej.
W procesorze 80386 w miejsce wymienionych wyżej rejestrów 16-bitowych wprowadzono rejestry 32-bitowe (i tak pozostało do procesorów o architekturze x86-32 pracujących w trybie 32-bitowym):
- EAX (32 bity),
- EBX (32 bity),
- ECX (32 bity),
- EDX(32 bity),
- ESI (32 bity),
- EDI (32 bity ,
- EBP (32 bity),
- ESP (32 bity).
Począwszy od procesora 80386 mamy też do dyspozycji 32-bitowy rejestr wskaźników rozkazów EIP, a także 32-bitowy rejestr znaczników EFLAGS. W rejestrze EFLAGS oprócz znanych z procesora 8086 znaczników stanu oraz znaczników sterujących mamy również znaczniki systemowe.
Równolegle do procesora 8086 powstał koprocesor arytmetyczny 8087, a później dla kolejnych procesorów 80286 i 80386 koprocesory 80287 i 80387. Koprocesor arytmetyczny posiada 80-bitowe rejestry oraz listę instrukcji pozwalającą na stosunkowo proste wykonywanie nawet bardzo złożonych operacji matematycznych na liczbach zmiennoprzecinkowych. Począwszy od procesora i486 (80486) koprocesor arytmetyczny włączony został do procesora głównego przy zachowaniu wszystkich swoich funkcji. W procesorach o architekturze x86-32 występuje w postaci tzw. jednostki zmiennoprzecinkowej. Liczby zmiennoprzecinkowe są tam składowane w rejestrach od R0 do R7. Programista nie ma bezpośredniego dostępu do tych rejestrów lecz może się do nich odwoływać poprzez aliasy jakimi są rejestry od ST(0) do ST(7).
Kolejne rejestry zostały udostępnione w procesorze Pentium MMX, który był przejściowym modelem i dlatego mówi się raczej, że procesorze Pentium 2 zostało wprowadzone rozszerzenie MMX. Rozszerzenie to wprowadza osiem 64-bitowych rejestrów oznaczonych od MM0 do MM7.
W procesorze Pentium 3 wprowadzono tym razem osiem rejestrów o rozmiarze 128 bitów, które stanowią podstawowe zasoby rozszerzenia procesora o architekturze x86-32 o nazwie SSE. W procesorze Pentium 4 nie przybyło już żadnych dostępnych programowo rejestrów, a została jedynie rozbudowana lista instrukcji. Rozszerzenie procesora Pentium 4 nazwano zatem SSE2. Rejestry rozszerzeń SSE i SSE2 nazywamy XMM (oznaczane od XMM0 do XMM7). Później Intel wprowadził na rynek wersję procesora Pentium 4 z jądrem o nazwie Prescott, w którym wprowadzono rozszerzenie SSE3. W następnych wersjach procesorów pojawiły się jeszcze rozszerzenia SSE3 i SSE4.
Dane przetwarzane w programie mogą znajdować się w opisanych wyżej rejestrach tudzież w pamięci operacyjnej. Pamięć operacyjna ma organizację bajtową czyli każdy bajt w pamięci ma swój własny fizyczny adres. Podzielona jest ona na części zwane segmentami. Maksymalna wielkość pamięci operacyjnej zależy od wielkości magistrali adresowej. Każdy bajt w pamięci adresować będziemy przez adres logiczny (a nie fizyczny), który składa się z dwóch części:
- adresu początku segmentu,
- adresu względem początku segmentu (offsetu).
Do określenia offsetu służą rejestry segmentowe. Procesor 8086 ma ich cztery, a procesor 80386 i kolejne ma ich sześć:
- CS (16 bitów) wskazuje segment z programem,
- DS (16 bitów) wskazuje segment z danymi,
- SS (16 bitów) wskazuje segment stosu,
- ES (16 bitów) wskazuje dodatkowe segmenty danych,
- FS (16 bitów) wskazuje dodatkowe segmenty danych,
- ES (16 bitów) wskazuje dodatkowe segmenty danych.
Jeżeli chodzi o tryb 64-bitowy procesorów to programista otrzymuje:
- rejestry ogólnego przeznaczenia, na których mapowane są 32-bitowe rejestry ogólnego przeznaczenia:
- RAX (64 bity),
- RBX (64 bity),
- RCX (64 bity),
- RDX (64 bity),
- RSI (64 bity),
- RDI (64 bity),
- RBP (64 bity),
- RSP (64 bity),
- nowe rejestry ogólnego przeznaczenia:
- R8 (64 bity),
- R9 (64 bity) ,
- R10 (64 bity),
- R11 (64 bity),
- R12 (64 bity),
- R13 (64 bity),
- R14 (64 bity),
- R15 (64 bity),
- nowe rejestry ogólnego przeznaczenia mapowane na rejestrach od R8 do R15:
- R8D (32 bity),
- R9D (32 bity) ,
- R10D (32 bity),
- R11D (32 bity),
- R12D (32 bity),
- R13D (32 bity),
- R14D (32 bity),
- R15D (32 bity),
- nowe rejestry ogólnego przeznaczenia mapowane na rejestrach od R8D do R15D:
- R8W (16 bitów),
- R9W (16 bitów),
- R10W (16 bitów),
- R11W (16 bitów),
- R12W (16 bitów),
- R13W (16 bitów)
- R14W (16 bitów),
- R15W (16 bitów),
- nowe rejestry ogólnego przeznaczenia mapowane na rejestrach od R8W do R15W:
- R8L (8 bitów),
- R9L (8 bitów),
- R10L (8 bitów),
- R11L (8 bitów),
- R12L (8 bitów),
- R13L (8 bitów),
- R14L (8 bitów),
- R15L (8 bitów),
- nowe rejestry XMM:
- XMM8 (128 bitów),
- XMM9 (128 bitów),
- XMM10 (128 bitów),
- XMM11 (128 bitów),
- XMM12 (128 bitów),
- XMM13 (128 bitów),
- XMM14 (128 bitów),
- XMM15 (128 bitów).
Źródło: Wróbel E., Praktyczny kurs asemblera. Wydanie II, Helion SA, 2011
Microsoft Foundation Classes
Struktura programu opartego na MFC składa się z dwóch pojęć związanych z aplikacją:
Aplikacja MFC posiada cztery podstawowe typy ("My" to nazwa naszej aplikacji):
Aby stworzyć nowy projekt MFC w środowisku Microsoft Visual Studio 2010 należy:
Aby wypisać nazwę pliku dokument stosujemy funkcję TextOut klasy CDC. Dodatkowo możemy określić położenie tekstu względem prostokąta otaczającego za pomocą SetTextAlign i jej następujących wartości argumentów:
Obiekt klasy CPrintInfo odgrywa podstawową rolę w procesie drukowania. Oto jej dane składowe:
- dokumentu czyli kolekcji danych w aplikacji, z którymi ma styczność użytkownik przy jednoczesnym wyborze tego czy użytkownik będzie korzystał Single Document Interface czy też Multiple Document Interface,
- widoku, który określa sposób w jaki dane będą wyświetlone w oknie oraz możliwe interakcje użytkownika z nimi (własna klasa widoku jest wyprowadzana z klasy CView).
Aplikacja MFC posiada cztery podstawowe typy ("My" to nazwa naszej aplikacji):
- CMyApp czyli klasę aplikacji,
- CMyWnd czyli klasę okna ramki,
- CMyView czyli klasę widoku, która definiuje w jaki w obszarze klienta okna utworzonego przez obiekt CMyWnd będą wyświetlane dane zawarte w CMyDoc,
- CMyDoc czyli klasę dokumentu definiującą dokument zawierający dane aplikacji.
Aby stworzyć nowy projekt MFC w środowisku Microsoft Visual Studio 2010 należy:
- uruchomić nowy projekt,
- jako szablon wybrać "Visual C++" i "MFC", a następnie "Aplikacja MFC",
- wprowadzić nazwę projektu i kliknąć "OK",
- w oknie "MFC Application Wizard" dla aplikacji SDI:
- wchodzimy w "Application Type",
- wciskamy pole "Single document",
- wyłącz opcję "Use Unicode libraries" (jeżeli jest aktywna to aplikacja spodziewa się wejścia w formacie Unicode, pliki są w formacie Unicode i na skutek tego nie można ich odczytać w programach, które oczekują formatu ASCII),
- określamy styl graficzny naszego programu,
- określamy w jaki sposób kod bibliotek MFC jest wykorzystywany w programie (aplikacje z łączeniem statycznym są szybsze natomiast te z łączeniem dynamicznym zapewniają lepsze użycie pamięci),
- wybieramy z menu po lewej stronie "Document Template Properties" i określamy jakie rozszerzenie ma tworzyć nasz program,
- przechodzimy do "User Interface Features" gdzie określamy takie opcje jak np. domyślna maksymalizacja okna,
- kolejna sekcja to "Advanced Features" i tutaj uaktywniamy opcje "Printing and print preview" (dodaje do menu "File" takie opcje jak np. "Page Setup", a kreator aplikacji dodaje kod obsługi) i "Context-sensitive Help (HTML)" (podstawowa obsługa pomocy zależnej od kontekstu),
- klikamy na "Generated Classes" po lewej stronie gdzie możemy sprawdzić jakie klasy zostaną wygenerowane w kodzie aplikacji i tak np. klasy bazowe to:
- CEditView: umożliwia prostę wielowierszową edycję tekstu, a także funkcje wyszukiwania, zamieniania oraz drukowania,
- CFormView: dostarcza widok będący formularzem (oknem dialogowym, które może zawierać kontrolki do wyświetlania danych oraz wprowadzania danych przez użytkownika),
- CHtmlEditView: rozszerza klasę CHtmlView i dodaje możliwość edytowania witryn HTML,
- CHtmlView: dostarcza widok, w którym można przeglądać strony WWW oraz lokalne pliki HTML,
- CListView: umożliwia korzystanie z architektury "dokument i widok" z kontrolkami list,
- CRichEditView: umożliwia wyświetlanie i edycję RTF,
- CScrollView: dostarcza widok, który automatycznie dodaje paski przewijania jeżeli wyświetlane dane tego wymagają,
- CTreeView: umożliwia korzystanie z architektury "dokument i widok" z kontrolkami drzewa,
- CView: dostarcza podstawowych możliwości przeglądania dokumentu,
- CChildFrame (dla MDI): dostarcza okno ramowe dla widoku dokumentu, który pojawia się wewnątrz okna aplikacji utworzone przez obiekt typu CMainFrame,
- jako klasę bazową klasy typu CMyView (gdzie "My" to nazwa projektu) wybierz np. CEditView aby automatycznie uzyskać podstawowe możliwości edycji tekstu,
- kliknij "Finish",
- dla aplikacji MDI robimy zmiany tylko w dwóch miejscach:
- w sekcji "Application Type" pozostawiamy domyślną opcję "Multiple documents",
- w sekcji "Generated Classes" pozstawiamy klasę CView jako klasę bazową CMyView.
- "My.vcxproj": to główny plik projektu zawierający informacje o wersji Visual C++, który wygenerował plik, a także informacje o platformach, konfiguracjach i cechach projektu wybranych w kreatorze aplikacji,
- "My.vcxproj.filters": plik filtrów projektu zawierający informacje o związku pomiędzy plikami a filtrami,
- "My.h": pliki nagłówkowe aplikacji (zawiera deklarację klasy CMyApp),
- "My.cpp": główny plik źródłowy zawierający klasę CMyApp oraz definicję klasy CAboutDlg,
- "My.rc": lista wszystkich zasobów Microsoft Windows, które używa program,
- "My.ico": plik ikon,
- "My.rc2": zawiera zasoby, które nie są edytowane przez Microsoft Visual C++ (wszystkie nieedytowalne przez edytor zasobów zasoby powinny zostać tutaj umieszczone),
- "My.reg": przykładowy plik pokazują rodzaj ustawień rejestracji struktury, które zostaną skonfigurowane,
- "MainFrm.h" i "MainFrm.cpp": zawiera klasę CMainFrame, która wywodzi się z CFrameWnd i kontroluje wszystkie właściwości ramki SDI,
- "LeftView.h" i "LeftView.cpp": zawiera klasę CLeftView wywodzącą się z CTreeView,
- "Toolbar.bmp": plik mapy bitowej użyty do stworzenia kafelkowych obrazów dla paska narzędzi (początkowy pasek narzędzi i pasek stanu są konstruowane w klasie CMainFrame),
- "MyDoc.h" i "MyDoc.cpp": klasa CMyDoc,
- "MyView.h" i "MyView.cpp": widok dokumentu i klasa CMyView,
- "StdAfx.h" i "StdAfx.cpp": pliki używane do budowanie prekompilowanych plików nagłówkowych i prekompilowanych plików typów,
- "Resource.h": standardowy plik zasobu, które definiuje nowe identyfikatory zasobu,
- "My.manifest": pliki manifestu aplikacji użyte przez Windows XP do opisu zależności aplikacji od konkretnych wersji zespołów "Side-by-Side".
- CMyApp:
- makrodefinicja "DECLARE_MESSAGE_APP": definiuje które funkcje klasy będą obsługiwały które komunikaty Windowsa,
- CMainFrame:
- składowe chronione "m_wndStatusBar" i "m_wndToolbar": egzemplarze klas MFC (odpowiednio CStatusBar i CToolBar) tworzące i zarządzające paskiem stanu pojawiającym się na dole okna aplikacji oraz paskiem narzędzi dostarczającym przyciski umożliwiające dostęp do standardowych elementów menu,
- CMyDoc:
- makrodefinicja "DECLARE_DYNCREATE": umożliwia dynamiczne tworzenie obiektu klasy poprzez jego syntezę danych odczytanych z pliku,
- makrodefinicja "DECLARE_MESSAGE_APP": obsługa komunikatów Windowsa przez jej funkcje składowe,
- CMyView:
- funkcja GetDocument: zwraca wskaźnik do obiektu dokumentu odpowiadającego widokowi.
- utworzenie zmiennej "theApp" typu CMyApp będącej obiektem aplikacji,
- wywołanie funkcji "WinMain" dostarczonej przez MFC,
- uruchomienie za pomocą "WinMain" funkcji "InitInstance", która tworzy szablon dokumentu, główne okno ramowe, dokument oraz widok,
- wywołanie za pomocą "WinMain" funkcji "Run", która uruchamia główną pętlę komunikatów pobierającą i przesyłającą komunikaty Windowsa.
Kategorie komunikatów:
- komunikat Windowsa: oprócz komunikatów "WM_COMMAND" są to standardowe komunikaty rozpoczynające się prefiksem "WM_",
- komunikaty powiadamiające z kontrolek: komunikaty "WM_COMMAND" przesyłane z kontrolek (takich jak lista wyboru) do okna, które utworzyło kontrolkę lub z okna potomnego do macierzystego,
- komunikaty poleceń: komunikaty "WM_COMMAND" pochodzące z elementów interfejsu użytkownika takich jak elementy menu i przyciski pasków narzędzi.
- "COMMAND": komunikat wysyłany gdy dany element menu został wybrany,
- "UPDATE_COMMAND_UI": przesyłany gdy menu powinno zostać uaktualnione zależnie od jego stanu.
- ContinuteRouting: przesyła komunikat do procedury o niższym priorytecie,
- Enable: włącza lub wyłącza stosowny element interfejsu,
- SetCheck: ustawia znacznik wyboru dla stosownego elementu interfejsu,
- SetRadio: włącza lub wyłącza przycisk w grupie przycisków opcji,
- SetText: ustawia tekst dla stosownego elementu interfejsu.
- "MM_TEXT": jednostką logiczną jest jeden piksel urządzenia o dodatnim x od lewej do prawej i dodatnim y od góry do dołu obszaru klienta okna,
- "MM_LOENGLISH": jednostką logiczną jest 0,01 cala o dodatnim x od lewej do prawej i dodatnim y od góry obszaru klienta okna w górę,
- "MM_HIENGLISH": jednostką logiczną jest 0,001 cala o kierunkach x i y jak dla "MM_LOENGLISH",
- "MM_LOMETRIC": jednostką logiczną jest 0,1 milimetra o kierunkach x i y jak dla "MM_LOENGLISH",
- "MM_HIMETRIC": ednostką logiczną jest 0,01 milimetra o kierunkach x i y jak dla "MM_LOENGLISH",
- "MM_ISOTROPIC": jednostka logiczna jest dowolnej długości jednak takiej samej na osi x i y (kierunki x i y są takie same jakd dla "MM_LOENGLISH"),
- "MM_ANISOTROPIC": ten tryb jest podobny do "MM_ISOTROPIC" jednak pozwala aby długość jednostki logicznej na osi x była inna niż na osi y,
- "MM_TWIPS": jednostką logiczną jest TWIP (0,05 punktu czyli 1/72 cala) o takich samych kierunkach x i y jak dla "MM_LOENGLISH".
- "PS_SOLID": rysuje ciągłą linię,
- "PS_DASH": rysuje linię przerywaną (ten styl linii jest prawidłowy tylko wtedy gdy pióro ma szerokość 1),
- "PS_DOT": rysuje linię złożoną z kropek (ten styl linii jest prawidłowy tylko wtedy gdy pióro ma szerokość 1),
- "PS_DASHDOT": rysuje linię składająca się na przemian z kresek i kropek (ten styl linii jest prawidłowy tylko wtedy gdy pióro ma szerokość 1), ,
- "PS_DASHDOTDOT": rysuje linię składająca się na przemian z kresek i dwóch kropek (ten styl linii jest prawidłowy tylko wtedy gdy pióro ma szerokość 1),
- "PS_NULL": pióro nic nie rysuje,
- "PS_INSIDEFRAME": pióro rysuje ciągłą linię jednak w przeciwieństwie do "PS_SOLID" punkty określające linię leżą na krawędzi pióra, a nie w jego środku więc rysowany obiekt nigdy nie wystaje poza opisujący prostokąt.
- "HS_HORIZONTAL": kreskowanie poziome,
- "HS_VERTICAL": kreskowanie pionowe,
- "HS_BDIAGONAL": kreskowanie w dół od lewej do prawej pod kątem 45 stopni,
- "HS_FDIAGONAL": kreskowanie w górę od lewej do prawej pod kątem 45 stopni,
- "HS_CROSS": kreskowanie poziome i pionowe,
- "HS_DIAGCROSS": kreskowanie pod kątek 45 stopni.
- "WM_LBUTTONDOWN": komunikat powstaje przy naciśnięciu lewego przycisku myszy,
- "WM_LBUTTONUP": komunikat powstaje przy zwolnieniu lewego przycisku myszy,
- "WM_MOUSEMOVE": komunikat powstaje przy przemieszczaniu myszy.
- liczba znaczników stanu wskazujących czy dane klawisze są wciśnięte:
- "MK_CONTROL": klawisz "Ctrl",
- "MK_LBUTTON": lewy przycisk myszy,
- "MK_MBUTTON": środkowy przycisk myszy,
- "MK_RBUTTON": prawy przycisk myszy,
- "MK_SHIFT": klawisz "Shift",
- obiekt CPoint definiujący pozycję kursora w momencie naciśnięcia lewego przycisku myszy.
- "R2_BLACK": wszystko jest rysowane na czarno,
- "R2_WHITE": wszystko jest rysowane na biało,
- "R2_NOP": operacje rysowania nie mają efektu,
- "R2_NOT": rysowanie przebiega zawsze w kolorze przeciwnym do wyświetlanego już na ekranie co zapewnia, że wyjście będzie zawsze widoczne,
- "R2_COPYPEN": rysowanie w kolorze pióra (domyślny),
- "R2_NOTCOPYPEN": rysowanie w kolorze przeciwnym do koloru pióra,
- "R2_MERGEPENNOT": rysowanie w kolorze powstałym z alternatywy bitowej koloru pióra i koloru przeciwnego do koloru tła,
- "R2_MASKPENNOT": rysowanie w kolorze powstałym z koniunkcji bitowej koloru pióra i koloru przeciwnego do koloru tła,
- "R2_MERGENOTPEN": rysowanie w kolorze powstałym z alternatywy bitowej koloru tła i koloru przeciwnego do koloru pióra,
- "R2_MASKNOTPEN": rysowanie w kolorze powstałym z koniunkcji bitowej koloru tła i koloru przeciwnego do koloru tła,
- "R2_MERGEPEN": rysowanie w kolorze powstałym z alternatywy bitowej koloru tła i koloru pióra,
- "R2_NOTMERGEPEN": rysuje w kolorze przeciwnym do "R2_MERGEPEN",
- "R2_MASKPEN": rysowanie w kolorze powstałym z koniunkcji bitowej koloru tła i koloru pióra,
- "R2_NOTMASKPEN": rysowanie w kolorze przeciwnym do koloru "R2_MASKPEN",
- "R2_XORPEN": rysowanie w kolorze powstałym z bitowej alternatywy wykluczającej koloru pióra i koloru tła,
- "R2_NOTXORPEN": rysowanie w kolorze przeciwnym do koloru "R2_XORPEN".
- tablica (klasa szablonu CArray): uporządkowany układ elementów, z którego każdy element może zostać pobrany z wykorzystaniem indeksu będącego liczbą całkowitą,
- lista (klasa szablonu CList): kolekcja uporządkowanych danych gdzie z każdym elementem powiązane są dwa wskaźniki, które wskazują na poprzedni i następny element na liście,
- mapa (klasa szablonu CMap): nieuporządkowana kolekcja danych gdzie każdemu elementowi przyporządkowany jest klucz używany do odzyskania elementu z mapy.
- GetHead: zwraca wskaźnik do początku listy,
- GetTails: zwraca wskaźnik do stopki listy,
- RemoveHead: usuwa pierwszy wskaźnik z listy,
- RemoveTail: usuwa ostatni wskaźnik z listy,
- GetNext: zwraca wskaźnik znajdujący się na pozycji określonej zmienną typu POSITION, która jest przesyłana jako referencja (wartość zmiennej jest aktualizowana aby wskazywała kolejny element listy),
- GetPrev: zwraca wskaźnik znajdujący się na pozycji określonej zmienną typu POSITION, która jest przesyłana jako referencja (wartość zmiennej jest aktualizowana aby wskazywała poprzedni element listy),
- GetAt: zwraca wskaźnik przechowywany na pozycji wskazywanej przez zmienną typu POSITION, która jest przesyłana jako argument,
- AddHead: dodaje na początku listy wskaźnik przesyłany jako argument,
- AddTail: dodaje na końcu listy wskaźnik przesyłany jako argument,
- RemoveAll: usuwa wszystkie elementy z listy,
- GetHeadPosition: zwraca pozycję elementu z początku listy,
- GetTailPosition: zwraca pozycję elementu z końca listy,
- RemoveAt: usuwa wskaźnik z pozycji listy,
- InsertBefore: wstawia nowy wskaźnik określony przez drugi argument przed pozycją określoną przez pierwszy argument,
- InsertAfter: wstawia nowy wskaźnik określony przez drugi argument za pozycją określoną przez pierwszy argument,
- Find: wyszukuje na liście wskaźnik, który jest taki sam jak wskaźnik określony przez argument,
- FindIndex: zwraca pozycję wskaźnika na liście określoną przesłanym jako argument indeksem będącym liczbą całkowitą,
- GetCount: zwraca liczbę elementów na liście,
- IsEmpty: zwraca prawdę jeżeli na liście nie ma żadnych elementów, a w przeciwnym razie fałsz.
- "pSender" to wskaźnik do bieżącego widoku (powstrzymuje on wywołanie funkcji składowej OnUpdate w widoku),
- "lHint" jest zmienną typu LAPARAM, który ma 32 bity w systemie Windows i może zostać użyty do przesłania informacji o obszarze, który ma zostać uaktualniony w obszarze klienta,
- "pHint" jest wskaźnikiem do obiektu mogącego dostarczyć informacji o fragmencie obszaru, który ma zostać zaktualizowany w obszarze klienta.
- "MapMode" może mieć następujące wartości:
- "MM_TEXT",
- "MM_LOENGLISH",
- "LO_METRIC",
- "MM_TWIPS",
- "MM_HEINGLISH",
- "MM_HIMETRIC",
- "Total" to całkowity obszar rysowania,
- "Page" definiuje odległość w poziomie i w pionie o jaką przewinąć stronę,
- "Line" definiuje odległość w poziomie i w pionie o jak przewinąć linie.
- "TPM_CENTERALIGN": wyśrodkowuje menu w poziomie względem współrzędnej x podanej jako drugi argument funkcji,
- "TPM_LEFTALIGN": umieszcza menu tak, że jego lewa strona jest wyrównana względem współrzędnej z podanej jako drugi argument funkcji,
- "TPM_RIGHALIGN": umieszcza menu tak, że jego prawa strona jest wyrównana względem współrzędnej x podanej jako drugi argument funkcji,
- "TPM_LEFTBUTTON": menu kontekstowe śledzi lewy przycisk myszy,
- "TPM_RIGHTBUTTON": menu kontekstowe śledzi prawy przycisk myszy.
- kontrolki statyczne: używane są dostarczania tytułów lub informacji opisowych,
- przyciski: dostarczają mechanizm wprowadzania danych jednym kliknięciem,
- paski przewijania: zwykle używane do przewijania tekstu lub obrazów,
- pola list: zawierają listę dostępnych możliwości wyboru,
- kontrolki edycji: pozwalają wprowadzić lub edytować wyświetlany tekst,
- listy kombinowane: przedstawiają listę dostępnych opcji, które można wybrać oraz dają możliwość samodzielnego wprowadzania tekstu.
- "MM_ISOTROPIC": w tym trybie Windows wymusza aby współczynnik skali dla osi x i y były równe,
- "MM_ANISOTROPIC": niezależne skalowanie osi x i y.
- "Window Origin": logiczne współrzędne lewego górnego rogu okna ustawiane przez wywołanie funkcji SetWindowOrg klasy CDC,
- "Window Entent": rozmiar okna określony we współrzędnych logicznych przez funkcję SetWindowExt klasy CDC,
- "Viewport Origin": współrzędne lewego górnego rogu okna we współrzędnych urządzenia (pikselach) ustawiane przez funkcję SetViewportOrg klasy CDC,
- "Viewport Entent": rozmiar okna we współrzędnych urządzenia (pikselach) ustawiany przez funkcję SetViewportExt klasy CDC.
- 0: tekst posiada ramkę, która jest zagłębiona w pasku zadań,
- "SET_NOBORDERS": tekst jest pisany bez obramowania,
- "SET_OWNERDRAW": tekst jest rysowany przez okno macierzyste,
- "SET_POPUP": tekst ma obramowanie, które wygląda jakby wystawało z paska zadań.
- makrodefinicję "DECLARE_DYNCREATE", która umożliwia dynamiczne tworzenie obiektów klasy CMyDoc przez platformę aplikacji podczas procesu serializacji wejścia,
- funkcję składową Serialize, która jest funkcją wirtualną i jest wywoływana w celu przeprowadzenia operacji serializacji wejścia i wyjścia na danych składowych klasy (każda serializowalna klasa musi ją zawierać),
- domyślny konstruktor klasy wykorzystywany przez platformę do składania obiektu podczas czytania z pliku, a poskładany obiekt jest następnie wypełniany danymi z pliku w celu ustawienia wartości danych składowych obiektu.
- funkcja OnPreparePrinting (składowa widoku) oblicza liczbę stron i wywołuje funkcję DoPreparePrinting,
- funkcja OnBeginPrinting (składowa widoku) alokuje zasoby Graphical Device Interface,
- funkcja StartDoc z klasy CDC,
- funkcja OnPrepareDC (składowa widoku) zmienia początek układu współrzędnych wziernika i ustawia atrybuty kontekstu urządzenia,
- rozpoczęcie pętli, która wykonuje się dopóki są jeszcze jakieś strony,
- funkcja StartPage klasy CDC,
- funkcja OnPrint (składowa widoku) drukuje nagłówki, stopki oraz bieżącą stronę,
- funkcja EndPage klasy CDC,
- koniec pętli,
- funkcja EndDoc klasy CDC,
- funkcja OnEndPrinting (składowa widoku) zwalniająca zasoby Graphical User Interface.
- "TA_LEFT",
- "TA_RIGHT",
- "TA_CENTER",
- "TA_TOP",
- "TA_BOTTOM",
- "TA_BASELINE": wyrównuje linię odniesienia czcionki użytej dla tekstu z punktem określającym pozycję tekstu.
Obiekt klasy CPrintInfo odgrywa podstawową rolę w procesie drukowania. Oto jej dane składowe:
- "m_PD": wskaźnik do obiektu CPrintDialog, który wyświetla okno dialogowe drukowania,
- "m_bDirect": jest ustawiana na "TRUE" przez platformę jeżeli operacja drukowania ma pominąć okno dialogowe drukowania (w przeciwnym razie ma wartość "FALSE"),
- "m_bPreview": składowa typu BOOL, której wartość to "TRUE" jeżeli wybrano "Print Preview" z menu "File" (w przeciwnym razie "FALSE"),
- "m_bContinuePrinting": składowa typu BOOL jest ustawiona na "TRUE" jeżeli platforma kontynuuje pętlę drukowania przedstawioną na schemacie, a "FALSE" jeżeli pętla jest skończona,
- "m_CurPage": wartość typu UINT przechowująca numer bieżącej strony,
- "m_nNumPreviewPages": wartość typu UINT określająca liczbę stron wyświetlanych w oknie podglądu wydruku,
- "m_lpUserData": jest okna typu LPVOID i przechowuje wskaźnik do obiektu, który tworzysz,
- SetMinPage: funkcja ustawiająca numer pierwszej strony dokumentu,
- SetMaxPage: funkcja ustawiająca numer ostatniej strony dokumentu,
- GetMinPage: zwraca numer pierwszej strony dokumentu,
- GetMaxPage: zwraca numer ostatniej strony dokumentu,
- GetFromPage: zwraca numer pierwszej strony dokumentu, który ma zostać wydrukowany,
- GetToPage: zwraca numer ostatniej strony dokumentu, który ma zostać wydrukowany.
- otworzyć okno tworzenia nowego projektu,
- wybrać jako szablon "MFC", a następnie "MFC DLL",
- w polu "Name:" podać nazwę biblioteki (w naszym przypadku będzie to "ExtDLLMy"),
- kliknąć "OK",
- w sekcji "Application Setting" zaznaczyć "MFC extension DLL",
- kliknąć "Finish".
- "ExtDLLMy.cpp": zawiera funkcję DllMain i jest głównym plikiem źródłowym biblioteki,
- "ExtDLLMy.def": informacje z tego pliku są używane podczas kompilacji (zawiera on nazwę DLL, a także można do niego dodać definicję tych elementów DLL, które mają być dostępne dla programów korzystających z biblioteki).
Subskrybuj:
Posty (Atom)