Programowanie Komputerów
Skrypt wykładów
Opracowali: Jarosław S. Walijewski
Zenon A. Sosnowski
ii
Literatura
Skrypt wykładów:
opal:// H:/C
Brian W. Kernighan Dennis M. Ritchie
„ Język ANSI C ”
WNT
Claude Delannoy
„Ćwiczenia z języka C ”
WNT
Marek Kotowski
„Wysokie C ”
Wydawnictwo Lupus
Jan Bielecki
„Encyklopedia Języka C dla IBM PC”
WNT
Marc J. Rochind
„Programowanie w systemie UNIX dla zaawansowanych”
WNT
iii
Program wykładów
1.
H
ISTORIA
............................................................................................ 1
2.
P
RZEGLĄD KONSTRUKCJI JĘZYKOWYCH
......................................... 3
3.
B
UDOWA PROGRAMU
....................................................................... 16
4.
P
ODSTAWOWE TYPY DANYCH
......................................................... 18
5.
O
PERATORY
..................................................................................... 23
6.
I
NSTRUKCJE
..................................................................................... 27
7.
F
UNKCJE
........................................................................................... 31
8.
T
ABLICE
1-
WYMIAROWE
................................................................. 40
9.
T
ABLICE WIELOWYMIAROWE
......................................................... 48
10. A
RGUMENTY MAIN
........................................................................... 52
11. D
EFINIOWANIE TYPÓW
.................................................................... 53
12. S
TRUKTURY
...................................................................................... 55
13. U
NIE
.................................................................................................. 62
14. M
ODELE PAMIECI
............................................................................ 65
15. D
YNAMICZNY PRZYDZIAŁ PAMIĘCI
................................................ 68
16. S
TRUKTURY LISTOWE
...................................................................... 70
17. S
TOS
.................................................................................................. 81
18. D
RZEWA BINARNE
............................................................................ 87
19. P
REPROCESOR
.................................................................................. 92
20. F
UNKCJE ZE ZMIENNĄ ILOŚCIĄ ARGUMENTÓW
........................... 105
21. W
EJŚCIE
/
WYJŚCIE
......................................................................... 110
22. C
W SYSTEMIE
UNIX ..................................................................... 122
23. W
YSOKIE
C .............................B
ŁĄD
!
N
IE ZDEFINIOWANO ZAKŁADKI
.
1
1.
Historia
1967 BCPL
Martin Richards
University, Cambridge, MA, USA
Język beztypowy,
słowo maszynowe, wskaźniki,
arytmetyka wskaźników
Bell Laboratories, New Jersey
1969 B
Ken Thompson
pierwsza wersja systemu UNIX
j. wewnętrzny + B 1970 rok
1972 C
Dennis Ritchie
pierwsza implementacja na PDP-11
Język systemowy systemu UNIX (90% kodu)
1978 The C Programming Language
klasyczna definicja Języka
1983 American National Standard Institute
ANSI C
koniec lat 80
standard Języka, usuwa niejednoznaczność i
wprowadza rozszerzenie
C++
2
1.1.
Dlaczego C?
język programowania, podstawowy język programowania.
Język programowania strukturalnego, ogólnego przeznaczenia. Pozwala
zbliżyć się do sprzętu i programowania operacji, które zwykle wyzmagają
j.maszynowowego.
C jest mały -
mniej słów kluczowych niż w Pascalu przy większych
możliwościach obliczeniowych.
C silnie związany z UNIX
nie potrzeba wbudowywać w programy kontroli we/wyj
lub
obslugi przerwań
w C używa się procedur bibliotecznych SO
C - przenaszalny
preprocesor (różne implementacje)
C - zwięzły
dużo operatorów
(dostęp do słowa, arytmetka adresowa)
C - modułowy
jeden rodzaj procedur - funkcje zewnętrzne z parametrami
przekazywanymi przez wartość
1.1.1.
Wady
skomplikowana składnia
brak automatycznej kontroli indeksów tablic
wielokrotne użycie operatorów +, =
= operator przypisania
= = operator równości
3
2.
Przegląd konstrukcji językowych
2.1.
Wyjście
#include <stdio.h>
main ()
{
printf("Programowanie w C nie jest trudne!\n");
}
printf ("Programowanie");
printf (" w C nie jest");
printf (" trudne\n");
Zmienne, wyrażenia, przypisanie wartości
/*Temperatura Fahrenheita-Celsjusza*/
#include <stdio.h>
main ( )
{
int fahr;
float cel;
cel = (5.0/9.0)*(fahr-32);
printf(
"%d stopni Fahrenheita to %f stopni Celsjusza \n",
fahr,cel);
}
4
2.2.
Preprocesor
#define PI 3.141592
#define C 299792.458
/* pr
ę
dko
ść
ś
wiatła w km/sec */
#include < .h> - standardowa
#include " .h" - zdefiniowanie przez u
ż
ytkownika
Plik ocean.h
#include <stdio.h>
#define OBSZAR 2337
#define MIL_KW_NA_KM_KW 0.3861021585424458
#define STOP_KW_NA_MIL_KW (5280 + 5280)
#define CALI_KW_NA_STOPE_KW 144
Plik ocean.c
/*Mierzenie Pacyfiku*/
#include "ocean.h"
main ( )
{
const int ocean = OBSZAR; /* w km kwadratowych*/
double mil_km,stop_kw,cal_kw;
mil_kw = MIL_KW_NA_KM_KW * ocean;
stop_kw = STOP_KW_NA_MIL_KW*mil_kw;
cal_kw = CAL_KW_NA_STOP_KW*stop_kw;
printf(
"Obszar Pacyfiku wynosi %d km kwadratowych \n",
obszar);
printf("W innych jednostkach \n");
printf("%22.7e mil kwadratowych \n", mil_kw);
. . .
}
5
2.3.
Funkcje wejścia i wyjścia
printf(const char* format,...)
c
znak
d
liczba calkowita
e
liczba zmiennoprzecinkowa
f
liczba zmiennoprzecinkowa
g
liczba zmiennoprzecinkowa
s
napis
printf("abc");
printf("%s","abc");
printf("%c%c%c",'a','b','c');
printf("%c%3c%5c",'A','B','C')
A B C
Wprowadzanie danych
scanf(constchar* format,...)
scanf("%d",&x);
& - operator adresowy
adres x
Kody konwersji:
c
d
f
s
lf -
double
Lf -
long double
#include <stdio.h>
main ( )
{
char c1,c2,c3;
int i; float x; double y;
printf("\n%s\n%s%s",
"Wprowad
ź
trzy znaki",
"liczb
ę
całkowit
ą
, zmiennoprzecinkow
ą
",
" i liczb
ę
podwójnej precyzji");
scanf("%c%c%c%d%f%lf",&c1,&c2,&c3,&i,&x,&y);
printf("\n oto dane, które wprowadziłes \n");
printf("%3c%3c%3c%5d%17e%17e\n\n",c1,c2,c3,i,x,y);
}
6
2.4.
Sterowanie
if (wyrażenie)
instrukcja
a=1;
if (b==3)
a=5;
printf("%d",a);
if(a==3)
{
b=5;
c=7;
}
if (wyrażenie)
instrukcja1
else
instrukcja2
if(l==0)
{
a=2;
b=3;
}
else
{
a=-2;
b=-3;
}
7
2.5.
Instrukcja pętli
while (wyrażenie)
instrukcja
#include <stdio.h>
main ( )
{
int i=1, sum=0;
while (i<=5)
{
sum+=i;
++i;
}
printf("suma=%d", sum);
}
sum+=i; sum = sum+i;
zmienna op= wyrażenie
zmienna = zmienna op wyrażenie
gdzie
op + - * /
++i; i=i+1; - -i; i=i-1;
++zm przed wyrażeniem
zm++ po wyrażeniu
h=5;
x=h++; 5
x=++h; 6
8
2.5.1.
Pętla for
for (wyrażenie1;wyrażenie2;wyrażenie3)
instrukcja
wyrażenie1;
while (wyrażenie2)
{
instrukcja;
wyrażenie3
}
for (i=1;i<=5;++i)
sum+=i;
/*obliczanie elementu minimalnego, maksymalnego
i sredniej arytmentycznej */
#include <stdio.h>
#include <stdlib.h>
main ( )
{
int i;
double x, min,max,sr,suma;
if(scanf("%lf",&x) != 1)
{ printf("Brak danych \n"); exit (1);}
min=max=suma=sr=x;
printf("Nr Min Max Suma Sr \n");
printf("%d %f %f %f %f %f" ,
1, x, min, max, suma, sr);
for (i=2;scanf("%lf",&x)==1;++i)
{
if(x<min) min=x;
else if (x>max) max=x;
suma+=x;
sr=suma/i;
printf("%d %f %f %f %f %f\n",
i, x, min, max, suma, sr)
}
}
9
2.6.
Funkcje
double potega (double x, double y)
prototyp funkcji
double potęga (double, double);
#include <stdio.h>
main ( )
{
int i,h;
float min, max, x;
float minimum
(float, float), maximum (float, float);
void prt_inf (void);
prt_inf( );
printf("Wprowad
ź
n:");
scanf("%d,&n);
printf("\n Wprowad
ź
%d liczb rzeczywistych :", n);
scanf("%f",&x);
min=max=x;
for(i=2; i<=n; ++i)
{
scanf("%f",&x);
min = minimum (min,x);
max = maximum (max,x);
}
printf(
"Warto
ść
minimalna: %f\nWarto
ść
maxymalna: %f\n",
min, max);
}
void prt_inf (void)
{
printf("\n\n %s \n%s \n",
"Program wczytuje długo
ść
ci
ą
gu"
.............................
}
10
float minimum (float x, float y)
{
if (x<y)
return x;
else
return y;
}
float maximum (float x, float y)
{
if (x>y)
return x;
else
return y;
}
2.6.1.
Przekazywanie parametrów "przez wartość"
#include <stdio.h>
main ( )
{
int a=1;
void proba_zamiany(int);
printf("%d \n",a);
/* drukuje si
ę
1 */
proba_zamiany (a);
printf("%d \n",a);
/*jeszcze raz drukuj si
ę
1 */
}
void proba_zamiany (int a)
{
a=2;
}
11
2.7.
Tablice
int t[3];
t[0], t[1], t[2]
Numeracja indeksów tablic zaczyna się od zera.
Przykład.
/***********************************************
Program wczytuje 5 warotosci, porzadkuje je,
oblicza sume i wartosc srednia
**********************************************/
#include <stdio.h>
#define
WYMIAR 5
main()
{
int i, j, tab[WYMIAR], sum = 0, rob;
printf("\n\nWprowadz %d wartosci: ", WYMIAR);
for (i = 0; i < WYMIAR; ++i)
{
scanf("%d", &tab[i]);
sum += tab[i];
}
for (i = 0; i < WYMIAR - 1; ++i)
/* Sortowanie babelkowe */
for (j = WYMIAR - 1; j > i; --j)
if (tab[j-1] < tab[j])
/* Sprawdzenie porzadku */
{
rob = tab[j-1];
tab[j-1] = tab[j];
tab[j] = rob;
}
printf("\nUporzadkowane wartosci:\n");
for (i = 0; i < WYMIAR; ++i)
printf(" tab[%d] = %5d\n", i, tab[i]);
printf("\n\n%s%d\n%s%f\n", "Suma = ", sum,
"Srednia arytmetyczna = ",
(double) sum / WYMIAR);
}
12
2.8.
Napisy
Napis jest tablicą znaków.
Funkcje standardowe (w <stdio.h>).
Czytanie znaku z klawiatury - getchar();
Wyprowadzenie znaku na ekran - putchar();
Przykład.
#include
<stdio.h>
#include
<ctype.h>
#define
MAX_DL 100
main()
{
char c, nazwa[MAX_DL];
int i, sum = 0;
printf("\n\nNapisz swoje imie: ");
for (i = 0; (c = getchar()) != '\n'; ++i)
{
nazwa[i] = c;
if (isalpha(c))
sum += c;
}
nazwa[i] = '\0';
printf("\n%s%s%s\n", "Witaj ", nazwa, " !");
printf("Suma kodow liter Twojego imienia daje %d.\n"
,sum);
printf("\n%s\n", "Zgadnii co to jest:");
for (--i; i >= 0; --i)
putchar(nazwa[i]);
}
Komentarz:
(c = getchar()) != '\n'
isalpha(); <ctype.h>
nazwa[i] = '\0';
\0 - null - koniec napisu
0 1 2 3 4 5 6 7 8 9 ...
B a r b a r a \0
13
2.9.
Wskaźniki
Wskaźnik jest adresem objektu w pamęci.
Przykład.
/********************************************/
/* Nazwa tablicy jest wska
ź
nikiem. */
/********************************************/
#include
<stdio.h>
#include
<string.h>
#define
MAX_DL 100
main()
{
char c = 'a', *w, s[MAX_DL];
w = &c;
printf("%c%c%c ", *w, *w + 1, *w + 2);
strcpy(s,"ABC");
printf("%s %c%c%s\n", s, *s + 6, *s + 7, s + 1);
strcpy(s,"program pokazuje przeksztalcanienapisow");
w = s + 8;
for ( ; *w != '\0'; ++w)
{
if (*w == 'p')
*w = 'P';
if (*w == ' ')
*w = '\n';
}
printf("%s\n",s);
}
Komentarze:
Wynik działania programu:
abc ABC GHBC
program Pokazuje
Przeksztalcanie
naPisow
w = &c;
14
printf("%c%c%c ", *w, *w + 1, *w + 2);
*w wartosc 'a'
*w+1 'b'
*w+2 'c'
strcpy(s,"ABC"); kopiowanie napisow <string.h>
0 1 2 3 4 . . .
A B C \0
printf("%s %c%c%s\n", s, *s + 6, *s + 7, s + 1);
s wskaźnik do s[0] (adres bazowy tablicy) ABC
*s wartośc s[0] czyli 'A'
a wieęc *s+6 daje 'G'
*s+7 'H'
s+1 arytmetyka na wskaźnikach
wartoscią jest wskaźnik do s[1] BC
w = s + 8
równoważne
w = &s[8];
w = s poprawne bo w zmienna wskaźnikowa
s = w źle , bo s stała wskaźnikowa
s[i]
równoważne
*(s + i)
w[i] *(w + i)
++w
poprawne
++s
błędne
15
2.10.
Pliki
#include
<stdio.h>
main()
{
FILE *wp;
wp = fopen("DANE.DAT","r");
. . .
Uwagi:
1. FILE - typ standardowy zdefiniowany w <stdio.h>
2. fopen(nazwa_pliku, tryb) - funkcja standardow <stdio.h>
tryb: "r" - czytanie
"w" - pisanie
"a" - dołączanie
Wartość funkcji NULL - pliku nie można otworzyć
3. Odwołania do pliku realizuje się za pomocą zmiennej typu wskaźnik do
FILE.
4. Wszystkie pliki są automatyczne zamykane po zakończeniu programu.
5. fclose() - zamknięcie pliku
Przykład.
#include
<stdio.h>
main()
{
int c, i, litery[26];
FILE *wpd, *wpw;
wpd = fopen("DANE", "r");
wpw = fopen("WYNIKI","w");
for (i = 0; i < 26; ++i)
litery[i] = 0;
while ((c = getc(wpd)) != EOF)
if (c >= 'A' && c <= 'Z')
++litery[c - 'A'];
for (i = 0; i < 26; ++i)
fprintf(wpw, "%c: %5d\n", 'A' + i, letter[i]);
putc('\n', wpw);
}
16
3.
Budowa programu
Program w języku C jest ciągiem znaków.
Alfabet:
małe litery
a b c ... z
duże litery
A B C ... Z
cyfry
0 1 2 ... 9
znaki specjalne + - * / = ( ) [ ] { } < > ' "
! @ # $ % & _ | ^ ~ \ . , ; : ?
spacja (odstęp), koniec linii, znak tabulacji
Proces kompilacji:
Tekst programu w języku C ----> grupowanie znaków w atomy
----> tłumaczenie na kod wynikowy
Atomy ANSI C:
słowa kluczowe
identyfikatory
stałe
napisy
operatory
separatory
Reguły syntaktyczne (gramatyka języka)
Metajęzyk - BNF (Backus-Naur Form)
Produkcja:
cyfra ::= 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
Kategoria syntaktyczna cyfra jest równa z definicji
0, lub 1, lub 2, ... , lub 9.
Symbole używane w produkcjach:
italics
kategoria syntaktyczna
::=
"równe z definicji"
|
lub
{ }
1
wybór 1 elementu z wymienionych
17
{ }
0+
powtórzenie 0 lub więcej razy
{ }
1+
powtórzenie 1 lub więcej razy
{ }
op
opcjonalne
Inne symbole są symbolami końcowymi języka.
Przykłady:
litera_lub_cyfra ::= litera | cyfra
litera ::= mała_litera | duża_litera
mała_litera ::= a | b | c | . . . | z
duża_litera ::= A | B | C | . . . | Z
ciąg_liter_lub_cyfr ::= { litera_lub_cyfra }
0+
instrukcja_warunkowa ::= if (wyrażenie) instrukcja
{ else instrukcja }
op
Słowa kluczowe:
auto
do
goto
signed
unsigned
break
double
if
sizeof
void
case
else
int
static
volatile
char
enum
long
struc
while
const
extern
register
switch
continue
float
return
typedef
default
for
short union
18
4.
Podstawowe typy danych
Deklaracja
deklaracja ::= typ identyfikator { , identyfikator }
0+
Podstawowe typy danych (pełny zapis):
char
signed char
unsigned char
signed short int
signed int
signed long int
unsigned short int unsigned int
unsigned long int
float
double
long double
Podstawowe typy danych:
char
signed char
unsigned char
short
int
long
unsigned short
unsigned
uunsigned long
float
double
long double
4.1.
TYPY całkowite
( Reprezentacja w Borland C++ )
TYP char
typ
ilość bitów
zakres
---------------------------------------------------------------------------------------
unsigned char
8
0 do 255
char, signed char
8
-128 do 127
Przykład:
char c = 'a';
printf("%c",c);
/* drukuje sie a */
printf("%d",c); /* drukuje si
ę
97 */
19
typ
ilość bitów
zakres
----------------------------------------------------------------------------------
short, int
16
-32768 do 32767
unsigned short, unsigned 16
0 do 65535
long
32
-2,147,483,648 do
2,147,483,647
unsigned long
32
0 do 4,294,967,295
Przykład:
#define
BIG
2000000000
main()
{
int a, b = BIG, c = BIG;
a = b + c;
?* przekroczenie zakresu */
}
Przykłady stałych:
32000
stała typu int
320000
stała typu long int
Kwalifikator
typ
stała
-----------------------------------------------------------------------
u lub U
unsigned
37U
l lub L
long
37L
ul lub UL
unsigned long
37UL
20
4.2.
Typy zmiennoprzecinkowe
typ
ilość bitów
zakres
precyzja
---------------------------------------------------------------------------------------
float
32
3.4*10^-38 do 3.4*10^38
7 cyfr
double
64
1.7*10^-308 do 1.7*10^308
15 cyfr
long double
80
3.4*10^-4932 do 1.1*10^4932
19 cyfr
Stała zmiennoprzecinkowa
- liczba zawierająca kropkę lub literę e lub E.
Przykład:
12.345
3. 0.53
2.34e-4
2.5E3
Uwaga:
stała zmiennoprzecinkowa jest typu double
Kwalifikator
typ
stała
--------------------------------------------------------------------------
f lub F
float
3.7F
l lub L
long double
3.7L
Operator sizeof
sizeof(obiekt) - ilość bajtów zajmowanych przez obiekt
Przykład:
sizeof(char) = 1
FUNKCJE MATEMATYCZNE
<math.h>
sqrt()
pow()
exp() log() sin() cos() tan()
21
Konwrsja typów
Każde wyrażenie arytmetyczne nma wartość i typ
Automatyczna konwersja typów wyrażenia
x op y
1. Typy char, short, signed char, unsigned char są zamieniane na typ
int.
2. Jezeli typy nadal nie są zgodne to typ niższy zamienia się na typ
wyższy zgodnie z hierarchi_ typów:
int < unsigned < long < unsigned long < float < double < long double
Przykład:
char c;
double d;
float f;
int i;
long l;
short s; unsigned u;
c - s / i
- int
u * 2.0 - i
- double
c + 3
- int
c + 5.0
- double
u * 7 - 1
- unsigned
7 * s * l
- long
d + s
- double
4.2.1.
Rzutowanie
Wymuszenie konwersji typów:
(typ) wyrażenie
Przykład:
(double) i
(long) ('A' + 1.0)
x = (float) ((int) y + 1)
(double) (x = 77)
(float) i + 3
22
4.3.
Stałe szesnastkowe i ósemkowe
Cyfra szesnastkowa
wartość dziesi_tna
0
0
1
1
2
2
. . .
9
9
A
10
B
11
C
12
D
13
E
14
F
15
h
n
h
n-1
. . .h
1
h
0
h
n
*16
n
+ h
n-1
*16
n-1
+ . . .+ h
1
*16 + h
0
0xA0F3C
688260
0x2A
2*16 + 10 = 42
0xB3
B*16 + 3 = 11*16 + 3 = 179
0x113
1*16
2
+ 1*16 + 3 = 275
23
5.
Operatory
Priorytety operatorów:
------------------------
1. () []
LP
2. ++ ~
-- !
sizeof
(typ)
jedn.arg.(*, -)
*(wsk)
&(adres)
PL
3. *
/
%
LP
4. +
-
LP
5. <<
>>
LP
6. <
<= >
>=
LP
7. ==
!=
LP
8. &
LP
9. ^
LP
10. |
LP
11. &&
LP
12. ||
LP
13. ?:
PL
14. =
+= -=
/= itp.
PL
15 , (operator przecinkowy)
LP
5.1.
Operatory relacji, operatory logiczne
1-arg (+ i -) ++ -- !
p -> l
* / %
l -> p
+ -
l -> p
< <= > >=
l -> p
== !=
l -> p
&& (and)
l -> p
||
(or)
l -> p
= += -= *= /=
p -> l
Przykład:
char c = 'w';
int i = 1, j = 2, k = -7;
double x = 7e+33, y = 0.0001;
24
'a' + 1 < c
1
-i - 5*j >= k+1
0
((-i) - (5*j)) >= (k+1)
3 < j < 5
1
j = 4
1
j = 7
1
Przykład.
char c = 'A';
int i = 7, j = 7;
double x = 0.0, y = 2.5;
!c
0
!(i - j)
1
!i - j
-7
!!(x+y)
1
5.2.
Operatory zwiekszanie i zmmiejszania
++i; i=i+1; - -i; i=i-1;
++zm
- -zm
przed wyrażeniem
zm++
zm- -
po wyrażeniu
h=5;
x=h++; 5
x=++h; 6
5.3.
Operatory Bitowe
Logiczne:
~ - negacja
& - koniunkcja
^ - różnica symetryczna
| - alternatywa
Przesunięcie:
>> - w prawo
<< - w lewo
25
5.3.1.
Uzupełnienie jedynkowe
long
a = 70707;
00000000 00000001 00010100 00110011
~a
11111111 11111110 11101011 11001100
5.3.2.
Bitowe operatory logiczne
a
b
a&b a^b a|b
------------------------
0
0
0
0
0
1
0
0
1
1
0
1
0
1
1
1
1
1
0
1
long
a = 33333, b = -77777;
a
00000000 00000000 10000010 00110101
b
11111111 11111110 11010000 00101111
a&b 00000000 00000000 10000000 00100000
a|b 11111111 11111110 11010010 00111111
a^b 11111111 11111110 01010010 00011010
5.3.3.
Operatory przesuwania
wyr1 op wyr2
char c = 'Z';
c
01011010
26
c << 1
10110100
cc>>1
01011010
x << y mnożenie x*2
y
x >> y dzielenie x / 2
y
27
6.
Instrukcje
6.1.
Instrukcja złożona
instrukcja_złożona ::= { { deklaracje }
0+
{instrukcje }
0+
}
{
int a;
a = b;
. . .
}
6.2.
Instrukcja pusta
;
6.3.
Instrukcja warunkowa
if ( wyrażenie1 )
if ( wyrażenie2 )
{
instrukcja1
else
instrukcja2
}
else wiąże się z najbliższym if
6.4.
Instrukcje iteracyjne
while ( wyrażenie ) instrukcja
for (wyr1; wyr2; wyr3)
instrukcja
do instrukcja while ( wyrażenie )
do
{
28
sum +=i;
scanf("%d", &i);
} while (i > 0);
6.5.
Instrukcja Skoku
goto etykieta
etykieta ::= identyfikator:
Przykład
goto dalej:
koniec : exit(1);
dalej:
while(1)
{
if(i++>10) goto koniec;
printf("%3d",i);
}
6.6.
Instrukcje strowania petlą
break
- przerwanie działania pętli
continue
- kontynuacja od początku pętli
while(1)
{
if(i++>10) break;
printf("%3d",i);
}
void main()
{ int x,y;
for(;;) {
printf("Podaj dwie liczby (0 0 by zakonczyc)");
scanf("%d%d",&x,&y);
if(!x&&!y)break;
printf("%3d + %3d = %6d\n",x,y,x+y);
printf("%3d - %3d = %6d\n",x,y,x-y);
printf("%3d * %3d = %6d\n",x,y,x*y);
if(!y)continue;
printf("%3d / %3d = %6d\n",x,y,x/y);
}
}
void main()
{ int x,y,c; char s[500]="",*w;
w=s;
29
for(x=0;x<10;x++)
for(y=0;y<50;y++) {
c=getchar(); if(c=='\n') break;
*w++=c;
}
w='\0'; printf(s);
}
6.7.
Instrukcja wyboru
instrukcja _switch ::= switch(wyrażenie_skoku)
istrukcja_wyboru | instrukcja_domyślna | blok_przełączeniowy
instrukcja_wyboru ::= / case stała_składowa : /1+ instrukcje
instrukcja_domyslna
::= default: instrukcje
blok_przełączeniowy::= { /grupa_wyboru /0+
/grupa_domyślna /opt
}
grupa_wyboru ::= / case stała_składowa : /1+
/instrukcje/1+
Grupa_domyślna ::= default: /instrukcje/1+
****************************************
switch(x)
{ case 0:break;
case 2:printf("bardzo ");
case 1:printf("powazny ");
default:printf("blad\n");
}
**************************************
switch(c)
{
case '+':w=a+b;break;
case '-':w=a-b;break;
case '*':w=a*b;break;
case '/':if(b) {e=1;break;}
w=a/b;break;
default:e=2; ;
}
switch(e){
case 0: printf("%d %c %d = %d\n",a,c,b,w);break;
case 2: printf(" Blad operatora");break;
case 1: printf(
30
" Blad dzialenia przez zero");break;
default: printf(" Nieznany blad ");break;
}
6.8.
Operator przecinkowy
wyrażenie_przecinkowe ::= wyrażenie ,
wyrażenie
Uwagi:
1. najniższy priorytet
2. od lewej do prawej
3. wartością jest ostatnie wyrażenie
or (sum = 0, i = 1; i <= n; sum += i, ++i)
;
Przykłady:
int i,j,k = 3;
double x = 3.3;
i = 1, j = 2, ++k+1
(((i=1),(j=2)),((++k)+1)
5
k != 1, ++x*2.0+1
((k != 1), (((++x)*2.0)+1)
9.6
31
7.
Funkcje
Funkcja jako realizacja dekompozycyjnej metody rozwiązywania
problemów zgodnie z Regułami programowania strukturalnego.
Program w języku C składa się z jednego lub więcej plików.
Każdy z nich może zawierać zero lub więcej funkcji.
Jedną z tych funkcji jest funkcja main.
Funkcje nie mogą być zagnieżdżane.
Działanie programu zaczyna się od funkcji main.
7.1.
Definicja funkcji
typ nazwa_funkcji(listałparametrów)
{
deklaracje
instrukcje
}
Przykłady:
int potega(int n)
{
int i, p = 1;
for (i = 2; i <= n; ++i)
p *= i;
return p;
}
void nic(void)
{
}
double podwojenie(double a)
{
return (2.0 * a);
}
32
int dodaj(int a, int b)
{
int c;
. . .
return(a + b + c);
}
Uwaga:
Jeżeli w definicji funkcji nie występuje określenie typu, to
funkcja jest typu
int
.
Zmienne zadeklarowane wewnątrz funkcji są lokalne (istnieją
tylko w tej funckcji). Inne zmienne używane w funkcji są
zmiennymi globalnymi.
#include
<stdio.h>
int a = 33;
/* zmienna globalna */
main()
{
int b = 77;
/* zmienna lokalna */
printf("%d %d\n", a, b);
}
33
7.2.
Instrukcja return
instrukcja_return ::= return; | return
wyrażenie ;
Przykłady:
return;
return ++a;
return (a + b);
Uwaga:
Wyrażenie po return może być ujęte w nawiasy.
Wykonanie instrukcji return powoduje zakończenie działania
funkcji i przekazanie sterowania do instrukcji wywołującej daną
funkcję.
Jeżeli instrukcja return zawiera wyrażenie, to wartość tego
wyrażenia
jest
przekazywana
również
do
instrukcji
wywołującej.
Jeżeli zachodzi potrzeba, to typ wartości jest zamieniany na typ
zgodny z typem funkcji.
Brak instrukcji return w funkcji powoduje zakonczenie jej
działania po napotkaniu znaku końca funkcji tj. } .
Przykład.
double wartosc_bezwzgledna(double x)
{
if (x >= 0.0)
return x;
else
return -x;
}
34
7.3.
Prototyp funkcji
typ
nazwa_funkcji (listałtypów_parametrów)
Przyklady:
double sqrt(double);
void f(char c, int i);
równoważne
void f(char, int);
7.4.
Wywołanie funkcji
Wywołanie funkcji zawiera nazwę funkcji i listę parametrów aktualnych.
Po wywołaniu funkcji następuje:
1. Obliczne są wartości wyrażeń reprezentujących parametry aktualne.
2. Typy obliczonych wartości wyrażeń są zamieniane na typy parametrów
formalnych zgodnie z deklaracją funkcji.
3. Wykonywana jest treść funkcji.
4. Po napotkaniu instrukcji return lub znaku końca funkcji sterowanie
powraca do miejsca wywołania funkcji.
Uwaga.
Parametry funkcji są przekazywane przez wartość.
35
7.5.
Reguły zasłaniania
Identyfikatory lokalne zasłaniają identyfikatory globalne.
Przykłady.
{
int a = 1, b = 2, c = 3;
printf("%d %d %d\n", a, b, c);
/* 1 2 3 */
a = b;
{
int b = 4;
float c = 5.0;
printf("%d %d %f\n", a, b, c); /* 1 4 5.0 */
a = b;
{
int c;
c = b;
printf("%d %d %d\n", a, b, c); /* 4 4 4 */
}
printf("%d %d %f\n", a, b, c);
/* 4 4 5.0 */
}
printf("%d %d %d\n", a, b, c);
/* 4 2 3 */
}
{
int a,b;
. . .
{
float b;
. . .
}
. . .
{
float a;
. . .
}
. . .
}
36
7.6.
Rodzaje zmiennych
Zmienne w języku C posiadają dwa atrybuty: typ i rodzaj.
Słowa kluczowe określające rodzaj zmiennej:
auto
extern
register
static
7.6.1.
Rodzaj auto (zmienne automatyczne)
Zmienne zadeklarowane wewnątrz funkcji lub bloku są rodzaju auto,
chba że ich rodzj został określony w deklaracji. (Słowo auto można
opuszczać).
Zmienne tego rodzaju istnieją tylko w czasie wykonywania funkcji
lub bloku w którym zostały zadeklarowane.
7.6.2.
Rodzaj extern (zmienne zewnętrzne)
Zmienne zadeklarowane poza funkcjami są rodzaju extern. (Słowo
extern można opuścić). Zmienne te są zmiennymi globalnymi.
Użycie słowa kluczowego extern w deklaracji zmiennej oznacza
poszukiwanie zmiennej o tej samej nazwie w całym programie (nawet w
innych plikach).
Przykład.
Plik p1.c:
#include <stdio.h>
int a = 1, b = 2, c = 3;
int f(void);
main()
{
printf("%3d\n", f());
printf("%d %d %d\n", a, b, c);
}
Plik p2.c:
37
intf(void)
{
extern int a;
int b, c;
a = b = c = 4;
return (a + b + c);
}
Uwaga.
Użycie zmiennych globalnych może powodować efekty
uboczne działania funkcji.
Wszystkie funkcje są funkcjami zewnętrznymi.
7.6.3.
Rodzaj register (zmienne rejestrowe)
Słowo kluczowe register sugeruje kompilatorowi aby umieścić daną
zmienną w rejestrze procesora. Czas dostępu do zmiennych rejestrowych
jest najkrótszy.
Przykład.
{
register int i;
for (i = 0; i < LIMIT; ++i)
. . .
}
Uwaga.
register i; jest równoważne register int i;
38
7.6.4.
Rodzaj static (zmienne statyczne)
Użycie w deklaracji zmiennej słowa static powoduje:
1. Dla zmiennej lokalnej - zachowanie wartości przy ponownym wejsciu
do funkcji lub bloku, w którym ta zmienna jest zadeklarowana.
2. Dla zmiennych globalnych - ograniczenie zakresu danej zmiennej
zewnętrznej od miejsca zadeklarowania do końca danego pliku.
Przykłady:
void f(void)
{
static int licz = 0;
++licz;
if (licz % 22 == 0)
. . .
else
. . .
}
void f(void)
{
. . .
}
static int i;
void g(void)
{
. . .
}
Uwaga.
Można deklarować rodzaj funkcji jako static ograniczając jej zakres
do jednego pliku.
39
7.7.
Inicjowanie zmiennych
Zmienne rodzaju extern i static są automatycznie inicjowane na
wartość 0, chyba że w zostały zainicjowane w programie.
Zmienne rodzaju auto i register nie są inicjowane.
7.8.
Rekurencja
Funkcja jest rekurencyjne, jeżeli w jej treści występuje wywołanie tej
samej funkcji.
Przykład.
int sum(int n)
{
if (n <= 1)
return n;
else
return (n + sum(n-1));
}
sum(1)
1
sum(2)
2 + sum(1)
3
sum(3)
3 + sum(2) = 3 + (2+sum(1)) 6
#include
<stdio.h>
main()
{
void pisz(void)
printf("Wprowadz tekst: ");
pisz();
printf("\n\n");
}
void pisz(void)
{
int c;
if ((c = getchar()) != '\n')
pisz();
putchar(c);
}
40
8.
Tablice 1-wymiarowe
int a[rozmiar];
/* a[0], a[1], ... , a[rozmiar-1] */
8.1.
Inicjowanie tablic
inicjalizator_tablcy_jednowym ::= {
listałinicjatorów }
listałinicjatorów ::= inicjator {, inicjator}
0+
inicjator ::= wyrażenie
float f[5] = { 0.0, 1.0, 2.0, 3.0, 4.0 };
Jeżeli lista inicjatorów jest krótsza od ilości elementów tablicy, to
pozostale elementy tablicy są inicjowane na wartość 0.
int a[100] = {0};
Niezainicjowane tablice rodzaju extern lub static są automatycznie
inicjowane na wartość 0.
Tablica zadeklarowana bez podania rozmiaru a zainicjowana ma ilość
elementów równą ilości inicjatorów.
int a[] = { 2 , 3, 5, -7};
równoważne
int a[4] = { 2, 3, 5, -7};
char s[] = "abc";
char s[] = {'a', 'b', 'c', '\0'};
8.2.
Indeksowanie
a[wyrażenie]
Zakres indeksów tablicy nie jest kontrolowany.
41
8.3.
Wskaźniki
x - zmienna
&x - adres zmiennej
int *p, i;
p = 0;
p = NULL;
/* równowa
ż
ne p = 0; */
p = &i;
p = (int *) 1776; /* adres bezwzgl
ę
dny */
int a = 1, b = 2, *w;
a
b
w
1
2
---------->
p = &a;
a
b
w
1
2
---------->
b = *p;
równowa
ż
ne
b = a;
Przykład.
#include
<stdio.h>
main()
{
int i = 7, *w;
w = &i;
printf("%s%d\n%s%u\n",
"Wartosc i : ", *w,
"Adres i : ", w);
}
42
Zmienne wskaźnikowe mogą być inicjowane.
int i, *w = &i;
Przykłady.
int i = 3, j = 5, *p = &i, *q = &j, *r;
double w;
p == &i
p == (&i)
1
* * & p
* (* (& p))
3
r = & x
r = (& x)
bł
ą
d
7 * * p / * q + 7
((7 * (* p)) / (* q)) + 7
11
* (r = & j) *= * p
(* (r = (& j))) *= (* p)
15
Uwaga.
W ANSI C konwersja typów wskaźnikowych jest niedozwolona, chyba że
jednym z typów jest typ void * lub prawym argumentem stała 0.
int *p;
float *q;
void *v;
Poprawne
Błędne
p = 0;
p = 1;
p = (int *) 1;
v = 1;
p = v = q;
p = q;
p = (int *) q;
43
Operatora adresowania & nie wolno stosować do:
- stałych
&3
- nazw tablic (nazwa tablicy jest stałą)
int a[77];
&a
- wyrażeń
&(k + 99)
- zmiennych rodzaju register
register v;
&v
Operator adresowania może być użyty do elementu tablicy:
&a[0]
&a[i+j+3]
8.4.
Przekazywanie parametrów przez referencję
#include
<stdio.h>
void zamien(int *p, int *q)
{
int rob;
rob = *p;
*p = *q;
*q = rob;
}
main()
{
int i = 3, j = 5;
void zamien(int *, int *);
zamien(&i, &j);
printf("%d %d\n", i, j);
/* 5 3 */
}
Aby osiągnąć efekt przekazania parametru przez referencję należy:
1. Zadeklarować parametr funkcji jako wskaźnik.
2. W treści funkcji użyć wartości wskazywanej przez wskaźnik.
3. Przekazać w wywołaniu jako argument adres.
44
8.5.
Tablice a wskaźniki
Nazwa tablicy reprezentuje adres.
Wskaźniki można indeksować.
a[i]
równoważne
*(a + i)
p[i]
*(p + i)
Przykład.
#define
N 100
int a[N], i, *p, sum = 0;
p = a;
rówoważne
p = &a[0];
p = a + 1;
p = &a[1];
for (p = a; p < &a[N]; ++p)
sum += *p;
for (i = 0; i < N; ++i)
sum += *(a + i);
p = a;
for (i = 0; i < N; ++i)
sum += p[i];
Wyrażenia niepoprawne:
a = p
++a
a += 2 &a
8.6.
Arytmetyka na wskaźnikach
double a[2], *p, *q;
p = a;
q = p + 1;
/* q = &a[1] */
printf("%d\n", q-p);
/* 1 */
printf("%d\n", (int) q - (int) p);
/* 8 */
45
8.7.
Tablice jako parametry funkcji
Tablice przekazuje się jako parametry w postaci wskaźników.
double sum(double a[], int n)
{
int i;
double sum = 0.0;
for (i = 0; i < n; ++i)
sum += a[i];
return sum;
}
lub
double sum(double *a, int n)
{
. . .
Przykłady wywołań:
(double v[100])
sum(v, 100)
v[0]+v[1]+...+v[99]
sum(v,88)
V[0]+V[1]+...+V[87]
sum(&v[7],k-7)
v[7]+v[8]+...+v[k-1]
sum(v+7, 2*k)
v[7]+v[8]+...+v[2*k-6]
8.8.
Napisy
char *p = "abc";
printf("%s %s\n", p, p+1);
/* wydrukuje sie abc bc */
"abc"[1]
*("abc" +2) - wyra
ż
enia poprawne
char *p = "abcde";
char s[] = "abcde";
p
s
a b c d e \0
a b c d e \n
Przykład.
46
/* Zliczanie słów w napisie */
#include
<ctype.h>
int licznik_slow(char *s)
{
int licz = o;
while (*s != '\0')
{
while (isspace(*s)) /* pomijanie spacji */
++s;
if (*s != '\0')
/* znaleziono słowo */
{
++licz;
while (!isspace(*s) && *s != '\0')
++s;
/* akceptowanie słowa */
}
}
return licz;
}
8.8.1.
Przetwarzanie napisów
#include <string.h>
char *strcat(char *s1, const char *s2);
Konkatenacja napisów s1 i s2. Wynikiem jest napis s1.
int strcmp(const char *s1, const char *s2);
Porównywanie napisów w porządku leksykograficznym. Wartością
funkcji jest liczba ujemna jeżeli napis s1 poprzedza napis s2, zero gdy
napisy są identyczne, lub liczba dodatnia (napis s2 jest wcześniejszy niż
s1).
char *strcpy(char *s1, const char *s2);
Kopiowanie napisu s2 do s1.
47
unsigned strlen(const char *s);
Wartością funkcji jest liczba znaków napisu s.
unsigned strlen(const char *s)
{
register int n;
for (n = 0; *s != '\0'; ++s)
++n;
return n;
}
char *strcat(char *s1, const char *s2)
{
register char *p = s1;
while (*p)
++p;
while (*p++ = *s2++)
;
return s1;
}
Przykłady.
char
s1[50] = "duzy ladny sloneczny dom",
s2[] = "maly pokoj";
strlen(s1);
24
strlen(s2 + 5);
5
strcmp(s1, s2);
warto
ść
ujemna
printf("%s",s1 + 11);
sloneczny dom
strcpy(s1 + 5, s2 +5);
strcat(s1," bez okien");
printf("%s",s1);
duzy pokoj bez okien
48
9.
Tablice wielowymiarowe
int a[100];
/* 100 elementów */
int b[2][7];
/* 14 elementów */
int c[5][3][2]; /* 30 elementów */
Tablice są lokowane w spójnych obszarach pamięci.
9.1.
Tablice 2-wymiarowe
int a[3][5];
kol.1
kol.2 kol.3 kol.4 kol.5
w.1
a[0][0]
a[0][1]
a[0][4]
w.2
a[1][0]
a[1][1]
a[1][4]
w.3
a[2][0]
a[2][1]
a[2][4]
Wyra
ż
enia równowa
ż
ne do a[i][j]:
*(a[i] + j)
(*(a + i))[j]
*((*(a +i)) + j)
*(&a[0][0] + 5*i + j)
Uwaga.
a oznacza
&a[0],
a nie
&a[0][0]
Jeżeli parametrem formalnym funkcji jest tablica wielowymiarowa, to
muszą być podane wszystkie wymiary za wyjątkiem pierwszego.
Przykład.
int sum(int a[][5])
{
int i, j, sum = 0;
for (i = 0; i < 3; ++i)
for (j = 0; j < 5; ++j)
sum += a[i][j];
return sum
}
int a[][5] int (*a)[5]
int a[3][5]
49
9.2.
Tablice 3-wymiarowe
int a[7][9][2];
a[i][j][k]
*(&a[0][0][0] + 9*2*i + 2*j +k)
int sum(int a[][9][2])
{
int i, j, k, sum = 0;
for (i = 0; i < 7; ++i)
for (j = 0; j < 9; ++j)
for (k = 0; k < 2; ++k)
sum += a[i][j][k];
return sum
}
int a[][9][2]
int (*a)[9][2]
int
a[7][9][2]
INICJALIZACJA TABLIC WIELOWYMIAROWYCH
int a[2][3] = {1, 2, 3, 4, 5, 6};
int a[2][3] = {{1, 2, 3}, {4, 5, 6}};
int a[][3] = {{1, 2, 3}, {4, 5, 6}};
int a[2][2][3] = {
{{1, 1, 0}, {2, 0, 0}},
{{3, 0, 0}, {4, 4, 0}}
};
int a[][2][3] = {{{1, 1}, {2}}, {{3}, {4, 4}}};
int a[2][2][3] = {0}; -
zerowanie
50
9.3.
Tablice wskaźników
9.4.
Nierówne tablice
#include
<stdio.h>
main()
{
char a[2][15] = {"abc","defgh"};
char *p[2] = {"abc","defgh"};
. . .
9.5.
Funkcje jako argumenty
(k)
f
2
n
m
=
k
∑
k
1
=
f(k)
(k)
=
f(k)
sin
double sum_kw(double f(double), int m. int n)
{
int k;
double sum = 0.0;
for (k = m; k <= n; ++k)
sum += f(k) * f(k);
return sum;
}
równoważnie
double sum_kw(double (*f)(double), int m, int n)
{
. . .
ale
51
double *f(double);
sum += f(k) * f(k) albo sum += (*f)(k) * (*f)(k)
f - wskaźnik do funkcji
*f - sama funkcja
(*f)(k) - wywołanie funkcji
Wywołanie.
#include
<stdio.h>
main()
{
double f(double), g(double),
sum_kw(double (*)(double), int, int);
printf("funkcja f: %.7f\n", sum_kw(f,1,10000);
printf("funkcja sinus: %.7f\n",
sum_kw(sin,2,15);
}
double f(double x)
{
return 1.0 / x;
}
52
10.
Argumenty main
#include <stdio.h>
void main(int argc, char *argv[],char *env[])
{
int i;
printf("argc = %d\n", argc);
for (i = 0; i < argc; ++i)
printf("argv[%d] = %s\n", i, argv[i]);
while(*env)
printf("%s\n",*env++ );
}
argc
- ilość argumentów (argc >= 1)
argv[0] - nazwa programu
( w MS DOS z pełną nazwą ścieżki
całość pisane wielkimi literami )
env
- zmienne środowiskowe
53
11.
Definiowanie typów
typedef
char * string;
typedef
int
CAL, STOPA, YARD;
typedef
float wektor[10];
typedef
double (*WFD)(double);
/* wsk. do funk. double */
CAL
dl, szer;
string
s1 = "abc", s2="xyz";
wektor
x;
float x[10];
WFD
f;
double (*f)(double);
#define
N 3
typedef
double
scalar;
typedef
scalar wektor[N];
typedef
scalar tablica[N][N];
/* lub typedef wektor
tablica[N]; */
void dodaj(wektor x, wektor y, wektor z)
{
int i;
for (i = 0; i < N; ++i)
z[i] = x[i] + y[i];
}
scalar il_skal(wektor x, wektor y)
{
int i;
scalar sum = 0.0;
for (i = 0; i < N; ++i)
sum += x[i] * y[i];
return sum;
}
void mnm(tablica a, tablica b, tablica c)
54
{
int i, j, k;
for (i = 0; i < N; ++i)
for (j = 0; j < N; ++j)
{
a[i][j] = 0.0;
for (k = 0; k < N; ++k)
a[i][j] += b[i][k] * c[k][j];
}
}
55
12.
Struktury
deklaracja_struktury ::=
specyfikacja_struktury
listałdeklaratorów;
specyfikacja_struktury ::= struct nazwa |
struct nazwa
op
{ { deklaracja_pola }
1+
}
nazwa ::= identyfikator
deklaracja_pola ::= typ listałdeklaracji;
listałdeklaratorów ::= deklarator {, deklarator
}
0+
struct karta
{
int wart;
char kolor;
};
definicja typu
struct karta k1, k2;
lub
struct karta
{
int wart;
char kolor;
} k1, k2;
k1.wart = 3;
k1.kolor = 'p';
k2 = k1;
typedef struct karta karta;
karta k3, k4, k5;
56
struct owoc
{
char *nazwa;
int kalorie;
};
struct warzywo
{
char *nazwa;
int kalorie;
};
struct owoc a;
struct warzywo b;
a.nazwa
b.nazwa
struct karta
{
int wart;
char kolor;
} talia[52];
struct
{
int
dzien, miesiac, rok;
char
*nazwa_dnia[7];
char *nazwa_mies[12];
} wczoraj, dzis, jutro;
struct data
{
int
dzien, miesiac, rok;
char
*nazwa_dnia[7];
char *nazwa_mies[12];
};
struct data wczoraj, dzis, jutro;
typedef struct
{
float re;
float im;
} complex;
complex a, b, c[100];
57
12.1.
Dostęp do skłdników struktury
zmienna_strukturalna.nazwa_pola
Plik stud.h
#define
LICZBA_STUD
100
struct student
{
char *nazwisko;
int *nr_indeksu;
float ocena;
};
Plik stud.c
#include "stud.h"
main()
{
struct student st, rok[LICZBA_STUD];
int i, licz = 0;
. . .
st.ocena = 4;
st.nazwisko = "Kowalski";
st.nr_indeksu = 91017;
. . .
for (i = 0; i < LICZBA_STUD; ++i)
licz += rok[i].ocena == 2;
. . .
Jeżeli wartością zmiennej wskaźnikowej jest adres struktury, to do jej
składników można odwołać się przez:
wskaźnik_do_struktury -> nazwa_składnika
co jest równoważne odwałaniu
(*wskaźnik_do_struktury).nazwa_składnika
58
Uwaga.
Nawiasy są konieczne. W przeciwnym przypadku otrzymamy
*(wskaźnik_do_struktury.nazwa_składnika)
co jest błędne, gdyż tylko do struktury można użyć operatora ".", a nie do
wskaźnika do struktury.
Przykład.
Dodawanie liczb zespolonych.
Plik complex.h:
struct complex
{
double re;
double im;
};
typedef
struct complex complex;
Plik complex.c:
#include
"complex.h"
void dodaj(complex *a, complex *b, complex *c)
{
a -> re = b -> re + c -> re;
a -> im = b -> im + c -> im;
};
Przykład:
struct student st, *p = &st;
st.ocena = 4;
st.nazwisko = "Kowalski";
st.nr_indeksu = 91017;
st.ocena
p->ocena
4.0
59
(*p).nr_indeksu
p->nr_indeksu
91017
*p->nazwusko + 1
(*(p->nazwisko))+1
L
*(p->nazwisko + 2)
(p->nazwisko)[2]
w
12.2.
Struktury i funkcje
Struktury tak jak wszystkie parametry są przekazywane przez wartość.
W wywołanej procedurze powsaje lokalna kopia. Przy dużych strukturach
może to być nieefektywne. Lepiej jest przekazywać adres do struktury.
Przykład.
typedef struct
{
char
nazwisko[25];
int
nr_id;
struct wydz
wydzial;
struct adres_dom
*a_d;
double
pensja;
. . .
} pracownik;
struct wydz
{
char nazwa[20];
int kod;
};
I sposób:
pracownik wczytaj_dane(pracownik p)
{
. . .
printf("Kod wydzialu: ");
60
scanf("%d", &k);
p.wydzial.kod = k;
. . .
return p;
}
p.wydzial.kod (p.wydzial).kod
main()
{
pracownik p;
. . .
p = wczytaj_dane(p);
/* dwa kopiowania */
II sposób:
void wczytaj_dane(pracownik *p)
{
. . .
printf("Kod wydzialu: ");
scanf("%d", &k);
p->wydzial.kod = k;
. . .
}
p->wydzial.kod (p->wydzial).kod
main()
{
pracownik p;
. . .
wczytaj_dane(&p);
61
12.3.
Inicjalizacja struktur
karta k = {8,'p'};
/* ósemka pik */
complex a[3][3] =
{
{{1.0, -0.1},{2.0, 0.2}, (3.0, 0.3}},
{{4.0, -0.5},{5.0, 0.5}, (6.0, 0.6}},
}; /* a[2][] zerowanie */
struct owoc o = {"gruszka", 150};
struct adres_dom
{
char *ulica;
char *miasto;
long kod:
} adres = {"Wiejska 45A", "Bialystok", 15351};
struct adres_dom a_poprz = {0};
62
13.
Unie
Skladnia unie bazuje na strukturach.
union u-tag {
int inval;
float fval ;
char *pval;
} uval;
Zmienna
uval
bedzie posiadac wystarczajacy obszar pamieci
aby przechowac najwiekszy z trzech typow
Dowolny typ z tych trzech typow moze byc przypisany zmiennej
uval. Na programiste naklada sie odpowiedzialnosc sledzenia tego jaki
typ jest aktualnie zapamietany w uni.
Dostęp do skladnikow uni
nazwa-uni.skladnik
lub
wskaznik-uni->skladnik
czyli tak samo jak do skladnikow struktur.
Jesli zmienna utype jest uzywana do sledzenia biezacego typu
zapamietanego w zmiennej uval, to mozna napisac:
if (utype == INT)
printf("%d\n", uval,ival);
else if (utype == FLOAT)
printf("%f\n", uval,fval);
else if (utype == STRING)
printf("%s\n", uval,pval);
else
printf("zly typ %d w typzl\n",
utype);
63
Unie moga wystepowac wewnatrz struktur i tablic.
Notacja dostepu do skladnikow uni w strukturze jest taka sama jak
notacja dostepu do skladnikow w zagniezdzonych strukturach.
Na przyklad w tablicy strukturalnej zdefiniowanej nastepujaco:
struct {
char *name;
int flags;
int utype;
unlon {
int ival;
float fval;
char *pval;
} uval;
} symtab [NSYM];
zmienna ival jest dostepowana przez
symtab [i].uval.ival
natomiast pierwszy znak lancucha pval przez
*symtab [i].uval,ival
Operacjami dozwolonymi na uniach sa:
dostęp do skladnika
pobieranie adresu.
Unie nie moga byc sobie przypisywane, przesylane do funkcji ani tez
przez funkcje zwracane.
Wskazniki na unie sa uzywane w taki sam sposob jak wskazniki na
struktury.
64
13.1.
Pola bitowe
Składniki struktur i uni typu int lub unsigned mogą być deklarowane z
określeniem ilości bitów przeznaczonych na reprezentację danego
składnika.
Przykład.
struct Data
{
unsigned d: 5;
unsigned m : 4;
unsigned r : 7;
};
struct Data d;
d.r=99;
d.m=10;
d.d=1;
Składnia:
pole bitowe ::= { int | unsigned }
1
{identyfikator}
opt
: wyr
wyr ::= stała_calkowita
Uwagi:
1. Nie wolno deklarować tablic pól bitowych.
2. Pole bitowe bez nazwy oznacza przejście do następnego słowa.
struct abc
{
unsigned a : 1, :0, b : 1, : 0, c : 1;
}
65
14.
Modele pamieci
Rozdział ten dotyczy kompilitorów
Turbo C, Borland C dla systemu MS-DOS.
Specyfikacja modelu pamięci który będzie używany przez program. Model ten
determinuje sposób adresowania zmiennych.
Default = Small
Segments
Pointers
Memory
Model
Code
Data Stack Code
Data
BCC
Option
Tiny
64K
near
near
-mt
Small
64K
64K
near
near
-ms
Medium
1MB
64K
far
near
-mm
Compact
64K
1MB
near
far
-mc
Large
1MB
1MB
far
far
-ml
Huge
1MB
64K
each
64K
stack
far
far
-mh
Tiny
Modelu Tiny używa się do bardzimałych programów.
Wszystkie rejestry segmtowe (CS, DS, ES, SS) są ustawione na te same adresy,
wobec tego możesz używać łącznie 64K dla danych, kodu i stost. Wskaźniki są
zawsze bliskie (near).
Model Tiny umożliwia generowanie programów konwertowalnych do .COM z
wykorzystaniem linkera z opcją TLINK /t.
Small
Modelu Smal używaj do średniej wielkości aplikacji.
Dane i kod są umieszczane w rozłącznych segmentach więc dla kodu jest 64K i
kolejne 64KB dla danych i kodu.
Domyslnie są używane wskaźniki near.
66
Medium
Jest to najlepszy model dla dużych programów, które nie przechowują w pamieci
dużo danych.
Dalekie wskaźniki są domyślne dla kodu, ale nie dla danych. Dane i stos są
ograniczone do 64K ale kod może zajmować do 1MB.
Compact
Modelu Compact używa się gdy program ma mały kod ale potrzebyje adresów do
dużej ilości danych.
Model ten jest przeciwieństwem modelu Medium: Dalekie wskaźniki są domuślne
dla danych ale nie dla kodu
Kod zajmuje do 64 Kb podczas gdy dane do 1MB.
Wszystkie funkcje są domyślnie near a wskaźnki na dane far.
Large
Tego modelu używa się tylko do bardzo dużych aplikacji. Wskaźniki zarówno
dodanych jak i do kodu są dalekie (far) a wielkości wspólnie są ograniczone do 1MB.
Wszystkie funkcje domyślnie są far.
Huge
Tego modelu używa się tylko do bardzo dużych aplikacji. Wskaźniki zarówno
dodanych jak i do kodu są dalekie (far).Wszystkie funkcje domyślnie są far.
Borland C++ normalnie limituje wszystkie dane do 64 KB. W modelu Huge tak nie
jest. Dopuszczalne jest aby pojedyńcze dane zajmowały ponad 64KB.
Modal ten dopuszcza wielosegmentowe dane (każdy segmeny 64KB), do 1MB kodu
i 64KB stos. Wszystkie funkcje i wskaźniki są domyślnie far.
67
Tiny
≤
64 KB
.COM
Small
≤
64 KB
≤
64 KB
Medium
N*
≤
64 KB
≤
64 KB
Compact
≤
64 KB
≤
64 KB
≤
64 KB
Large
N *
≤
64 KB
≤
64 KB
≤
64 KB
Huge
N*
≤
64 KB
N *
≤
64 KB
≤
64 KB
Kod
Dane
Heap
↓
Stos
↑
Kod
Dane
Heap
↓
Stos
↑
Far Heap
↓
Kod
Kod
Dane
Heap
↓
Stos
↑
Far Heap
↓
Kod
Dane
Stos
↑
Heap
↓
Kod
Kod
Dane
Stos
↑
Heap
↓
Kod
Kod
Dane
Dane
Stos
↑
Heap
↓
68
15.
Dynamiczny przydział pamięci
15.1.
Funkcje alokacji pamięci
void * malloc(size_t size);
••••
przydziela obszar wielkości size
••••
zwraca wskaźnik do przydzielonego obszaru
••••
typ wskaźnika void * może być automatycznie konwertowany do
dowolnego typu wskaźnikowego.
••••
jeśli brak odpowiedniej ilości pamięci to zwraca NULL
void *calloc(size_t nitems, size_t size);
••••
przydziela obszar wielkości nitimes*size
••••
dane przydzielonego bloku są ustawione na 0
void *realloc(void *block, size_t size);
••••
zmienia przydział pamięci dla obszaru blok na wielkości size
••••
umożliwia zwiększenie bądź zmniejszenie wcześniejszego
przydziału pamięci
••••
dane z realokowanego obszaru są kopiowane
••••
jeśli size wynosi 0 to zwalnia pamięć przydzieloną dla block
••••
jeśli block równa się NULL to funkcja działa jak malloc
void free(void *block);
••••
zwalnia przydział pamięci dla obszaru blok przydzielony wcześniej
przez malloc, calloc lub realloc
unsigned coreleft(void);
••••
szacuje ilość wolnego miejsca na stercie
••••
(DOS)dla modeli Compact Large i Huge istnieje funkcja
unsigned long coreleft(void);
void *memset(void *s, int c, size_t n);
••••
ustawia n bajtów począwszy od *s na wartość c
••••
#include <mem.h>
Odpowiedniki powyższych funkcji do obsługo dalekiej sterty(dotyczy
Small i Medium)
void far *farmalloc(unsigned long nbytes);
void far *farcalloc(unsigned long nunits, unsigned long unitsz);
void farfree(void far * block);
unsigned long farcoreleft(void);
void far * far _fmemset (void far *s, int c, size_t n)
69
15.2.
Tablice dynamiczne
int t[20];
tablica 20 elementów typu int
int *p;
wskaźnik na typ int;
main()
{
int *p,i,N;
printf("Podaj ilo
ść
elementów\n");
scanf("%d",&N);
p=(int*)calloc(N,sizeof(int));
/* p=malloc(20* sizeof(int));
memset(p,0,20*sizeof(int)); */
for(i=0;i<N;i++)
*(p+i)=rand()%198-99;
for(i=0;i<N;i++)
printf("%4d",*(p+i));
printf("\n");
realloc(p,2*N*sizeof(int));
for(i=N;i<2*N;i++)
*(p+i)=rand()%198-99;
for(i=0;i<2*N;i++)
printf("%4d ",*(p+i));
free(p);
}
char* srev1(const char *s)
/* odwracanie napisu*/
{
char *p;
int l=slen(s);
p=malloc(l+1);
p=p+l;
*p=0;
for(;*s;)
*p--=*s++;
return ++p;
}
70
16.
Struktury listowe
struct lista
{
int dane;
struct lista *nast;
} a;
------->
dane
nast
struct lista b,c;
a.dane = 1;
b.dane = 2;
c.dane = 3;
a.nast = b.nast = c.nast = NULL;
1
NULL
2
NULL
3
NULL
a.nast = &b;
b.nast = &c;
1
---->
2
---->
3
NULL
a.nast->dane
2
a.nast->nast->dane
3
NULL - “zerowy” wskażnik
Zdefiniowany przez dołączenie jednego z plików
alloc.h mem.h stddef.h stdio.h stdlib.h
a fizycznie zdefiniowane w _null.h
71
16.1.
Listy liniowe
Plik lista.h:
#include <stdlib.h>
typedef
char
Dane;
typedef
struct lista_lin
{
Dane
d;
struct lista_lin
*nast;
}
Element,
*Lista;
16.1.1.
Dynamiczny przydział pamięci
#include <stdlib.h>
lub
#include<alloc.h>
void *malloc(size_t size);
Funkcja daje jako wskaźnik do obszaru pamięci o wielkońci rozmiar
(w bajtach). Wynik jest typu void * , konwresja do konkretnego tupu nie
jest konieczna.
Lista pocz;
pocz = (Lista)malloc(sizeof(Element));
pocz -> d = 'n';
pocz -> nast = NULL;
pocz
------> n
NULL
pocz ->nast = (Lista)malloc(sizeof(Element));
pocz ->nast ->d = 'a';
pocz ->nast ->nast = NULL;
pocz
-------> n
---->
a
NULL
72
16.1.2.
Operacje na listach
1)
Tworzenie listy
2)
Obliczanie liczby Elementów
3)
Szukanie Elementu
4)
Łączenie list
5)
Wstawianie Elementów
a)
pomiędzy dwa wskazane Elementy
b)
po wskazanym elemencie
c)
po elemencie danych
d)
przed elemantem danych
6)
Przetworzenie (np. wypisanie) wszystkich Elementów listy
7)
Usuwanie Elementów
8)
Kasowanie całej listy
9)
Odwracanie listy jednokierunkowej
#include
<stdlib.h>
#include
"lista.h"
/* Tworzenie listy - wersja rekurencyjna */
Lista napis_na_liste(char s[])
{
Lista
pocz;
if (*s)
{
pocz = (Lista)malloc(sizeof(Element));
pocz -> d = *s;
pocz -> nast = napis_na_liste(s+1);
return pocz;
}
return NULL;
}
73
/* Tworzenie listy - wersja iteracyjna */
Lista np_na_lst(char s[])
{
Lista
pocz = NULL, kon;
if (s[0])
{
pocz = (Lista)malloc(sizeof(Element));
pocz -> d = *s;
kon = pocz;
for (; *++s ; )
{
kon -> nast = (Lista)malloc
(sizeof(Element));
kon = kon -> nast;
kon -> d = *s;
}
kon -> nast = NULL;
}
return pocz;
}
16.1.3.
Przetwarzanie list
/* ilosc Elementów - wersja rekurencyjna */
int ilosc_Elementow(Lista pocz)
{
if (pocz == NULL)
return 0;
else
return (1 + ilosc_Elementow(pocz -> nast));
}
74
/* ilosc Elementów - wersja iteracyjna */
int il_el(Lista pocz)
{
int licz = 0;
for ( ; pocz ; pocz = pocz ->nast)
++licz;
return licz;
}
/* Ł
ą
czenie list */
void lacz_r(Lista a, Lista b)
{
if(a==NULL) a=b;
else
if (a -> nast == NULL)
a -> nast = b;
else
lacz(a -> nast, b);
}
void lacz(Lista a, Lista b)
{
if (a)
{
for(;a->nast;a=a -> nast) ;
a->nast=b;
}
else a=b;
}
/* wstawianie Elementu */
/* wstawianie pomiedzy dwa wskazane Elementy */
void wstaw(Lista e1, Lista e2, Lista q)
{
q -> nast = e2;
e1 -> nast = q;
}
75
e1 e2
q
/* Szukanie Elementu
pocz
–
pocz
ą
tek listy
e
-
pop
- poprzedni Element listy */
Lista szukaj(Lista pocz,Dane e,Lista *pop)
{
*pop=NULL;
while(pocz&& pocz->d!=e)
{
*pop=pocz;
pocz=pocz->nast;
}
return pocz;
}
e e->nast
/* wstawianie po wskazanym elemencie */
void wstaw_po(Lista e, Dane x)
{
Lista q=(Lista)malloc(sizeof(Element));
q->d=x;
q -> nast = e->nast;
e -> nast = q;
}
x
76
pocz psz psz->nast
newp
pocz pop psz
newp
/* wstawianie po elemencie danych */
void wstaw_po_d(Lista *pocz,Dane s, Dane x)
{
Lista psz,pop,newp;
psz=szukaj(*pocz,s,&pop);
newp=(Lista)malloc(sizeof(Element));
newp->d=x;
newp->nast=psz;
if(psz)
psz->nast=newp;
else
pop->nast=newp;
}
pocz pop psz
newp
pop pocz psz
newp
s
x
x
s
x
s
x
77
/* wstawianie przed Elementem danych */
void wstaw_przed_d(Lista *pocz,Dane s, Dane x)
{
Lista psz,pop,newp;
psz=szukaj(*pocz,s,&pop);
newp=(Lista)malloc(sizeof(Element));
newp->d=x;
newp->nast=psz;
if(pop)
pop->nast=newp;
else
*pocz=newp;
}
/*wypisz Elementy listy */
void pisz_liste(Lista pocz)
{
printf("\n");
while(pocz)
{
printf("%c ",pocz->d);
pocz=pocz->nast;
}
}
/* kasowanie Elementu */
Lista usun(Lista *pocz,Dane x)
{
Lista psz,pop;
psz=szukaj(*pocz,x,&pop);
if(psz)
{
if(pop)
pop->nast=psz->nast;
else
*pocz=psz->nast;
free(psz)
}
}
78
pocz pop psz
/* kasowanie listy */
void kasuj_liste(Lista pocz)
{
if (pocz != NULL)
{
kasuj_liste(pocz -> nast);
free(pocz);
}
}
n pn po
/* odwracanie listy jednokierunkowej*/
Lista odwracanie(Lista *pn)
{
Lista po,n;
po=NULL;
while(*pn)
{
n=(*pn)->nast;
(*pn)->nast=po;
po=*pn;
*pn=n;
}
return *pn=po;
}
s
x
3
2
1
4
79
16.2.
Listy dwukierunkowe
typedef
char
Dane;
struct lista_dwa
{
Dane
d;
struct lista_dwa
*nast,*pop;
};
typedef
struct lista_dwa Element2;
typedef
Element2
*Lista2;
16.2.1.
Tworzenie listy
Lista2 napis_na_liste(char s[])
{
Lista2
pocz = NULL, kon;
if (*s)
{
pocz =
(Lista2)malloc(sizeof(Element2));
pocz -> d = *s;
pocz->pop=NULL;
kon = pocz;
for (; *++s ;)
{
kon -> nast = (Lista2)malloc
(sizeof(Element2));
kon->nast->pop=kon;
kon = kon -> nast;
kon -> d = *s;
}
kon -> nast = NULL;
}
return pocz;
}
80
16.2.2.
Operacje na listach
Lista2 koniec(Lista2 pocz)
{
if(pocz)
while(pocz->nast)
pocz=pocz->nast;
return pocz;
}
void pisz_n(Lista2 p)
{
printf("\n");
while(p)
{
pisz_d(p->d);
p=p->nast;
}
}
Lista2 szukaj(Lista2 pocz,Dane x)
{
while(pocz && pocz->d!=x)
pocz=pocz->nast;
return pocz;
}
Lista2 usun(Lista2 *pocz,Dane x)
{
Lista2 pop,psz=szukaj(*pocz,x);
if(psz)
{
pop=psz->pop;
if(pop)
pop->nast=psz->nast;
else
*pocz=psz->nast;
if(psz->nast)
psz->nast->pop=pop;
free(psz);
}
return *pocz;
}
81
17.
Stos
szczyt
----> Dane
Dane
Dane
NULL
Stos jest strukturą typu LIFO (Last In First Out).
Prototypy funkcji:
int pusty(Stos st);
Wartosc 1, gdy stos pusty;
0 w przypadku przeciwnym/
Dane wart(Stos st);
Wartość elementu na szczycie stosu.
void pop(Stos *st, Dane *d);
Usuwa element ze szczytu stosu
i daje jesgo wartość.
void push(Stos *st, Dane d);
Umiescza wartosc d na stosie.
Plik stos.h:
#define
NULL
0
typedef
char
Dane;
struct stos
{
Dane
d;
struct stos *nast;
};
typedef
struct stos
Element;
typedef
Element
*Stos;
82
/* operacje na stosach */
int pusty(Stos st)
{
return (st == NULL);
}
Dane wart(Stos st)
{
return (st -> d);
}
void pop(Stos *st, Dane *d)
{
Stos st1 = *st;
if (!pusty(st1))
{
*d = st1 -> d;
*st = st1 -> nast;
free(st1);
}
else
printf("Stos jest pusty\n");
}
void push(Stos *st, Dane d)
{
Stos rob;
rob = malloc(sizeof(Element));
rob -> d = d;
rob -> nast = *st;
*st = rob;
}
/* odwracanie napisu */
void odwroc(Dane s[])
{
int i;
Stos st = NULL;
for (i = 0; s[i] != '\0'; ++i)
push(&st,s[i]);
for (i = 0; s[i] != '\0'; ++i)
pop(&st, &s[i]);
}
83
17.1.
Stos i odwrotna notacja polska
17.1.1.
Notacja polska
Notacja ta bywa zwana też - od nazwiska jej twórcy-
NOTACJĄ
Ł
UKASIEWICZA
Notacja infiksowa
Notacja polska Odwrotna notacja polska
x+y
+ x y
x y +
2*(2-4)/5
/ *2 -2 4 5
2 2 4 - * 5 /
Cechy notacji polskiej
umożliwia zapis dowolnego wyrażenia bez konieczności użycia
nawiasów
ułatwia kontrole poprawności wyrażenia
jest to notacja często stosowana w wiekszości kolkulatorów
wymusza konieczność rozróżnienia operatorów binarnych i unarnych
(np. + - )
skomplikowany zapis
17.1.2.
Zamiana notacji infiksowej na odwrotną notacje polska
Potrzebne są nam trzy stosy.
••••
Stos wejściowy
(WE)
••••
Stos wynikowy
(WY)
••••
Stos pomocniczy (POM)
Założmy, że na stosie WE dane mamy wyrażenie rozbite na atomy i
ułożone w takiej kolejmości aby pobieranie z niego danych odpowiadało
kolejności pojawiania się atomów w wyrażeniu. Mogła by tu być użyta
kolejka.
Np. 2*(3+7)
2
*
(
3
+
7
)
1)
Przetwarzamy wyrazenie pojawiające na stosie WE
2)
Jeśli atom jest:
a)
Liczbą
to „wrzycamy” ja na stos (WY)
b)
Nawiasem „(” to „wrzycamy” ja na stos (POM)
c)
Nawiasem „)” to ze stos (POM) zdejmujemy operator i kładziemy
go na stos (WY)
operacje tą powtarzamy az do momentu zdjęcia ze
stosu (POM) atomu „)”-tego atomu nie kładziemu na
stosie
84
d)
Operatorem O sprawdzamy jaki operator jest na stosie (POM)
-jeśli stos jest pusty to kładziemy operator O na stos
(POM)
-jeśli operator na stosie ma wyższy priorytet to
kładziemy go z powrotem na stos (POM) i nastepnie
dokładamy operator O
- jeśli operator na stosie ma niższy bądź równy
priorytet to kładziemy na stos (WY)
nastepnie powtarzamy operację dla kolejnego
poeratora na stosie (POM)
na koniec kładziemy operator O na sios (POM)
3)
Gdy stos (WE) jest już pusty to przekładamy wszystkie pozostałe
operatory ze stosu (POM) na stos (WY)
4)
Otrzymujemy stos (WY) z wyrażeniem w odwrotnej notacji polskiej,
ale od jego końca westarczy go tyklo odwrócić
5)
Zamiast punktu 3) można przenieść dane ze stosu (WY) na stos (POM).
Wtedy wynikiem jest stos (POM).
17.1.3.
Kalkulator stosowy
Potrzebne są nam dwa stosy.
••••
Stos wejściowy
(WE)
••••
Stos wynikowy
(WY)
Założmy, że na stosie WE dane mamy wyrażenie w odwrotnej notacji
polskiej rozbite na atomy.
1)
Przetwarzamy wyrazenie pojawiające na stosie WE
2)
Jeśli atom jest:
a)
Liczbą
to „wrzycamy” ja na stos (WY)
b)
Operatorem O to ze stosu (WY) pobiaramy arg2
następnie ze stosu (WY) pobiaramy arg1
i na stos (WY) wkładamy wynik wyrażenia
arg1 O arg2 (zapis w notacji infiksowej)
3)
Wynikiem jest liczba na stosie WE
4)
Jeśli na stosie WE zabraknie argumentów, bądź jeśli zostanie więcej niż
jedna liczba, oznacze to sytuację błędną.
Możliwa jest zatem kontrola poprawności wprowadzonego wyrażenia.
Przykład:
85
3- (4*5)*(6+7)/8 = -29.5
1.
Zamiana na odwrotną notację polską
1.
3 - ( 4 * 5 ) * ( 6 + 7 ) /8 WE
POM
WY
2.
- ( 4 * 5 ) * ( 6 + 7 ) /8 WE
POM
3 WY
3.
( 4 * 5 ) * ( 6 + 7 ) /8 WE
- POM
3 WY
4.
4 * 5 ) * ( 6 + 7 ) / 8
WE
( - POM
3 WY
5.
5 ) * ( 6 + 7 ) / 8
WE
* ( - POM
4 3 WY
6.
) * ( 6 + 7 ) / 8
WE
* ( - POM
5 4 3 WY
7.
* ( 6 + 7 ) / 8
WE
- POM
* 5 4 3 WY
8.
( 6 + 7 ) / 8
WE
* - POM
* 5 4 3 WY
9.
6 + 7 ) / 8
WE
( * - POM
* 5 4 3 WY
10.
+ 7 ) / 8
WE
( * - POM
6 * 5 4 3 WY
11.
7 ) / 8
WE
+ ( * - POM
6 * 5 4 3 WY
12.
) / 8
WE
+ ( * - POM
7 6 * 5 4 3 WY
13.
/ 8
WE
* - POM
+ 7 6 * 5 4 3 WY
14.
8
WE
/ - POM
* + 7 6 * 5 4 3 WY
15.
WE
/ - POM
8 * + 7 6 * 5 4 3 WY
16.
A
WE
POM
- / 8 * + 7 6 * 5 4 3 WY
16.
B
WE
3 4 5 * 6 7 + * 8 / - POM
WY
86
2. Wyliczenie wartości wyrażenia
1.
3 4 5 * 6 7 + * 8 / - WE
WY
2.
4 5 * 6 7 + * 8 / - WE
3 WY
3.
5 * 6 7 + * 8 / - WE
4 3 WY
4.
* 6 7 + * 8 / - WE
5 4 3 WY
5.
6 7 + * 8 / - WE
20 3 WY
6.
7 + * 8 / - WE
6 20 3 WY
7.
+ * 8 / - WE
7 6 20 3 WY
8.
* 8 / - WE
13 20 3 WY
9.
8 / - WE
260 3 WY
10.
/ - WE
8 260 3 WY
11.
- WE
32.5 3 WY
12.
WE
-29.5 WY
87
18.
Drzewa binarne
Drzewo jest skończonym zbiorem elementów zwanych węzłami.
Drzewo posiada jeden wyróżniony element zwany korzeniem, pozostałe
węzły tworzą rozłączne poddrzewa. Jeżeli węzeł r ma poddrzewa T
1
, T
2
,
..., T
n
, to korzenie tych drzew są potomkami węzła r. Węzeł, który nie ma
potomków nazywamy liściem.
drzewo
a
kkorzeń
poddrzewo
b
f
g
c
d
e
h liście
Drzewami binarnymi nazywamy drzew, których węzły mają dwóch
potomków.
Plik drzewo.h:
#define
NULL
0
typedef
char
Dane;
struct wezel
{
Dane d;
struct wezel *lewy;
struct wezel *prawy;
};
typedef struct wezel WEZEL;
typedef WEZEL
*DRZEWO;
88
18.1.
Przeglądanie drzew binarnych
Kierunek:
LKP
KLP
LPK
Lewe
Korzeń
Lewe
Korzeń
Lewe
Prawe
Prawe
Prawe
Korzeń
void lkp(DRZEWO korzen)
{
if (korzen != NULL)
{
lkp(korzen -> lewy);
printf("%c ", korzen -> d);
lpk(korzen -> prawy);
}
}
G
D
I
B
F
H
J
A
C
E
lpk : A B C D E F G H I J
void klp(DRZEWO korzen)
{
if (korzen != NULL)
{
printf("%c ", korzen -> d);
lkp(korzen -> lewy);
lpk(korzen -> prawy);
}
}
klp:
G D B A C F E I H J
89
18.2.
Tworzenie drzewa binarnego
DRZEWO nowy_wezel()
{
return (malloc(sizeof(WEZEL)));
}
DRZEWO inic_wezel(Dane d1, DRZEWO p1, DRZEWO p2)
{
DRZEWO t:
t = nowy_wezel();
t -> d = d1;
t -> lewy = p1;
t -> prawy = p2;
return t;
}
Tworzenie drzew binarnego z tablicy:
potomkami a[i] są: a[2*i+1] i a[2*i+2]
DRZEWO tw_drzewo(Dane a[], int i, int ilosc)
{
if (i >= ilosc)
return NULL;
else
return (inic_wezel(a[i],
tw_drzewo(a, 2*i+1, ilosc),
tw_drzewo{a, 2*i+2, ilosc));
}
90
18.3.
Drzewa wielokierunkowe
Drzewo wielokierunkowe -
węzły mają dowolną liczbę potomków.
Reprezentacja:
Lista liniowa dla każdego węzła.
Każda lista jest potomkiem jednego węzła.
Tablica pierwszych potomków.
t[0]
a
b
f
g
c
d
e
h
NULL
NULL
NULL
NULL NULL
Plik w_drzewo.h:
#define
NULL
0
typedef
char
Dane;
struct wezel
{
int
nr_pot;
Dane
d;
struct wezel
*r;
};
typedef struct wezel WEZEL;
typedef WEZEL *W_DRZEWO;
91
Tworzenie węzła:
W_DRZEWO nowy_wezel()
{
return(malloc(sizeof{WEZEL)));
}
W_DRZEWO inic_wezel(Dane d1, int num, W_DRZEWO rd)
{
W_DRZEWO rob:
rob = nowy(wezel);
rob -> d = d1;
rob -> nr_pot = num;
rob -> r = rd;
return rob;
}
t[0] = inic_wezel('a', 1, NULL);
t[1] = inic_wezel('b', 2, NULL);
t[1] -> r = inic_wezel('f', 6, NULL);
t[1] -> r -> r = inic_wezel('g', 7, NULL);
t[2] = inic_wezel('c', 3, NULL);
t[2] -> r = inic_wezel('d', 4, NULL);
t[2] -> r -> r = inic_wezel('e', 5, NULL);
t[3] = t[4] t[5] = NULL;
t[6] = inic_wezel('h', 8, NULL);
t[7] = t[8] = NULL;
18.4.
Przeglądanie drzewa wielokierunkowego
void przeg(W_DRZEWO t, int ind)
{
W_DRZEWO rob;
rob = t[ind];
while (rob != NULL)
{
printf("%c %d\n", rob->d, rob->nr_pot);
przeg(t, rob->nr_pot);
rob = rob->r;
}
}
92
19.
Preprocesor
19.1.
Sklejanie wierszy
Wiersze zakończone znakiem \ są sklejane z następnym wierszem.
Nastepuje to przed podziałem pliku na leksemy.
int nazwa\
zmiennej;
Definicja zmiennej
nazwazmiennej
Derektywy preprocesora dotyczą jednej lini kodu, jeśli dyrektywa jest dość
długa to powyższa metoda może być użyteczna.
19.2.
Dyrektywa #include
#include
<
>
#include
"
"
Przykład.
Plik prog.h:
#include <stdio.h>
#include <stdlib.h>
. . .
Plig prog.c:
#include "prog.h"
19.3.
Dyrektywa #define
#define identyfikator wartość
#define
ILOSC_SEKUND_W_DOBIE \
(60 * 60 * 24)
#define
PI
3.141592
#define
EOF
(-1)
#define
MAXINT
2147483647
Przykład.
93
#define EQ ==
#define do
while ( i EQ 1 ) do
{
. . .
while ( i == 1)
{
. . .
19.3.1.
Użycie makrdefinicji z parametrami
#define nazwa(nazwa, ..., nazwa)
wartość
#define
SQ(x) ((x) * (x))
SQ(7 + w)
((7 + w) * (7 + w))
SQ(SQ(*p))
((((*p) * (*P)) * (((*p) * (*p))))
ale
#define
SQ(x) x * x
SQ(a+b)
a + b * a + b
zamiast
((a + b) * (a + b))
Uwagi:
1.
#define SQ(x) (x) * (x)
94
4 / SQ(2)
4 / (2) * (2)
zamiast
4 / ((2) * (2))
2.
#define SQ (x) ((x) * (x))
SQ(7)
(x) ((x) *(x)) (7)
3. #define SQ(x) ((x) * (x)) ;
x = SQ(x);
x = ((x) * (x)) ;;
ale
if (x == 2)
x = SQ(y);
else
++x;
Przykłady.
1. #define min(x,y)
(((x) < (y)) ? (x) : (y))
m = min(u,v);
m = (((u) < (v)) ? (u) : (v));
2. #define min4(a,b,c,d) min(min(a,b), min(c,d))
3. #define SQ(x) ((x) * (x))
#define CUBE(x)
(SQ(x) * (x)) /* x ^ 3 */
#define P_4_3 sqrt(sqrt(CUBE(x)))
PRZYKŁAD
#include
<stdio.h>
#include
<stdlib.h>
95
#define
M 32 /* wymiar a[] */
#define N 11 /* wymiar b[] */
#define
czesc_ulamkowa(x) (x - (int) x)
#define
random_char()
(rand() % 26 + 'a')
#define
random_float()
(rand() % 100 / 10.0)
#define INIC(a,c,typ) \
for (i = 0; i < n; ++i) \
a[i] = (*typ == 'c') ? random_char() : \
random_float()
#define DRUKUJ(a, n, zn_ster)
\
for (i = 0; i < n; ++i)
\
printf(zn_ster, a[i]); \
putchar('\n')
main()
{
char a[M];
float b[N];
int i;
int lex(void *, void *);
int por_cz_ul(void *, void *);
INIC(a, M, "char");
DRUKUJ(a, M, "%-2c");
qsort(a, M, siceof(char), lex);
DRUKUJ(a, M, "%-2c");
printf("--------------\n");
INIC(b, N, "float");
DRUKUJ(b, N, "%-6.1f");
qsort(b, N, siceof(float), por_cz_ul);
DRUKUJ(b, N, "%-6.1f");
}
96
int lex(void *vp, void *vq)
{
char *p = vp, *q = vq;
return (*p - *q);
}
int por_cz_ul(void *vp, void *vq)
{
float *p = vp, *q = vq, x;
x = czesc_ulamkowa(*p) - czesc_ulamkowa(*q);
return ((x == 0.0) ? 0 : (x < 0.0 ? -1 : +1));
}
19.4.
Dyrektywa #undef
#undef nazwa
odwołanie definicji makroinstrukcji
#ifdef max
#undef max
#endif
#define max (x,y) (((x) > (y)) ? (x) : (y))
97
19.5.
Kompilacja warunkowa
wiersz-if tekst częsci-elif część-else
opc
#endif
wiersz-if
#if wyrażenie stałe
#ifdef identyfikator
#ifndef identyfikator
części-elif
wiersz –elif tekst
części-elif
opc
wiersz-elif
#elif wyrażenie-stałe
część-else
wiesz-else tekst
wiesz-else
#else
wyrażenie-stałe
defined(identyfikator)
!defined(identyfikator)
Przykład:
#define __WERSJA_1_0
printf(
#ifdef __WERSJA_1_0
" Wersja 1.0"
#elif defined(__WERSJA_2_0)
" Wersja 2.0"
#else __DEMO__
" Wersja DEMONSTRACYJNA"
#endif
);
98
Przykład:
Warunkowe dołączanie plików nagłówkowych:
#ifndef _ _napisy_h_ _
#define _ _napisy_h_ _
import int slen(const char *s);
/*dlugosc napisu*/
char* scopy(char *cel,const char * zrodlo);
/* kopiowanie*/
char* sncopy(char *cel,const char *
zrodlo,int n);
/*kopiowanie co najwyzej n znakow*/
char* scat(char *cel,const char * zrodlo);
/*laczenie*/
int scomp(const char *s1,const char * s2);
/*porownanie*/
int sicomp(const char *s1,const char * s2);
/*porownanie z ignorowaniem wilekosci liter*/
char* srev(char *p,const char *s);
/* odwracanie*/
#endif
99
19.6.
Inne dyrektywy
#define
#undef
#include
19.6.1.1.
#
Pusta instrukcja preprocesora -nie ma żadnych skutków
19.6.1.2.
# pragma derektywa
Wykonuje dyrektywe specyficzną dla kompilatora. Jeśli kompilator nie
rozpoznaje dyrektywy to ją ignoruje bez zgłoszenie błędu czy nawet
ostrzeżena.
void koniec()
{
printf("Koniec");
}
#pragma exit koniec
19.6.1.3.
# error tekst
Powoduje, że w trakcie kompilacji wypiany zostanie tekst
#error To jest blad kompilacji
19.6.1.4.
# line nr_lini ‘’plik’’
Dyrektywa powoduje, że dla celów diagnostycznych zakłaga się, że
następną linią jest linia nr_lini w pliku plik. Jeśli ''plik'' zostanie
opuszczony to za plik uznaje się plik bieżący.
Dyrektywa używana dla innych kompilatorów generujących kod w języku
C.
19.7.
Makrorozwinięcia w stdio.h i ctype.h
#define
getchar()
getc(stdin)
#define
putchar(c)
putc((c), stdout)
isalpha(c)
isupper(c)
islower(c)
. . .
100
19.8.
Nazwy zdefiniowane w proprocesorze
_ _ LINE _ _
numer wiersza pliku źródłowego
_ _ FILE _ _
nazwa pliku źródłowego
_ _DATE _ _
data tłumaczenia programy ''Mmm dd rrr''
_ _ TIME_ _
czas tłumaczenia programy ''gg:mm:ss''
_ _ STDC_ _
stała 1- ma to oznaczać zgodność ze standardem ANSI
Definicji tych nie można zmieniać.
#include<stdio.h>
#ifndef __STDC__
#include <conio.h>
#else
#define cprintf printf
#endif
main()
{
#ifndef __STDC__
textcolor(YELLOW);
#endif
cprintf("Koplilacja za pomocą %s\n\r",
#ifndef __STDC__
"BORLAND C++"
#else
"ANSI C"
#endif
);
#ifndef __STDC__
textcolor(LIGHTGRAY);
#endif
cprintf("Kod Źródłowy %s \n\rData kompilacji %s %s \n\rIlość lini %d",
__FILE__,__DATE__, __TIME__,__LINE__+1);
return;
}
101
19.9.
Tworzenie aplikacji wielomodułowych
19.9.1.
Projekty
Zalety i wady projektów:
••••
Podział programu na moduły
••••
Możliwość korzystania z gotowych bibliotek (LIB)
••••
Zapamiętanie układu scieżek
102
19.9.2.
Tworzenie bibliotek
Tworzenie bibliotek umożliwia udostępnianie funkcji bibliotecznych z
jednoczesnym ukruciem ich kodu.
103
19.9.3.
Udostępnianie danych
W każdym module przynajmniej jedna funkcja musi być publiczna
Wskazane jest do każdego modułu ( konieczne gdy uzywamy modułów
już wcześniej skompilowanych) dołączenie pliku nagłówkowego
19.9.4.
Ukrywanie danych
Jest to klasyczne podejście w programowaniu obiektowym, ale istnieje od
dawna w języku C.
Widoczność zmniennej zadeklarowana jako
static
jest ograniczona
tylko do pliku, w którym występuje je definicja.
static int PrivateCounter;
/* Funkcje publiczna */
int DecCounter()
{
if(PrivateCounter)
return --PrivateCounter;
}
int IncCounter()
{
if(PrivateCounter<CounterLimit)
return ++PrivateCounter;
}
19.9.5.
Niepełne definicje typów
Ukrywać dane można także poprzez nieudostępnianie pełnej struktury
danych.
typedef struct StacTag STACK;
STACK *Creat(int size);
int pop(STACK *Stack);
int push(STACK *Stack,int Value);
int empty(STACK *Stack);
Programista może posługiwać się strukturą tylko w ograniczonym
kontekście-gdy nie jest potrzebna jej wielkość , stosując np. wskaźnik do
struktury
104
Kompilator w momencie definicji zakłada, że pełna definicja struktury
pojawi się później
typedef struct StackTag
{
int *Ptr;/* miejsce na dane */
int Size;
int Current;
}
Kompilator , ponieważ struktura
StackTag
nie będzie znana w innych
modułach będzie generował odpowiednie ostrzeżenia, ale obsługa tego
typu będzie poprawna.
Takie podejście uniemożliwia bezpośredni dostęp do pól sturuktury -
nawet jeśli ich nazwy będą znane programiście.
STACK * s;
...
s->Counter++/* bł
ą
d kompilacji*/
19.9.6.
Funkcje prywatne
Funkcje także można uczynić prywatnymi:
static int FunkcjaPomocnicza();
Zasady widoczności funkcji prywatnych są identycze jak przy zmiennych.
Łatwo znaleźć wiele przykładów gdzie zarówno dane jak i kod należy
chronić przed ich niepowołanym dostępem. Może to dotyczyc również
dostępu przez samego autora.
105
20.
Funkcje ze zmienną ilością argumentów
Niezbędne definicje znajdują się w pliku:
#include <stdarg.h>
typ
va_list
typ umozliwiający dostęp do
argumentów
makro
va_start
inicjacja zmiennej typu
va_list
na
pierwszy argument z listy
Nie zwraca wartości.
void va_start(va_list ap, lastfix);
makro
va_arg
indeksowanie listy zmiennych
argumentów
type va_arg(va_list ap, type);
makro
va_end
polwala funkcji na poprawne
zakończenie. Nie zwraca wartości.
void va_end(va_list ap);
Przykład:
#include <stdio.h>
#include <stdarg.h>
void sum(char *msg1,char* msg2, ...)
{
int total = 0;
va_list ap;
int arg;
va_start(ap, msg2);
printf("\n%s ",msg1);
while ((arg = va_arg(ap,int)) != 0) {
total += arg;
printf("%d +",arg);
}
printf("\b %s %d",msg2, total);
va_end(ap);
}
int main(void) {
sum("Suma","wynosi", 1,2,3,4,0);
return 0;
}
106
#include <stdarg.h>
#include <ctype.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int print( const char *format,...);
int main()
{
float f=3.5;
print(" %f %5c %c %d\n",f+1.3,'
ů
','b',10);
print("\t%3d+%d=%4d \n%f\tok",12,3,12+3,f);
return 0;
}
int print( const char *format,...)
{
va_list ap;
int arg,count=0,precision=3,wide=0;
char FillChar='.';
const char *FFF=format;
va_start(ap, format);
while(*format){
int wproc=1;
switch(*format)
{
case '%':
do{ // do przetwarzania formatu
switch(*++format)
{ // szeroko
ść
pola i
case '0':case '1':case '2':case '3':case '4':
case '5': case '6': case '7':case '8': case '9':
{
char Wide[10]="",cw=0;
while(isdigit(*format))
Wide[cw++]=*format++;
Wide[cw]=0;
wide=atoi(Wide);
if(*format=='.')
107
{
char Prec[10]="",cp=0;
++format;
while(isdigit(*format))
Prec[cp++]=*format++;
Prec[cp]=0;
precision=atoi(Prec);
}
--format;
}continue;
case 'd':{// liczba int
int sl,arg=va_arg(ap,int);
char *a="...........";
itoa(arg,a,10);
sl=strlen(a);
while(wide>sl++)putchar(FillChar);
while(*a)
putchar(*(a++));
count++;
}
break;
case 'f':{
// liczba double (float)
int sl;
double arg=va_arg(ap,double);
int dec,sign;
char *a=fcvt(arg,precision,&dec,&sign);
sl=strlen(a);
if(dec<sl)sl++;
if(sign)sl++;
while(wide>sl++)
putchar(FillChar);
if(sign)
putchar('-');
while(dec--&&*a)
putchar(*(a++));
putchar('.');
while(*a)
putchar(*(a++));
count++;
}
108
break;
case 'c':{
char arg=va_arg(ap,char);
while(--wide>0) putchar(FillChar);
putchar(arg);
count++;
}
break;
default:putchar(*format);
}
wproc=0;// wyj
ś
cie z p
ę
tli
}while(wproc);
case '\\': switch(*++format)
{
case 'n':putchar('\n');break;
case 'r':putchar('\r');break;
case 't':putchar('\t');break;
default:putchar(*format);
}
break;
default: putchar(*format);break;
}
*++format;
precision=3;wide=0;
} ;
return count;
}
109
20.1.
Funkcje biblioteczne
int printf(const char *format[, argument, ...]);
int sprintf(char *buffer, const char *format[, argument, ...]);
int fprintf(FILE *stream, const char *format[, argument, ...]);
int vprintf(const char *format, va_list arglist);
int vfprintf(FILE *stream, const char *format, va_list arglist);
int vsprintf(char *buffer, const char *format, va_list arglist);
Przykład:
#include <stdio.h>
#include <stdarg.h>
int vpf(char *fmt, ...)
{
va_list argptr;
int cnt;
va_start(argptr, fmt);
cnt = vprintf(fmt, argptr);
va_end(argptr);
return(cnt);
}
int main(void)
{
int inumber = 30;
float fnumber = 90.0;
char *string = "abc";
vpf("%d %f %s\n",inumber,fnumber,string);
return 0;
}
110
21.
Wejście/wyjście
21.1.
Standardowa funkcja wejścia
int printf(const char *format[, argument, ...]);
-
dosuniecie do lewej strony
d
liczba dziesietna
x
l. szesnastkowa
o
l. ósemkowa
f
l. float lub double
e
liczba float lub double w zapisie naukowym
g
jeden z d f e (o najmniejszym polu)
s
łańcuch znzków
p
wskaźnik
u
liczba bez znaku
c
zank
%
znak %
.
dzieli szerokość pola od precyzji
h
liczba krótka
l
liczba długa
*
umożliwia pobranie szerokości pola
21.2.
Standardowa funkcja wejścia
int scanf(const char *format[, address, ...]);
ld
liczba typu long
lf
liczba typy double
Lf
liczbatypu long double
[]
wczytuje tylko znaki ujęte w klamry
[^]
znak w klamrze jest końcem pobierania łancucha
*
ignoruje pobrany element
111
#include <stdio.h>
int main(){
char imie[10],nazw[21],adres[31],tel[16];
printf(
"wizytówka\npodaj swoje dane\n\timie(imiona)\t");
scanf("%*[\n]%10[^\n]%*[^\n]",imie);
printf("\tnazwisko\t");
scanf("%*[\n]%20[^\n]%*[^\n]",nazw);
printf("\tadres\t\t");
scanf("%*[\n]%30[^\n]%*[^\n]",adres);
printf("\ttelefon\t\t");
scanf("%*[\n]%15[^\n]%*[^\n]%*c",tel);
printf("%s %s \n%s\ntel. %s " ,
imie,nazw,adres,tel);
return;
}
Przykład 2:
#include <string.h>
#include <stdio.h>
int main(){
char imie[256];
FILE *f=fopen("file1.c","r");
while(1) {
if((fscanf(f,"%256[a-zA-z_]",imie))==EOF)break;
printf("%s ",imie);
if((fscanf(f,"%256[^a-zA-z_]",imie))==EOF)break;
printf("%s ",imie);
}
fclose(f);
return;
}
Przykład 3:
#include<stdio.h>
int main(){
int i,d; unsigned u; char c,s[10]; float f;
printf(" podaj znak ascii, liczbe calkowita , l. \
nieujemna, l.rzeczywista napis l. calkowita");
scanf("%c %d %u %f %9s%* %x",&c,&i,&u,&f,s,&d);
printf("%d %u %c %g %9s %x",i,u,c,f,s,d);
return;
}
112
21.3.
Napisowe funkcje wejścia/wyjścia.
int sprintf(char *s, const char *format, ... );
int sscanf(char *s, const char *format, ... );
Przykład.
char str1[] = "3 4 start", str2[100], rob[100];
int a,b;
sscanf(str1, "%d%d%s", &a, &b, rob);
sprintf(str2, "%s %s %d %d\n", rob, rob, a, b);
printf("%s", str2);
21.4.
Plikowe funkcje IO
Pliki standardowe:
stdin
wejscie standardowe
klawiatura
stdout
wyjście standardowe
ekran
stderr
standardowy plik błędów
ekran
inne pliki deklarujemy używając FILE zdefiniowanego w stdio.h.
int fprintf(FILE *fp, const char *format, ... );
int fscanf(FILE *fp, const char *format, ...);
fprintf(stdout, ... )
printf( . . .)
fscanf(stdin, ...)
scanf( . . .)
Uwaga.
fprintf(stderr - działa przy zmianie wyjscia)
113
21.5.
FUNKCJE fopen() i fclose()
#include <stdio.h>
FILE fopen(char *nazwa_pliku, char *tryb);
tryb
znaczenie
-------------------------------------
"r"
plik tekstowy - czytanie
"w"
plik tekstowy - pisanie
"a"
plik tekstowy - dołączanie
"r+"
plik tekstowy - czytanie i pisanie
"rb"
plik binarny - czytanie
"wb"
plik binarny - pisanie
"ab"
plik binarny - dołącznie
"wb+"
plik binarny - pisanie i czytanie
int fclose(FILE *stream);
Przykład.
#include
<stdio.h>
#include
<stdlib.h>
void podw_odstp(FILE *, FILE *);
void prn_info(char *);
main (int argc, char **argv)
{
FILE *ifp, *ofp;
if (argc != 3)
{
prn_info(argv[0]);
exit(1);
}
ifp = fopen(argv[1], "r"); /* plik wej
ś
ciowy */
ofp = fopen(argv[2], "w");
/* plik wynikowy */
podw_odstp(ifp, ofp);
fclose(ifp);
fclose(ofp);
}
void podw_odstp(FILE *ifp, FILE *ofp)
114
{
int c;
while ((c = getc(ifp)) != EOF)
{
putc(c, ofp);
if (c == '\n')
putc('\n', ofp); /* podwójny odst
ę
p */
}
}
void prn_info(char *pgm_name)
{
printf("\n%s%s%s\n\n%s%s\n\n",
"Uzycie : ", pgm_name, " dane wyniki",
"Zawartosc pliku dane bedzie skopiowana do ",
"pliku wyniki z podwojnymi
odstepami.");
}
Przykład.
#include
<stdio.h>
#include
<stdlib.h>
#include
<ctype.h>
main(int argc, char **argv)
{
int c;
FILE *fp, *tmp_fp;
FILE *gfopen(char *nazwa, char *tryb);
if (argc != 2)
{
fprintf(stderr, "\n%s%s%s\n\n%s%s\n\n",
"Uzycie: ", argv[0], " plik",
"Zawartosc pliku bedzie podwojona",
" o tekst z duzymi literami.");
exit(1);
}
fp = gfopen(argv[1], "r+");
tmp_fp = tmpfile();
while ((c = getc(fp)) != EOF)
115
putc(toupper(c), tmp_fp);
rewind(tmp_fp);
fprintf(fp, "------\n");
while ((c = gestc(tmp_fp)) != EOF)
putc(c, fp);
}
FILE *gfopen(char *nazwa, char *tryb)
{
FILE *fp;
if ((fp = fopen(nazwa, tryb)) == NULL)
{
fprintf(stderr, "Blad otwarcia pliku %s\n",
nazwa);
exit(1);
}
return(fp);
}
FILE *freopen(const char *filename, const char *mode, FILE
*stream);
#include <stdio.h>
int main(void)
{
/* pzrekierowanie standoadowego struminenia
wyj
ś
ciowego */
if (freopen("OUTPUT.FIL", "w", stdout)
== NULL)
fprintf(stderr, "error redirecting
stdout\n");
/* pisanie do pliku */
printf("This will go into a file.");
/* zamkni
ę
cie strumienia */
fclose(stdout);
return 0;
116
}
size_t fread(void *ptr, size_t size, size_t n, FILE *stream);
size_t fwrite(const void *ptr, size_t size, size_t n, FILE *stream);
#include <string.h>
#include <stdio.h>
int main(void)
{
FILE *stream;
char msg[] = "this is a test";
char buf[20];
if ((stream = fopen("DUMMY.FIL", "w+"))
== NULL)
{
fprintf(stderr, "Cannot open output
file.\n");
return 1;
}
/* write some data to the file */
fwrite(msg, strlen(msg)+1, 1, stream);
/* seek to the beginning of the file */
fseek(stream, SEEK_SET, 0);
/* read the data and display it */
fread(buf, strlen(msg)+1, 1, stream);
printf("%s\n", buf);
fclose(stream);
return 0;
}
117
21.6.
BEZPOŚREDNI DOSTĘP DO PLIKU
long int ftell(FILE *stream);
- daje bieżące położenie wskaźnika pliku
(liczba bajtów od początku pliku)
fseek(FILE *wsk_pliku, n, miejsce)
- przesuwa wskaznik pliku o n bajtów od miejsca
(miejsce:
SEEK_SET
0 - początek,
SEEK_CUR 1 - bieżąca pozycja,
SEEK_END 2 - koniec pliku)
void rewind(FILE *stream);
fseek(stream, 0L, SEEK_SET)
Przykład.
Przepisanie pliku końca.
#include
<stdio.h>
#define MAX 100
main()
{
char nazwa_pliku[MAX];
int c;
FILE *wp;
fprintf(stderr, "\nWprowadz nazwe pliku: ";
scanf("%s", nazwa_pliku);
wp = fopen(nazwa_pliku, "rb");
fseek(wp, 0, 2);
/* koniec pliku */
fseek(wp, -1,1);
/* 1 do tyłu */
while (ftell(wp) >= 0)
{
c = getc(wp);
/* 1 do przodu */
putchar(c);
fseek(wp, -2, 1);
/* 2 do tyłu */
}
}
118
21.7.
UCHWYTY DO PLIKU
Funkcje istnieją w DOS, Windows, OS2 i UNIX ale nie w ANSI
#include <fcntl.h>
#include<io.h>
#include<sys\stat.h>
int open(const char *path, int access [, unsigned mode]);
Otwarcie pliku identyfikowanego przez path do czytania bądź pisania.
O trybie otwarcia decyduje parametr access, do którego należy przypisać
bitową kombinację następujących stałych zdefiniowanych w pliku fcntl.h.
Flagi podstawowe:
O_RDONLY
Otwarcie tylko do czytania
O_WRONLY
Otwarcie tylko do pisania
O_RDWR
Otwarcie do czytania i pisania.
Inne flagi:
O_NDELAY
Flaga ma znaczenie dla łączy komunikacyjnego i plików
specjalnych. Nie ma żadnego znaczenia dla zwykłych
plików czy katalogów.
O_APPEND
Znacznik pliku będzie ustawiany na końcu pliku przy
każdej operacji pisania.
O_CREAT
Jeśli plik nie istnieje to zostanie stworzony a parametr
mode posłuży do ustawienia praw dostępu do pliku.
Jeśli plik istnieje to flaga ta nic ne wnosi.
O_TRUNC
Jeśli plik istnieje jego długość jest zmniejszana do 0.
Atrybuty pozostają niezmienione.
O_EXCL
Dopuszczalne w połączeniu z O_CREAT. Istnienie pliku
powoduje wystąpienie błędu.
O_BINARY
Otwarcie w trybie binarnym.(tylko DOS)
O_TEXT
Otwarcie w trybie tekstowym.
Wartości parametru Mode (DOS,Windows
)
S_IWRITE
Prawo do pisania
S_IREAD
Prawo do czytania
S_IREAD|S_IWRITE
Prawo do czytania i pisania
Funkcja zwraca nieujemną liczbę bedącą uchwytem do pliku
119
lub -1 gdy wystąpił błąd.
FILE *fdopen(int handle, char *type);
handle -
uczwyt do pliku
type -
tryb otwarcia ( rwar+ tb )
#include <sys\stat.h>
#include <stdio.h>
#include <fcntl.h>
#include <io.h>
int main(void)
{
int handle;
FILE *stream;
/* open a file */
handle = open("DUMMY.FIL", O_CREAT,
S_IREAD | S_IWRITE);
/* now turn the handle into a stream */
stream = fdopen(handle, "w");
if (stream == NULL)
printf("fdopen failed\n");
else
{
fprintf(stream, "Hello world\n");
fclose(stream);
}
return 0;
}
int write(int handle, void *buf, unsigned len);
handle -
uczwyt do pliku
buf
-
wskaźnik do obszaru danych
len
-
ilość bajtów do zapisania
int read(int handle, void *buf, unsigned len);
int close(int handle);
120
#include <stdio.h>
#include <io.h>
#include <alloc.h>
#include <fcntl.h>
#include <process.h>
#include <sys\stat.h>
#define BUFSIZE 1024
int main(void)
{
void *buf;
int hin,hout, bytes;
long total=0;
buf = malloc(BUFSIZE);
if ((hin =
open("handle.bak", O_RDONLY, S_IWRITE |
S_IREAD)) == -1)
{
printf("Error Opening Input File\n"); exit(1);
}
if ((hout = open("TEST.$$$", O_WRONLY | O_CREAT |
O_TRUNC,0400)) == -1)
{
printf("Error Opening Output File.\n");exit(2);
}
do {
if ((bytes = read(hin, buf, BUFSIZE)) != -1)
{
printf("Read: %6d bytes read.\r", total+=bytes);
if ((bytes = write(hout, buf, bytes)) == -1)
{
printf("Write Failed.\n"); exit(4);
}
}
}while((bytes>0));
if(bytes==-1)
{
printf("Read Failed.\n"); exit(3);
}
close(hin); close(hout);
return 0;
121
}
122
22.
C w systemie UNIX
22.1.
Kompilator języka ANSI C
W kazdej instalacji UNIX powinien istnieć standardowy kompilator jezyka
C o nazwie cc
Jego wywołanie:
cc nazwa
cc nazwa –o wynik
gdzie
nazwa nazwa pliku z kodem zródłowym – niektóre kompilatory
wymagają aby plik posiadał rozszerzenie .c
wynik nazwa pliku wynikowego (domyślna w pierwszej wersji
wywołania to a.uot )
Uwaga
System UNIX rozróżnia wielkość liter w poleceniach nazwach plików i
katakogów.
22.2.
Pliki w systenie UNIX
/
katalog główny
root
/usr/myid
katoalog użytkownika
HOME ~
Pojęcie pliku i katalogu w systemach DOS i UNIX są podobne. Bardzo
ważna różnicą są pwawa dostępu do pliku (katalogu) : prawo czytania
(read) pisania (write) i uruchamiania (execute).
Uprawnienia dostępu ósemkowo
Użyteczne stają się tu funkcje dostępu do pliku poprzez uchwyty:
int open(const char *path, int access [, unsigned mode]);
int write(int handle, void *buf, unsigned len);
int read(int handle, void *buf, unsigned len);
int close(int handle);
r
w
x
u 0400 0200 0100
g
040 020 010
a
04
02
01
123
22.3.
Funkcje inicjacja procesu
int execl (char * f_path, char* arg1,...,null);
int execve(char *f_path, char *argv[],char *env[]);
Funkcja
Format parametrów Przekazywanie środowiska PATH
Execl
Lista
Automatyczne
Nie
Execv
Tablica
Automatyczne
Nie
Execle
Lista
Ręczne
Nie
Execve
Tablica
Ręczne
Nie
Execlp
Lista
Automatyczne
Tak
Execvp
Tablica
Automatyczne
Tak
Funkcja systemowa exec dokonuje ponownego zainicjowania procesu na
podstawie wskazanego programu. Proces pozostaje zatem ten sam, a
zmianie ulga wykonywany program.
Proces - to środowisko wykonywania programu, któte składa się z
segmentu instrukcji, segmentu danych użytkownika oraz
segmentu danych systemowych
Program - to plik zawierający instrukcje i dane służące do
inicjalizacji procesu.
excectest()
{
printf("To jest przyklad");
execl("/bin/echo","echo","wywaloania","funkcji","ex
ecl",
NULL);
perror("EXEC:");
}
Funkcja zwraca -1 w przypadku błędu. W przypadku wywołania
poprawnego powrót nie nastąpi.
124
22.4.
int fork()
Funkcja fork tworzy nowy proces, ale nie inicjuje go na podstawie
jakiegoś nowego programu. Segmenty instrukcji, danych użytkownika i
systemowych są wielokrotnymi kopiami starego procesu.
Funkcja ta tworzy proces potomka. Zazwyczaj proces potomny wywołuje
funkcją exec, a proces macierzysty albo czeka na zakończenie procesu
potomnego, albo zajmuje się czymś innym.
Funkcja fork gdy wystąpi błąd (wyczerpanie zasobów systemu) -1. W
innych przypadkach do procesu potomnego zwraca 0, przodek otrzymuje
identyfikator procesu potomka.
Identyfikatory potomka i przodka są różne- przecież to są inne procesy
Potomek otrzymuje kopie deskryptorów do plików od przodka, jednak
wskaźniki do pliku są wspólne- lseek zmieni ich położenie dla obu
procesów, a zamknięcie zamknie dostęp tylko dla jednego z nich
Czas wykonywania potomka będzie na starcie wynosił 0
testfork()
{
ind idp;
printf(„Poczatek”);
idp=fork();
printf(„powrot %d”,idp);
}
wynik:
Poczatek
powrot 0
powrot 13412
22.5.
Inne funkcje systemowe
void exit(int stan)
zakończenie procesu
int wait(int *w) czekaj na zakończenie potomka
int chdir(char* sciezka)
zmień bieżący katalog
int pipe(int pdeskr[2]) tworzy łącze komunikacyjne /0 OK -1 błąd /
pdeskr[0]
do czytania
pdeskr[1]
do pisania
int write(int handle, void *buf, unsigned len);
pisz do łącza (pliku)
int read(int handle, void *buf, unsigned len);
czytaj z łacza (pliku)
125
main()
{
int fork(void), value;
value = fork();
value = fork(); .
printf("In main: value = %d\n", value);
}
In main: value = 1788 In main: value = 0 In main: value = 0 In main: value
= 1789
Kolejność wykonywania procesów - niedeterministyczna.
Komunikacja między procesami:
int pipe(int pd[2]);
Tworzenie bufora we/wy pomiędzy procesami równoległymi.
pd[0], pd[1]- opisy plików (input, output)
Wartością pipe jest 0 gdy połączenie jest utworzone lub -1 gdy nie.
/* równoległe sumowanie wierszy macierzy */
#include <stdio.h>
#include <stdlib.h>
#define N 3
int add_vector();
void error_exit();
int fork();
int pipe();
int read();
int write();
main()
{
int a[N][N],
i, row_sum, sum = 0,
pd[2];
a[0][1] = a[0][2] = a[0][0] = 1;
126
a[2][1] = a[2][2] = a[2][0] = 2;
a[1][1] = a[1][2] = a[1][0] = 3;
if (pipe(pd) == -1)
error_exit("pipe failed");
for (i = 0; i < N; ++i)
if (fork() == 0)
{
row_sum= add_vector(a[i]);
if(write(pd[1],&row_sum, sizeof(int))==-1)
error_exit("write failed");
printf(" ******* %d \n", row_sum);
return;
}
for (i = 0; i < N; ++i)
{
if (read(pd[0], &row_sum, sizeof(int)) ==-1)
error_exit("read failed");
sum += row_sum;
}
printf("Suma el tablicy = %d", sum);
}
int add_vector(v)
int v[];
{
int i, vector_sum = 0;
for (i = 0; i < N; ++i)
{ printf("v[%d]= %d\n", i,v[1]);
vector_sum += v[i];
}
return vector_sum;
}
void error_exit(s) char *s;
{
fprintf(stderr, "\n ERROR: %s \n", s);
exit(1);
}