Język C - Sphere Online Judge (SPOJ)

Download Report

Transcript Język C - Sphere Online Judge (SPOJ)

Język C
Michał Małafiejski
[email protected]
Nic nie jest tak proste, by nie można było wykonać tego źle
Prawo Murphy’ego
Plan wykładu (propozycja)
• wykład I(6): standard ANSI C, proste programy, podstawowe opcje
kompilatora, składnia i semantyka języka C, instrukcje przypisania,
bloki instrukcji, instrukcje sterujące (pętle), instrukcje warunkowe
• wykład II(4): wyrażenia i operatory arytmetyczne, kolejność
wykonywania obliczeń w wyrażeniach, typy danych, konwersja
typów, definicja struktur
• wykład III(4): operacje na plikach, formatowane wejście oraz
wyjście, funkcje i struktura programu, zasięg nazw, pliki
nagłówkowe, preprocesor języka C, kompilacja warunkowa
• wykład IV(4): wskaźniki i tablice, arytmetyka na adresach, związki
tablic i wskaźników, tablice znakowe, wskaźniki do funkcji,
struktury i funkcje, skomplikowane deklaracje, unie i pola bitowe
• wykład V(6): struktury i funkcje rekursywne, abstrakcyjne typy
danych i ich implementacje: stos, kolejka, listy, drzewa
• wykład VI(1)*: przegląd funkcji bibliotek standardowych języka C,
biblioteki: wejścia/wyjścia, funkcji matematycznych, operacji na
tekstach i łańcuchach
*: dla studiów dziennych
Zasady
• kontakt:
– email: [email protected]
– GG: 2590739
• wykład kończy się egzaminem pisemnym (test + zadania)
• ocena 5.0 z laboratorium zwalnia z egzaminu z oceną 4.0
• pomoce:
– Brian W. Kernighan, Dennis M.Ritchie, Język ANSI
C, WNT (2002)
– kurs oraz opis bibliotek standardowych:
http://binboy.sphere.pl dział Programowanie / ANSI C
– testowanie umiejętności programistycznych (zadania)
http://pl.spoj.pl
Plan wykładu I
•
•
•
•
•
•
Zasady
Historia języka C
Standard ANSI C
Pierwszy program
Struktura programu
Kompilacja i
uruchomienie
• Opcje kompilatora
• Drugi program
•
•
•
•
•
•
•
•
Biblioteki standardowe
Trzeci program
Jednostki leksykalne
Strukturalizacja
Instrukcja warunkowa
Instrukcja wyboru
Instrukcje powtarzania
Przykłady
Historia języka C
• Poprzednikiem języka C był interpretowany język B
który Ritchie rozwinął w język C. Pierwszy okres
rozwoju języka to lata 1969-1973.
• W roku 1973 w języku C udało się zaimplementować
jądra (kernel) systemu operacyjnego Unix. W 1978 roku
Brian Kernighan i Dennis Ritchie opublikowali
dokumentację języka p.t. C Programming Language.
• C stał się popularny poza Laboratoriami Bella (gdzie
powstał) po 1980 roku i stał się dominującym językiem
do programowania systemów operacyjnych i aplikacji.
Na bazie języka C w latach osiemdziesiątych Bjarne
Stroustrup stworzył język C++, który wprowadza
możliwość programowania obiektowego.
Standard ANSI C
• Pierwsze wersje systemu UNIX były rozpowszechniane w
szkołach wyższych wraz z pełnym kodem źródłowym
napisanym w języku C.
• Potrzeba stworzenia standardu języka wynikała z jego
popularności - coraz więcej osób z niego korzystało
(głównie na uniwersytetach).
• Amerykański standard języka - ANSI C (1983-1988).
• Stanowił on znaczne rozszerzenie w stosunku do wersji
Kernighan'a i Ritchie'go.
• Kolejna wersja standardu języka: ISO 9899:1990 modyfikacja standardu ANSI. Język zgodny z tą wersją
standardu określany jest nieformalnie jako C89. Od tego
czasu powstało wiele uaktualnień tej normy (np.
powszechnie obowiązująca C99).
Standard ANSI C (2)
• W języku C zawarte są podstawowe instrukcje sterujące,
niezbędne w programowaniu strukturalnym:
– „wykonuj jeden po drugim”: grupowanie instrukcji
(nawiasy klamrowe { })
– „wykonuj dla wszystkich”, „wykonuj aż do”: powtarzanie
ze sprawdzeniem warunku zatrzymania (pętle for, while)
– „wybierz z”: podejmowanie decyzji (if - else)
• Możliwość tworzenia funkcji w języku C – wydzielanie
(powtarzalnych) fragmentów programu – kolejne wsparcie
dla strukturalizacji.
• Język C jest językiem niskiego poziomu: duża część kodu
języka C jest bezpośrednio tłumaczona na język wewnętrzny
komputera (operacje na adresach, znakach i liczbach).
Standard ANSI C (3)
• Standard języka C miał na celu zagwarantowanie
poprawności (istniejących oraz przyszłych) programów –
bez względu na architekturę komputera.
• Standard definiuje konstrukcje języka, jego składnię oraz
zawiera specyfikacje funkcji standardowej biblioteki.
• W stosunku do pierwszych wersji języka C, standard ANSI
C wprowadził kilka istotnych rozszerzeń:
– deklaracje funkcji mogły zawierać opis parametrów,
– specyfikacja bibliotek towarzyszących C
• funkcje odwołujące się do systemu operacyjnego
• operacje na pamięci
• funkcje matematyczne
• operacje na plikach (realizacja wejścia – wyjścia)
Pierwszy program
1:
2:
3:
4:
5:
6:
kod źródłowy
#include <stdio.h>
int main()
{
printf(„Witaj przyjacielu!\n”);
return 0;
}
• kod źródłowy składa się z dyrektyw preprocesora (include),
słów kluczowych (int, return) oraz nazw funkcji (main,
printf) i ich argumentów (w nawiasach), nawiasy klamrowe
grupują wiele instrukcji
Pierwszy program (2)
1:
#include <stdio.h>
dyrektywa informująca kompilator o wykorzystaniu funkcji z
biblioteki standardowej stdio.h
2:
int main()
nagłówek funkcji głównej programu (w każdym programie musi
znaleźć się funkcja main), typ zwracanej wartości–całkowity (int)
3:
{
nawias otwierający blok instrukcji – ciało funkcji głównej main
4:
printf(„Witaj przyjacielu!\n”);
funkcja z biblioteki standardowej stdio.h – wydrukowanie
podanego w nawiasie napisu na standardowe wyjście
5:
return 0;
zwrócenie wartości 0 (funkcja ma typ całkowity int)
6:
}
nawias zamykający blok instrukcji – ciało funkcji głównej main
Struktura programu
• Kod programu może zostać umieszczony w jednym bądź
kilku plikach.
• Program składa się z dyrektyw preprocesora, definicji
struktur, funkcji oraz zmiennych.
• Funkcje zbudowane są z instrukcji.
• Instrukcje składają się ze słów kluczowych, operatorów,
nazw zmiennych oraz znaków grupujących i separujących
(nawiasy, przecinki, średniki).
• Program zaczyna działanie od wykonania funkcji main.
• Pozostałe funkcje mogą być napisane przez programistę lub
pochodzić z bibliotek.
• Przed uruchomieniem program należy skompilować i scalić
z funkcjami bibliotecznymi.
Kompilacja i uruchomienie
• Po skończeniu edycji kodu źródłowego zapisujemy tekst do
pliku (nazwa dowolna, rozszerzenie .c): first.c
• Kompilacja, czyli przetłumaczenie kodu źródłowego na
postać binarną zrozumiałą dla komputera (ale jeszcze nie
wykonywalną):
– poleceniem gcc –c first.c tworzymy plik first.o
• Scalanie, czyli połączenie skompilowanego kodu naszego
programu oraz funkcji z bibliotek zewnętrznych (np.
standardowych):
– poleceniem gcc –o first first.o tworzymy plik wykonywalny
first
• kompilację oraz scalanie: jednym poleceniem gcc first.c
tworzymy plik wykonywalny a.out
Kompilacja i uruchomienie (2)
biblioteki
funkcja printf
first.c
first.o
kompilacja
first
scalanie
• wykonanie programu: polecenie ./first (lub first)
• pod Windowsem: first.exe (zamiast first)
Opcje kompilatora
• składnia polecenia: gcc opcje nazwy_plików
• brak opcji: kompilacja i scalanie, standardowa nazwa pliku
wykonywalnego a.out
• -c - tylko kompilacja
• -o name – utworzenie pliku wykonywalnego o nazwie name
• -ansi – sprawdzenie kodu pod kątem zgodności ze
standardem ANSI
• -Wall – pokazanie wszystkich ostrzeżeń podczas
kompilacji (ostrzeżenia (warnings) nie przerywają procesu
kompilacji, w przeciwieństwie do błędów (errors))
Drugi program
1:
2:
3:
4:
5:
6:
7:
kod źródłowy
#include <stdio.h>
int main() {
/* (i): usuń int */
int a;
/* (ii): wstaw: ,b */
scanf(„%d”, &a);
printf(„%d * %d = %d\n”, a, a, a * a);
return 0; /* (iii): zakomentuj linię */
}
kompilacja: -ansi -Wall
Drugi program (2)
1: funkcje biblioteczne scanf oraz printf realizują
sformatowane operacje wejścia – wyjścia (odczyt –
zapis)
2: format wejścia – czyli kolejność oraz typy danych, które
pojawiają się na wejściu – opisany jest w nawiasie, np.:
scanf(„%d”, &a) oznacza wczytanie z wejścia liczby
całkowitej oraz zapisanie jej wartości do komórki
(zmiennej) o nazwie a
3: kolejne opisy pól z formatu wejścia oddzielamy
przecinkami, wartość przypisana w danym miejscu
może zostać wyznaczona z dowolnego poprawnego
wyrażenia (zmienne wraz z operacjami na nich)
Drugi program (3)
(i):
kompilacja kodu źródłowego z parametrem –ansi –Wall
spowoduje wygenerowanie ostrzeżenia:
second.c:2: warning: return type defaults to `int'
(ii)
zmienna b nie jest nigdzie wykorzystana, stąd
kompilator zgłosi ostrzeżenie:
second.c :3: warning: unused variable `b‘
(iii)
brak zwracanej wartości kompilator odnotuje:
second.c :7: warning: control reaches end of non-void function
Biblioteki standardowe
•
•
•
•
•
•
•
assert.h – diagnozowanie programów
ctype.h – klasyfikacja znaków
errno.h – zmienne przechowujące informacje o błędach
math.h – funkcje matematyczne
signal.h – mechanizmy obsługi zdarzeń wyjątkowych
stdio.h – funkcje wejścia oraz wyjścia
stdlib.h – funkcje narzędziowe (przekształcanie liczb,
operacje na pamięci)
• string.h – operacje na tekstach
• time.h – obsługa daty oraz czasu
Biblioteki standardowe (2)
• Biblioteka math – przykłady funkcji
–
–
–
–
–
double sin(double x);
float sinf(float x);
double exp(double x);
double log(double x);
double pow(double x, double y);
• Biblioteki stdlib oraz string – przykłady funkcji
– int atoi(const char *nptr);
– long atol(const char *nptr);
– char *strcat(char *dest, const char *src);
Trzeci program
1:
2:
3:
3:
4:
5:
5:
6:
7:
8:
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int main() {
double a = 2;
char* liczba = „123”;
printf(„%f\n”, exp(a));
printf(„%d\n”, atoi(liczba));
return 0;
}
kompilacja: -ansi –Wall -lm
Trzeci program (2)
1: parametr –lm „podpowiada” programowi scalającemu,
że powinien wykorzystać bibliotekę matematyczą
2: printf(„%f\n”, exp(a));
wydrukowanie wartości exp(2)
3: printf(„%d\n”, atoi(liczba));
konwersja łańcucha znaków „123” do formatu liczby
całkowitej 123
Jednostki leksykalne - identyfikatory
• Jednostki leksykalne są to niezależne, oddzielone
separatorami (spacje, średnik, przecinek) fragmenty
kodu źródłowego, np. int (typ zmiennej), scanf
(identyfikator – nazwa funkcji), for, while (słowa
kluczowe)
• Identyfikatory są to nazwy zmiennych lub funkcji.
– Identyfikator jest sekwencją liter i cyfr oraz znaków
podkreślenia (‘_’).
– Rozróżniane sa małe oraz duże litery.
– Pierwszy znak nie może być cyfrą.
– Należy unikać stosowania nazw zaczynających się od ‘_’
(zarezerwowane dla bibliotek).
Jednostki leksykalne – słowa kluczowe
• Podane poniżej identyfikatory są słowami
kluczowymi (zarezerwowane dla języka C):
–
–
–
–
–
char, int, float, double, enum, void
long, short, signed, unsigned
const, static, volatile, extern, register, struct, union
for, while, do, switch, case, default, if, else
break, continue, return, goto, inline, sizeof, typedef
Strukturalizacja - sterowanie
1. „wykonuj jeden po drugim”: grupowanie
instrukcji (nawiasy klamrowe { })
2. „wykonuj dla wszystkich”, „wykonuj aż do”:
powtarzanie ze sprawdzeniem warunku
zatrzymania (pętle for, while)
3. „wybierz z”: podejmowanie decyzji (if - else)
Strukturalizacja
• Instrukcja: wyrażenie zakończone średnikiem, np.
– x = 0;
– printf(„Hello!”);
– return 1;
• Nawiasy klamrowe służa do grupowania instrukcji w instrukcję
złożoną, czyli blok np.
{
x = 0;
printf(„Hello!”);
}
• instrukcje dzielimy na:
–
–
–
–
instrukcje przypisania (=)
instrukcje warunkowe (if-else)
instrukcje wyboru (switch-case)
instrukcje powtarzania (pętle: for, while)
Instrukcja warunkowa
if ( wyrażenie )
instrukcja1
else
instrukcja2
•
•
Sprawdzana jest wartość wyrażenia, w przypadku
gdy jest różna od zera wykonywana jest
instrukcja1, w przeciwnym wypadku – instrukcja2
instrukcja1 może być instrukcją prostą lub złożoną
Program Warunek Trójkąta
Problem: Napisać program sprawdzający czy z podanych
trzech długości można zbudować trójkąt.
Wejście: liczby całkowite: a, b, c
Wyjście: odpowiedź TAK lub NIE
Rozwiązanie:
1. Wczytujemy 3 liczby ze standardowego wejścia
2. Należy sprawdzić warunki:
a+b>c
a+c>b
b+c>a
3. Jeżeli wszystkie warunki są spełnione, drukujemy TAK,
w przeciwnym razie drukujemy NIE (na wyjście)
Program Warunek Trójkąta
1:
2:
3:
4:
5:
7:
8:
#include <stdio.h>
int main()
{
int a, b, c;
scanf(„%d%d%d”, &a, &b, &c);
if ( a + b > c )
if ( b + c > a )
if ( a + c > b )
printf(„TAK”);
else printf(„NIE”);
else printf(„NIE”);
else printf(„NIE”);
return 0;
}
Program Warunek Trójkąta
1:
2:
3:
4:
5:
#include <stdio.h>
int main()
{
int a, b, c;
scanf(„%d%d%d”, &a, &b, &c);
if (( a + b > c ) && ( b + c > a ) && ( a + c > b ))
printf(„TAK”);
else printf(„NIE”);
7:
return 0;
8:
}
zastąpienie kolejnych warunków jednym, spójnik logiczny
&& = AND (logiczne „i”)
Instrukcja wyboru
switch ( wyrażenie ) {
case etykieta1: instrukcje
case etykieta2: instrukcje
........
default instrukcje
}
Wyrażenie użyte jako selektor wyboru musi przyjmować wartości całkowite,
etykiety muszą być stałymi całkowitymi.
Wykonanie instrukcji switch-case:
1. Wyrażenie porównywane jest kolejno z etykietami, jeżeli jedna z etykiet
ma wartość wyrażenia, to wykonywane są instrukcje po niej następujące.
2. Instrukcje po etykiecie default są wykonywane, jeżeli żadna z etykiet nie
ma wartośći równej selektorowi wyboru.
3. Aby uniknąć sprawdzania kolejnych przypadków, stosujemy instrukcję
break.
Instrukcja wyboru - przykład
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
#include <stdio.h>
int main()
{
int cyfra_mala = 0, cyfra_duza = 0, inne = 0;
char znak;
do {
/* instrukcja powtarzania
*/
scanf(„%c”, &znak);
switch ( znak ) { /* instrukcja wyboru
*/
case ‘0’: case ‘1’: case ‘2’: case ‘3’: case ‘4’:
cyfra_mala++; break;
case ‘5’: case ‘6’: case ‘7’: case ‘8’: case ‘9’:
cyfra_duza++; break;
default: inne++;
}
} while ( znak != EOF );
printf(„%d %d %d”, cyfra_mala, cyfra_duza, inne);
return 0;
}
Instrukcje powtarzania
while ( wyrażenie )
instrukcja
do
instrukcja
while ( wyrażenie )
for ( W1; W2; W3 )
instrukcja
Instrukcje powtarzania - zadania
1.
2.
3.
Napisz program, który wyznacza wartość: n! = 1 · 2 · ... ·
n
Napisz program, który drukuje trójkąt z gwiazdek:
*
***
*****
*******
Napisz program, który wczytuje ze standardowego wejścia
kolejne znaki – cyfry i tworzy z nich liczbę dziesiętną.
Plan wykładu II
•
•
•
•
•
•
•
•
•
Proste typy danych
Stałe
Deklaracje
Operatory i wyrażenia
Kolejność wykonywania obliczeń w wyrażeniach
Proste funkcje
Konwersja typów
Definicja struktur
Odwrotna Notacja Polska*
*: dla studiów dziennych (wymagane na egzaminie)
Proste typy danych
•
W języku C wsytępuje tylko kilka podstawowych typów danych:
–
–
–
–
•
char jeden bajt, typ znakowy
int typ cakowity, standard ANSI określa rozmiar na co najmniej dwa
bajty
float typ zmiennoprzecinkowy pojednycznej precyzji
double typ zmiennoprzecinkowy podwójnej precyzji
Kwalifikatory short (krótki, nie dłuższy niż int) oraz long (długi,
przynajmniej 4 bajty) odnoszą się do obiektów całkowitych, np.:
–
–
short int a;
long int b;
lub zmiennoprzecinkowych, np.:
–
•
long double c;
Kwalifikatory signed (ze znakiem liczby) oraz unsigned (bez znaku
liczby) można stosować z typem char lub dowolnym typem
całkowitym, np.:
–
–
signed char znak;
unsigned char;
/* od –128 do 127
/* od 0 do 255
*/
*/
Tablice oraz wskaźniki (wstęp)
•
•
Jeżeli chcemy utworzyć zmienną, która będzie przechowywać
wiele wartości danego typu prostego deklarujemy tablicę, czyli
obszar pamięci złożony z podanej w nawiasie liczby komórek,
z którego każda jest zadanego typu prostego (uwaga: można
tworzyć tablice tablic)
– int a[10];
– float tab[15];
Jeżeli nie znamy rozmiaru tablicy możemy utworzyć zmienną
wskaźnikową
– int* a;
– float* b;
Uwaga! Deklaracja zmienne wskaźnikowej nie powoduje
zarezerwowania dla niej pamięci!
Stałe
•
•
Każda stała jest jakiegoś typu (np. występująca w wyrażeniach).
Stałe całkowite
–
–
–
–
–
–
•
1234
1234L
1234U
1234UL
012
0x12
domyślnie typu int
typu long int
unsigned int
unsigned long int
system ósemkowy
system szesnastkowy
= 10
= 18
Stałe zmiennoprzecinkowe
–
–
–
–
•
notacja dziesiętna z kropką:
-314.15
notacja wykładnicza:
-3.1415e2 (lub –3.1415E2)
domyślnie typu double
użycie literki F lub L zmienia typ na float lub long double
Stałe znakowe oraz łańcuchowe
–
–
‘a’
„ala ma psa”
ujęte w pojedyncze apostrofy, typ char
ciągi znaków ujęte w apostrofy
Deklaracje
•
Deklaracje zmiennych
–
–
–
–
•
int cyfra = 7;
char znak;
char znak = ‘o’; /* deklaracja zmiennej znak wraz z inicjacją wartości */
double a, b = 3.2e-1;
Deklaracje stałych
–
const double pi = 3.1415;
–
const int liczba = 37;
Uwaga! Wartość zmiennych poprzedzonych kwalifikatorem const nie może
być zmieniona w trakcie działania programu (próba zmiany: zależna od
implementacji kompilatora).
•
Wyliczenia
–
–
enum dni {pn, wt, sr, czw, pt}; /* domyślnie: 0, 1, 2, 3, 4 */
enum rok {smoka = 1, koguta, malpy}; /* kolejne =2, =3, ... */
Operatory i wyrażenia
•
Język C oferuje programiście znaczną liczbę operatorów:
–
–
–
–
–
–
–
–
–
–
–
–
operatory arytmetyczne: addytywne (+ -), multiplikatywne (* / %),
inkrementacji (++) oraz dekrementacji (--)
operatory bitowe (& ^ | ~ << >>)
operatory logiczne (&& || !)
operatory relacyjne (> < <= >= == !=)
operatory przypisania (=)
operator warunkowy (? :)
operator wyliczeniowy (,)
operator wyboru składowych (. ->)
operator pobrania adresu oraz dostępu do zmiennej wskazywanej
(& *)
operator pobrania rozmiaru (sizeof)
operator indeksowania ([])
operator konwersji (nazwa-typu)
Operatory i wyrażenia
•
operatory arytmetyczne: addytywne (+ -), multiplikatywne
(* /), inkrementacji (++) oraz dekrementacji (--)
– addytywne
•
•
–
multiplikatywne
•
•
•
–
dodawanie
a+b
typy arytmetyczne
odejmowanie
a–b
typy arytmetyczne
(a,b – operandy, +,- operatory)
musi zachodzić zgodność typów, oba arytmetyczne lub jeden
całkowity, a drugi wskaźnikowy
mnożenie
dzielenie
reszta modulo
a*b
a/b
a%b
typy arytmetyczne
typy arytmetyczne
typy całkowite
inkrementacji i dekrementacji
•
•
przedrostkowe
--a
++a
przyrostkowe
a-a++
operand musi być typu arytmetycznego lub wskaźnikowego
Operatory i wyrażenia
•
operatory bitowe (& ^ | ~)
–
–
–
–
iloczyn
AND
a&b
typy całkowite
suma modulo 2 EXOR
a^b
typy całkowite
alternatywa
OR
a|b
typy całkowite
negacja
NOT
~a
typ całkowity
przed obliczaniem wartości dokonywane są konwersje arytmetyczne
Działania logiczne na bitach
a
b
AND
EXOR
OR
NOT
0
0
0
0
0
1
1
0
0
1
1
0
0
1
0
1
1
1
1
1
1
0
1
0
–
operatory przesunięcia bitowego >> <<
a >> b
przesuń liczbę a o b bitów w prawo
a << b
przesuń liczbę a o b bitów w lewo
dla liczb ze znakiem przesunięcie w prawo powiela bit znaku
Operatory i wyrażenia
•
operatory logiczne (&& || !)
–
–
–
koniunkcja
a && b
alternatywa
a || b
negacja
!a
wartość wyrażenia logicznego jest zawsze typu int
Tabela wartości wyrażeń logicznych
a
b
koniunkcja
0
0
fałsz
1
0
fałsz
0
1
fałsz
1
1
prawda
•
alternatywa
fałsz
prawda
prawda
prawda
negacja
prawda
fałsz
prawda
fałsz
operatory warunkowy (? :)
–
a?b:c
w pierwszej kolejności obliczana jest wartość a, jeżeli jest ona niezerowa
jest obliczana wartość b, natomiast c jest ignorowane, w przeciwnym
razie (a jest równa zero) - odwrotnie
Operatory i wyrażenia
•
operatory relacyjne (> < <= >= == !=)
–
a<b
a mniejsze niż b
–
a>b
a większe niż b
–
a <= b
a mniejsze lub równe b
–
a >= b
a większe lub równe b
–
a == b
a równe b
–
a != b
a różne od b
Oba operandy muszą być typu arytmetycznego lub oba wskaźnikami
zgodnych typów
•
operatory przypisania (= oraz złożone)
–
–
a=b
w przypadku róźnych typów zachodzi konwersja do typu a
złożone operatory przypisania
a += b
-= *= /= %= <<= >>= &= ^= |=
Operatory i wyrażenia
•
operator wyliczeniowy (,)
a, b, c;
dowolne typy
Opracowywanie tego wyrażenia przebiega zgodnie z kolejnością
•
operator wyboru składowych (. ->)
w celu odwołania się do struktury lub unii należy posłuzyć się jednym z
operatorów wyboru składowej
a.x
a->x
w zależności od typu a
•
operator pobrania adresu oraz dostępu do zmiennej
wskazywanej (& *)
–
–
•
&a
*a
pobranie adresu
odwołanie do zmiennej wskazywanej przez a
operator pobrania rozmiaru (sizeof)
–
–
•
sizeof(typ)
sizeof(a)
rozmiar w bajtach typu
rozmiar pamięci zajmowanej przez a
operator indeksowania ([])
–
a[2]
(*a+2)
dostęp do trzeciej  komórki (liczone od 0)
Kolejność wykonywania obliczeń
•
Każde wyrażenie musi zostać przeanalizowane przez
kompilator w celu ustalenia kolejności wykonywanych obliczeń
oraz zgodności typów występujących argumentów
–
–
•
a+b*c
3/2


(a + b) * c
1.5
?
?
1
a + (b * c)
Pierwszą rzeczą jest zatem informacja dla kompilatora, które
działania mają wyższy priorytet, drugą rzeczą jest informacja o
typach operandów w wyrażeniach
Podstawowe pojęcia:
•
–
–
–
–
–
priorytet operatorów
stanowi o kolejności wykonywanych działań
wiązanie
sposób łączenia operatora z operandami
operatory unarne (jednoargumentowe) i binarne (dwuargumentowe)
l-wyrażenia
wyrażenia identyfikujące obszar pamięci,
specyfikator const powoduje, że l-wartość nie jest modyfikowalna
opracowywanie wyrażenia
wszystkie obliczenia i inne operacje
jakie sa wykonywane podczas przetwarzania wyrażenia
Kolejność wykonywania obliczeń
•
•
Priorytety operatorów:
a+b*c 
(a + b) * c
?
a + (b * c)
wyrażenie zostanie opracowane zgodnie z priorytetami
operatorów + oraz *: jeżeli + będzie miał wyższy priorytet niż *
wtedy dodawanie zostanie wykonane przed mnożeniem, jeżeli
zaś odwrotnie, to mnożenie zostanie wykonane przed
dodawaniem
Wiązanie operatorów:
a+b–c
(a + b) – c
?
a + (b – c)
jeżeli kolejność obliczeń nie została określona za pomocą
nawiasów, zostanie rozstrzygnięta na podstawie wiązania
jeżeli wiązanie następuje od lewej do prawej (tak jak w C), to
wyrażenie zostanie potraktowane jako (a + b) – c, jeżeli od
prawej do lewej, to jako a + (b – c)
Kolejność wykonywania obliczeń
•
•
Operatory unarne:
operatory wymagające jednego operanda (argumentu),
np. ~ (negacja binarna), ! (negacja logiczna), &
(operator pobrania adresu), * (operator dostępu do
zmiennej wskazywanej)
Operatory binarne:
operatory dwuargumentowe, wymagające dwóch
operandów, wiązanie dla wszystkich operatorów
zdefiniowane jest od lewej do prawej, poza złożonymi
operatorami przypisania oraz operatorem
warunkowym
Proste funkcje
•
•
•
Podczas pisania programu (implementacji algorytmu) zachodzi
potrzeba wielokrotnego wykonywania tych samych operacji
(instrukcji) w różnych sytuacjach (dla różnych danych), np.
znalezienie najmniejszego elementu w ciągu liczb (w
szczególnym przypadku dwóch liczb), posortowanie ciągu
liczb, wyznaczenie rozwiązań równania kwadratowego.
W obrębie funkcji można zamknąć operacje wykonywane dla
zadanych paramtetrów wejściowych, np. ciąg liczb,
współczynniki równania kwadratowego a, b, c
W praktyce każdy (większy) program jest zbudowany z wielu
funkcji (dobry styl programowania, wiele krótkich i czytelnych
funkcji), poprawia to przejrzystość i czytelność programu,
zapewnia oszczędność czasu (funkcja raz napisana może być
wielokrotnie wykorzystana), pozwala wreszcie zaoszczędzić
pamięć – funkcja jest umieszczona w jednym miejscu w
pamięci podczas wykonywania programu.
Proste funkcje - przykłady
•
Funkcja obliczająca minimum dwóch liczb a oraz b, to
znaczy zwraca wartość równą mniejszej z obu liczb
1:
2:
3:
4:
int min(int a, int b) {
if ( a > b ) return b;
else
return a;
}
•
Wykorzystanie funkcji w programie:
–
•
wywołanie min(a, b)
Napisz funkcje realizujące (poniżej podano deklaracje)
–
liczenie silni: int silnia(int a);
–
znajdującą element minimalny:
int min(int* tablica);
Funkcje – deklaracja i definicja
•
Deklaracja funkcji, w odróżnieniu od definicji jest pojęciem
logicznym, stanowi informację dla kompilatora, że funkcja o
określonej nazwie, typie parametrów może zostać użyta (ale nie
musi!) w programie.
int min(int a, int b);
•
1:
2:
3:
4:
Definicja funkcji określa natomiast co funkcja robi, stanowi
zatem zapis jakiegoś algorytmu, definicja funkcji, w
odróżnieniu od deklaracji, powoduje przydzielenie obszaru
pamięci, w którym znajduje się kod wynikowy funkcji.
int min(int a, int b) {
if ( a > b )
return b;
else
return a;
}
Konwersja typów
•
Operator konwersji (jawnej) ma postać
(nazwa-typu) Operand
•
Konwersja niejawna zachodzi w sytuacji, gdy
operandy danego operatora są różnych typów, ogólna
zasada mówi, że automatycznie wykonuje się tylko
takie przekształcenia, dla których argument zajmujący
mniej pamięci jest zamieniany na argument zajmujący
więcej pamięci (char->int, int->float) bez utraty
informacji, np.:
–
–
int a=5/2;
float b = a/2;
a=?
b=?
Typy złożone - definicja tablic i struktur
•
Typy proste służą do opisu jednej cechy (wartości)
danej zmiennej, czasami jednak zachodzi potrzeba
zgromadzenia większej liczby informacji w ramach
danej zmiennej, np. liczby zespolone to pary liczb,
współrzędne w przestrzeni to trójki liczb, sekwencje
liczb jako ciągi n-elementowe, rekordy w bazach
danych grupujące informacje osobowe (wiek, imię,
nazwisko, etc..)
Tablica 10 liczb całkowitych:
•
–
–
int tablica[10];
/* jeden sposób */
int* tablica; /* drugi sposób – bardziej uniwersalny */
Następnie konieczne jest wywołanie funkcji malloc
powodującej przydzielenie w pamięci 10 komórek typu int:
tablica = (int*) malloc(sizeof(int) * 10);
Typy złożone - definicja tablic i struktur
•
struct Punkt { /* typ zmiennej */
int wsp_x, wsp_y;
char kolor;
char znak;
}; /* możliwe jest stworzenie od razu zmiennej typu Punkt */
• Utworzenie zmiennych:
struct Punkt a,b;
• Dostęp do pól zmiennej strukturalnej:
a.wsp_x = 1; a.kolor = 0; a.znak = ‘c’;
Plan wykładu III
•
•
•
•
•
•
•
•
•
•
Wskaźniki i adresy
Wskaźniki i tablice
Wskaźniki i argumenty funkcji
Arytmetyka na adresach
Złożone tablice (tablice tablic i tablice struktur)
Tablice o niejawnie deklarowanych rozmiarach funkcja malloc
Operacje na plikach, formatowane wejście oraz wyjście
Struktura programu: metody projektowania złożonych
programów
Zmienne globalne i zasięg nazw
Kilka uwag o stylu programowania
Wskaźniki i adresy
• Wskaźnik jest zmienną, której wartość jest adresem
innej zmiennej, np.
– int a = 2, *c; c = &a;
c=?
*c=?
• ANSI C wprowadza jawne reguły operowania na
wskaźnikach
• Jednoargumentowy operator & zwraca adres obiektu
• Jednoargumentowy operator * pozwala nam uzyskać
wartość obiektu wskazywanego przez wskaźnik
– c  adres zmiennej a
*c  wartość zmiennej a (=2)
• Organizacja pamięci globalnej - sterta oraz lokalnej
(funkcji) - stos
Wskaźniki i tablice
• W języku C istnieje bezpośrednia zależność pomiędzy
wskaźnikami i tablicami, tzn. tablice są jawnie
deklarowane jako ciąg bezpośrednio następujących po
sobie komórek a zmienna tablicowa jest wskaźnikiem
na pierwszy element tablicy (początek tablicy)
– int a[5] = {0,1,2,3,4}; a = ?
a[1] = ?
*a = ?
*(a+2) = ?
• Wyraźenie „tablica i indeks” jest równoważne
wyrażeniu „wskaźnik i przesunięcie”
• Tablice do funkcji przekazujemy poprzez wskaźnik na
pierwszy jej element, czyli po prostu przez jej nazwę
• Podobnie jeżeli funkcja zwrócić ma tablicę jako
wartość – posługujemy się wskaźnikiem
Wskaźniki i argumenty funkcji
• W języku C argumenty przekazywane są przez
wartość, np.:
– void zamiana(int a, int b) { int c; c = a; a = b; b = c; }
– czy wywołanie funkcji zamiana(a,b) spowoduje zamianę
wartości zmiennych a oraz b
• Zmiana wartość zmiennych podanych jako argumenty
do funkcji jest możliwa poprzez odwołanie się do nich
przez wskaźniki, np.
– void zamiana(int* a, int* b) { int c; c = *a; *a = *b; *b = c; }
Arytmetyka na wskaźnikach
• Ogólna zasada
Jeżeli p jest wskaźnikiem do pewnego elementu tablicy, to
po wykonaniu operacji p++ wskazywać on będzie następny
element w tablicy (operacje arytmetyczne analogicznie).
• Następny element w tablicy (np. tablicy struktur) ma
adres większy o liczbę bajtów potrzebnych do
przechowania wartości zmiennej w jednej komórce.
Złożone tablice
• Tablice tablic
– int a[2][3]; /* tablica dwuwymiarowa */
– a=?
a[1] = ?
a[1][2] = ?
– int a[2][2] = {{1,2}, {3,4}}; int **c; c = &a; c[2][2] = ?
• Tablice struktur
– struct Bakteria { int rozmiar; char nazwa[32]; };
– struct Bakteria kolonia[10];
– kolonia = ?
kolonia[0] = ?
kolonia[1].nazwa = ?
Plan wykładu IV
•
•
•
•
Unie i pola bitowe
Skomplikowane deklaracje
Wybrane komputerowe prawa Murphy’ego
Abstrakcyjne typy danych i ich implementacje:
stos, kolejka, listy
• Struktury i funkcje rekursywne
Unie i pola bitowe
• Unie, podobnie jak struktura składa się z pól, z tą
różnicą, że zaczynają się one w tej samej komórce
pamięci, rozmiar unii jest równy rozmiarowi
największej składowej
union example {
double a;
long b;
char* c; };
/* średnik jest ważny! */
w programie:
union example u = { 1234.5678 };
printf(”%lf\n%d\n%s\n”,u.a, u.b, u.c);
Skomplikowane deklaracje
• Chcemy stworzyć tablicę 7 wskaźników do tablic o
rozmiarach 2 x 3 przechowujących dane typu double:
- 7 wskaźników
*tablica[7]
- wskazują na tablice o rozmiarze 2 x 3
(*tablica[7])[2][3]
- typ wskazywany double
double (*tablica[7])[2][3]
tworzymy elementy (tablice) korzystając z funkcji
malloc
Skomplikowane deklaracje
• Chcemy zadeklarować tablicę dwuelementową
wskaźników do funkcji o dwóch parametrach
całkowitych i wartości zwracanej typu double:
- tablica dwuelementowa
tablica[2]
- elementami tablicy będą wskaźniki
*(tablica[2])
- wskaźniki te będą wskazywać na funkcje o dwóch
parametrach typu int oraz wartościach typu double
double (*(tablica[2]))(int,int)