Sa consideram o structura pentru memorarea unei date calendaristice:
struct data
In aceasta forma structura ocupa in memorie 6 octeti (3 campuri de tip int inseamna 3x2=6 octeti).
Daca analizam mai atent lucrurile, observam ca fiecare din cele trei campuri ar putea fi memorat pe mai putin de doi octeti. Spre exemplu campul zi va lua valori intre 1 si 31, deci ar putea fi memorat pe 5 biti (cea mai mica putere a lui 2 mai mare decat 31 este 32=2^5). Campul luna va lua valori intre 1 si 12, deci ar putea fi memorat pe 4 biti (cea mai mica putere a lui 2 mai mare decat 12 este 16=2^4). Iar campul an sa presupunem ca va lua valori intre -9999 si 9999, deci poate fi reprezentat pe 15 biti (avem din start un bit de semn deoarece vrem sa putem retine si numere negative, iar cea mai mica putere a lui 2 mai mare decat 9999 este 16384=2^14; rezulta in total 15 biti). Vedem ca cele trei campuri impreuna ar necesita de fapt doar 5+4+15=24 biti, adica 3 octeti. Cu alte cuvinte folosim un spatiu de memorie dublu fata de cat ar fi necesar.
In limbajul C p 454e47e utem specifica pentru campurile de tip int sau char dimensiunea in biti pe care sa o ocupe. Dimensiunea in biti se specifica plasand imediat dupa definirea campului caracterul urmat de numarul de biti pe care dorim sa il ocupe campul. Putem redefini structura de mai sus astfel:
struct data_biti
Daca urmarim (cu ajutorul operatorului sizeof) dimensiunea ocupata de structura redefinita astfel, vom vedea ca este intradevar de 3 octeti.
Intr-o structura pot alterna campurile definite pe biti cu cele definite clasic. Spre exemplu sa consideram o structura pentru descrierea unor produse de panificatie. Vom retine tipul produsului (paine, covrig sau corn), greutatea produsului in kg (numar real) si numarul de cereale ce intra in compozitia produsului (minim 0, maxim 7). Tipul produsului poate lua trei valori pe care le codificam cu 0, 1 si 2. Ca urmare avem nevoie de doi biti pentru memorarea tipului. Greutatea este numar real si o vom pastra intr-un camp de tip float. Numarul de cereale poate lua valori de la 0 pana la 7, deci incape pe 3 biti. Avem urmatoarea definire a structurii:
struct panificatie
Primul camp ocupa 2 biti, al doilea ocupa 4 octeti, fiind de tip float, iar al treilea ocupa 3 biti. In total avem 2+32+3=37 biti, care incap in 5 octeti. Totusi daca urmarim dimensiunea structurii folosind operatorul sizeof, vom vedea ca in realitate ocupa 6 octeti. Asta deoarece atunci cand un camp pe biti este urmat de un camp definit clasic, campul pe biti este completat automat cu biti neutilizati pana la multiplu de 8 biti, adica pana se ajunge la inceputul unui octet nou. La fel se intampla si daca ultimul camp din structura este definit pe biti: el va fi completat cu biti neutilizati pana la multiplu de 8.
Pentru structura noastra harta memoriei arata astfel:
2 biti pentru campul tip |
6 biti neutilizati |
4 octeti (32 de biti) pentru campul greutate |
3 biti pentru campul cereale |
5 biti neutilizati |
Avem in total 6+5=11 biti neutilizati. Pentru a scadea la minim risipa de spatiu, trebuie sa grupam campurile pe biti unul langa altul. Rescriem structura astfel:
struct panificatie_optim
De data aceasta se adauga biti neutilizati doar dupa campul cereale. Harta memoriei arata astfel:
2 biti pentru campul tip |
3 biti pentru campul cereale |
3 biti neutilizati |
4 octeti (32 de biti) pentru campul greutate |
Numarul bitilor neutilizati a scazut la 3, iar dimensiunea totala a structurii a scazut la 5 octeti.
Atentie la modificatorul unsigned. Un camp definit pe doi biti cu modificatorul unsigned va retine valori de la 0 pana la 3. Daca nu apare modificatorul unsigned, atunci campul este cu semn si va retine valori de la -2 pana la 1. Trebuie avut in vedere acest lucru atunci cand definim structuri folosind campuri pe biti.
Pointeri la functii
Putem declara pointeri catre orice tip de date, atat catre tipurile standard C, cat si catre tipurile pe care le definim noi. In plus, limbajul C ne permite sa declaram pointeri la functii. In anumite situatii pointerii la functii se dovedesc foarte utili.
Sintaxa de definire a pointerilor la functii este asemanatoare cu sintaxa de definire a functiilor. In forma generala, o functie se declara astfel:
tip_returnat nume_functie(lista parametri);
Un pointer la functie se declara dupa sintaxa:
tip_returnat (*nume_variabila_pointer)(lista_parametri);
Practic numele functiei se inlocuieste cu constructia (*nume_pointer).
In rest cu pointerii la functie se lucreaza la fel ca si cu pointerii la orice tip de date. Se foloseste operatorul & pentru a obtine adresa de memorie a unei functii. Se foloseste operatorul pentru a obtine functia spre care indica un pointer.
Urmariti urmatorul program pentru a vedea un exemplu de folosire a pointerilor la functii.
#include <stdio.h>
/* Definim doua functii cu doi parametri
de tip int si care returneaza void. */
void o_functie(int a, int b)
void alta_functie(int c, int d)
int main(void)
In urma executiei acestui program se va afisa:
Eu sunt o functie cu doi parametri de tip int: 11 si 22.
Eu sunt o functie cu doi parametri de tip int: 12 si 23.
Eu sunt alta functie cu doi parametri de tip int: 33 si 44.
Eu sunt alta functie cu doi parametri de tip int: 34 si 45.
Tipul pointerilor este foarte important. Daca definim pointeri la functii cu un anumit numar si tip de parametri, nu putem sa le atribuim decat adrese de functii care au exact acel numar si tip de parametri. Urmariti programul urmator pentru a vedea un exemplu in acest sens.
#include <stdio.h>
/* Definim o functie cu un parametru int si
care returneaza void. */
void tip_void_un_param(int a)
/* Definim o functie cu un parametru de tip int,
dar ca returneaza int. */
int tip_int_un_param(int a)
/* Definim o functie cu doi parametri de tip int,
care returneaza void. */
void tip_void_doi_param(int a, int b)
int main(void)
O modalitate de folosire a pointerilor la functii este construirea de vectori de pointeri. Urmariti programul urmator pentru a vedea despre ce este vorba:
#include <stdio.h>
/* Definim patru functii fara parametru,
care returneaza void. Fiecare functie
afiseaza cate un caracter pe ecran. */
void steluta()
void plus()
void minus()
void dolar()
int main(void)
Am definit patru functii care au aceeasi semnatura (acelasi numar/tip de parametri, si acelasi tip de date returnat). Pe urma am definit un vector de patru pointeri la functie si am memorat in vectorul respectiv adresele celor patru functii. Pe urma putem apela intr-un mod flexibil cele patru functii folosindu-ne de vectorul definit si de cicluri for. In urma rularii programului, pe ecran se va afisa:
O alta modalitate de folosire a pointerilor la functii este scrierea de cod generic. Urmariti programul urmator pentru a vedea un exemplu:
#include <stdio.h>
#include <math.h>
/* Functia sinus. */
float my_sin(int n)
/* Functia cosinus. */
float my_cos(int n)
/* Functie care calculeaza maximul unei
alte functii. Folosim pointeri la functie
pentru a preciza care este functia pentru
care se calculeaza maximul. */
float max(float (*un_pointer_la_functie)(int))
/* Returnam maximul gasit. */
return max;
int main(void)
Dupa cum se vede, avem o functie generica de calcul al maximului oricarei functii. Este vorba de functia max, care primeste ca parametru un pointer la functie. Folosind acel pointer se calculeaza maximul, fara a se sti care este functia concreta pentru care se face calculul.
In functia main ne folosim de aceasta functie generica max pentru a calcula maximul a doua functii trigonometrice. Observam avantajul: am scris functia o singura data si acum o putem apela pentru orice functii matematice, fara sa fie nevoie de modificari in cod.
Atentie din nou la tipul pointerilor la functii. Nu vom putea transmite ca parametru pentru functia generica decat adrese de functii care respecta modul in care am definit parametrul pointer la functie.
In urma rularii programului de mai sus se va afisa pe ecran:
Maxim pentru sinus: 0.989358.
Maxim pentru cosinus: 1.000000.
Cand scriem un program C, pentru a putea rula programul trebuie sa il transformam din fisier text (codul sursa este pastrat in fisiere text) in fisier binar executabil. Aceasta transformare se face prin doua operatii: compilare si link-editare. In general ambele operatii sunt efectuate automat de mediul de editare pe care il folosim (Borland C++ 3.1, Dev-C++, Visual C++, etc.), astfel incat noi nu suntem constienti de ele.
Ceea ce putem constata noi e ca daca avem un fisier sursa, spre exemplu "prog.c", si il compilam si link-editam cu succes (in Borland C++ 3.1 se selecteaza "Make" din meniul "Compile"), pe disc apare un fisier numit "prog.exe Fisierul "prog.c" este fisier text, iar fisierul "prog.exe" este fisier binar executabil. Daca deschidem o fereastra cu linie de comanda (in Windows se selecteaza Start, apoi Run si se tasteaza "cmd"), putem rula fisierul binar executabil. Efectul va fi acelasi ca si cand am rula fisierul C direct din mediul de editare.
Pentru a rula fisierul binar din linie de comanda, scriem numele lui si apasam tasta ENTER. Daca fisierul sursa se numeste "prog.c", iar fisierul binar se numeste "prog.exe", atunci in linia de comanda putem scrie "prog.exe" sau "prog
Sistemele de operare ne permit sa transmitem parametri la programele pe care le rulam. Pentru a transmite parametri, trebuie doar sa ii insiram dupa numele programului pe care il rulam. Spre exemplu scriem in linia de comanda "prog.exe parametru1 parametru2 parametru3
Pentru a putea citi din program parametri care au fost transmisi din linia de comanda, trebuie sa folosim constructii specifice ale limbajului de programare. In limbajul C parametri din linia de comanda ajung in program ca si argumente ale functiei main. Mai exact ca si doua argumente: un prim argument de tip int care specifica numarul parametrilor transmisi, si un al doilea parametru care este un sir de pointeri la char. Fiecare din pointerii din acest sir de pointeri indica spre un sir de caractere reprezentand unul din parametri veniti din linia de comanda.
Un program C care afiseaza toti parametri primiti din linia de comanda este:
#include <stdio.h>
int main(int argc, char* argv[])
Daca salvam programul intr-un fisier "lcom.c"si il compilam si link-editam va rezulta fisierul binar "lcom.exe
Daca deschidem o fereastra cu linie de comanda si rulam programul fara nici un parametru, ne-am astepta sa se afiseze ca avem zero parametri din linia de comanda. Vom vedea ca in realitate avem un parametru. Asta deoarece intotdeauna sistemul de operare trimite automat spre program ca prim parametru din linia de comanda numele fisierului executabil care a fost rulat.
Daca noi scriem in linia de comanda "lcom.exe", rezultatul va fi:
F:>lcom.exe
Avem 1 parametri din linia de comanda.
Parametrul 0: 'lcom.exe'
Daca scriem doar "lcom" fara extensie, rezultatul va fi:
F:>lcom
Avem 1 parametri din linia de comanda.
Parametrul 0: 'lcom'
Daca scriem cativa parametri din linia de comanda, programul ii va afisa pe toti. Din nou sistemul de operare va plasa pe prima pozitie numele programului care a fost rulat.
F:>lcom unu doi trei patru cinci
Avem 6 parametri din linia de comanda.
Parametrul 0: 'lcom'
Parametrul 1: 'unu'
Parametrul 2: 'doi'
Parametrul 3: 'trei'
Parametrul 4: 'patru'
Parametrul 5: 'cinci'
Toti parametri care ajung la program din linia de comanda, ajung sub forma de siruri de caractere. Chiar daca noi dorim sa trimitem numere, ele sunt transmise ca siruri de caractere. Pentru a le folosi ca numere, trebuie sa le convertim noi explicit, folosind functiile de conversie atoi si atof din bilioteca stdlib.h
Un exemplu de program care foloseste parametri din linia de comanda sub forma de numere intregi este urmatorul:
#include <stdio.h>
#include <stdlib.h>
void stelute(int n)
void spatii(int n)
int main(int argc, char* argv[])
printf('n');
return 0;
Programul citeste numere care i se transmit din linia de comanda. In functie de aceste numere el afiseaza pe o linie stelute si spatii. Spre exemplu daca se transmit numerele 3, 4, 5 si 6, programul afiseaza 3 stelute, 4 spatii, 5 stelute si 6 spatii. Observati ca s-a folosit functia atoi pentru a converti parametri din sir de caractere in intreg.
De regula parametri din linie de comanda se folosesc pentru a automatiza executia programelor. Automatizarea executiei programelor se face prin script-uri care sunt rulate de sistemul de operare. Pentru windows, asemenea script-uri se scriu sub forma de fisiere cu extensia BAT. Un script contine o secventa de comenzi care se doreste sa fie rulate impreuna.
Daca salvam programul anterior intr-un fisier cu numele "stelute.c", astfel incat fisierul binar rezultat sa fie "stelute.exe", atunci putem scrie urmatorul fisier BAT:
@echo off
stelute 7 3 5
stelute 0 3 1 6 1 4 1
stelute 0 3 1 6 1 5 1
stelute 0 3 1 6 1 5 1
stelute 0 3 1 6 1 4 1
stelute 0 3 1 6 5
stelute 0 3 1 6 1
stelute 0 3 1 6 1
stelute 0 3 1 6 1
stelute 0 3 1 6 1
stelute 0 3 1 6 1
Daca rulam acest fisier BAT, efectul va fi ca se va rula in mod automat programul stelute.exe, de 11 ori, cu secventele de parametri specificati. Prima linie (@echo off) are rolul de a suprima mesajele tiparite de sistemul de operare la rularea fiecarei comenzi in parte. Pe ecran vor apare literele TP (de la Tehnici de Programare):
*****
* * *
* * *
* * *
* * *
* *****
* *
* *
* *
* *
* *
Definiti o structura pentru memorarea urmatoarelor informatii despre animale:
numarul de picioare: numar intreg, minim 0 (ex. sarpe), maxim 1000 (ex. miriapod)
greutatea in kg: numar real
periculos pentru om: da/nu
abrevierea stiintifica a speciei: sir de maxim 8 caractere
varsta maxima in ani: numar intreg, minim 0, maxim 2000
Unde este posibil, codificati informatiile prin numere intregi. Spre exemplu "da"=0, "nu"=1.
Definiti structura in asa fel incat sa ocupe spatiul minim de memorie posibil. Afisati spatiul de memorie ocupat, folosind operatorul sizeof
Sa se scrie un program pentru tabelarea unor functii matematice. Programul trebuie sa stie sa tabeleze functiile sin(x), cos(x), sin(x)*cos(x) si x*x.
Programul va citi de la tastatura doua valori reale A si B si o valoare intreaga N. Pe urma va imparti intervalul [A, B] in N-1 parti egale, astfel incat sa rezulte N puncte echidistante in interval. Doua din cele N puncte vor coincide cu capetele intervalului, A si B. Programul va afisa valorile functiilor in cele N puncte astfel obtinute.
Solutia voastra trebuie sa foloseasca pointeri la functii. Scrieti o functie generica pentru tabelarea unei functii matematice oarecare. Aceasta functie generica va avea un parametru de tip pointer la functie. Folosind functia generica, tabelati cele patru functii matematice amintite mai sus.
Scrieti un program care cripteaza sau decripteaza un sir de cuvinte.
Criptarea se face dupa un algoritm simplu. Stim ca fiecare litera din alfabet are asociat un numar de ordine. Numerele de ordine ale tuturor caracterelor (nu doar litere) formeaza ceea ce se numeste tabela de coduri ASCII. Pentru a afla numarul de ordine al unui caracter, e suficient sa il afisam ca si intreg. Spre exemplu:
char c = 'a';
printf('Numarul de ordine al lui '%c' este %d.n', c, c);
Pentru a cripta un anumit cuvant, avem nevoie de un numar intreg N numit cheie de criptare. Criptarea se realizeaza astfel: la numarul de ordine al fiecarei litere a cuvantului se aduna N si rezulta o noua litera. Cuvantul criptat este constituit din noile litere ce se obtin.
Spre exemplu sa consideram cuvantul "abecedar" si cheia de criptare
Literele originale |
a b e c e d a r |
Numerele de ordine ale literelor originale | |
Numerele de ordine ale literelor criptate | |
Literele criptate |
b c f d f e b s |
Incrementarea numerelor de ordine se face circular, adica litera z incrementata cu devine litera a
Operatia de decriptare se face exact invers. Avand un cuvant si o cheie de decriptare N, se scade din numerele de ordine ale literelor cuvantului valoarea N, obtinandu-se astfel literele cuvantului decriptat.
Scrieti un program care face operatii de criptare si decriptare conform algoritmului descris. Programul va primi urmatorii parametri din linia de comanda:
Primul parametru va fi una din valorile "enc" sau "dec". Daca valoarea este "enc" atunci se va face criptare, daca este "dec" se va face decriptare. Daca primul parametru are alta valoare, se va afisa un mesaj de eroare.
Al doilea parametru este un numar intreg reprezentand cheia N de criptare/decriptare.
Urmatorii parametri sunt cuvinte care trebuie criptate sau decriptate, dupa caz, in functie de valoare primului parametru. Cuvintele pot contine atat litere mari, cat si litere mici, dar ele vor trebui convertite la litere mici inainte de a face operatia de criptare/decriptare.
Programul va afisa pe ecran cuvintele obtinute in urma criptarii/decriptarii cuvintelor trimise din linia de comanda.
Spre exemplu daca rulam programul cu parametri "criptare enc 1 abecedar" programul trebuie sa afiseze "bcfdfebs". Daca rulam "criptare dec 1 bcfdfebs" programul va afisa "abecedar
Daca rulam "criptare enc 1 zaraza" programul va afisa "absbab
Daca rulam "criptare enc 10 vine VINE primavara PRImaVAra" programul va afisa "fsxo fsxo zbswkfkbk zbswkfkbk
|