Wykład 2 Różnice pomiędzy językiem C++ a C# Podstawowe typy danych w C#: - Typy liczbowe oraz znakowy - Operatory - Konwersje typów podstawowych - Typy wartościowe i referencyjne - Łańcuchy Sterowanie przepływem - instrukcja warunkowa if else - instrukcja wyboru switch - pętle for, while, do while
Podstawowe typy danych Typy liczbowe oraz znakowy W C/C++ istniały typy danych nazywane typami prostymi dla odróżnienia od typów strukturalnych (tablice, struktury, klasy). W języku C# - tzw. typy proste są strukturami, a słowa kluczowe znane z C/C++, takie jak int lub double, są aliasami do nazw tych struktur. Przykładowo stała typu int, np. 1, jest instancją (egzemplarzem) struktury o nazwie System.Int32 i pozwala na dostęp do wszystkich jej metod. Poprawny jest zatem przykładowy ciąg instrukcji: int i = 10; string s = i.tostring(); lub nawet string s = 10.ToString(); 2
Kategoria Typy wartościowe Typy proste Opis Całkowite ze znakiem: sbyte, short, int, long Całkowite bez znaku: byte, ushort, uint, ulong Typy wyliczeniowe Struktury Typy referencyjne Klasy Typy dopuszczające wartość pustą Znaki unicode: char Zmiennoprzecinkowe: float, double Dziesiętne wysokiej precyzji: decimal Boole'owskie: bool Definiowane przez użytkownika w postaci enum E {...} Definiowane przez użytkownika w postaci struct S {...} Rozszerzenia wszystkich innych typów wartościowych o wartość null Ostateczna klasa bazowa wszystkich innych typów: object Łańcuchy Unicode: string Definiowane przez użytkownika w postaci class C {...} Interfejsy Tablice Delegacje Definiowane przez użytkownika w postaci interface I { } Jedno- i wielowymiarowe, na przykład int [] i int [,] Definiowane przez użytkownika w postaci na przykład delegate int D( ) 3
Tabela 2.1 Typy proste w C# Nazwa typu (słowo kluczowe, alias klasy) Klasa z przestrzeni nazw System Liczba zajmowanych bitów Zakres wartości Wartość domyślna bool Boolean true, false false sbyte SByte 8 bitów ze znakiem Od -128 do 127 0 byte Byte 8 bitów bez znaku Od 0 do 255 0 short Int16 2 bajty ze znakiem Od -32 768 do 32 767 0 int Int32 4 bajty ze znakiem Od -2 147 483 648 Do 2 147 483 647 long Int64 8 bajtów ze znakiem Od -9 223 372 036 854 775 808 Do 9 223 372 036 854 775 807 ushort UInt16 2 bajty bez znaku maks. 65 535 0 uint UInt32 4 bajty bez znaku maks. 4 294 967 295 0 ulong UInt64 8 bajtów bez znaku maks. 18 446 744 073 709 551 615 char Char 2 bajty Znaki Unicode od 0 do 65535 \0 0 0L 0L float Single 4 bajty (zapis zmiennoprzecinkowy) double Double 8 bajtów (zapis zmiennoprzecinkowy) decimal Decimal 16 bajtów (większa 2014-11-07 10:54 dokładność Języki... wykład mniejszy 2 zakres niż double Najmniejsza bezwz. 1.4 *10-45 0.0F Największa ok. 3.403 * 10 38 Najmniejsza 4.9 * 10-324 0.0D Największa 1.798 * 10 308 Najmniejsza wartość 10-28 0.0M Największa wartość 7.9*10 28 4
Tabela 2.2. Stałe liczbowe Zapis w kodzie C# Typ Opis 1 int Liczba całkowita ze znakiem 1U uint Liczba całkowita bez znaku 1L long Liczba całkowita 64-bitowa 1F, 1.0F float Liczba zmiennoprzecinkowa 1E0, 1.0E0, 1.0 double Liczba zmiennoprzecinkowa podwójnej precyzji 1M decimal Liczba zmiennoprzecinkowa o zwiększonej precyzji 01 Liczba ósemkowa 0x01 Liczba szesnastkowa 5
Tabela 2.3. Operatory nie istniejące w C/C++ Grupa operatorów Operator Opis Podstawowe x.y Dostęp do pola, metody, właściwości i zdarzenia a[n] Odwołanie do elementu tablicy lub indeksatora typeof checked, unchecked Zwraca zmienną typu System.Type opisującą typ lub klasę podaną jako argument, np. typeof(int).tostring() jest równy System.Int32 Kontrolują zgłaszanie wyjątku przekroczenia zakresu przez wynik operacji na liczbach całkowitych Jednoargumentowe (typ)x Jawne rzutowanie konwersja zmiennej x na typ typ. Uwaga (int)0.99 = 0 Porównanie wartości i typów is Porównanie typów, np. 1 is int = true as Rzutowanie równoznaczne z: wyrażenie is typ? (typ)wyrażenie:(typ)null 6
Konwersje typów podstawowych Oprócz możliwości wykonywania w kodzie C# tzw. jawnej konwersji typów (za pomocą operatora rzutowania) można doprowadzić do tzw. niejawnej konwersji typów (konwersji domyslnej). Ze względów bezpieczeństwa możliwa jest tylko konwersja niejawna z typu całkowitego na typ zmiennoprzecinkowy (ogólnie na typ o większej precyzji) oraz z typu o mniejszym zakresie wartości na typ o większym zakresie. Konwersja w drugą stronę ponieważ jej konsekwencją jest utrata informacji wymaga zastosowania operatora rzutowania. Przykładowo próba przypisania wartości typu double do zmiennej typu int spowoduje sygnalizację błędu. 7
Typy wartościowe i referencyjne Język C# jest konsekwentnie obiektowy. Obiektami są nawet stałe liczbowe. Z faktu, że wszystko jest w C# obiektem, nie wynika wcale, że wszystkie typy obiektów są klasami. Nie wszystkie obiekty są w C# instancjami klas. Oprócz klas istnieją także struktury. W C# struktura, tak jak w C++, może mieć nie tylko pola, ale także metody. Ograniczeniem struktur w stosunku do klas jest zakaz dziedziczenia zarówno ze zdefiniowanych struktur, jak i dziedziczenia przez struktury. Struktury mogą natomiast implementować interfejsy. Jednak najważniejsza różnica między klasami a strukturami pojawia się przy próbie stworzenia obiektu będącego instancją klasy lub struktury. Obiekt będący instancją klasy należy utworzyć za pomocą operatora new. Powstaje on w obszarze pamięci nazywanym stertą (ang. heap). W przypadku struktur jest inaczej. 8
Rozważmy przykładowe polecenia: (1) Int32 i; lub int i; (2) Button b; (3) Int32 i = new Int32(); lub int i = new int(); (4) Button b = new Button(); Ponieważ System.Web.UI.WebControls.Button jest klasą, wyrażenie (2) jest jedynie deklaracją zmiennej b, która jest niezainicjowaną referencją do obiektu typu Button. Aby powstał obiekt - instancja klasy, musimy użyć operatora new, czyli umieścić w kodzie polecenie (4). Obiekty - instancje struktur możemy tworzyć deklarując je. Polecenie (1) tworzy obiekt w obszarze pamięci nazywanym stosem (ang. stack). Obiekt ten jest utożsamiany ze zmienną i. Jeżeli nie użyjemy operatora new do wywołania konstruktora struktury, to pola tak stworzonego obiektu pozostaną niezainicjowane i obiekt taki nie może być użyty, tj. nie może być argumentem metody (poza metodami wymuszającymi przypisanie modyfikatorem out) ani operatorów poza operatorem przypisania z lewej strony. W przypadku struktur implementujących typy liczbowe inicjacja możliwa jest za pomocą stałej liczbowej np. Int32 i = 1; (1 jest też formalnie obiektem), lub przez użycie operatora new. 9
W przypadku użycia instrukcji typu (3) konstruktor domyślny inicjuje pola obiektu zerami, wartościami false lub null odpowiednio do ich typu. W celu utworzenia instancji struktury można użyć również przeciążonego konstruktora, który pozwala zainicjować pola struktury wartościami innymi niż domyślne, np.: Int32 i = new Int32(1); Operatory działające na strukturach zdefiniowano w taki sposób, że operują na stanie obiektu (wartości liczby). W języku C# - inaczej niż w C++ o wyborze miejsca gdzie tworzony jest obiekt (stos albo sterta) decyduje nie sposób jego utworzenia a typ. Z podziałem na struktury i klasy wiąże się jeszcze jedno rozróżnienie, a mianowicie podział na zmienne wartościowe oraz zmienne referencyjne. Używanie w C# trzeciego, dostępnego (ze względów historycznych) typu zmiennych wskaźników, jest oznaką złego gustu programisty. Taka aplikacja wymaga specjalnego oznaczenia jako niebezpieczna. 10
Zmienne typów wartościowych utożsamiane są z instancjami struktur znajdującymi się na stosie. Zmienne typów referencyjnych to referencje do instancji klas, które powstają na stercie. Utożsamienie zmiennej wartościowej z obiektem, który reprezentuje, jest całkowite. Polecenie kopiowania wartości int j = i; tworzy nowy obiekt j na stosie o wartości skopiowanej z i. W przypadku zmiennych referencyjnych łatwo jest odseparować sam obiekt i wskazującą na niego referencję. Kopiowanie referencji powoduje jedynie stworzenie nowej zmiennej wskazującej na ten sam obiekt: Button a=b;. Zmienna a zawiera adres wskazujący na obiekt b. 11
Łańcuchy W C# klasa System.String (alias string) implementuje napisy (łańcuchy) co znacznie ułatwia (w porównaniu z C++) ich przetwarzanie. W klasie string zdefiniowano przykładowo przeciążony operator + który pozwala łączyć łańcuchy i wiele użytecznych metod i właściwości. Najczęściej używane metody i właściwości klasy System.String przedstawiono w tabeli 2.4. Listing 2.1 ilustruje działanie metod zebranych w tej tabeli. W klasie System.String zdefiniowano przeciążone operatory dodawania + oraz +=, za pomocą których możemy łączyć ze sobą łańcuchy. Przykład działania tego operatora pokazano na zamieszczonym dalej listingu 2.2. W klasie System.String zdefiniowano również metodę Split(tablica_ograniczników) - nie wymieniono jej w tabeli 2.4. Metoda ta dzieli łańcuch na podłańcuchy według ograniczników przekazanych w tablicy. Zwraca tablicę podłańcuchów. Przykład zastosowania tej metody pokazano na listingu 2.3. 12
Tabela 2.4. Najczęściej używane właściwości i metody klasy string Metoda lub właściwość Opis Przykład użycia indeksator [] Zwraca znak na wskazanej pozycji "Świdnica"[0] to 'Ś' bool Equals(string) Porównuje łańcuch z podanym w argumencie "Świdnica".Equals("Wrocław") int IndexOf(char), int IndexOf(string) int LastIndexOf(char), int LastIndexOf(string) Zwraca pierwsze położenie litery lub łańcucha (-1 jeżeli nie znalazła) Zwraca ostatnie położenie litery lub znaku (-1 jeżeli nie ma) "Świdnica".IndexOf('i') zwróci 2 "Świdnica".LastIndexOf('i') zwróci 5 int Length Zwraca długość łańcucha (właściwość) "Świdnica".Length to 8 string Replace(string, string) string Substring(int,int) Zamienia wskazany fragment tekstu (I argument) innym (II argument) Zwraca fragment łańcucha od pozycji wskazanej I argumentem o długości równej argumentowi II "Świdnica".Replace("dn", "n") zwróci "Świnica" "Świdnica".Substring(3,3) zwróci "dni" string Remove(int,int) Usuwa fragment łańcucha "Świdnica".Remove(3,1) zwróci "Świnica" string Insert(int, string) string ToLower() string ToUpper() string Trim(), string TrimStart(), string TrimEnd() string PadLeft(int, char), string PadRight(int, char) bool EndsWith(string), bool StartsWith(string) Wstawia łańcuch przed znakiem na podanej pozycji Zmienia wielkość wszystkich liter Usuwa spacje z przodu i z tyłu łańcucha Uzupełnia łańcuch znakiem podanym jako II argument do osiągnięcia podanej długości Sprawdza, czy łańcuch kończy się lub rozpoczyna podanym fragmentem "Świdnica".Insert(7,"zank") zwróci "Świdniczanka" "Świdnica".ToUpper() zwróci "ŚWIDNICA" " Świdnica ".Trim() zwróci "Świdnica" "Świdnica".PadLeft(10,'*') zwróci "**Świdnica" "Świdnica".EndsWith("ca") zwróci true 13
Listing 2.1. 14
15
Listing 2.2. 16
17
Listing 2.3. 18
19
Operator dodawania (+ +=) w klasie string działa w ten sposób, że każde kolejne takie działanie tworzy nową kopię łańcucha, tzn. wszystkie cztery instrukcje stanowiące ciało pętli powtarzają się dla każdego fragmentu łańcucha źródłowego. Klasa System.Text.StringBuilder służy do tworzenia i modyfikowania łańcuchów znaków. Jej użycie pozwala wyeliminować niedogodności związane z tworzeniem każdorazowo kopii modyfikowanego łańcucha. W przykładzie tym użyto pętli foreach (specyficznej dla języka C#), która służy do przeglądania kolekcji obiektów. O niej nieco dalej. Najważniejsze składowe klasy System.Text.StringBuilder przedstawiono w tabeli 2.5. 20
Tabela 2.5. Najważniejsze składowe klasy StringBuilder Chars Length Metoda Mechanizm indeksowania Działanie Przypisuje lub zwraca długość obiektu StringBuilder Append() AppendFormat() Insert() Remove() Replace() Przeciążona metoda publiczna, która dodaje łańcuch znaków na koniec aktualnego obiektu StringBuilder Przeciążona metoda publiczna, która zamienia specyfikatory formatu aktualnego łańcucha na sformatowaną wartość obiektu Przeciążona metoda publiczna, która wstawia łańcuch znaków podany jako jej argument na określoną pozycję obiektu Usuwa określone znaki Przeciążona metoda publiczna, która zamienia wszystkie wystąpienia danych znaków na nowe znaki 21
Listing 2.4. using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace Przyklad_4_lancuchy { class Program { static void Main() { // Utworzenie łańcucha znaków string s1 = "Sterowanie przepływem danych,pętle,warunki"; // Stałe reprezentujące spacje i przecinki const char spacja = ' '; const char przecinek = ','; // Tablica znaków rozdzielajacych tekst char[] przerywniki = new char[] { spacja, przecinek }; StringBuilder wyjście = new StringBuilder(); int licznik = 1; // Rozdziela łańcuch na podłańcuchy i przegląda otrzymaną tablicę foreach (string podłańcuch in s1.split(przerywniki)) { // Metoda AppendFormat dodaje sformatowany łańcuch znaków wyjście.appendformat("{0}: {1}\n", licznik++, podłańcuch); } Console.WriteLine(wyjście); Console.ReadKey(); } } } 22