Wstęp do programowania w Języku C


Wstęp do programowania w języku C/C++

Pojęcie algorytmu

Programowanie komputerów jest podstawową dziedziną informatyki i obejmuje:

• projekt programu - konstrukcja algotytmu,

• zapis programu w dowolnym języku programowania,

• testowanie programu.

Algorytmem nazywamy sposób postępowania (metodę) z danymi, który w skończonej liczbie kroków

(czynności) prowadzi do otrzymania oczekiwanych wyników.

Języki programowania

Mikroprocesor wykonując program interpretuje rozkazy zapisane w tzw. kodzie maszynowym, będącym ciągiem

bitów zero-jedynkowych. Ze względu na małą czytelność tak zapisanego programu stworzono szereg języków i

systemów programowania.

Język definiuje reguły (zasady) zgodnie z którymi powinien być zapisany program, aby możliwe było jego

wykonanie, system programowania natomiast służy do tłumaczenia programu na kod maszynowy.

Języki programowania ze względu na sposób złożoności dzieli się na:

• niskiego (asembler),

• wysokiego (Turbo Pascal, C/C++, Java, Fortran, Visual Basic, Algol)

poziomu. W języku niskiego poziomu większość instrukcji odpowiada pojedynczym rozkazom procesora,

dlatego też zapis programu w tym języku jest procesem bardzo pracochłonnym. Języki wysokiego poziomu

używają sformułowań stosowanych w życiu codziennym, zaś ich instrukcje zastępują kilka czy nawet

kilkanaście instrukcji języka niskiego poziomu.

Generacja kody wynikowego

System programowania do tłumaczenia programu na kod maszynowy używa osobnego programu zwanego

translatorem. Ze względu na sposób działania translatory dzieli się na dwie grupy:

• interpretery,

• kompilatory.

Interpreter po przetłumaczeniu jednej instrukcji programu przekazuje ją do wykonania mikroprocesorowi, po

czym tłumaczy kolejną instrukcję programu. Interpreter nie tworzy pliku wynikowego zawierającego kod

maszynowy programu (zbiory "exe" lub "com"). Oznacza to, że wykonanie programu jest możliwe tylko w

środowisku programowania przy pomocy interpretera.

Kompilator natomiast nie wykonuje programu lecz tłumaczy go na kod maszynowy w całości, w wyniku czego

powstaje zbiór wykonywalny przez procesor.

Zmienne w językach programowania

W języku programowania pod pojęciem danych występują takie wartości jak liczby naturalne, liczby całkowite,

liczby rzeczywiste, znaki, ciągi znaków, czyli napisy (łańcuchy) posiadające sens lub nie, itp. Z każdym

algorytmem (programem) związany jest pewien zbiór danych, który musi zostać odpowiednio przetworzony w

wyniku czego wykonanie algorytmu prowadzi do otrzymania wyników. Podczas przetwarzania danych

wykonuje się na nich różnego typu operacje jak np. modyfikacja danych czy obliczanie wyników pomocniczych.

Do przechowywania wartości danych używa się w języku programowania zmiennych, które są odpowiednikami

występujących w matematyce niewiadomych. Każda niewiadoma ma ściśle określony zbiór z którego może

przyjmować wartości, zwany zakresem zmienności zmiennej. Na podstawie tego właśnie zbioru mówimy o

zmiennej naturalnej, całkowitej, wymiernej czy rzeczywistej. Takie zbiory występują również w językach

programowania i nazywane są typami. W odróżnieniu od matematyki nie mówimy że zmienna X należy do typu

A, tylko fakt ten wypowiadamy krócej: X jest typu A.

Typy całkowite języka C/C++

Do przechowywania liczb całkowitych służą w języku C/C++ typy całkowite. W matematyce ze względu na

tradycję zbiory danych oznacza sie dużymi literami alfabetu, w informatyce nie ma takich ograniczeń i typy jako

zbiory posiadają swoje słowne nazwy.

Język C/C++ posługuje się trzema podstawowymi typami całkowitymi:

Nazwa

typu

Opis typu

char

typ reprezentujący wszystkie możliwe do uzyskania

znaki, zbiór 256 znaków

int typ służący do reprezentacji liczb całkowitych

long typ reprezentujący duże liczby całkowite

Każdą z wyżej wynienionych nazw typów można poprzedzić słowem kluczowym signed lub unsigned, co będzie

oznaczało odpowiednio zmienną całkowitą ze znakiem lub bez znaku. Otrzymamy wówczas sześć różnych

typów całkowitych o zakresach:

Nazwa

typu

Zakres

Ilość

bajtów

signed char -128..127 1

unsigned

char

0..255 1

signed int -32768..32767 (-216..216-1) 2

unsigned

char

0..65535 (0..216-1) 2

signed long

-2.147.483.648..2.147.483.647 (-

231..231-1)

4

unsigned

long

0..4.294.967.295 (0..232-1) 4

Liczby całkowite bez znaku (dodatnie) są zapisywane w tzw. naturalnym kodzie dwójkowym, w którym wartość

liczby obliczana jest jako suma kolejnych potęg liczby 2:

110101 = 1*25+1*24+0*23+1*22+0*21+1*20 = 32+16+4+1 = 53 .

W przypadku liczb ze znakiem najstarszy bit (pierwszy z zapisie) ma wagę ujemną, pozostałe natomiast

reprezentują liczbę zapisaną w naturalnym kodzie binarnym. Wartość powyższej liczby liczy się w następujący

sposób:

110101 = -1*25+1*24+0*23+1*22+0*21+1*20 = -32+16+4+1 = -11 .

Liczba ośmiobitowa 10000000 ze znakiem ma wartość -128, liczba 11111111 wartość -1. Taki sposób

reprezentacji liczb ujemnych nazywamy kodem uzupełnień U2.

Aby znaleźć zapis dwójkowy liczby ujemnej ze znakiem x na n bitach należy wyznaczyć zapis liczby 2n-1-1+x na

n-1 bitach, a następnie dopisać bit 1 na pierwszym miejscu jej rozwinięcia dwójkowego.

Przykład

Znaleźć zapis liczby -99 na ośmiu bitach. Odp: Obliczamy najpierw 28-1-1-99=127-99=28. Wyznaczamy zapis

liczby 28 na siedniu bitach: 0011100(2)=28 i dopisujemy bit 1: 10011100(2)=-99.

Typy rzeczywiste języka C/C++

W języku C/C++ istnieją trzy podstawowe typy rzeczywiste:

• float

• double

• long double

Zakresy poszczególny typów i ilość pamięci zajmowanej przez zmienne poszczególnych typów są następujące:

Nazwa

typu

Zakres

Ilość

bajtów

float 3.4*10-38..3.4*1038 4

double 1.7*10-308..1.7*10308 8

long double 3.4*10-4932..1.1*104932 10

Deklaracje zmiennych

Wszystkie używane w programie do przechowywania wartości zmienne muszą być zadeklarowane. Deklaracja

zmiennej jest informacją dla kompilatora, aby przydzielił (zarezerwował) odpowiednią ilość pamięci na

zmienne, które będą w trakcie wykonywania programu używane.

Język C/C++ jest bardziej elastyczny w tym względzie niż Pascal: zmienne programu można deklarować niemal

wszędzie.

Deklaracja zmiennych może mieć postać:

int x, y=1;

float pole, obwod, pi=3.14;

char znak='&', znak1=48, znak2='\60', znak3='\0x32';

Deklarując zmienne podajemy najpierw typ zmiennej, a następnie pooddzielane znakiem przecinka nazwy

zmiennych. Podczas deklaracji zmiennej można przypisać dowolną wartość początkowa - deklarację taką

nazywamy deklaracją z inicjacją zmiennej.

Stałe liczbowe rzeczywiste podaje się z kropką dziesiętną, natomiast stałe znakowe ujmuje się w pojedyncze

apostrofy.

Zmiennej typu char można przypisać wartości całkowitą. W takiej sytuacji kompilator przypisze zmiennej

znakowej znak o podanym w postaci liczby kodzie - czynność tą nazywa się automatyczną konwersją (zamianą z

jednego typu na inny).

Wyrażenie '\60' oznacza znak o kodzie ósemkowym 60 (6*8+0), zaś wyrażenie '\0x32' znak o kodzie

szesnastkowym 30 (3*16+2).

Funkcje w językach programowania

W języku programowania często mamy do czynienia z funkcjami wieloargumentowymi. Przykładem takiej

funkcji może być funkcja zwracająca iloczyn liczb i zdefiniowana następująco:

f(x, y) = x * y, dla dowolnych rzeczywistych wartości x i y.

Jest to przykład funkcji dwuargumentowej (dwa argumenty) o argumentach rzeczywistych.

W programach jednak bardzo często używa się funkcji o argumetach nierzeczywistych, czyli nie będących

liczbami, np.:

SumaZnakow(znak1, znak2) = znak1znak2

Jest to funkcja posiadająca dwa argumenty typu znakowego i o wartościach będących dwuliterowymi napisami

utworzonymi z podanych znaków. I tak np.: SumaZnakow('3', 'A')="3A" (napis po prawej stronie podano w

cudzysłowiu, zgodnie z zasadą obowiązującą w języku C/C++).

Funkcja może również posiadać różne typy argumentów - można sobie wyorazić funkcję F, której pierwszym

argumentem jest liczba całkowita n, drugim natomiast znak c, zaś wartością funkcji napis składający się z tylu

znaków c ile wynosi n. Mielibyśmy wówczas:

F(2, 'A')="AA"

F(5, '8')="88888"

F(0, '?')="" (napis pusty)

Ogólnie: F(n, c)="ccc...c" (n razy)

Definicja matematyczna funkcji zawsze wskazuje dwa zbiory: dziedzinę, czyli zbiór argumentów funkcji oraz

przeciwdziedzinę, zwaną też zbiorem wartości.

O dziedzinie mówimy nawet wówczas, gdy tak naprawdę nie jest ona potrzebna: funkcja stała f(x)=2 dla

każdego argumentu zawsze posiada wartość 2, tyle tylko, że w matematyce zawsze organiczamy zbiór na którym

można obliczyć tą wartość, czyli wskazujemy dziedzinę.

Języki programowania posuwają się nieco dalej i dopuszczają istnienie funkcji o niegraniczonej (nieokreślonej)

dziedzinie. O ile w matematyce o funkcji f(x)=2 wypada powiedzieć, że jej wartością jest liczba 2 dla każdego

argumentu będącego liczbą rzeczywistą, o tyle w języku C/C++ można powiedzieć, że wartością tej funkcji jest

liczba 2 dla każdego możliwego argumentu, czyli...

f(123)=2

f('K')=2

f("Borland C/C++")=2

f(monitor mojego komputera)=2

Funkcję taką (o nieokreślonej dziedzinie) nazywamy bezargumentową lub bezparametrową.

W matematyce każda funkcja f ma jednoznacznie określony zbiór wartości (przeciwdziedzinę) - wyrażenie f(x),

gdzie x jest elementem dziedziny jest wartością funkcji dla argumentu x, a tym samy elementem zbioru będącego

przeciwdziedziną.

Z taką samą sytuacją mamy doczynienia w języku C/C++. Wszystkie funkcje języka mają jednoznacznie

określony zbiór wartości (przeciwdziedzinę). O funkcji f, której zbiorem wartości jest zbiór Y mówimy, że f jest

typu Y, lub też, że f zwraca wartość typu Y.

Jezyk C/C++ w przypadku przeciwdziedziny posuwa się nieco dalej niż niż definicja matematyczna, a

mianowicie przeciwdziedzina może być zbiorem nieokreślonym, tak samo jak miało to miejsce w przypadku

dziedziny. Dla funkcji f o wartościach typu int wyrażenie f(x) jest wartością typu int, dla funkcji o wartościach

nieokreślonych, wyrażenie f(x) jest nieokreślone.

Funkcję taką nazywamy funkcją nieokreśloną (o nieokreślonym typie wyniku).

Podsumowując:

każda funkcja języka C/C++ określona jest podobnie jak w matematyce:

f : X --> Y

gdzie X i Y są dowolnymi typami danych, przy czym oba zbiory (typy) X i Y mogą być nieokreślone. Typ

nieokreślony języka C/C++ nosi nazwę void.

Definiując funkcję należy wyraźnie wskazać (określić) zbiór dozwolonych argumentów (dziedzinę) oraz typ

wyniku funkcji - jeżeli natomiast funkcja nie posiada argumentów lub nie zwraca żadnego wyniku, to każdy z

tych zbiorów określamy jako void.

Ogólna postać definicji funkcji jest następująca:

typ_wyniku nazwa_funkcji(lista argumentów)

{

instrukcja1;

instrukcja2;

:

instrunkcjak;

}

Linię pierwszą nazywamy nagłówkiem funkcji, zaś wszystkie pozostałe ciałem funkcji (blokiem). Blok składa się

z ciągu instrukcji języka ujętych w nawiasy klamrowe i pooddzielanych od siebie znakiem średnika.

Przykłady definicji funkcji:

long double suma_kwadratow(double a, double b)

{

return a*a+b*b;

}

float stala_pi(void)

{

return 3.14;

}

void czysc(void)

{

clrscr();

}

Pierwsza ze zdefiniowanych funkcji suma_kwadratow zwraca wartość (liczbę) typu long double będącą sumą

kwadratów dwóch liczb rzeczywistych typu double.

Funkcja stala_pi nie posiada arguentów (bezparametrowa), zwraca natomiast wartość typu float będącą

przybliżeniem liczby pi.

Funkcja czysc jest bezwartościowa (nie posiada wartości) i bezparametrowa (nie posiada argumentów) - jej

wykonanie polega na wykonaniu jednej umieszczonej w ciele funkcji o nazwie clrscr.

W pierwszych dwóch funkcjach występuje instrukcja return. Jej wykonanie, powoduje natychmiastowe

zakończenie wykonywania funkcji. Jeśli definiowana funkcja posiada określony typ wyniku, to po słowie return

należy podać wyrażenie, którego wartość stanie się wartością definiowanej funkcji. W przypadku takiej funkcji

konieczne jest wykonanie instrukcji return, w przeciwnym przypadku kompilator nie byłby w stanie określić

wartości funkcji, a tym samym przypisać jej wyniku.

Jeśli funkcja nie posiada określonego typu wyniki, to w wywołaniu return nie umieszczamy żadnej wartości -

nie można przypisać wartości funkcji, która zadeklarowana została jako nieokreślona. Można również w bloku

funkcji nie umieszczać instrukcji return - wówczas wykonywanie funkcji kończy się w momencie wykonania

wszystkich instrukcji zawartych w jej ciele.

Budowa programu

Funkcje są chyba najistotniejszym elementem języka C/C++. Każdy napisany w tym języku program składa się z

modułów (bibliotek, może być jeden), w których zdefiniowane zostały różne funkcje. Jeden z dołączonych do

programu modułów musi zawierać tzw. funkcję główną o nazwie main, od której rozpoczyna się wykonywanie

programu.

Najprostszy wykonywalny program języka ma postać:

void main(void)

{}

i zawiera tylko definicję (pustą) funkcji głównej. Niestyty nic jeszcze nie robi, ponieważ w bloku nie

umieszczono żadnych instrukcji przeznaczopnych do wykonania.

Operacje wejścia-wyjścia

Wprowadzanie danych do programu i wyprowadzanie wyników z programu realizuje się w języku C/C++ za

pomocą tzw. strumieni. Standardowo zdefiniowano trzy strumienie wejścia-wyjścia:

• stdin - standardowy strumień wejściowy

• stdout - standardowy strumień wyjściowy

• stderr - standardowy strumień błędów

Obsługą tych strumieni (wczytywaniem i wysyłaniem danych) zajmują się funkcje języka zdefiniowane w

module (bibliotece) o nazwie stdio.h.

Wyprowadzanie danych na standardowe wyjście programu (stdout, na ogół ekran monitora) wykonuje funkcja o

nazwie printf. W najprostszej postaci funkcję tą można wywołać podając jako jedyny argument łańcuch (napis):

#include<stdio.h>

void main(void)

{

printf("To jest mój pierwszy program...\nI nic jeszcze nie robi...");

}

Moduł do programu dołącza się za pomocę dyrektywy (polecenia) kompilatora include - deklarację dołączenia

modułu umieszczamy na ogół na początku pliku źródłowego programu.

Funkcja printf może mieć wiele argumentów, które ponadto mogą posiadać różne typy danych. Spośród

wszystkich argumentów najważniejszą rolę pełni argument pierwszy, zwany łańcuchem formatującym. Łańcuch

oprócz zwykłych wartości znakowych (liter i znaków) może zawierać znaki lub tzw. sekwencje formatujące - w

powyższym przykładzie łańcuch formatujący zawiera znak specjalny '\n', będący poleceniem nowego wiersza

(nowej linii). Mówiąc dość ogólnie: funkcja printf wypisze na standardowym wyjściu wszystkie znaki łańcucha

formatującego, łącznie ze znakiem '\n', którego wypisanie polega na przeniesieniu kursora tekstowego na

początek nowego wiersza ekranu.

A oto inny przykład łańcucha formatującego:

printf("Mam na imię %s, mam %d lat.\n", "Tomasz", 18);

Powyższe wywołanie funkcji zawiera aż trzy argumenty: łańcuch formatujący, łańcuch "Tomasz" oraz liczbę

całkowitą 18. W łańcuchu formatującym występują dwie sekwencje formatujące: %s oraz %d. Pierwsza z nich

jest dla kompilatora informacją, że w to miejsce należy podstawić napis podany jako drugi argument funkcji,

druga sekwencja %d informuje kompilator, że w miejsce jej wystąpienia należy wstawić liczbę całkowitą ze

znakiem będącą trzecim argumentem funkcji.

W efekcje wykonania powyższych czynności na ekranie monitora zostanie wypisany tekst: Mam na imię

Tomasz, mam 18 lat..

UWAGA:

Kolejność wszystkich argumentów funkcji oprócz pierwszego musi być dostosowana do sekwencji

formatujących zawartych z łańcuchu formatującym. Przestawienie w powyższym przykładzie imienia "Tomasz"

i liczby 18:

printf("Mam na imię %s, mam %d lat.\n", 18, "Tomasz");

spowoduje, że kompilator podejmie próbę odczytania liczby jako napisu i napisu jako liczby, co przyniesie

dosyć dziwne skutki i raczej na pewno nie zostanie wykonanie poprawnie (można spróbować).

W łańcuchu formatującym można używać następujących sekwencji formatujących:

• %d - liczba całkowita ze znakiem

• %i - liczba całkowita ze znakiem

• %o - liczba ósemkowa bez znaku

• %u - liczba całkowita bez znaku

• %x - liczba szesnastkowa bez znaku

• %X - liczba szesnastkowa bez znaku

• %f - liczba rzeczywista zapisana w postaci [-]ddd.ddd

• %e - liczba rzeczywista zapisana w postaci wykładniczej

• %c - znak

• %s - łańcuch (napis)

Pomiędzy znakiem % a literą wskazującą typ argumentu mogą wystąpić dwa przydatne specyfikatory

określające szerokość pola i ilość miejsc po przecinku:

printf("%10.3f", 1.4242135);

Liczba przed kropką wskazuje szerokość pola, czyli ilość komórek na ekranie, które zajmie wypisywana wartość

(jeśli jest krótsza na początku wypisane zostaną spacje), liczba po kropce określa dokładność, czyli ilość miejsc

po przecinku. W przypadku argumentów w stosunku do których nie można określić dokładności (np. znak,

napis, liczba całkowita) nie podaje się znaku kropki i liczby po niej występującej, można określić jedynie

szerokość pola.

Przed literą określającą typ argumentu można jeszcze umieścić tzw. modyfikator rozmiaru. W przypadku typów

całkowitych umieszczenie litery 'l' (np. %ld, %lu) spowoduje potraktowanie argumentu jako long (czyli

odpowiednio long i unsigned long), w przypadku argumentów rzeczywistych (np. %lf) jako double.

Liczbę typu long double należy wypisać używając sekwencji %Lf lub %Le.

Funkcja służąca do wprowadzania danych ze standardowego wejścia stdin nosi nazwę scanf i również

zdefiniowana jest w module stdio.h. Ogólna składnia funkcji nie odbiega od składni funkcji printf - pierwszy

argument przeznaczony jest na łańcuch formatujący, pozostałe, na wczytane przez funkcję wartości.

W łańcuchu fomatującym można używać następujących sekwencji określających typ wczytywanej wartości:

• %d - liczba całkowita (dziesiętna) ze znakiem

• %o - liczba ósemkowa bez znaku

• %x - liczba szesnastkowa bez znaku

• %i - liczba dziesiętna, ósemkowalub szesnastkowa

• %f - liczba rzeczywista typu float

• %e - liczba rzeczywista typu float

Podobnie jak w przypadku funkcji printf znak oreślający typ można poprzedzić modyfikatorem rozmiaru 'l' lub

'L' (np. %lu, %Le).

W odróżnieniu od funkcji printf funkcja scanf wymaga oprócz łańcucha formatującego argumentów w postaci

adresów zmiennych (tzw. wskaźników). Na obecnym etapie będzie dosyć trudne zrozumienie istoty wskaźnika,

dlatego też należy przyjąć poniższy sposób podawania argumentów funkcji jako konieczność:

printf("Podaj wartość zmiennej rzeczywistej x=");

//odczytaj wartość typu long double

scanf("%Lf", &x);

printf("Podaj nieujemną wartość całkowitą k=");

//odczytaj wartość typu unsigned int

scanf("%u", &k);

WAŻNE

Obie funkcje wejścia-wyjścia posiadają więcej opcji stwarzających programiście dodatkowe możliwości -

początkującym programistom zaleca się jednak wykorzystywanie tylko tych podstawowych, w jak najprostszej

możliwej postaci, czyli tak, jak pokazuje to powyższy przykład.

Linie programu poprzedzone parą znaków '/' oznaczają komentarz i podczas wykonywania programy nie są

brane pod uwagę. Dłuższe komentarze (kilkuwierszowe) można umieścić w programie ujmując tekst w

wyrażenia '/*' oraz '*/':

/*

komentarz

komentarz

komentarz

*/

Pierwszy program w języku C/C++

Może to wydać nawet dziwne, ale znając zaledwie dwie instrukcje języka można napisać niebagatelny program,

przynajmniej na tyle, że napisanie tego samego w języku Pascal wymagałoby znajomości wielu instrukcji z

pętlami włącznie.

Zadanie

Napisać program, który wczyta nieujemną liczbę całkowitą, wypisze jej wartość ósemkową i szesnastkową,

następnie wczyta liczbe szesnastkową i wypisze jej wartość dziesiętną.

#include<conio.h>

void main(void)

{

unsigned long x;

clrscr();

printf("Podaj nieujemną liczbę całkowitą x=");

scanf("%lu", &x);

printf("Dziesiętnie : %lu\n", x);

printf("Ósemkowo : %lo\n", x);

printf("Szesnastkowo : %lx\n", x);

printf("\nPodaj liczbę szesnastkową x=");

scanf("%lx", &x);

printf("Dziesiętnie : %lu", x);

printf("\nNaciśnij jakiś klawisz...");

getch();

}

Operatory języka C/C++

Właściwości operatorów

Operatory (symbole działań) języka C/C++ tworzą znaczące dla programisty narzędzie umożliwiające

wykonywania czynności, które w innych językach programowania wymagałyby definiowania funkcji czy

wykonywania tych samych czynności w bardzie pracochłonny sposób.

Cechą charakterystyczną każdego operatora jest jego arność, czyli ilość argumentów. W matematyce

korzystamy na ogół z operatorów dwuargumentowych (binarnych), takich jak +, -, *, /, rzadziej z operatorów

jednoargumentowych (unarnych), którego przykładem może być operator '!' przy pomocy którego zapisujemy

działanie silnia (n!=1*2*...*n).

W języku C/C++ oprócz operatorów unarnych i binarnych występuje również operator trzyargumentowy, zwany

operatorem wyboru.

Każdemu operatorowi można przypisać liczbę naturalną zwaną priorytetem - wartość tej liczby odwzorowuje

matematyczną kolejność wykonywania działań - operatory o priorytecie wyższym oznaczają działania

wykonywane wcześniej. Wyrażenie a-b*c zostanie zrozumiane przez kompilator jako a-(b*c) ponieważ operator

'*' ma wyższy priorytet niż operator '-'.

Wyobraźmy sobie funkcję f zdefiniowaną następująco:

| 0, gdy x<>0,

f(x) = |

| 1, gdy x=0.

Język C/C++ zawiera jednoargumentowy operator negacji '!' będący odpowiednikiem tak zdefiniowanej funkcji.

W odróżnieniu od matematyki zapis działania z tym operatorem zapisujemy w odwrotny sposób, czyli !n, a nie

jak w matematyce n!.

Tą cechę operatora (cechę wskazującą na położenie argumentów) nazywamy wiązaniem. Powiemy, że w

matematyce operator silnia ma wiązanie lewostronne, odnosi się do wartości występującej po jego lewej stronie,

w języku C/C++ operator negacji ma wiązanie prawostronne, związany jest z wartością po stronie prawej.

Uwaga:

W poniższym tekście opisano jedynie wybrane operatory języka, których znajomość jest niezbędna do

zrozumienia dalszej części dokumentu.

Operator przypisania

Jednym z najniższych priorytetów (2) charakteryzuje się operator przypisania '=', służący do nadawania wartości

zmiennym. W języku Pascal czynność tą wykonuje się za pomocą instrukcji zwanej instrukcją przypisania -

język C/C++ wykonuję tą czynność przy pomocy operatora.

Niski priorytet tego operatora gwarantuje m. in. poprawność wykonania instrukcji:

x=y+1

a=b*!c

W obu przykładach najpierw zostaną wykonane działania po prawej stronie operatora, a następnie obliczona

wartość zostanie przypisana podanej po lewej stronie zmiennej - dzieje się tak dlatego, że operatory występujące

po prawej stronie znaku '=' mają wyższy priorytet niż operator '='.

Operatory arytmetyczne

Do podstawowych operatorów arytmetycznych należą operatory addytywne dodawania i odejmowania ('+' i '-') o

priorytecie 12.

Tymi samymi znakami określa się również operatory unarne wskazujące znak zmiennej (+x, -x), w tym jednak

wypadku priorytet obu operatorów jest bardzo wysoki i wynosi 15. W wyrażeniu:

x=+y+-z

pierwszy znak '+' oznacza operator unarny (operator znaku) i ma priorytet 15, drugi znak '+' zostanie

zinterpretowany jako operator binarny oznaczający dodawanie o priorytecie 12, zaś znak '-' jako operator znaku

o priorytecie 15. Ostatecznie całe wyrażenie będzie odczytane jako x=(+y)+(-z).

Drugą grupę operatorów arytmetycznych stanowią operatory mutiplikatywne mnożenia, dzielenia i reszty z

dzielenia ('*', '/' i '%'). Operatory multiplikatywne posiadają priorytet 13, czyli wyższy niż addytywne, niższy

jednak niż operator negacji.

Uwaga

W języku C/C++ nie mam operatora dzielenia całkowitego, ponieważ każde dzielenie, którego argumentami są

wyrażenia całkowite posiada wynik całkowity, czyli wykonywane jest dzielenie z resztą, a nie dzielenie zwykłe.

Na przykład po deklaracjach:

signed int a=12;

float x=a/5;

----------------

signed int a=12;

float x=a/5.0;

zmienna x otrzyma w pierwszym przypadku wartość 2, w przypadku drugim wartość 2.4 (dzielnik zapisany z

kropką traktowany jest jako rzeczywisty).

Trzecia grupa operatorów arytmetycznych to operatory unarne inkrementacji i dekrementacji ('++' i '--') o

priorytecie 15, wykonujące działanie polegające na powiększeniu lub pomniejszeniu wartości zmiennej o 1.

Każdy z tych operatorów może wystąpić w wyrażeniu jako przedrostkowy lub przyrostowy, tzn. (++x, --x, x++,

x--). Operator przedrostkowy posiada wiązanie prawostronne, przyrostkowy lewostronne.

Ale nie jest to jedyna różnica związana w miejscem wystąpienia operatora. Kluczowe znaczenie ma to wówczas,

gdy operatory występują w wyrażeniu, np.

int m=5, n=2;

int x, y;

x=(--m)+(n++);

y=m*n;

Ufff... Operator przedrostkowy wykonywany jest przed obliczeniem wartości wyrażenia - w pierwszej kolejności

zostanie wykonane działanie --m, które spowoduje pomniejszenie wartości zmiennej m o jeden z 5 na 4.

Następnie nastąpi obliczenie wartości (4+2) i przypisanie jej zmiennej x. Po wykonaniu przypisania zostanie

wykonane działanie n++, które powiększy zmienną n o jeden z 2 na 3. W wyniku wykonania kolejnej instrukcji

zmienna y otrzyma wartość 12 (4*3).

Podsumowując: działanie z użyciem operatora przedrostkowego wykonywane jest przed obliczeniem wartości

wyrażenia, działanie z użyciem operatora przyrostkowego po obliczeniu wartości całego wyrażenia.

Początkującym użytkownikom odradza się budowanie skomplikowanych wyrażeń, co może prowadzić do

trudnych do wykrycia błędów.

Operatory bitowe

Operacje bitowe na liczbach całkowitych wykonuje się za pomocą operatorów:

& - koniunkcja, "i", priorytet 8,

| - alternatywa, "lub", priorytet 6,

^ - różnica symetryczna, "różne", priorytet 7,

~ - negacja, "nie", priorytet 15.

Operator ~ wykonuje negację wszystkich bitów, bity o wartości 0 są zamieniane na 1 i odwrotnie, np. ~128 jest

równe 127, bo 128=10000000, zaś 127=01111111.

Operator | ustawia bit wyniku na 1 jeśli przynajmniej jeden z odpowiadających bitów w argumentach ma

wartość 1, np. 13 | 129 jest równe 10001101=141, bo 13=00001101 oraz 129=10000001.

Operator & ustawia bit wyniku na 1 jeśli oba z odpowiadających bitów w argumentach są równe 1, np. 128 &

127 jest równe 0, a 13 & 129 jest równe 00000001=1.

Operator ^ ustawia bit wyniku na 1 jeśli odpowiadające bity argumentów są sobie różne, np. 128 ^ 127 jest

równe 255, 73 ^ 73 jest równe 0, 13 ^ 129 jest równe 10001100=140.

Operatory przesunięcia bitowego

Operacje przesunięć bitowych wykonujemy za pomocą operatorów:

<< - przesunięcie bitowe w lewo, priorytet 11,

>> - przesunięcie bitowe w prawo, priorytet 11.

Operatory przesunięcia bitowego posiadają dwa argumenty, z których lewy jest wyrażeniem całkowitym, prawy

natomiast określa ilość miejsc, o którą należy wykonać przesunięcie.

W przypadku liczb bez znaku wolne miejsca w obu przypadkach wypełniane są zerami. W przypadku liczb ze

znakiem wolne miejsca wypełniane są zerami przy przesunięciu w lewo. Podczas przesuwania w prawo wolne

miejsca wypełniane będą takim bitem jaki aktualnie występuje w bicie znaku (najstarszy bit). Przykładowo:

39<<2 jest równe 156, bo 39=00100111 a 156=10011100, 128>>6 jest równe 2.

Niech dana będzie zmienna x typu unsigned int o wartości 11447. Zmienna ta zajmuje 2 bajty pamięci, które

nazywamy odpowiednio starszym i młodszym bajtem. Dwójkowy zapis tej zmiennej ma następującą postać

00101100 10110111. Bajt starszy ma wartość 00101100 czyli 32+8+4=44, bajt młodszy 10110111 wartość

128+32+16+4+2+1=183. Nie jest przypadkiem, że wyrażenie 256*44+183 jest równe 11447. Mając daną

zmienną n typu unsigned int wartość jej starszego bajtu można otrzymać jako wynik dzielenia całkowitego x /

256, wartość bajtu młodszego jest natomiast resztą z tego dzielenia, czyli x % 256.

Tą samą regułę można zastosować w drugą stronę a mianowicie, jeżeli zmienna a zawiera wartość starszego

bajtu pewnej liczby x typu unsigned int, zmienna b wartość młodszego bajtu tej liczby, to wyrażenie 256*a+b

jest równe liczbie x.

Często zachodzi potrzeba odwołania się (sprawdzenia lub zmiany wartości) do poszczególnych bitów zmiennej.

Niech dana będzie zmienna x typu unsigned int. Ostatni najmłodszy bit tej zmiennej można otrzymać wykonując

operację bitową z liczbą 1:

x = aaaaaaaa aaaaaaab

1 = 00000000 00000001

---------------------------

x & 1 = 00000000 0000000b

Pierwsze piętnaście bitów wyniku bedzie posiadać wartość 0, ponieważ bity liczby 1 są równe zero. Natomiast

bit ostatni wyniku otrzyma taką wartość, jaką posiada bit ostatni zmiennej x. Łącznie w wyniku otrzymamy

liczbą zero lub jeden, czyli liczbowo będzie to wartość równa ostatniemu bitowi zmiennej x.

Podobnie należy postąpić chcąc otrzymać np. 5 bit (od końca) zmiennej:

x = aaaaaaaa aaabaaaa

1<<4 = 00000000 00010000

------------------------------------

x & (1<<4) = 00000000 000b0000

------------------------------------

(x & (1<<4))>>4 = 00000000 0000000b

Po wykonaniu operacji bitowej & otrzymamy liczbę zawierającą co najwyżej jeden bit o wartości 1: liczbowo

będzie to wartość równa 0 lub 24, w zależności od wartości bitu b. Wykonując następnie przesunięcie bitowe o 4

w prawo otrzymamy podobnie jak poprzednio liczbę zero lub jeden,czyli wartość piątego od końca bitu.

Zauważyć należy, że można zmienić kolejność operacji wykonując najpierw przesunięcie bitowe o 4, a następnie

operację & z liczbą 1.

Ogólnie: n-ty od końca bit zmiennej x można otrzymać za pomocą wyrażenia:

x >> (n-1) & 1

Zadanie

Dana jest zmienna całkowita x typu unsigned int oraz liczba całkowita n z zakresu 1..16 będąca numerem bitu

zmiennej x (od końca).

1. Jakie operację należy wykonać na zmiennej x, aby jej n-ty bit otrzymał wartość 1.

2. Jakie operację należy wykonać na zmiennej x, aby jej n-ty bit otrzymał wartość 0.

Operatory relacyjne (porównań)

Do porównywania wartości liczbowych i znakowych stosuje się operatory:

< - mniejsze, priorytet 10,

<= - mniejsze lub równe, priorytet 10,

> - większe, priorytet 10,

>= - większe lub równe, priorytet 10,

== - równe, priorytet 9,

!= - różne, priorytet 9.

Ważne:

Każde z możliwych porównań, np. a<=10 jest wyrażeniem posiadającym wartość - jeżeli porównanie jest

prawdziwe, to wartością wyrażenia jest liczba 1, jeśli fałszywe wyrażenie ma wartość 0.

Operatory logiczne

Język programowania Pascal zawiera typ logiczny Boolean zawierający dwie wartości True i False, na których

można wykonywać operacje logiczne. W języku C/C++ operacje logiczne wykonywane są na wartościach

liczbowych, przy czym każdą wartość różną od zera uważa się za prawdziwą - wartość zero jest odpowiednikiem

fałszu.

Operacje logiczne wykonuje się za pomocą operatorów:

|| - alternatywa,, priorytet 4,

&& - koniunkcja, priorytet 5,

! - negacja, priorytet 15.

Operator wyboru (warunkowy)

Operator warunkowy posiada następującą składnię:

wyrażenie ? wartość gdy prawda : wartość gdy fałsz

Pierwszy argument może być dowolnym wyrażeniem numerycznym lub wyrażeniem, które można przekształcić

do postaci numerycznej, np. nierównością lub porównaniem.

Wykonanie działania operatora polega na obliczeniu wartości podanego wyrażenia i porównaniu obliczonej

wartości z liczbą zero:

• jeśli obliczona wartość jest różna od zera, to nastąpi obliczenie wyrażenia wartość gdy prawda i

zwrócenie obliczonej wartości jako wartość całego wyrażenia,

• jeśli obliczona wartość jest równa zero, to nastąpi obliczenie wyrażenia wartość gdy fałsz i zwrócenie

obliczonej wartości jako wartość całego wyrażenia.

Przykłady:

x = y>=0 ? 1 : 0;

a = x<10 ? x+1 : x++;

printf("%s", x==0 ? "Równe zero" : "Różne od zera");

Złożone operatory przypisania

W języku C/C++ podobnie jak w języku Pascal poprawne są instrukcje postaci x:=x+2 (dla przypomnienia:

instrukcja ta powoduje powiększenie aktualnej wartości zmiennej x o dwa). Efekt uzyskujemy korzystając z

operatora +=:

x += 2; //powiększ x o 2

x -= 3; //pomniejsz x o 3

x *= 4; //pomnóż x przez 4

x /= 5; //podziel x przez 5

x %= 6; //przypisz zmiennej x resztę z dzielenia x przez 6

itd.

Dozwolone są również operacje wykonywane przy użyciu operatorów &=, |=, ^=, <<=, >>=.

Wszystkie operatory przypisania mają niski priorytet 2.

Ważne:

W języku C/C++ operator przypisania, tym samym wyrażenie x=... posiada swoja wartość. Wykonanie działania

przypisania polega na obliczeniu wyrażenia po prawej stronie operatora i przypisaniu obliczonej wartości

zmiennej występującej po lewej stronie operatora. Wartością całego wyrażenia jest wartość przypisana zmiennej.

Konsekwencją tej właściwości jest poprawność instrukcji:

x=y=1; //obie zmienne otrzymają wartość 1

a=1+(b=2); //zmienna b otrzyma wartość 2, zmienna a wartość 3

x=y ? x++ : y++; //przypisz zmiennej x wartość zmiennej y, jeśli będzie to

wartość

//różna od zera, to powiększ zmienną x o jeden, jeśli

będzie to

//wartość zero, to powiększ zmienną y

Przykład 1

Napisz program, który wczyta liczbę całkowitą typu unsigned int a następnie wyznaczy dwójkowy zapis tej

liczby.

#include<stdio.h>

void main(void)

{

unsigned int x;

printf("Podaj liczbę całkowitą = ");

scanf("%u", &x);

printf("Dwójkowo = ");

printf("%u", x & (1<<15) ? 1 : 0);

printf("%u", x & (1<<14) ? 1 : 0);

printf("%u", x & (1<<13) ? 1 : 0);

printf("%u", x & (1<<12) ? 1 : 0);

printf("%u", x & (1<<11) ? 1 : 0);

printf("%u", x & (1<<10) ? 1 : 0);

printf("%u", x & (1<<9) ? 1 : 0);

printf("%u", x & (1<<8) ? 1 : 0);

printf("%u", x & (1<<7) ? 1 : 0);

printf("%u", x & (1<<6) ? 1 : 0);

printf("%u", x & (1<<5) ? 1 : 0);

printf("%u", x & (1<<4) ? 1 : 0);

printf("%u", x & (1<<3) ? 1 : 0);

printf("%u", x & (1<<2) ? 1 : 0);

printf("%u", x & (1<<1) ? 1 : 0);

printf("%u", x & (1<<0) ? 1 : 0);

printf("\n");

}

Konwersje typów

Konwersje (czyli zamiany) typu wyrażenia na inny bardz często wykonywane są w sposób niejawny

(automatyczny). Najprostszym przykładem takich konwersji są poniższe deklaracje:

unsigned char znak1 = 64;

unsigned char znak2 = 300;

signed int x = 3.14;

signed int y = znak1+i;

Inicjacja zmiennej znak1 będzie wymagać konwersji z typu int do typu unsigned char - liczba 64 zamieniona

zostanie na znak o kodzie 64.

Inicjacja zmiennej znak2 również wiąże się z wykonaniem tej samej konwersji, wcześniej jednak liczba 300

zostanie zmieniejszona do pewnej wartości z zakresu 0..255 - zmniejszenie to wykonywane jest poprzez

odrzucenie starszego bajtu tej liczby - w wyniku otrzymana zostanie wartość 44, którą to następnie kompilator

zamieni na znak o kodzie 44.

Trzecia deklaracja wymaga wykonania konwersji z typu float do typu signed int - konwersje tego typu, tj. z

typów rzeczywistych do całkowitych wykonuje się poprzez odrzucenie części ułamkowej - zmienna x otrzyma

wartość 3.

Ostatnia deklaracja wymaga zamiany znaku znak1 na wartość liczbową typu unsigned int, zmiany typu zmiennej

x z signed int na unsigned int, wykonania działania w wyniczego czego otrzymana zostanie wartość typu

unsigned int, a następnie konwersji otrzymanego wyniku do typu signed int.

Ostatni przykład pokazuje jak bardzo jest to skomplikowane zagadnienie. Początkujący programista nie musi

znać wszystkich zasad związanych z konwersją danych, aczkolwiek te, które są niezbędne przytaczam poniżej:

• każde wykonywane działanie wykonuje się zawsze na argumentach tego samego typu, przyprowadzane

z związku z tym automatyczne konwersje wykonuje się zawsze do typów większych,

• konwersja z typu rzeczywistego do całkowitego polega na obcięciu części ułamkowej,

• konwersje pomiędzy różnymi typami rzeczywistymi wykonywane są poprzez zaokrąglenie do

najbliższej dozwolonej wartości typu wynikowego (np. z long double do double),

• wartości typów signed char i unsigned char przekształcane są do typów odpowiednio signed int i

unsigned int,

• jeśli wyrażenie zawiera tylko argumenty całkowite, to wszystkie one zostaną przekształcone do typu

argumentu o największym rozmiarze (nie do końca jest to prawdą, ale można tak przyjąć),

• jeśli jednym argumentem wyrażenia jest liczba całkowita bez znaku, zaś drugim liczba całkowita ze

znakiem o takim samym rozmiarze (np. unsigned long i signed long), to agrument drugi zostanie

przekstałcony do typu bez znaku.

Operator konwesji

I pozostał do wyjaśnienia jeszcze jeden problem, a mianowicie dzielenie liczb całkowitych o wyniku będącym

liczbą rzeczywistą.

unsigned long a, b;

doule x;

scanf("%lu", &a);

scanf("%lu", &b);

x = a / b;

Abu zmienna x otrzymała rzeczywisty wynik dzielenia należy wykonać jawna konwersję przynajmniej jednego z

argumentów a i b do typu rzeczywistego double. Czynność tą wykonuje się za pomocą operatora konwersji (typ

wynikowy)wyrażenie o bardzo wysokim priorytecie 15.

Instrukcja przypisania w powyższym przykładzie powinna mieć postać:

x = (double)a / b;

Instrukcja warunkowa

Instrukcja warunkowa pozwala na wykonanie lub zaniechanie wykonania pewnych czynności, w zależności od

konkretnego warunku logicznego. Instrukcja ma następującą składnię:

if(wyrażenie) instrukcja;

Wykonanie instrukcji warunkowej polega na wyliczeniu wartości wyrażenia i porównaniu tej wartości z zerem.

Jeżeli wartość ta jest różna od zera, to wykonana zostanie podana instrukcja, np.

if(x<0) printf("Ujemna wartość");

if(x==0) printf("Liczba zero");

if(x>0) printf("Dodatnia wartość");

Z trzech funkcji printf wykonana zostanie tylko jedna, w zależności od wartości zmiennej x.

printf("Podaj liczbę rzeczywistą różną od zera = ");

scanf("%lf", &x);

if(x==0) return;

Powyższy fragment kodu wczyta do zmiennej x liczbę typu double a następnie jeśli podana liczba okaże się

zerem zostanie wykonana instrukcja return, która zakończy wykonywanie funkcji głównej programu main.

Rozważmy dwie instrukcje:

if( wyrażenie) instrukcja1;

if(!wyrażenie) instrukcja2;

Z dwóch powyższych instrukcji wykonana zostanie tylko jedna: jeśli wyrażenie ma wartość różną od zera

program wykona instrukcję1, jeśli wyrażenie ma wartość równą zero, czyli !wyrażenie ma wówczas wartość

równą 1, program wykona instrukcję2.

Parę dwóch instrukcji warunkowych w powyższej postaci wykonuje się przy pomocy rozbudowanej formy

instukcji warunkowej:

if(warunek) instrukcja1;

else instrukcja2;

Słowo else oznacza "w przeciwnym wypadku". Jeżeli podane wyrażenie ma wartość różną od zera program

wykona instrukcję1, w przeciwny wypadku, tj. gdy wyrażenie ma wartość zero, program wykona instrukcję2.

Uwaga:

Po instrukcji występującej po słowie if wymagany jest zawsze średnik - odmiennie niż ma to miejsce w języku

Pascal.

Podana po słowach if lub else instrukcja może być pojedyńczą instrukcją lub blokiem instrukcji ujetym w

nawiasy klamrowe, np.

if(x!=0 && y>0)

{

z=y*(1/x);

t=++z-1;

}

else z=t=0;

Jeśli wyrażenie ma wartość różną od zera wykonany zostanie blok, w przeciwnym wypadku jedna instrukcja po

słowie else.

Ważne:

Po nawiasie zamykającym blok po słowie if nie umieszcza się średnika - może on natomiast wystąpić po bloku

występującym po słowie else, aczkolwiek nie jest wówczas konieczny.

Przykład

Dane są liczby rzeczywiste a, b i c będące współczynnikami trójmianu kwadratowego y=ax2+bx+c. Napisz

program, który wyznaczy pierwiastki tego trójmianu.

#include<stdio.h>

#include<math.h>

void main(void)

{

long double a, b, c;

long double delta, x1, x2;

//Wczytaj pierwszy współczynnik a i jeśli jest równy zero zakończ program

printf("\n\nPodaj współczynnik a trójmianu = ");

scanf("%Lf", &a);

if(a==0)

{

printf("Współczynnik a nie może być równy zero!");

return;

}

//Wczytaj pozostałe współczynniki b i c

printf("Podaj współczynnik b trójmianu = ");

scanf("%Lf", &b);

printf("Podaj współczynnik c trójmianu = ");

scanf("%Lf", &c);

//Oblicz wyróżnik trójmianu

delta=b*b-4*a*c;

//Jeśli dodatni oblicz dwa pierwiastki i wypisz je

if(delta>0)

{

x1=(-b-sqrt(delta))/(2*a);

x2=(-b+sqrt(delta))/(2*a);

printf("Trójmian ma dwa pierwiastki rzeczywiste x1 i x2 :\n");

printf(" x1 = %.3Lf\n x2 = %.3Lf\n", x1, x2);

}

else //W przeciwnym wypadku sprawdź czy jest równy zero

if(delta==0)

{

//Oblicz pierwiastek podwójny i wypisz go

x1=-b/(2*a);

printf("Trójmian ma jeden pierwiastek rzeczywisty podwójny x0 :\n");

printf(" x0 = %.3Lf\n", x1);

}

else //W przeciwnym wypadku delta jest ujemna

printf("Trójmian nie posiada pierwiastków rzeczywistych.");

}

Po obliczeniu wyróżnika delta program zawiera już tylko jedną instrukcję warunkową if w postaci

rozbudowanej: instrucją po słowie if jest blok, natomiast po słowie else instrukcją jest kolejna instrukcja

warunkowa if, również w postaci rozbudowanej.

Przy obliczaniu wartości pierwiastów x1 i x2 wywoływana jest standardowa funkcja sqrt zdefiniowana w

module math.h, której wartością jest pierwiastek kwadratowy.

Instrukcja iteracyjna for

Instrukcje iteracyjne (zwane pętlami) służą do wielokrotnego wykonywania podobnych do czynności. Pętle

języków programowania dzieli się dzieli na:

• pętle o ustalonej liczbie powtórzeń (iteracji),

• pętle o nieustalonej liczbie powtórzeń (iteracji).

Pętla for jest przykładem pętli o ustalonej liczbie powtórzeń i posiada następującą składnię:

for(instrukcja_początkowa; wyrażenie1; wyrażenie2) instrukcja;

na przykład:

for(x=0; x<100; x++) printf("\n%10lu", x*x);

Powyższa instrukcja spowoduje wypisanie na ekranie kwadratów wszystkich liczb naturalnych z zakresu 0..999,

każdej w osobnej linii na dziesieciu komórkach ekranu.

Pierwsza instrukcja o nazwie instrukcja_początkowa wykonywana jest zawsze przed rozpoczęciem

wykonywania pętli(w powyższym przykładzie zmienna x otrzyma wartość 10. Wykonanie pętli składa sie trzech

kroków powtarzanych sekwencyjnie:

1. zostanie obliczone wyrażenie1 zwane wyrażeniem warunkowym i porównane z liczbą zero. Jeśli

wartość tego wyrażenia będzie równa zero, wykonanie instrukcji kończy się, w przeciwnym wypadku

następuje przejście do pkt. 2,

2. Zostanie wykonana podana instrukcja i nastąpi przejście do pkt. 3,

3. Zostanie obliczone wyrażenie2 zwane wyrażeniem zwiększającym i nastąpi przejście do pkt. 1.

Z powyższego schematu wynika, że zakończenie pętli może nastąpić tylko w pkt. 1, przy porównywaniu

wyrażenia1 z liczbą zero: pętla kończy się jeśli wyrażenie to ma wartość zero, inaczej mówiąc, jeśli osiągnie

wartość fałszu.

W przytoczonym przykładzie każde wykonanie wyrażenia zwiększającego powoduje zwiększenie wartości

zmiennej x o jeden. Pętla będzie kontynuowana do momentu, aż zmienna x osiągnie wartość 100 - wówczas

porównanie x<100 będzie fałszywe i nastąpi zakończenie pętli.

Przykład

Dane są: liczba rzeczywista p oraz nieujemna liczba całkowita n. Napisz program, który wyznaczy potęgę pn.

#include<stdio.h>

long double potega(long double podstawa, unsigned int wykladnik)

{

long double wynik=1;

unsigned int i;

for(i=1; i<=wykladnik; i++) wynik*=podstawa;

return wynik;

}

void main(void)

{

long double p;

unsigned int n;

printf("Podaj podstawę potęgi p = ");

scanf("%Lf", &p);

printf("Podaj nieujemny wykładnik potęgi n = ");

scanf("%u", &n);

printf("\nWyznaczona potęga: p^n = %Lf\n", potega(p, n));

}

W powyższym programie zdefiniowana została funkcja o nazwie potega, dwóch argumentach typów

odpowiednio long double i unsigned int oraz wartościach typu long double.

Zwrócona przez tą funkcję wartość nie wynika rzecz jasna z jej nazwy, tylko z umieszczonego w ciele funkcji

algorytmu.

Jeśli potęgę 58 zapiszemy w postaci iloczynu 5*5*5*5*5*5*5*5, to dojdziemy do wniosku, że jej obliczenie

wymaga ośmiokrotnego pomnożenia liczby 1 przez 5 (kolejno: 1*5, 5*5, 25*5, 125*5, 625*5, 3125*5, 15625*5

i 78125*5 = 390625, każda z wymienionych liczb jest kolejną potęgą liczby 5). Ten sam algorytm (metodę)

można zastosować dla innej podstawy niż 5 i innego wykładnika niż 8. Metoda zatem jest uniwersalna i będzie

skuteczna dla wszystkich możliwych wartości zarówno podstawy jak i wykładnika - z jednym wyjątkiem rzecz

jasna, a mianowicie wykładnika równego zero.

Funkcja potega wyznacza potęgę w taki właśnie sposób, mnożąc wynik (początkowo równy jeden) przez

podstawę potęgi podstawa tyle razy, ile wynosi wykladnik: po pierwszym wykonaniu pętli zmienna wynik

otrzyma wartość 1*podstawa, po drugim wykonaniu wartość 1*podstawa*podstawa=podstawa2, po trzecim

wartość 1*podstawa*podstawa*podstawa=podstawa3, itd.

Po zakończeniu pętli zmienna wynik będzie równa podstawawykladnik.

A wyjątek...

Wystarczy zwrócić uwagę na zainicjowanie zmiennej wynik liczbą 1 oraz warunek pętli i<wykladnik: jeśli

wykładnik będzie równy zero, to pętla zakończy się bez żadnego przebiegu (wykonania podanej instrukcji) i

funkcja jako wartość zwróci wynik, czyli liczbę 1.

W ciele funkcji głównej main wczytywane są jedynie wartości p i n, wypisanie wyniku funkcją printf następuje

poprzez wywołanie zdefiniowanej funkcji potega, która zwróci oczekiwaną potegę pn.

Jeżeli przeanalizujemy działanie funkcji potega dla argumentow p=0 i n=0 to dojdziemy do wniosku, że wynik

również będzie jeden, co nie do końca jest zgodne z prawami matematyki.

Warto również zwrócić uwagę na typ wykładnika unsigned int. Jeżeli po uruchomieniu tego programu jako

wykładnik wpiszemy liczbę ujemną, np. -12, to funkcja scanf odczyta ją jako dodatnią dzięki automatycznej

konwersji z typu signed int do typu unsigned int (będzie to liczba 65524). Być może programista chce w takim

przypadku zwrócić użytkownikowi uwagę, na błędnie wprowadzoną wartość wykładnika - należy wówczas

wczytać wykładnik do zmiennej signed int a następnie sprawdzić istrukcją warunkową if, czy nie jest ujemny.



Wyszukiwarka

Podobne podstrony:
Wstep do programowania w jezyku C wstpcp
Wstęp do programowania w języku C 2
Wstep do programowania w jezyku C 2
Wstep do programowania w jezyku C wstpcp
Wstep do programowania w jezyku C wstpch
Wstep do programowania w jezyku C wstpch
Wstep do programowania w jezyku C wstpcp
Wstep do programowania w jezyku C#
ebook Adam Boduch Wstęp do programowania w języku C# (wstpch) helion onepress free ebook darmowy e
Wstep do programowania w jezyku C wstpcp
Wstęp do programowania w języku C
Wstep do programowania w jezyku C
Projektowanie oprogramowania Wstep do programowania i techniki komputerowej
2011-2012 wstęp do P program, wstęp do psychologii k
WSTĘP DO NAUKI O JĘZYKU egzamin materiał
zarz procesami planowanie, stud, I semsetr, WSTEP DO PROGRAMOWANIA, WDI
Gorazd T Kurs C Wstęp do Programowania
PHP Praktyczne wprowadzenie R 4 Wstęp do programowania Proste skrypty PHP

więcej podobnych podstron