Tof-21-3 C Forritun-Bendar

You might also like

Download as pdf or txt
Download as pdf or txt
You are on page 1of 25

Tölvutækni og forritun

Fyrirlestur 3: C forritun og bendar

Hjálmtýr Hafsteinsson
Haust 2021

"Reyna að gera allar fyrirlestraæfingar, bæði


gagnlegt og hærri líkur á að vinna bol!"
Guðmundur Bjarki, haust '19
Í þessum fyrirlestri
• Almennt um C
– Breytur, virkjar, föll, skipanir, ...
• Bendar í C
– Eiginleikar
– Notkun í viðföngum
– Hættur
• GDB kembiforritið
Gagnatög í C
• Heiltölutög Gagnatag 32-bita 64-bita printf
– char, int char 1 1 %c
short int %hd
• Kommutölutög 2 2
int 4 4 %d
– float, double
long int 4 8 %ld
• Breytar: long long int 8 8 %lld
– short float 4 4 %f
– long double 8 8 %lf
Breytur í C
Visualizer
#include <stdio.h>
• Víðværar breytur
– Skilgreindar utan falla, aðeins int a;
extern int b;
sýnilegar í föllum í skránni const int c;
– Víðværar breytur með extern eru
int f() {
sjáanlegar fyrir utan skránna static int t = 0;
– Fastar eru skilgreindir með const t++;
return t;
• Kyrrlegar breytur }
– Skilgreindar með static int main() {
– Halda gildi sínu milli keyrsla á falli printf("%d\n", f());
printf("%d\n", f());
return 0;
Úttak:
}
1
2
Virkjar (operators) í C
• Allir þeir sömu og í Java:
– Útreikningsvirkjar
+, −, *, /, %, &, |, ^, <<, >>
– Samanburðarvirkjar
==, <, <=, >, >=, !=, &&, ||, !
– Gildisveitingarvirkjar
=, ++, −−, +=, −=, *=, /=, %=
– Aðrir virkjar
a?b:c, a,b
Þríundarvirkji
Framkvæmir a og
sjá Wikipedia síðu
b í röð, skilar b
Föll í C
Visualizer
• Grunneiningin í C #include <stdio.h>
– Keyrsla hefst í main fallinu
double pow(double x, unsigned int a){
– Forritasöfn samanstanda af if (a == 0)
föllum return 1.0;
– Öll vinnsla fer fram í föllum return x*pow(x, a-1);
}
– Í Java er klasi (class)
grunneiningin
int main(){
• Skilar yfirleitt einu gildi double p;
– Skilatagið void þýðir að p = pow(10.0, 5);
printf("Utkoma: %0.2f\n", p);
engu er skilað return 0;
}
Bendar (pointers)
• Breyta sem geymir vistfang sem gildi
Breytan p getur geymt
int *p;
vistfang heiltölubreytu

• Fáum vistfang breytu með &-virkjanum


int i = 3; 3
p = &i; p i

• Fáum gildi tilvísunar (dereferencing) með *


(*p)++; Visualizer
4
Hvað gerist ef sviga p i
vantar? Sjáum næst!
Ýmislegt um benda

• Skilgreinum benda af tilteknu tagi


– Dæmi: int*, float*, char*, ...
• Bendir á tag A vísar til minnissvæðis
af stærð sizeof(A) bæti
frá xkcd.com
• Gildið NULL er vistfang á engu minnishólfi
– Bendir sem inniheldur NULL bendir ekki á neitt
– Fáum keyrsluvillu ef * er beitt á bendi sem inniheldur NULL
• Margir bendar geta bent á sama minnishólfið
– Kallast samnefni (aliasing) og er algeng uppspretta villa
Fallaviðföng
• Í C eru öll viðföng send sem gildi (call by value)
– Þegar kallað er á fallið er viðfangsgildið afritað yfir í
staðværa breytu í fallinu
– Tryggir það að föll geta ekki breytt viðfangsbreytum í
köllunarforriti
int fa(int x) {
Fallið fa breytir staðværu x += 2;
viðfangsbreytunni x, og return x;
skilar gildi hennar
}

Gildið á a breytist ekki (áfram 3),


því gildið þess var afritað yfir í x, ...
en gildi b verður 5 a = 3;
b = fa(a);
...
Breyting viðfanga
• Hvað ef við viljum breyta gildi viðfangsins?
void inc(int x, int a) {
x += a;
}

• Þetta gengur ekki


– Jafnvel þó x sé breytt þá breytist gildi upphaflegu
breytunnar ekki
• Lausn: Senda tilvísun (bendi) á upphaflegu breytuna
og breyta því sem hún bendir á!
Kall með tilvísun (call by reference)
• Viðfangið er bendir á breytu
void inc(int *x, int a) {
3
*x += a;
x i
}
5
• Hækkum svo það sem hún bendir á x i
– Þurfum þá að senda inn vistfang á breytu
...
i = 3; ATH: Innihaldið í x
inc(&i, 2); breytist ekki. Það er
... áfram sama vistfangið

Visualizer
Dæmi um kall með tilvísun
• Ekki hægt að útfæra fall til að víxla á gildi tveggja
breyta ef við notum gildisviðföng
• Auðvelt að gera það með bendum
... 5
3
i = 3; a i
j = 5;
void swap(int *a, int *b) {
swap(&i, &j); 3
5
int tmp = *a;
... b j
*a = *b;
*b = tmp;
} 3
tmp

Athugið að fallið swap breytir ekki gildum Visualizer


a og b, heldur bara því sem þær benda á
Gagnsemi benda
• Notum benda á ýmsa vegu í C:
– Senda stóra gagnahluti inn í föll án þess að afrita þá
– Vinna með kviklegt (dynamic) minni
• Minni sem úthlutað er á keyrslutíma (með malloc)
– Í kvikum gagnagrindum
• Dæmi: tengdir listar, tré, net, ...
– Bendar geta vísað á föll
• Ákveðum hvaða fall á að keyra á keyrslutíma
• Bendar gera forrit mun sveigjanlegri
– Geta lagað sig að aðstæðum á keyrslutíma
Hættur í notkun benda
• Bendar þurfa að vera gildir (valid)
– Þurfa að benda á löglegt minnishólf sem forritið hefur umráð
yfir
int *bendir() {
int i = 5;
Fallið bendir skilar tilvísun (vistfangi) return &i;
á staðværu breytuna i, sem hættir að }
vera til um leið og fallið hættir keyrslu!!
int main() {
Breytan p inniheldur vistfang breytu int *p = bendir();
sem er ekki lengur lögleg *p = 8;
...
}
Reyndar mun forritið keyra og við fáum
enga villu hér, en um leið og kallað er á
Visualizer
annað fall þá er minnið endurnýtt
Samsett gagnatög í C
• Færsla (structure) er samsett úr tengdum
gagnagildum struct nem {
int nemnr;
float eink;
• Vísum í einstök svið (fields) };
með .-virkjanum
struct nem jon;
jon.nemnr = 10110;
jon.eink = 8.5;
• Í samanburði við Java þá er
struct eins og klasi, án aðferða, erfða o.fl.
Bendar á færslur
• Stundum þarf að vísa til færsla með bendum
• Þá notum við p->eink, sem er jafngilt (*p).eink
– Ef við notum *p.eink þá er það tekið sem *(p.eink)
– Þetta gefur villu, því sviðið eink er ekki bendir!
void haekka(struct nem *p) {
p->eink += 1.0;
}

• Algengt að nota benda á færslur í tengdum listum:


p = head;
while(p->next != NULL)
p = p->next;
Eintengdir listar
• Tengdir listar samanstanda af hnútum (nodes)
– Hnútur inniheldur gögn og bendi á næsta hnút
– Til að geta unnið með hnút data next

þurfum við bendi á hann:


• Sjálfstæða bendabreytu, eða struct Node {
• next-bendinn í öðrum hnúti int data;
struct Node *next;
head };
5 8

struct Node *head;


head = (struct Node *)malloc(sizeof(struct Node));
head->data = 5;
head->next = (struct Node *)malloc(sizeof(struct Node));
Sýnidæmi um tengdan lista
struct Node {
int data;
struct Node *next;
};

int main() { Smíða þrjá sjálfstæða benda


struct Node *head, *p, *q;
int val, i;
Smíða fyrsta hnútinn
printf("Enter 5 numbers: \n");
head = p = (struct Node *)malloc(sizeof(struct Node));
Lesa inn gildi
printf("1: "); scanf("%d", &val);
head->data = val; head->next = 0;
Setja gildi í hnútinn
for(i=2; i<=5; i++) {
printf("%d: ", i); scanf("%d", &val);
q = (struct Node *)malloc(sizeof(struct Node)); Lesa gildi, smíða hnút
q->data = val; q->next = 0; og setja gildi í hann
p->next = q;
p = q; Láta síðasta hnút
} benda á nýja hnútinn

for(p=head; p!=0; p=p->next)


printf("%d \n", p->data); Prenta út öll gildin í tengda listanum

return 0;
}
Sýnidæmi um tengdan lista
struct Node *head, *p, *q;
head = (struct Node *)malloc(sizeof(struct Node));
head->data = val;
p = head;
q = (struct Node *)malloc(sizeof(struct Node));
q->data = val;
p->next = q;
p = q;

head: bendir alltaf á fremsta hnút


p: bendir alltaf á síðasta hnút
head p q q: vinnubendir, fyrir nýjan hnút

5 8 11 ... Sjá líka í Visualizer


Kembun með GDB
• GDB er skipanalínuforrit til að rekja sig í gegnum forrit
– Notum það núna fyrir C, en skoðum síðar fyrir smalamál
• Þýðum forritið með -g í gcc
• Keyrum forritið með: gdb ./forrit
• Byrjum á að setja rofstaði (breakpoints) í forritinu
(break)
• Keyrum svo forritið (run)
– Það hættir svo keyrslu á fyrsta rofstað
– Gefum svo skipanir til að skoða breytur (print), fara áfram
eitt skref (step), halda áfram (continue), hætta (quit)
Oftast nóg að slá bara inn fyrsta bókstaf
skipunar (r = run, p = print, ...)
GDB sýnidæmi
$ gcc -Wall -g -o pow pow.c
$ gdb ./pow
... #include <stdio.h>
(gdb) list rpow Sýna fyrstu 10
línur forrits double rpow(double x, unsigned int a)
... {
if (a == 0)
(gdb) break main Rofstaður í main
return 1.0;
(gdb) run Keyra forritið return x*rpow(x, a-1);
(gdb) step Framkvæma eina skipun }

(gdb) print a Prenta innihald a int main(){


(gdb) break 5 Rofstaður í línu 5 double p;
p = rpow(10.0, 5);
(gdb) continue Keyra áfram printf("Utkoma: %0.2f\n", p);
(gdb) print a Prenta innihald a return 0;
}
...

sjá svindlblað frá Brown, svindlblað frá Marc Haisenko


Skipanalínuviðföng
• C forrit getur náð í viðföngin sem það var keyrt með
– argc inniheldur fjölda viðfanga
– argv er strengfylki með viðföngunum

#include <stdio.h>
argv[0] er nafn
keyrsluskrárinnar int main( int argc, char *argv[] ) {
if( argc == 2 ) {
printf("Viðfangið er %s\n", argv[1]);
Hin viðföngin eru } else if( argc > 2 ) {
í argv[1] til printf("Of mörg viðföng\n");
argv[argc-1] } else {
printf("Þarf eitt viðfang\n");
}
}
C forrit í mörgum skrám
• Stærri C forrit eru oftast brotin upp í margar skrár
– Hvert fall í sérstakri skrá
• Einfaldar þýðingu
– Þurfum þá aðeins að þýða skránna sem breytist
• Í upphafi eru allar c-skránar þýddar yfir í o-skrár
– fall1.c þýtt yfir í fall1.o
• Svo eru allar o-skrárnar tengdar (linked) yfir í
keyrsluskrá
Margar C-skrár
main.c
#include <stdio.h>
Skýrgreining
(declaration) ytra falls
int summa(int a, int b);

int main(int argc, char** argv) { sum.c


int gildi = summa(5, 3); int summa(int a, int b) {
printf("5 + 3 = %d\n", gildi); return a + b;
} }

• Sjálfstæð þýðing og tenging:


$ gcc -c main.c • Þýða og tengja með þýðendarekli:
$ gcc -c sum.c $ gcc -o prog main.c sum.c
$ gcc -o prog main.o sum.o
Fyrirlestraæfingar
1. Hvaða þekkt fall er útfært með eftirfarandi skipun:
m = x > y ? y : x;

2. Hvað er rangt við skipunina hér að neðan?


int *p = 5;

3. Teiknið upp breyturnar sem eru skilgreindar hér fyrir


neðan og útskýrið merkingu þeirra
int i = 5;
int *p = &i;
int **q = &p;

You might also like