Użycie zmiennych
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:
Potrafić definiować zmienne.
Znać podstawowe operatory w języku C#.
Definicja zmiennej
Definiując zmienną oprócz jej nazwy, czyli identyfikatora zmiennej, musimy
podać jej typ
1
. Typ określa:
liczbę bajtów zajmowanych przez zmienną,
sposób interpretacji bitów zmiennej,
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ą:
Dostępne typy w języku C#.
Sposób definicji zmiennej.
Dopuszczalne identyfikatory.
oraz nauczą się
Nadać zmiennej żądaną wartość.
Wypisać wartość zmiennej na ekranie.
Kopiować wartość zmiennej do zmiennej innego typu.
1
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.
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:
Typy wartości
Typy referencyjne
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".
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:
Typy wartości
o typ logiczny: bool
o typy numeryczne
typy całkowite: sbyte, byte, short, ushort, int, uint,
long, ulong, char
typy rzeczywiste: float, double
typ rzeczywisty o dużej precyzji: decimal
Typy referencyjne:
o object,
o napisy: string
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.
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:
Nazwa może składać się z ciągu liter, cyfr i znaku podkreślenia. Nazwa
nie może zawierać, więc takich znaków jak np.: spacja, *, +. !.
Nazwa nie może zaczynać się od cyfry, czyli niedopuszczalna jest
deklaracja:
int 1x;
natomiast następująca deklaracja jest poprawna:
int x1;
Nazwa nie może być słowem kluczowym. Słowa kluczowe są to
wyrazy zastrzeżone dla danego języka programowania. W języku C#
występują następujące słowa kluczowe:
abstract
event
new
struct
as
explicit
null
switch
base
extern
object
this
bool
false
operator
throw
break
finally
out
true
byte
fixed
override
try
case
float
params
typeof
catch
for
private
uint
char
foreach
protected
ulong
checked
goto
public
unchecked
class
if
readonly
unsafe
const
implicit
ref
ushort
continue
in
return
using
decimal
int
sbyte
virtual
default
interface
sealed
volatile
delegate
internal
short
void
do
is
sizeof
while
double
lock
stackalloc
else
long
static
enum
namespace
string
Istnieją wyrazy w języku C#, które stają się słowami kluczowymi
dopiero w pewnym kontekście. Należą do nich:
get
partial
set
value
where
yield
Visual Studio przy domyślnych swoich ustawieniach wszystkie słowa
kluczowe oznacza kolorem niebieskim.
Uwaga:
Można używać słów kluczowych jako identyfikatorów stosując prefiks
@: int @if;. Znak @ nie jest częścią identyfikatora. Sposób ten
jest mocno niezalecany i stosuje się go, gdy łączymy program, z
bibliotekami napisanymi w innych językach, w których występują
identyfikatory będące słowami kluczowymi języka C#.
Nazwa musi być unikalna w danym bloku kodu. W odróżnieniu od
języka C++, w bloku zagnieżdżonym nie można przesłaniać
identyfikatorów z bloku nadrzędnego:
{
...
int x;
...
{
double x; //błąd, ta sama nazwa
}
...
}
ale
{
...
int x;
...
}
{
...
double x; //OK, inny blok
...
}
Uwaga:
Od te reguły występują pewne wyjątki. Np.: w klasie można
zadeklarować zmienną o danym identyfikatorze (blok klasy), a
następnie w metodzie klasy użyć ponownie tego identyfikatora dla
nowej zmiennej (blok metody jest zagnieżdżony w bloku klasy).
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ę.
Stosuj identyfikatory mówiące, do czego służy zmienna. Nazwa
powinna mieć konkretne znaczenie.
Zawsze używaj formatu PascalCase lub formatu camelCase przy
tworzeniu nazw. W formacie PascalCase każdy wyraz, z których
składa się identyfikator, rozpoczynamy wielką literą
np.:WriteLine, ReadLine, Console, SByte. Formatu
PascalCase używamy dla nazw typów, stałych nazwanych,
właściwości, przestrzeni nazw.
W przypadku formatowania camelCase pierwszy wyraz będący częścią
identyfikatora piszemy małą literą, pozostałe rozpoczynamy wielką
literą. np.: loopCountMax. Formatu camelCase używamy dla nazw
zmiennych lokalnych, parametrów funkcji.
Unikaj znaku podkreślenia.
Nie używaj notacji węgierskiej. W notacji węgierskiej w prefiksie była
zawarta informacja o typie zmiennej.
Unikaj skrótów i akronimów. Jeżeli już używamy skrótów, powinny
być one powszechnie znane i rozumiane.
Stosujemy w miarę możliwości nazwy anglojęzyczne. Szczególnie jest
to ważne przy tworzeniu bibliotek.
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
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.
Do oznaczenia wartości typu logicznego używamy wyrazów true
(prawda) oraz false (fałsz) np.:
bool isHeight = true;
Do określania wartości liczb całkowitych możemy użyć systemu
dziesiętnego lub szesnastkowego. W przypadku systemu dziesiątkowego
wartość zapisujemy tak jak w matematyce:
int heiht = 234;
int debt = -23;
//wartość ujemna
W przypadku systemu szesnastkowego przed ciągiem cyfr szesnastkowych
umieszczamy 0x. Do oznaczenia brakujących cyfr szesnastkowych
używamy liter: a, b, c, d, e, f.
long weight = 0x2a;
//42 w dziesiętnym
Samemu literałowi jest przyporządkowany pierwszy z typów int, uint,
long albo ulong w którym dana wartość jest reprezentowana. Możemy
również w sposób jawny określić typ literału przez dodanie sufiksu:
u - uint albo ulong, gdy w typie uint się nie mieści,
l - long albo ulong, gdy w typie long się nie mieści
ul, lu - ulong, np.:
long depth = 23l;
Wielkość liter w przypadku sufiksów nie ma znaczenia
Uwaga:
Gdy wartość literału jest poza zakresem typu ulong generowany jest błąd
kompilatora.
Stałe rzeczywiste możemy wyrażać:
o przy pomocy znaku kropki:
double x = 3.54;
double y = .34;
double z = -3.0;
o
stosując tzw. postać naukową:
double x = 354e-2;
double y = 3.4e-1;
double z = -0.03e2;
gdzie np.: 354e-2 = 354 * 10
-2
o
używając sufiksów
double a = 2d;
float b = 4.4f;
decimal c = 6m;
Wielkość liter w przypadku sufiksów nie ma znaczenia.
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.
Stałe znakowe reprezentują pojedynczy znak i zwykle zawierają dany znak
umieszczony w apostrofach.
char c = 'a', d = '1';
Wewnątrz apostrofów możemy również umieścić pewną sekwencję znaków,
która reprezentuje pojedynczy znak. Sekwencja ta rozpoczyna się od znaku
lewego ukośnika (backslash). Poniżej umieszczono dopuszczalne sekwencje
znaków:
Sekwencja
Znak
Nazwa znaku
\'
'
apostrof
\"
"
cudzysłów
\\
\
lewy ukośnik (backslash)
\0
null
\a
sygnał dźwiękowy
\b
cofnięcie o jeden znak
\f
nowa strona
\n
nowy wiersz
\r
powrót karetki
\t
tabulacja pozioma
\v
tabulacja pionowa
Ponadto można skorzystać z szesnastkowej sekwencji znaków (hexadecimal
escape sequence), która jest poprzedzona znakami \x oraz sekwencji w
kodzie Unicode (Unicode character escape sequence), która jest
poprzedzona znakami \u. W obu przypadkach podajemy szesnastkowy
kod Unicode żądanego znaku.
char c='\x4d';
//można też c = '\x004d';
char d = '\u004d';
Uwaga:
W języku C# sekwencja w kodzie Unicode (Unicode character escape
sequence) może rozpoczynać się od znaków \U po którym należy umieścić
ciąg składający się z ośmiu cyfr szesnastkowych. Znaki o kodach z
przedziału U+10000 do U+10FFFF nie są dozwolone w literałach
znakowych i napisowych. Znaki o kodzie powyżej 0x10FFFF nie są w ogóle
wspierane w języku C#.
Literał napisowy jest ciągiem znaków ujętym w cudzysłów:
string s = "Dowolny napis";
Jest to tzw. regularny literał napisowy. Może on zawierać zero lub więcej
znaków, ale nie może być dzielony na dwie i więcej linii oraz nie może
zawierać znaku " i pojedynczego znaku \. Może natomiast zawierać
sekwencje znaków rozpoczynających się od lewego ukośnika, które zostały
opisane przy literałach znakowych. Polecenie:
Console.WriteLine("\"Pan Tadeusz\"");
spowoduje pojawienie się na ekranie:
"Pan Tadeusz"
Natomiast po wykonaniu polecenia:
Console.WriteLine("Cześć!\nTu Twój komputer.");
Na ekranie pojawi się napis w dwóch liniach:
Cześć!
Tu Twój komputer.
W C# istnieją również literały napisowe dosłowne (verbatim-string-literal).
Różnią się od regularnych literałów napisowych tym, że przed cudzysłów
otwierający wstawiamy znak małpy @.
string s = @"Dowolny napis";
W literałach napisowych dosłownych znak lewy ukośnik nie ma żadnego
specjalnego znaczenia. Polecenie:
Console.WriteLine(@"Cześć!\nTu Twój komputer.");
spowoduje pojawienie się na ekranie:
Cześć!\nTu Twój komputer.
Jeżeli chcemy uzyskać cudzysłów " w literale napisowym dosłownym,
należy użyć dwóch znaków cudzysłów "". Polecenie
Console.WriteLine(@"""Pan Tadeusz""");
spowoduje pojawienie się na ekranie:
"Pan Tadeusz"
Literały napisowe dosłowne mogą rozciągać się na wiele linii.
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.
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();
}
4. Zbuduj i uruchom program.
5. 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.
6. Z menu Debug wybierz pozycję Stop Debugging.
7. 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:
• C lub c – dodawany jest symbol waluty
• D lub d – format tylko dla liczb całkowitych. Precyzja określa z ilu
cyfr składa się liczba. Gdy liczba składa się z mniejszej ilości cyfr
brakujące miejsca uzupełniane zerami z lewej strony.
• E lub e – liczba jest wyświetlana w notacji naukowej - postać
wykładnicza. Jedna cyfra zawsze poprzedza znak dziesiętny.
• F lub f - liczba jest konwertowana do postaci stałopozycyjnej (fixed-
point).
• G lub g - liczba jest konwertowana do najbardziej zwięzłej postaci
(notacja naukowa lub stałopozycyjna)
• N lub n - wstawiany jest separator tysięcy.
• X lub x - format tylko dla liczb całkowitych. Liczba jest wyświetlana w
systemie szesnastkowym.
• P lub p - wartość liczby jest mnożona przez sto i dodawany jest znak
procentu.
• R lub r - gwarantuje, że po skonwertowaniu liczby do napisu (stringu) i
późnie wykonaniu konwersji odwrotnej (napisu do liczby) otrzymamy
tą samą wartość numeryczną.
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.
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żą:
Typy wbudowane numeryczne(byte, sbyte, short, ushort, int, uint, long,
ulong, char, float, double, decimal).
Typ wbudowany bool (logiczny).
Typy wyliczeniowe (typ ten zostanie omówiony w dalszej części tego
modułu).
Struktury - jest to typ definiowany przez użytkownika. Więcej o tym
typie będzie w rozdziale 11 - "Struktury"
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.
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:
Zapewnia, że zmiennym są przypisane odpowiednie wartości.
W środowisku Visual Studio ułatwia pisanie kodu dzięki
automatycznemu uzupełnianiu nazw (IntelliSense).
Typ referencyjny
Drugą kategorią typów są tak zwane typy referencyjne. Do typów
referencyjnych zaliczamy:
Typy wbudowane string i object
Klasy
Interfejsy
Delegacje
Tablice
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;
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.
Ala ma kota
.
.
Obiekt
Stos
Zarządzana sterta
s1
s2
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:
Konwersje niejawne.
Konwersje jawne.
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
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ą:
Znali operatory występujące w języku C#.
Potrafili budować wyrażenia i obliczać ich wartość.
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#
Omawiając operatory podzielimy je na grupy.
Operatory arytmetyczne - oznaczają proste działania matematyczne. Do
operatorów arytmetycznych zaliczamy: + (dodawanie), - (odejmowanie), *
(mnożenie), / (dzielenie), % (reszta z dzielenia). Jako argumenty mogą
przyjmować wartość (zmienną) dowolnego typu numerycznego..
double x = 20, y = 10;
double z = x + y;
//z = 30
z = x - y;
//z = 10
z = x * y;
//z = 200
z = x / y;
//z = 2
z = 7 % 3;
//z = 1
z = -7 % 3;
//z = -1
z = 7 % -3;
//z = 1
z = -7 % -3;
//z = -1
Uwaga:
W przypadku dzielenie liczb całkowitych wynik jest również całkowity.
z = 6 / 5;
// z = 1 nie 1.2
W przypadku liczb całkowitych oraz liczb typu decimal przy dzieleniu
przez zero oraz reszty z dzielenia przez zero zgłaszany jest wyjątek.
W przypadku dzielenia liczb typu float lub double przez zero,
wynikiem jest nieskończoność (+ lub minus) lub wynik "nie jest liczbą".
W przypadku dodawania, odejmowania, mnożenia licz całkowitych, jeżeli
wynik przekracza zakres dopuszczalnych wartości danego typu, zachowanie
jest uzależnione od opcji kompilatora /checked (patrz temat "Konwersja
typów" w tym rozdziale). W przypadku typów rzeczywistych, jeżeli wynik
nie mieści się w zakresie dopuszczalnych wartości danego typu, wynikiem
jest plus lub minus nieskończoność. Dla typu decimal przy przekroczeniu
zakresu dopuszczalnych wartości rzucany jest wyjątek. OverflowException.
Dokładny opis sposobu działania w sytuacjach kłopotliwych (zero i
nieskończoność, dwie nieskończoności itp.) można znaleźć w specyfikacji
języka C# w rozdziale 7.7 - C# Language Specification 1.2.doc.
Uwaga:
Operatory dodawania i odejmowania są również automatycznie dostępne dla
typów wyliczeniowych. Wynikiem dodawania lub odejmowania dwóch
wartości danego typu wyliczeniowego, jest wartość typu całkowitego
bazowego dla danego typu wyliczeniowego.
Operator konkatenacji - łączenia napisów +.
W przypadku znaku dodawania, gdy po jego obu stronach występują
wartości typu string oznacza on łączenie napisów, tworzony jest nowy
obiekt reprezentujący połączone napisy.
string s = "Ala ma ";
string s2 = s + "kota";
//s2 = "Ala ma kota"
Operatory relacyjne.
Wynikiem ich działania jest prawda lub fałsz - typ logiczny.
Dla typów numerycznych oraz typów wyliczeniowych mamy następujące
operatory relacyjne: == (czy równe), != (czy różne), < (czy mniejsze), >
(czy większe), <= (czy mniejsze lub równe), >= (czy większe lub równe)
np. :
bool b;
int i =10, j =20;
b = i > j;
//b = false
Uwaga:
W przypadku typów rzeczywistych należy pamiętać że:
–∞ == –∞< –max < ... < –min < –0.0 == +0.0 < +min
< ... < +max < +∞ == +∞
gdzie max oraz min są odpowiednio największą i najmniejszą wartością
dodatnią danego typu rzeczywistego.
W przypadku typów referencyjnych predefiniowane są operatory: == (czy
równe), != (czy różne). Wersje predefiniowane tych operatorów sprawdzają
czy zmienne odwołują się do tego samego obiektu (równość referencji), a
nie czy obiekty mają tę samą wartość (równość wartości). Programista może
zmienić znaczenie tych operatorów. Jest tak np. w przypadku typu string.
Dla typu string operatory te porównują wartość nie referencje. W
przypadku, gdy obie zmienne danego typu referencyjnego mają wartość
null przyjmuje się, że są równe.
Uwaga:
Gdy chcemy porównywać napisy, możemy użyć metody
string.Compare. Opertory <,>,<=, >= dla typu string nie są
zdefiniowane.
Operatory == (czy równe), != (czy różne) zdefiniowane są również dla typu
logicznego.
Operatory przesunięcia << lub >>
Są to dwuargumentowe operatory pracujące na argumentach typu
całkowitego lub typów wyliczeniowych.
x<<n;
//przesunięcie w lewo
x>>n
//Przesunięcie w prawo
n - określa ilość bitów przesunięcia.
Uwaga:
Do obliczenia rzeczywistej ilości bitów do przesunięcia, jeżeli x jest typu
int lub uint, branych jest tylko pięć najmniej znaczących bitów n (n &
0x1F), natomiast gdy x jest typu long lub ulong, branych jest sześć
najmniej znaczących bitów n (n & 0x3F)
W przypadku przesunięcia w lewo sposób obliczania wartości liczby po
przesunięciu jest prosty. Zapisujemy liczbę w notacji dwójkowej. Następnie
skreślamy n najbardziej znaczących cyfr dwójkowych (z lewej strony),
gdzie n oznacza ilość bitów przesunięcia i dopisujemy n 0 z prawej strony.
int x = 0x2f;
//101111
int y = x << 2;
//10111100
Uwaga:
W przypadku liczb ze znakiem liczby kodowane są następująco. Najbardziej
znaczący bit jest nazywany bitem znaku i jeżeli jest ustawiony do równania
obliczającego wartość liczby zamiast liczby 1 wstawiamy -1. Zostanie to
pokazane na przykładzie typu sbyte (osiem bitów):
d7
(16)
= 1101 0111
(2)
= -1 * 2
7
+ 1 * 2
6
+ 0 * 2
5
+ 1 * 2
4
+ 0 * 2
3
+ 1 * 2
2
+
+ 1 * 2
1
+ 1 * 2
0
= -128 +64 + 16 + 4 + 2 + 1 = -41
int x = -1;
// 0xffffffff
int y = x << 1;
// 0xfffffffe, y=-2
W przypadku przesunięcia w prawo sytuacja jest bardziej skomplikowana.
Dla liczb dodatnich skreślamy n najmniej znaczących bitów i z przodu
dodajemy n zer. Dla liczb ujemnych również skreślamy n najmniej
znaczących bitów, ale z przodu dopisujemy jedynki.
Przesunięcie w lewo jest równoważne mnożeniu przez n-tą potęgę liczby
dwa, natomiast przesunięcie w prawo jest równoważne dzieleniu przez n-tą
potęgę dwóch.
Operatory bitowe (logical operators):
Do operatorów bitowych należ: & (koniunkcja), ^ (różnica symetryczna), |
(alternatywa). ~ (negacja bitowa). Zasadę ich działania dla typów
całkowitych oraz wyliczeniowych przedstawia poniższa tabela:
x =
1 1 0 0
...(pozostałe bity)
y =
1 0 1 0
...(pozostałe bity)
x & y = 1 0 0 0
...(pozostałe bity)
x | y = 1 1 1 0
...(pozostałe bity)
x ^ y = 0 1 1 0
...(pozostałe bity)
~x =
0 0 1 1
...(pozostałe bity)
Argumentami operatorów bitowych & (koniunkcja), ^ (różnica
symetryczna), | (alternatywa) mogą być zmienne typu logicznego. Działanie
ich jest następujące:
x =
true
false
true
false
y =
true
true
false
false
x & y = true
false
false
false
x | y = true
true
true
false
x ^ y = false
true
true
false
Operatory logiczne (conditional logical operators)
Do operatorów logicznych zaliczamy: && (iloczyn logiczny), || (suma
logiczna) oraz ! (negacja logiczna). Argumentami tych operatorów mogą
być wartości logiczne.
Wyrażenie x && y zwraca wartość true, jeżeli x i y ma wartość true.
Od wyrażenia x & y różni się tym, że wartość y jest obliczana tylko wtedy,
gdy x ma wartość true.
Wyrażenie x || y zwraca wartość true, jeżeli x lub y ma wartość
true
. Od wyrażenia x | y różni się tym, że wartość y jest obliczana tylko
wtedy, gdy x ma wartość false.
Operator ! (negacja logiczna) zmienia wartość true na false i na
odwrót.
bool x = true;
bool y = ! x;
//y=false
Do operatorów logicznych zaliczamy również operatory true oraz false.
Wykorzystywane są przy przeciążeniu operatorów && oraz || i są poza
zakresem tego kursu.
Operatory inkrementacji (zwiększania o 1) i dekrementacji (zmniejszania o
1).
Oba operatory mają dwie wersje: przyrostkową oraz przedrostkową. Zasada
działania zostanie pokazana na przykładzie
int x = 10, y = 10;
int z = x++;
int v = ++y;
Wartość zmiennych x, y, v po wykonaniu powyższego kodu będzie 11,
natomiast wartość v będzie wynosić 10. Dzieje się tak, ponieważ wersja
przyrostkowa x++ najpierw wstawia wartość zmiennej do wyrażenia, a
następnie zwiększa jej wartość o jeden, natomiast wersja przedrostkowa
(++y) najpierw zwiększa wartość zmiennej o jeden, a następnie zwraca jej
wartość, już zwiększoną do wyrażenia.
Dla operator dekrementacji będzie to wyglądało następująco:
int a = 10, b = 10;
int c = a--;
//c=10, a = 9
int d = --b;
//d=9, b=9
Operatory te można stosować do wszystkich zmiennych typów
numerycznych (int, char, double, decimal ...).
Operatory przypisania.
Wcześniej w tym rozdziale był używany operator przypisania
(podstawienia) oznaczony pojedynczym znakiem równości =. Dla
zmiennych typów wartości oznacza on skopiowanie wartości zmiennej lub
wyrażenia znajdującego się po prawej stronie znaku równości, do zmiennej
znajdującej się po lewej stronie znaku równości. Dla zmiennych typów
referencyjnych oznacza skopiowanie tylko odwołania. Do operatorów
przypisania zaliczmy również: +=, -=, *=, /=, %=, &=, |=,
^=, <<=, >>=.
Znaczenie poszczególnych operatorów jest zdefiniowane poniżej.
x += 2;
x = x + 2;
x -= 2;
x = x - 2;
x *= 2;
x = x * 2;
x /= 2;
x = x / 2;
x %= 2;
x = x % 2;
x &= 2;
x = x & 2;
x |= 2;
x = x | 2;
x ^= 2;
x = x ^ 2;
x <<= 2;
x = x << 2;
x >>= 2;
x = x >> 2;
Plus + i minus - jednoargumentowy.
Operatory te są predefiniowane dla typów numerycznych i mają identyczne
znaczenie jak w zapisie matematycznym. W przypadku operator + do
wyrażenia wstawiana jest wartość jego argumentu, nie zmienia on wartości
swojego argumentu. Operator - oznacza liczbę przeciwną. Operator - nie
może być stosowany do zmiennych typu ulong, generowany jest błąd
kompilacji. W przypadku argumentu typu uint, rezultat jest konwertowany
do long.
Operator dostępu do składowej kropka . .
Znak kropki oddziela nazwę kontenera (nazwa przestrzeni nazw, nazwa
klasy, nazwa struktury) od nazwy rzeczy zagnieżdżonej (nazwa
podprzestrzeni, nazwa klasy, nazwa metody ...). Pełni podobną rolę jak znak
ukośnika przy podawaniu ścieżki dostępu, np.:
System.Collections.Generic;
System.Console.WriteLine();
Wyrażenie warunkowe.
Ogólna postać wyrażenia warunkowego jest następująca:
warunek ? wyrażenie1 : wyrażenie2;
Wartość wyrażenia warunkowego jest obliczany w następujący sposób.
Jeżeli warunek jest prawdziwy to wartością całego wyrażenia
warunkowego jest wyrażenie1, w przeciwnym wypadku wartością
wyrażenia warunkowego jest wyrażenie2. W zależności od wartości
warunku obliczane jest tylko wyrażenie1 albo wyrażenie2. Nigdy nie
są obliczane oba wyrażenia. Poniżej zostanie przedstawiony kod obliczający
wartość bezwzględną (moduł liczby) przy pomocy wyrażenia
warunkowego:
double x, modul;
...
modul = x < 0 ? -x : x;
A
rgumentu warunek musi być typu logicznego. Natomiast wyrażenie1 i
wyrażenie2 muszą być tego samego typu, albo między typami tych
argumentów musi istnieć konwersja niejawna.
Tworzenie nowego obiektu - operator new.
W przypadku wywołania operatora new dla typów referencyjnych,
następuje alokacji pamięci dla obiektu i inicjalizacja pamięci (wywołanie
metody zwanej konstruktorem).
Ranodom r =new Random();
//utworzenie obiektu klasy Random
Operator new możemy również używać do "tworzenia" zmiennych typów
wartości. W przypadku typów wartości nie ma jednak dynamicznej alokacji
pamięci. Wywoływana jest, podobnie jak dla typów referencyjnych, metoda
inicjalizująca zmienną zwana konstruktorem.
int i = new int(); //int i = 0;
Uwaga:
Więcej o metodzie zwanej konstruktorem można znaleźć w kursie
"Programowanie obiektowe".
Operator rzutowania () i operatory kontrolujące przepełnienie typów
całkowitych checked i unchecked zostały opisane wcześniej w tym
rozdziale przy omawianiu konwersji jawnych.
Operator indeksowania [] zostanie dokładnie omówiony w rozdziale
szóstym tego kursu.
Operatory informujące o typie (as, is, sizeof, typeof) zostaną
przybliżone w kursie "Programowanie obiektowe".
Operatory operujące na wskaźnikach (adresach) mogą być używane tylko w
kodzie niezarządzanym (unsafe context). Jest to tzw. kod niebezpieczny i
jest poza zakresem tego kursu.
Właściwości operatorów
Operatory (funkcje operatorowe) charakteryzują się pewnymi właściwościami.
Należą do nich:
liczba argumentów
priorytet
łączność
typ argumentów
Ze względu na liczbę argumentów operatory możemy podzielić na:
operatory jednoargumentowe (unary operators), np.: inkrementacji ++,
dekrementacji --, minus jednoargumentowy -, ...
operatory dwuargumentowe (binary operators), np.: operatory
arytmetyczne, przypisania, relacyjne...
operator trójargumentowy (ternary operator), istnieje tylko jeden -
wyrażenie warunkowe (? :)
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:
Obliczamy wartość wyrażenia znajdującego się w najbardziej
zagnieżdżonych nawiasach, czyli x+y. Po uproszczeniu otrzymujemy
wyrażenie (2*15+z)*3.
Obliczamy wartość wyrażenia w nawiasie. Ponieważ mnożenie ma
wyższy priorytet, jest wykonywane najpierw. Po wykonaniu działań w
nawiasie otrzymujemy wyrażenie 32*3, którego wartość wynosi 96
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;
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
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
a. Z menu File wybierz New/Project...
b. W oknie dialogowym New Project określ następujące właściwości:
i. typu projektu: Visual C#/Windows
ii. szablon: Console Application
iii. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\
iv. nazwa projektu: Zamiana.
v. nazwa rozwiązania: Lab2
3. Wewnątrz metody Main napisz następujący kod:
a. 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;
b. 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());
c. Wymień wartości zmiennych a i b, przy pomocy zmiennej tmp.
tmp = a;
a = b;
b = tmp;
d.
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:
1. Dodaj do bieżącego rozwiązania nowy projekt
a. Z menu File wybierz Add/New Project...
b. W oknie dialogowym Add New Project określ następujące
właściwości:
i.
typu projektu: Visual C#/Windows
ii.
szablon: Console Application
iii.
nazwa projektu: Wyrazenie.
2. Uczyń nowo utworzony projekt projektem startowym
a. 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
a. Z menu File wybierz New/Project...
b. W oknie dialogowym New Project określ następujące właściwości:
)
1
(
3
4
2
z
y
x
v
i. typu projektu: Visual C#/Windows
ii. szablon: Console Application
iii. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\
iv. nazwa projektu: Wyrazenie.
v. nazwa rozwiązania: Lab2
3. Wewnątrz metody Main napisz następujący kod:
a. Zadeklaruj cztery zmienne rzeczywiste x, y, z, v.
double x,y,z,v;
b. 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());
c.
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));
d.
Wypisz wartości obliczonego wyrażenia, a następnie zatrzymaj
działanie programu:
Console.WriteLine("Wartość wyrażenia wynosi:
¬{0}.", v);
Console.ReadKey();
4. 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
a. Z menu File wybierz Add/New Project...
b. W oknie dialogowym Add New Project określ następujące
właściwości:
i.
typu projektu: Visual C#/Windows
ii.
szablon: Console Application
iii.
nazwa projektu: Kolo.
2. Uczyń nowo utworzony projekt projektem startowym
a. 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
a.
Z menu File wybierz New/Project...
b. W oknie dialogowym New Project określ następujące właściwości:
i. typu projektu: Visual C#/Windows
ii. szablon: Console Application
iii. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\
iv. nazwa projektu: Kolo.
v. nazwa rozwiązania: Lab2
3. Wewnątrz metody Main napisz następujący kod:
a.
Zadeklaruj zmienną rzeczywistą r.
double r;
b. Pobierz od użytkownika długość promienia:
Console.Write("Podaj długość promienia: ");
r = Convert.ToDouble(Console.ReadLine());
c. 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;
d. 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();
4. 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
a. Z menu File wybierz Add/New Project...
b. W oknie dialogowym Add New Project określ następujące
właściwości:
i. typu projektu: Visual C#/Windows
ii. szablon: Console Application
iii. nazwa projektu: Podatek.
2. Uczyń nowo utworzony projekt projektem startowym
a. 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
a. Z menu File wybierz New/Project...
b. W oknie dialogowym New Project określ następujące właściwości:
i.
typu projektu: Visual C#/Windows
ii.
szablon: Console Application
iii.
lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\
iv.
nazwa projektu: Podatek.
v.
nazwa rozwiązania: Lab2
3. Wewnątrz metody Main napisz następujący kod:
a. Zadeklaruj stałą symboliczną rzeczywistą o dużej precyzji o nazwie
Vat
i nadaj jej wartość 0,22.
const decimal Vat = 0.22M;
b. Zadeklaruj zmienną rzeczywistą o dużej precyzji netto.
decimal netto;
c.
Pobierz od użytkownika cenę netto:
Console.Write("Podaj cenę netto: ");
netto = Convert.ToDecimal(Console.ReadLine());
d. Zadeklaruj zmienne podatek oraz brutto i odpowiednio je
zainicjalizuj..
decimal podatek = Vat * netto;
decimal brutto = netto + podatek;
e.
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();
4. 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
a. Z menu File wybierz Add/New Project...
b. W oknie dialogowym Add New Project określ następujące
właściwości:
i. typu projektu: Visual C#/Windows
ii. szablon: Console Application
iii. nazwa projektu: Maksimum.
2. Uczyń nowo utworzony projekt projektem startowym
a. 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
a. Z menu File wybierz New/Project...
b. W oknie dialogowym New Project określ następujące właściwości:
i. typu projektu: Visual C#/Windows
ii. szablon: Console Application
iii. lokalizacja: Moje Dokumenty\Visual Studio 2005\Projects\
iv. nazwa projektu: Maksimum.
v. nazwa rozwiązania: Lab2
3. Wewnątrz metody Main napisz następujący kod:
a. Zadeklaruj zmienne całkowite: a, b, max.
int a, b, max;
b.
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());
c. Wyznacz wartość maksymalną z liczb a i b:
max = a > b ? a : b;
d. Wypisz obliczoną wartość maksymalną, a następnie zatrzymaj
działanie programu:
Console.WriteLine("Wartość maksymalna wynosi:
¬{0}.", max);
Console.ReadKey();
4. Skompiluj i uruchom program.