Modul2, Courseware Development Tools


Użycie zmiennych

0x08 graphic
Przegląd zagadnień

Podczas definicji algorytmu było wspomniane pojęcie dane, czyli coś co przetwarza, na czym pracuje algorytm. Dane w przypadku programu komputerowego nazywamy zmiennymi. Zmienną w pewnym uproszczeniu można zdefiniować jako pewien obszar pamięci (ciąg bitów), do którego można się odwołać przy pomocy pewnej nazwy, nadanej temu obszarowi przez programistę. Przy pomocy nazwy zmiennej, lub krócej zmiennej, możemy "manipulować" (zmieniać, pobierać wartość) bitami obszaru pamięci reprezentowanym przez zmienną. Przy tych "manipulacjach" wykorzystujemy pewne instrukcje zwane operatorami.

Po skończeniu tego modułu studenci powinni:

0x08 graphic
Definicja zmiennej

Definiując zmienną oprócz jej nazwy, czyli identyfikatora zmiennej, musimy podać jej typ. Typ określa:

czyli można powiedzieć, że typ definiuje zbiór wartości, jakie może przyjmować zmienna. Typ również określa zbiór operacji, jakie możemy przeprowadzać na zmiennej.

W tej części modułu studenci poznają:

oraz nauczą się

0x08 graphic
Wspólny system plików

Wspólny system plików (Common Type System - CTS) - określa w jaki sposób środowisko uruchomieniowe (CLR) definiuje i używa typy oraz jak nimi zarządza. Dostarcza również zorientowany obiektowo model oraz definiuje reguły, które muszą być stosowane we wszystkich językach programowania, których używane są do tworzenia oprogramowania na platformę .NET. Reguły te pozwalają częściom programu napisanych w różnych językach współpracować ze sobą. Mówiąc prościej, nasz program napisany w C#, może używać typów zdefiniowanych np. w bibliotece napisanej w Visual Basic-u.

Wszystkie typy w języku C#, możemy podzielić na dwie rozłączne kategorie:

Obie kategorie typów zostaną dokładnie omówione w dalszej części rozdziału.

Uwaga:
W języku C# wszystkie typy dziedziczą po klasie Object. Pojęcie dziedziczenia jest dokładnie omówione w kursie "Programowanie obiektowe".

0x08 graphic
Typy wbudowane

Język C# dostarcza pewien zbiór typów wbudowanych. Typy te są reprezentowane przez zarezerwowane wyrazy - słowa kluczowe. Każdy z typów wbudowanych ma swój odpowiednik (alians) w przestrzeni nazw System. Typy wbudowane możemy podzielić na następujące kategorie:

W poniższej tabeli zostały opisane wszystkie typy wbudowane. Zostało podane słowo kluczowe identyfikujące typ, nazwa typu w przestrzeni nazw System, liczba bajtów zajmowanych przez zmienne danego typu oraz przedział dopuszczalnych wartości.

Słowo kluczowe

Alians

Liczba bajtów

Przyjmowane wartości

bool

Boolean

1

true lub false

sbyte

SByte

1

od -128 do 127

byte

Byte

1

od 0 do 255

short

Int16

2

od -32768 do 32767

ushort

UInt16

2

od 0 do 65535

int

Int32

4

od -2147483648 do 2147483647

uint

UInt32

4

od 0 do 4294967295

long

Int64

8

od -9223372036854775808
do 9223372036854775807

ulong

UInt64

8

od 0 do 18446744073709551615

char

Char

2

od 0 do 65535
typ znakowy (Unicody)

float

Single

4

±1.5 × 10e−45 do ±3.4 × 10e38
pojedyncza precyzja (7 cyfr)

double

Double

8

±5.0 × 10−e324 to ±1.7 × 10e308
podwójna precyzja (15-16 cyfr)

decimal

Decimal

12

±1.0 × 10e−28 to ±7.9 × 10e28
(28-29 cyfr)

object

Object

-

typ podstawowy dla wszystkich innych typów

string

String

-

Napis - dowolny ciąg znaków

Wybór typu dla zmiennej jest determinowany przez jej zastosowane, czyli żądane wartości. W przypadku typów reprezentujących liczby rzeczywiste oprócz wielkości przedziału (jak duże liczby), bardzo ważna jest również precyzja - ilość cyfr znaczących. Tylko skończoną ilość liczb możemy zakodować na skończonej ilości bajtów.

0x08 graphic
Deklaracja zmiennych

Przed użyciem zmiennej musimy ją zadeklarować, czyli podać jej typ i nazwę np.:

int x;

Powyższa linijka rezerwuje cztery bajty pamięci (tyle zajmuje typ int) i nadaje temu obszarowi pamięci w naszym programie nazwę x.

Uwaga:
Kolejność deklaracji jest ważna tylko przy deklaracji zmiennych lokalnych, czyli deklaracji w obrębie bloku metody. W przypadku deklaracji zmiennych jako pól klasy, dostępne one są w całym bloku klasy, zarówno przed jak i po deklaracji. Podobnie jest z przestrzenią nazw. Nazwa typu zdefiniowanego bezpośrednio w bloku przestrzeni nazw lub bloku klasy jest dostępna w całym bloku przestrzeni nazw lub bloku klasy. Można powiedzieć, że przestrzeń nazw nie jest liniowa.
Zmienne możemy deklarować tylko w bloku metody, tzw. zmienne lokalne oraz w bloku klasy (struktury), tzw. pola klasy (struktury). W języku C# nie ma zmiennych globalnych, czyli deklarowanych bezpośredni w bloku przestrzeni nazw lub poza jakimkolwiek blokiem.

Można deklarować wiele zmiennych w pojedynczej deklaracji. Poszczególne nazwy zmiennych oddzielamy przecinkami. Przykład deklaracji trzech zmiennych typu double:

double a,b,c;

W miejscu deklaracji zmiennej lub zmiennym również możemy nadać wartość. W tym celu po nazwie zmiennej umieszczamy znak równości i następnie podajemy wartość zmiennej (literał lub wyrażenie, którego typ jest zgodny z typem zmiennej):

int p =10;

int q = 2*p, r = 4;
Uwaga:
Literały i wyrażenia zostaną omówione w dalszej części tego modułu.

Nazwy, które możemy nadawać zmiennym, czyli ich identyfikatory, muszą spełniać następujące reguły:

Istnieje też szereg wskazówek, co do tworzenia identyfikatorów. Nieprzestrzeganie ich nie powoduje błędów. Stosowanie się jednak do nich świadczy o dobrym stylu i może ułatwić późniejszą analizę.

Więcej na temat konwencji stosowanych przy tworzeniu nazw można znaleźć w MSDN pod tematem: “Naming Guidelines” oraz na stronach www: http://home.comcast.net/~lancehunt/CSharp_Coding_Standards.pdf, http://www.idesign.net/idesign/download/Idesign Csharp Coding Standard.zip

0x08 graphic
Literały

Literał jest to część kodu źródłowego, która reprezentuje konkretną wartość danego typu. Może służy na przykład do nadawania zmiennym żądanych wartości.

Literałom rzeczywistym bez dodanego sufiksu przyporządkowany jest typ double.
W przypadku literałów rzeczywistych, gdy wartość literału nie ma dokładnej reprezentacji w danym typie, ale mieści się w zakresie dopuszczalnych wartości danego typu, jest zaokrąglana. W przypadku, gdy wartość literału jest poza zakresem dopuszczalnych wartości dla danego typu rzeczywistego, generowany jest błąd kompilatora.

Do deklaracji zmiennej można dodać modyfikator const. Otrzymujemy w ten sposób stałą nazwaną, często nazywaną po prostu stałą.

const int SpeedLight = 300000; //km/s

Wartość stałych nazwanych musi być podana w miejscu deklaracji i później jej nie można zmieniać. Wartość wyrażenia inicjalizującego stałą nazwaną jest obliczana w czasie kompilacji. Wyrażenie zatem nie może zawierać odwołania do zmiennych. Może natomiast zawierać odwołania do innych stałych.
Przykładami stałych zdefiniowanych w bibliotece .NET są:

System.Math.PI; //liczba pi
System.Math.E; //podstawa logarytmu naturalnego

Zastosowanie stałych nazwanych, zamiast bezpośrednio literałów, ułatwia zrozumienie i analizę programu. Program staje się bardziej czytelny.

Uwaga:
Stała nazwana zdefiniowana jako pole klasy jest automatycznie polem statycznym tej klasy.

0x08 graphic
Pobieranie i wypisywanie wartości zmiennych

W programie niektóre wartości zmiennych muszą być wprowadzone przez użytkownika programu. W aplikacjach konsolowych wykorzystuje się do tego najczęściej metodę ReadLine klasy Console. Metoda ta udostępnia dane wprowadzone przez użytkownika w formie napisu - zmiennej typu string. Napis trzeba zamienić, przekonwertować, na wartość żądanego typu. Można wykorzystać do tego metody klasy Convert: ToInt32, ToBoolean, ToDecimal...

Czyli aby pobrać od użytkownika wartość liczby rzeczywistej należy napisać następujący kod:

string s = Console.ReadLine();
double x = Convert.ToDouble(s);

lub krócej:

double x = Convert.ToDouble(Console.ToString());

W celu wypisania wartości zmiennej na ekranie możemy użyć metody Write lub WriteLine w następujący sposób:

Console.WriteLine(nazwaZminnej);
Console.Write(nazwaZminnej);

Często gdy wypisujemy wartość zmiennej, łączymy to z pewnym opisem. Metoda Write i WriteLine umożliwia również wypisanie wartości kilku zmiennych w pojedynczym wywołaniu metody. Załóżmy, że mamy zmienne:
x = 10, y = 20, z = 30. Plecenie:

Console.WriteLine("x={0}, y={1}, z={2}",x,y,z);

spowoduje wypisanie na ekranie:

x=10, y=20, z=30

Z powyższego przykładu widać, że znaki {index} zostały zastąpione wartością odpowiedniej zmiennej. Index wskazuje numer pozycji argumentu, którego wartość należy wstawić w danym miejscu. Argumenty indeksujemy od zera.

Uwaga:
Gdy wartość indeksu jest większa lub równa liczbie argumentów zgłaszany jest wyjątek.

Gdy chcemy wypisać znak nawias klamrowy otwierający {, musimy podać dwa nawisy klamrowe otwierające {{, aby wyświetli znak nawias klamrowy zamykający }, należy podać dwa nawisy klamrowe zamykające }}.

W celu zademonstrowania wprowadzania i wypisywania wartości zmiennych napisz program obliczający sumę dwóch liczb rzeczywistych. Gotowy program jest dostępny w katalogu Kurs\Demo\Modul2\Modul2.sln projekt Dodawanie, gdzie Kurs jest katalogiem gdzie skopiowano pliki kursu

  1. Uruchom Visual Studio

  2. Utwórz aplikację konsolową o nazwie: Dodawanie

  3. W metodzie Main napisz następujący kod:

static void Main(string[] args)

{

double x, y, suma;

Console.Write("Podaj x: ");

x = Convert.ToDouble(Console.ReadLine());

Console.Write("Podaj y: ");

y = Convert.ToDouble(Console.ReadLine());

suma = x + y;

Console.WriteLine("{0}+{1}={2}", x, y, suma);

Console.ReadKey();

}

  1. Zbuduj i uruchom program.

  2. Jako wartość zmiennej x wprowadź:
    2.5 Enter
    Prawdopodobnie program został przerwany. Został zgłoszony wyjątek. Spowodowane jest to tym, że metody klasy Convert przy zamianie wykorzystują domyślnie informacje o formatowaniu liczb, dat, godziny, znaku symbolu dziesiętnego itp. z ustawień systemu (Panel Sterowania, Opcje regionalne i językowe). W przypadku, gdy wszystkie formaty ustawione są dla Polski, symbolem dziesiętnym jest przecinek.
    Uwaga:
    Tak naprawdę ustawienia regionalne (formatowanie liczb, daty) zawarte są we właściwościach bieżącego wątku (Thread.CurrentThread.CurrentCulture) i można je modyfikować w programie, czyli mogą być niezależne od ustawień systemowych.

  3. Z menu Debug wybierz pozycję Stop Debugging.

  4. Uruchom program ponownie i wprowadź wartości zmiennych w odpowiednim formacie.

Wypisywaną wartość można poddać formatowaniu. Uzyskuje się to przez dodanie kilku znaków między nawiasy klamrowe. Oto postać ogólna:

{index[,n][:symbol]}

index - jak już wspominano, numer argumentu który należy wypisać.

n - liczba pozycji (miejsc), które zajmuje dana wartość. Brakujące miejsca uzupełniane są spacjami. Wartość dodatnia oznacza wyrównywanie do prawej, ujemna do lewej.

symbol - pewna litera oznaczająca sposób formatowania liczb. Po literze może wystąpić liczba z przedziału od 0 do 99 oznaczająca precyzję. Dostępne litery:

Więcej informacji można znaleźć w MSDN Library pod tematem "Standard Numeric Format Strings".

Przykłady zastosowania formatowania można znaleźć w programie Kurs\Demo\Modul2\Modul2.sln projekt Formatowanie, gdzie Kurs jest katalogiem gdzie skopiowano pliki kursu.

W podobny sposób, możemy tworzyć napisy przy pomocy metody Format klasy String:

string s;
s = String.Format("{0} + {1} = {2}", 2, 2, 2+2);

Zmienna s będzie zawierała następujący napis: 2 + 2 = 4.

0x08 graphic
Typ wartości

Jak wspominano wcześniej w tym module, każdy typ należy do jednej z dwóch kategorii typów: typy wartości lub typy referencyjne.

Do typów wartości należą:

Zmienne typu wartości bezpośrednio zawierają dane, dlatego też niektórzy nazywają typ ten typem bezpośrednim. Każda zmienna typu wartości reprezentuje oddzielny obszar pamięci, dlatego operacja na jednej zmiennej nie wpływa na drugą. Zmienne lokalne przechowywane w obszarze pamięci zwanym stosem. Z miejscem przechowywania i sposobem zarządzania tym obszarem związany jest czas życia zmiennej. Czas życia zmiennej możemy zdefiniować jako czas, przez który jest zarezerwowana pamięć dla zmiennej. Pamięć jest zarezerwowana dla zmiennej lokalnej od miejsca jej zadeklarowania do końca bloku, w którym została zadeklarowana.

{
double x; //początek "życia" zmiennej x
...
{
...
int y; ////początek "życia" zmiennej y
...
} //koniec "życia" zmiennej y
...
} //koniec "życia" zmiennej x

Uwaga:
Zasada działania stosu zostanie omówiona dokładnie w rozdziale 11, natomiast pojęcie zmiennej lokalnej w rozdziale 8 tego kursu.

Przed użyciem zmienna musi być zainicjalizowana - musi mieć nadaną wartość:

int x;
Console.Write(x); //Błąd kompilacji

Zmienne typu wartości posiadają zawsze jakąś wartość, nie mogą być null.

Uwaga:
W wersji C# 2.0 dla wszystkich typów wartości wprowadzono pewną formę typu (nullable types), której zmienne mogą przyjmować wartości null (wartość nieokreślona). Typ ten uzyskujemy przez dodanie do końca nazwy typu wartości znaku zapytania ?.

int? x;
x = null; //OK
int y;
y = null; //Błąd kompilacji

Typy nullable wprowadzono, aby ułatwić współpracę z bazami danych.

0x08 graphic
Typ wyliczeniowy

Typ wyliczeniowy jest to typ definiowany przez użytkownika (programistę). Stosujemy go, gdy chcemy, aby zmienne tego typu mogły przyjmować wartości z określonego zestawu stałych. Definiuje się go przy pomocy słowa kluczowego enum, po którym występuje nazwa nowego typu, a następnie w nawiasach klamrowych podajemy nazwy symboliczne dopuszczalnych wartości, oddzielone przecinkami. Nazwy, jakie możemy nadawać typom wyliczeniowym oraz poszczególnym elementom, podlegają tym samym zasadom co identyfikatory zmiennych. Oto przykład definicji tego typu:

enum PoryRoku {Wiosna, Lato, Jesien, Zima};

Domyślnie pierwszy element ma przypisaną wartość 0, wartość każdego kolejnego elementu jest większa o jeden od wartości poprzednika. Można również przypisać w sposób jawny wartości poszczególnym elementom np.:

enum Kolory{czerwony=4, zielony, niebieski=7};

Element zielony ma w sposób niejawny przypisaną wartość 5.

Wartości elementom typu wyliczeniowego mogą być nadawane w dowolnej kolejności, a nawet można im przypisać wartości jednakowe, gdy kilka symboli oznacza tę samą wartość.

Uwaga:
Typy wyliczeniowe, jak inne typy definiowane przez użytkownika (klasy, struktury), nie można definiować wewnątrz bloku metody.

Oto prosty przykład użycia typu PoryRoku:

PoryRoku sezon = PoryRoku.Wiosna;

Console.WriteLine("Ulubiona pora roku: {0}", sezon);

Na ekranie zostanie wypisane:

Ulubiona pora roku: Wiosna.

Widać więc, że zamiast wartości liczbowej zmiennej zostanie wypisana wartość symboliczna.

Przykłady zastosowania typu wyliczeniowego można znaleźć w programie Kurs\Demo\Modul2\Modul2.sln projekt PoryRoku oraz Konsola, gdzie Kurs jest katalogiem gdzie skopiowano pliki kursu. Konwersja wykorzystywana w projekcie PoryRoku jest opisana w dalszej części tego modułu.

Uwag:
Przy definicji typu wyliczeniowego można podać, jaki typ całkowity jest typem bazowym dla definiowanego typu wyliczeniowego. Typem bazowym może być dowolny tym całkowity poza typem char.

enum PoryRoku : byte {Wiosna, Lato, Jesien, Zima};

Przy tworzeniu nazwy typu wyliczeniowego, podobnie jak w przypadku nazw innych typów, stosuje się format PascalCase.

Stosowanie typu wyliczeniowego, podobnie jak stałych nazwanych czyni nasz kod bardziej czytelnym. Oprócz tego:

0x08 graphic
Typ referencyjny

Drugą kategorią typów są tak zwane typy referencyjne. Do typów referencyjnych zaliczamy:

Klasy, interfejsy oraz delegacje zostaną dokładnie omówione w kursie "Programowanie obiektowe". Tablice zostaną omówione w rozdziale szóstym i siódmym tego kursu.

Zmienne typów referencyjnych zawierają odwołanie, nie zawiera bezpośrednio danych. Dane (obszar pamięci) do których odwołuje się zmienna nazywamy obiektem. Zmienne, które nie odwołują się do żadnego obiektu, posiadają wartość null, nie mylić ze zmiennymi niezainicjalizowanymi. Często stosuje się pewien skrót myślowy i zmienną nazywa się obiektem danego typu. Obiekty przechowywane są na w obszarze pamięci, który nazywa się zarządzą stertą.
Dwie zmienne danego typu referencyjnego mogą odwoływać się do tego samego obiektu, więc operacja na jednej zmiennej może zmieć dane innej zmiennej.

Obiekty tworzy się przy pomocy słowa kluczowego new, chociaż istnieją pewne uproszczenia składni np. typ string. Przykład utworzenia obiektu klasy Random, która reprezentuje generator liczb pseudolosowych.

Random r; //deklaracja zmiennej
//r.Nex(0,101); //błąd kompilatora, zmienna
//niezainicjalizowana
r = null; //inicjalizacja wartością null
r = new Random(); //utworzenie obiektu klasy
//Random
int i = r.Next(0,101); //losowanie liczby całkowitej
//z przedziału <0;100>

Uwaga:
Użycie zmiennej referencyjnej o wartości null powoduje generację wyjątku w czasie działania programu.

Oczywiście zmienną możemy zainicjalizować w miejscu deklaracji:

Random r = new Random();

Na zmienną r stosując skrót myślowy można powiedzieć, że jest obiektem klasy Random.

Poniższy kod:

string s1 = "Ala ma kota";

string s2 = s1;

0x08 graphic
można przedstawić graficznie w następujący sposób:

Czas życia obiektu jest zarządzany przez proces automatycznego odzyskiwania pamięci (garbage collection). Nie jesteśmy wstanie określić dokładnie miejsca w programie, kiedy pamięć zajmowana przez obiekt zostanie zwolniona, czyli kiedy obiekt zostanie usunięty z pamięci. Możemy być jednak pewni, że proces automatycznego odzyskiwania pamięci nie usunie obiektu, jeżeli istnieje przynajmniej jedna zmienna, która odwołuje się do danego obiektu.

0x08 graphic
Konwersja typów

Skopiowanie wartości zmiennej do zmiennej innego typu nazywamy konwersją. Konwersja oczywiście nie jest możliwa między dowolnymi dwoma typami. W języku C# istnieje szereg konwersji standardowych.

Uwaga:
W języku C# dla typów użytkownika może definiować własne metody konwersji. Szerzej o tym jest napisane w kursie "Programowanie obiektowe".

Konwersje standardowe możemy podzielić na:

Konwersja niejawna zachodzi automatycznie. Programista nie musi jej oznaczać:

int x = 10;

long y = x; //konwersja z typu int do typu long

W przypadku konwersji niejawnej nigdy nie ma utraty wartości, może być tylko utracona precyzja - dokładność. Można ją porównać do przelewania zawartości pojemnika o małej objętości do pojemnika o dużej objętości. Konwertować niejawnie można z typu "małego" do typu "dużego". "Duży" typ zawiera wartości typu "małego". Poniższa tabela przedstawia standardowe konwersje niejawne w języku C#.

Z

Do

sbyte

short, int, long, float, double, decimal

byte

short, ushort, int, uint, long, ulong, float, double, decimal

short

int, long, float, double, decimal

ushort

int, uint, long, ulong, float, double, decimal

int

long, float, double, decimal

uint

long, ulong, float, double, decimal

long, ulong

float, double, decimal

float

double

char

ushort, int, uint, long, ulong, float, double, decimal

Konwersje jawną można porównać do przelewania z dużego naczynia do małego. Przelewając ryzykujemy, że część płynu może się nie zmieścić w małym naczyniu i się rozleje. W przypadku konwersji jawnej zachodzi, więc obawa o utratę wartości. Programista musi poinformować, że dokonuje tej konwersji świadomie. Konwersję jawną zapisujemy przy pomocy operatora konwersji:

short s =300;
//byte b = s; //błąd, musi być konwersja jawna
byte b = (byte)i; //OK
Console.WriteLine(b);

Na ekranie zostanie wypisane:

44

Została utracona wartość liczby. Chcąc kontrolować takie sytuacje można użyć polecenia checked.

checked {
int n = int.MaxValue;
Console.WriteLine(++n);
// OverflowException
}

lub operatora checked

int m = int.MaxValue;
int n = checked(m+1); // OverflowException
short s= 300;
byte b = checked((byte)s); // OverflowException

Wyraz checked powoduje, że w czasie wykonania programu jest kontrolowane, czy nie nastąpiło przepełnienie. Wystąpienie przepełnienia powoduje rzucenie wyjątku. Wyjątki zostaną przybliżone w następnym rozdziale.

Przełącznik kompilatora /checked+ domyślnie sprawdza przepełnienie. Domyślną opcją kompilatora jest /checked-, wyłączenie sprawdzania przepełnienia. Przy włączonej opcji kompilatora sprawdzenia przepełnienia, można ją wyłączyć przy pomocy słowa unchecked. Stosuje się je w identyczny sposób jak słowo checked.

Uwaga:
checked i unchecked dotyczą tylko liczb całkowitych.

Literały rzeczywiste traktowane są jako typ double, więc następujący kod powoduje błędy kompilatora:

float f = 4.2;
decimal d = 2.4;

Należy to zapisać to:

float f = (float)4.2;
decimal d = (decimal)2.4;

lub krócej

float f = 4.2f;
decimal d = 2.4d;

Kopiując wartość zmiennej typu wyliczeniowego do zmiennej typu całkowitego i dokonując konwersji odwrotnej, również musimy stosować konwersję jawną,.

enum PoryRoku {Wiosna, Lato, Jesien, Zima};
...
int i = 2;
PoryRoku poryRoku = (PoryRoku)i;
int j = (int)poryRoku;

Uwaga:
Podczas konwersji nie jest sprawdzane, czy dana wartość całkowita ma swoją reprezentację w danym typie wyliczeniowym - nie jest zgłaszany żaden błąd, ani nie ma generacji żadnego wyjątku.

W języku C# istnieje jeszcze jedna standardowa konwersja - opakowanie (boxing). Opakowanie wartości polega na niejawnym przekształceniu typu wartości w typ object.

int ilosc = 10;
object obj = i; //opakowanie

W wyniku operacji opakowania na zarządzanej stercie tworzona jest instancja obiektu, do której jest dowiązana zmienna obj. Wartość zmiennej ilosc została skopiowana do obiektu obj. Zmienna iosc oraz obiekt obj są niezależne. Zmiana wartości obiektu obj nie wpływa na wartość zmiennej ilosc.

Operacja odwrotna do opakowania - rozpakowanie musi być wywoływana jawnie. Kompilator musi być poinformowany, jaki typ wartości ma uzyskać z obiektu na zarządzanej stercie. Podczas wykonywania rozpakowania następuje sprawdzenie, czy instancja obiektu zawiera wartość żądanego typu. Jeżeli jest tak w istocie, to wartość zostanie rozpakowana. W przeciwnym wypadku zostanie zgłoszony wyjątek - InvalidCastException. Więcej informacji na temat wyjątków można znaleźć w następnym rozdziale.

int ilosc = 10;
object obj = i; //opakowanie
int ilosc2 = (int)obj; //rozpakowanie

double d = (double)obj; //wyjątek

0x08 graphic
Operatory

Dla wbudowanych typów danych w językach programowania wprowadza się pewną liczbę prostych, standardowych operacji. Operacje te wyrażone są przy pomocy ciągu znaków, które nazywamy operatorami. Ciąg znaków składa się najczęściej z pojedynczego znaku lub dwóch znaków. Przykładem operatora jest znak równości = - operator przypisania. Oznacza on skopiowanie wartości zmiennej lub wyrażenia znajdującego się po prawej stronie do zmiennej znajdującej się po lewej stronie znaku równości. Pojęcie wyrażenie, występujące w poprzednim zdaniu, oznacza ciąg operatorów i argumentów. Wyrażenie może dawać w wyniku pewną wartość i powodować efekty uboczne. Argumentem może być identyfikator zmiennej, literał, wywołanie metody, nazwa typu lub inne wyrażenie. Wyrażenie może składać się tylko z pojedynczego argumentu - identyfikatora zmiennej, literału, wywołania metody.

Po skończeniu tej części modułu studenci będą:

Uwaga:
Dla typów definiowanych przez użytkownika można przeciążyć, zdefiniować własną wersję większości operatorów. Temat przeciążenie operatorów jest omawiany w kursie "Programowanie obiektowe".

Predefiniowane operatory w C#

0x08 graphic

Omawiając operatory podzielimy je na grupy.

0x08 graphic
Właściwości operatorów

Operatory (funkcje operatorowe) charakteryzują się pewnymi właściwościami. Należą do nich:

Ze względu na liczbę argumentów operatory możemy podzielić na:

Priorytet operatorów to inaczej kolejność działań. W wyrażeniach zawierających wiele operatorów priorytet (precedence) kontroluje, w jakiej kolejności poszczególne operatory są "obliczane". Rozważmy prosty przykład:

double x = 10, y = 5, z = 2;
double a = x + y * z;

Wartością zmiennej a będzie 20, a nie 30, ponieważ najpierw zostanie wykonane mnożenie, ma wyższy priorytet, a dopiero następnie dodawanie, czyli identycznie jak w matematyce. Kolejność działań możemy zmieniać stosując nawiasy. W języku C# do ustalenia kolejności działań możemy stosować tylko nawiasy okrągłe, wielokrotnie je zagnieżdżając.

double x = 10, y = 5, z = 2;
double a = (x + y) * z; //a = 30
double b = (2*(x+y) + z) * 3; //b= 96

Kolejność obliczeń wartości zmiennej b jest następująca:

Nawiasy okrągłe stosowane są nie tylko w celu zmiany kolejności działań, ale również, aby wyrażenie stało się bardziej czytelne.
Poniższa tabela przedstawia operatory w kolejności od najwyższego priorytetu do najniższego. Operatory należące do tej samej kategorii mają ten sam priorytet.

Kategoria

Operatory

Operatory o najwyższym priorytecie

x.y - dostęp do składowej - kropka
f(x) - wywołanie metody
a[x] - operator indeksowania []
x++ - inkrementacja przyrostkowa
x-- - dekrementacja przyrostkowa
new, typeof, checked, unchecked

Jednoargumentowe (unary)

+, -, - operatory jednoargumentowe plus i minus
! - negacja logiczna
~ - negacja bitowa (dopełnienie do dwóch)
++x - inkrementacja przedrostkowa
--x - dekrementacja przedrostkowa
(T)x - operator rzutowania

Operatory multiplikatywne

* - mnożenie
/ - dzielenie
% - reszta z dzielenia

Operatory addytywne

+ - dodawanie
- - odejmowanie

Operatory przesunięcia

<<, >>

Relacyjne i sprawdzające typ

<, >, <=, >=
is, as

Równości

==, !=

Iloczyn bitowy
(Logical AND)

&

Różnica symetryczna
(Logical XOR)

^

Suma bitow
(Logical OR)

|

Koniunkcja
(Conditional AND)

&&

Alternatywa
(Conditional OR)

||

Wyrażenie warunkowe

? :

Operatory przypisania

=, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=

Nasuwa się pytanie, co będzie, jeżeli w wyrażeniu występują operatory o tym samym priorytecie. Kolejność działań w takim wypadku jest ustawiana na podstawie właściwości zwanej łącznością operatorów. Rozważmy następujący przykład:

double x = 10, y = 5, z = 2;
double a = x - y - z;

Do zmiennej a zostanie podstawiona wartość 3, ponieważ najpierw zostanie wykonane odejmowanie x-y, a następnie od powyższego wyniku odjęta zostanie wartość z. Działania są wykonywane od lewej do prawej strony. O tak działających operatorach mówi się, że są lewostronnie łączne.
Rozważmy teraz inny przykład:

double x = 10, y = 5, z = 2;
double a = x = y = z;

Wszystkie zmienna x, y, z, a, po wykonaniu tego kodu będą miały wartość 2, ponieważ przypisanie jest wykonywane od prawej do lewej strony. Czyli najpierw zostanie wykonane y = z, gdzie wartością tego wyrażenia jest wartość stojąca po prawej stronie znaku równości. Wartość wyrażenia y = z, czyli w tym przypadku 2, zostanie następnie przypisana do x. O tak działających operatorach mówi się, że są prawostronnie łączne.
Do operatorów prawostronnie łącznych należą wszystkie operatory jednoargumentowe, operatory przypisania oraz wyrażenie warunkowe ( ? :).
Natomiast do operatorów lewostronnie łącznych należą wszystkie operatory dwuargumentowe poza operatorami przypisania.

Typ argumentów stojących obok operatora wpływa na jego znaczenie. I tak operator +, gdy stoją obok niego wartości numeryczne oznacza dodawanie, natomiast w przypadku, gdy stoją zmienne typu string, oznacza łączenie napisów (konkatenację).
W przypadku operatorów działających na typach numerycznych należy zwrócić uwagę na proces zwany promocją typów.
W przypadku operatorów jednoargumentowych: +, -, ~, gdy ich argumentem jest zmienna typu sbyte lub byte lub short lub ushort lub char, automatycznie jej typ jest konwertowany do typu int. Dodatkowo dla operatora - zmienna typu uint jest konwertowana do typu long. Wobec powyższego poniższy kod spowoduje błąd kompilator, niedozwolonej konwersji niejawnej.

short x = 10;
short y = +x;

W przypadku natomiast operatorów dwuargumentowych: +, -, *, /, %, &, |, ^, ==, !=, >, <, >=, <= regułę promocji można podać w następujących punktach, których kolejność ma znaczenie:

  1. Jeżeli jeden argument jest typu decimal, drugi argument jest konwertowany do typu decimal, albo jest generowany bład kompilacji gdy konwercja niejawna jest niedozwolona (drugi argument jest typu double lub float).

  2. W przeciwnym wypadku, jeżeli jeden argument jest typu double drugi argument jest konwertowany do typu double.

  3. W przeciwnym wypadku, jeżeli jedne argument jest typu float drugi argument jest konwertowany do typu float.

  4. W przeciwnym wypadku, jeżeli jeden argument jest typu ulong drugi argument jest konwertowany do typu ulong, albo jest generowany bład kompilacji gdy drugim argumentem jest liczba ze zankiem (sbyte, short, int, long).

  5. W przeciwnym wypadku, jeżeli jeden argument jest typu long drugi argument jest konwertowany do typu long.

  6. W przeciwnym wypadku, jeżeli jeden argument jest typu uint drugi argument jest typu sbyte, short lub int oba argumenty konwertowane są do typu long.

  7. W przeciwnym wypadku, jeżeli jeden argument jest typu uint drugi argument jest konwertowany do typu uint.

  8. W przeciwnym wypadku, oba argumenty konwertowane są do typu int.

Wobec powyższego poniższy kod spowoduje błąd kompilator, niedozwolonej konwersji niejawnej:

short x = 10, y = 10;
short z = x + y;

0x08 graphic
Pytania sprawdzające

  1. Ile bajtów zajmuje zmienna typu char w języku C#?

    Odp.
    Dwa bajty.

  2. Czy w języku C# można używać niezainicjalizowanych zmiennych?

    Odp.
    Nie, Przed użyciem każda zmienna musi być zainicjalizowana.

  3. W jakiej przestrzeni nazw znajduje się klasa Console?

    Odp.
    W przestrzeni nazw System.

  4. Jak wywoływana jest konwersja jawna?

    Odp.
    Konwersję jawną wywołujemy przy pomocy operatora rzutowania - (typ).

  5. Podaj wartości zmiennych, po wykonaniu następującego kodu:
    int x = 10, y = 5, z =20;
    int a = z - x * y;
    int b = x++;
    double c = 11 / y;

    Odp.
    x = 11, y = 5, z = 20, a = -30, b = 10, c = 2.0

0x08 graphic
Laboratorium

Ćwiczenie 1
Utworzenie programu, który zamienia (wymienia) wartości dwóch zmiennych. Algorytm ten po angielsku nosi nazwę Swap (zamiana). Jest to jeden z podstawowych algorytmów używany do budowy bardziej skomplikowanych programów.

  1. Uruchom Visual Studio
    Naciśnij przycisk Start systemu Windows, wybierz Wszystkie Programy następnie Microsoft Visual Studio 2005/ Microsoft Visual Studio 2005.

  2. Utwórz nowy projekt

    1. Z menu File wybierz New/Project...

    2. W oknie dialogowym New Project określ następujące właściwości:

      1. typu projektu: Visual C#/Windows

      2. szablon: Console Application

      3. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\

      4. nazwa projektu: Zamiana.

      5. nazwa rozwiązania: Lab2

  3. Wewnątrz metody Main napisz następujący kod:

    1. Zadeklaruj trzy zmienne rzeczywiste a, b, tmp. Zmienna a i b są to te zmienne, między którymi będziemy wymieniać wartości, zmienna tmp jest zmienną pomocniczą:

      double a, b, tmp;

    2. Pobierz od użytkownika wartości zmiennych a i b:

      Console.Write("Podaj wartość zmiennej a: ");
      a = Convert.ToDouble(Console.ReadLine());
      Console.Write("Podaj wartość zmiennej b: ");
      b = Convert.ToDouble(Console.ReadLine());

    3. Wymień wartości zmiennych a i b, przy pomocy zmiennej tmp.

      tmp = a;
      a = b;
      b = tmp;

    4. Wypisz wartości zmiennych po wymianie, a następnie zatrzymaj działanie programu:

      Console.WriteLine("a = {0}, b = {1}", a, b);
      Console.ReadKey();

  4. Skompiluj i uruchom program.

Ćwiczenie 2
Utworzenie programu, który oblicza wartość wyrażenia:

0x08 graphic


  1. Dodaj do bieżącego rozwiązania nowy projekt

    1. Z menu File wybierz Add/New Project...

    2. W oknie dialogowym Add New Project określ następujące właściwości:

      1. typu projektu: Visual C#/Windows

      2. szablon: Console Application

      3. nazwa projektu: Wyrazenie.

  2. Uczyń nowo utworzony projekt projektem startowym

    1. Zaznacz projekt Wyrazenie w okienku Solution Explorer i z menu kontekstowego wybierz Set as StartUp Project.

albo, gdy rozpoczynasz laboratorium od tego ćwiczenia

  1. Uruchom Visual Studio
    Naciśnij przycisk Start systemu Windows, wybierz Wszystkie Programy następnie Microsoft Visual Studio 2005/ Microsoft Visual Studio 2005.

  2. Utwórz nowy projekt

    1. Z menu File wybierz New/Project...

    2. W oknie dialogowym New Project określ następujące właściwości:

      1. typu projektu: Visual C#/Windows

      2. szablon: Console Application

      3. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\

      4. nazwa projektu: Wyrazenie.

      5. nazwa rozwiązania: Lab2

  1. Wewnątrz metody Main napisz następujący kod:

    1. Zadeklaruj cztery zmienne rzeczywiste x, y, z, v.

      double x,y,z,v;

    2. Pobierz od użytkownika wartości zmiennych x, y i z:

      Console.Write("Podaj wartość zmiennej x: ");
      x = Convert.ToDouble(Console.ReadLine());
      Console.Write("Podaj wartość zmiennej y: ");
      y = Convert.ToDouble(Console.ReadLine());
      Console.Write("Podaj wartość zmiennej z: ");
      z = Convert.ToDouble(Console.ReadLine());

    3. Napisz zadany wzór z użyciem operatorów języka C#, wartość utworzonego wyrażenia wstaw do zmiennej v:

      v = (4*x-y)/(3*(z*z+1));

    4. Wypisz wartości obliczonego wyrażenia, a następnie zatrzymaj działanie programu:

Console.WriteLine("Wartość wyrażenia wynosi:
¬{0}.", v);
Console.ReadKey();

  1. Skompiluj i uruchom program.

Ćwiczenie 3
Program obliczający pole i obwód koła o zadanym promieniu

  1. Dodaj do bieżącego rozwiązania nowy projekt

  1. Z menu File wybierz Add/New Project...

  2. W oknie dialogowym Add New Project określ następujące właściwości:

      1. typu projektu: Visual C#/Windows

      2. szablon: Console Application

      3. nazwa projektu: Kolo.

  1. Uczyń nowo utworzony projekt projektem startowym

  1. Zaznacz projekt Kolo w okienku Solution Explorer i z menu kontekstowego wybierz Set as StartUp Project.

albo, gdy rozpoczynasz laboratorium od tego ćwiczenia

  1. Uruchom Visual Studio
    Naciśnij przycisk Start systemu Windows, wybierz Wszystkie Programy następnie Microsoft Visual Studio 2005/ Microsoft Visual Studio 2005.

  2. Utwórz nowy projekt

  1. Z menu File wybierz New/Project...

  2. W oknie dialogowym New Project określ następujące właściwości:

  1. typu projektu: Visual C#/Windows

  2. szablon: Console Application

  3. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\

  4. nazwa projektu: Kolo.

  5. nazwa rozwiązania: Lab2

  1. Wewnątrz metody Main napisz następujący kod:

    1. Zadeklaruj zmienną rzeczywistą r.

      double r;

    2. Pobierz od użytkownika długość promienia:

      Console.Write("Podaj długość promienia: ");
      r = Convert.ToDouble(Console.ReadLine());

    3. Zadeklaruj zmienne pole oraz obwod i odpowiednio je zainicjalizuj. We wzorach na obwód i pole skorzystaj ze stałej Math.PI.

      double pole = Math.PI * r * r;
      double obwod = 2 * Math.PI * r;

    4. Wypisz obliczone wartości pola i obwodu koła z dokładnością do trzech miejsc po przecinku, a następnie zatrzymaj działanie programu:

      Console.WriteLine("Pole koła o promieniu {0}
      ¬wynosi: {1:f3}.", r, pole);
      Console.WriteLine("Obwód koła o promieniu {0}
      ¬wynosi: {1:f3}.", r, obwod);
      Console.ReadKey();

  1. Skompiluj i uruchom program.

Ćwiczenie 4
Program obliczający cenę brutto oraz podatek od podanej ceny netto..

  1. Dodaj do bieżącego rozwiązania nowy projekt

  1. Z menu File wybierz Add/New Project...

  2. W oknie dialogowym Add New Project określ następujące właściwości:

  1. typu projektu: Visual C#/Windows

  2. szablon: Console Application

  3. nazwa projektu: Podatek.

  1. Uczyń nowo utworzony projekt projektem startowym

  1. Zaznacz projekt Podatek w okienku Solution Explorer i z menu kontekstowego wybierz Set as StartUp Project.

albo, gdy rozpoczynasz laboratorium od tego ćwiczenia

  1. Uruchom Visual Studio
    Naciśnij przycisk Start systemu Windows, wybierz Wszystkie Programy następnie Microsoft Visual Studio 2005/ Microsoft Visual Studio 2005.

  2. Utwórz nowy projekt

  1. Z menu File wybierz New/Project...

  2. W oknie dialogowym New Project określ następujące właściwości:

  1. typu projektu: Visual C#/Windows

  2. szablon: Console Application

  3. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\

  4. nazwa projektu: Podatek.

  5. nazwa rozwiązania: Lab2

  1. Wewnątrz metody Main napisz następujący kod:

  1. Zadeklaruj stałą symboliczną rzeczywistą o dużej precyzji o nazwie Vat i nadaj jej wartość 0,22.

    const decimal Vat = 0.22M;

  2. Zadeklaruj zmienną rzeczywistą o dużej precyzji netto.

    decimal netto;

  3. Pobierz od użytkownika cenę netto:

    Console.Write("Podaj cenę netto: ");
    netto = Convert.ToDecimal(Console.ReadLine());

  4. Zadeklaruj zmienne podatek oraz brutto i odpowiednio je zainicjalizuj..

    decimal podatek = Vat * netto;
    decimal brutto = netto + podatek;

  5. Wypisz obliczone wartości podatku i ceny brutto, dodając symbol waluty, a następnie zatrzymaj działanie programu:

    Console.WriteLine("Cena wynosi {0:C}, w tym
    ¬kwota podatku: {1:C}.", brutto, podatek);
    Console.ReadKey();

  1. Skompiluj i uruchom program.

Ćwiczenie 5
Program wyznaczający maksimum z dwóch liczb całkowitych przy pomocy wyrażenia warunkowego..

  1. Dodaj do bieżącego rozwiązania nowy projekt

  1. Z menu File wybierz Add/New Project...

  2. W oknie dialogowym Add New Project określ następujące właściwości:

      1. typu projektu: Visual C#/Windows

      2. szablon: Console Application

      3. nazwa projektu: Maksimum.

  1. Uczyń nowo utworzony projekt projektem startowym

  1. Zaznacz projekt Maksimum w okienku Solution Explorer i z menu kontekstowego wybierz Set as StartUp Project.

albo, gdy rozpoczynasz laboratorium od tego ćwiczenia

  1. Uruchom Visual Studio
    Naciśnij przycisk Start systemu Windows, wybierz Wszystkie Programy następnie Microsoft Visual Studio 2005/ Microsoft Visual Studio 2005.

  2. Utwórz nowy projekt

  1. Z menu File wybierz New/Project...

  2. W oknie dialogowym New Project określ następujące właściwości:

  1. typu projektu: Visual C#/Windows

  2. szablon: Console Application

  3. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\

  4. nazwa projektu: Maksimum.

  5. nazwa rozwiązania: Lab2

  1. Wewnątrz metody Main napisz następujący kod:

  1. Zadeklaruj zmienne całkowite: a, b, max.

    int a, b, max;

  2. Pobierz od użytkownika wartości zmiennych a i b:

    Console.Write("Podaj pierwszą wartość: ");
    a = Convert.ToInt32(Console.ReadLine());
    Console.Write("Podaj drugą wartość: ");
    b = Convert.ToInt32(Console.ReadLine());

  3. Wyznacz wartość maksymalną z liczb a i b:

    max = a > b ? a : b;

  4. Wypisz obliczoną wartość maksymalną, a następnie zatrzymaj działanie programu:

    Console.WriteLine("Wartość maksymalna wynosi:
    ¬{0}.", max);
    Console.ReadKey();

  1. Skompiluj i uruchom program.0x01 graphic

W wersji C# 3.0 zostają wprowadzone typy domniemane, gdzie nie podajemy nazwy typu w sposób jawny, ale kompilator na podstawie jak inicjalizujemy zmienną jest w stanie określić jej typ.

.

.

Ala ma kota

s2

s1

Zarządzana sterta

Stos

0x01 graphic

Obiekt

L

L



Wyszukiwarka