sâmbătă, 29 iunie 2013

Web Hosting gratuit cu Google Drive

Cu Google Drive puteţi hosta resurse web HTML, CSS, şi Javascript. Drive nu oferă suport pentru scripturile server-side scrise, de exemplu, în PHP.
Practic vă puteţi hosta websiteul pe Google Drive.

Iată cum puteţi face asta:

  1. Deschideţi Drive (trebuie să aveţi cont Google), creaţi un nou folder şi partajaţi-l (Share) ca Public on the web.
    Click dreapta pe folder, selectaţi Share, o nouă fereastră va apărea, click pe Change... şi faceţi-l Public.
  2. Uploadaţi-vă fişierele în acest folder.
  3. Click dreapta pe un fişier din acest folder şi algeţi Details. O nouă fereastră va apărea în dreapta.
  4. Scroll down până găsiţi Hosting. Acela este URL-ul către fişierul hostat.
Iată un exemplu cu un fişier hostat pe Drive: hangme.js.

miercuri, 19 iunie 2013

Bacalaureat 2013 - Problema 4 - Subiectul 3 (Sesiunea speciala)

Fişierul bac.txt conţ ine un şir de cel pu ţin trei şi cel mult 1000000 de numere naturale cu cel mult nouă cifre. Numerele din şir sunt separate prin câte un spa ţiu.
Se cere să se afişeze pe ecran, separate printr-un spa ţiu, două numere distincte, anume cel mai mic număr par cu două cifre şi cel mai mare număr par cu două cifre care NU fac parte din şir.
Dacă nu există două astfel de valori, pe ecran se afisează mesajul Nu exista.
Pentru determinarea numerelor cerute se utilizează un algoritm eficient din punctul de vedere al timpului de executare.
Exemplu: dacă fişierul bac.txt con ţine valorile
7 2 40 5 10 15 11 12 18 350
se afişează pe ecran numerele 14 98.

Arată Soluţia

Complexitatea algoritmului este `O(n)` (liniar).

Bacalaureat 2013 - Problema 3 - Subiectul 3 (Sesiunea speciala)

Se consideră subprogramul sub, cu trei parametri:
  • n, prin care primeşte un număr natural (2<n<50);
  • v, prin care primeşte un tablou unidimensional cu n elemente, numere naturale cu cel mult 4 cifre;
  • x, prin care primeşte un număr natural cu cel mult 4 cifre. Cel pu ţin unul dintre elementele tabloului are valoarea x.
Subprogramul modifică ordinea valorilor din tablou, astfel încât toate valorile egale cu x să ocupe primele poziţ ii din v, iar celelalte valori să se regăsească în continuarea acestora.
Tabloul modificat este furnizat tot prin parametrul v.
Scrie ţi defini ţia completă a subprogramului.
Exemplu: dacă n=9, v=(2, 1, 0, 1, 7, 0, 1, 4, 5) şi x=1, atunci, după apel, o soluţ ie posibilă este v=(1, 1, 1, 2, 0, 7, 0, 4, 5).

Arată Soluţia

Bacalaureat 2013 - Problema 5 - Subiectul 2 (Sesiunea speciala)

Se consideră un text cu cel mult 100 de caractere (litere mici ale alfabetului englez şi spaţ ii), în care cuvintele sunt separate prin câte un spaţ iu. Înaintea primului cuvânt şi după ultimul cuvânt nu există spa ţii.
Scrieţ i un program C/C++ care citeşte de la tastatură un text de tipul men ţionat mai sus şi determină transformarea acestuia în memorie prin înlocuirea fiecărui cuvânt format din trei litere cu simbolul *. Programul afişează pe ecran textul ob ţinut.
Exemplu: pentru textul

bun este izvorul ce are apa rece

se afişează

* este izvorul ce * * rece

Algoritmul reţine adresa de început a fiecărui cuvânt şi numără numărul de litere din fiecare cuvânt până când întâlneşte un caracter spaţiu.
Aici se verifică dacă a fost întâlnit un cuvânt de 3 litere, caz în care se deplasează tot şirul spre stânga, iar pe prima poziţie din cuvânt se inserează caracterul *.
Indiferent dacă a fost găsit un cuvânt de 3 litere, după fiecare spaţiu începe un nou cuvânt ceea ce înseamnă că start este actualizat, iar cnt este resetat.

Arată Soluţia

luni, 17 iunie 2013

Mărimi fizice - Sistemul Internaţional

Mărimea fizică este o proprietate fizică măsurabilă.
Ea caracterizează starea unui sistem fizic. Are forma:

marime fizică = valoare numerică x unitatea de măsură

A măsura o mărime fizică înseamnă a o compara cu o altă mărime fizică pe care o numim unitate de măsură.
Acestei unităţi standard îi este atribuit un nume unic şi valoarea 1.0, reprezentând 1 unitate din respectiva mărime fizică.
Exemplu de mărimi fizice: lungimea, masa, viteza, forţa, timpul, etc.

Prin convenţie internaţională (1971, a 14-a Conferință Generală de Măsuri și Greutăți) au fost stabilite 7 mărimi fizice fundamentale care formează baza Sistemul Internaţional de Unităţi, prescurtat SI.
Celelalte unităţi (mărimi) sunt derivate din acestea, adică definite în termeni de unităţi fundamentale.
De exemplu, forţa se măsoară în newtoni.

1 newton = 1 N = 1 kg m s-2

N este simbolul SI pentru newton. Kilogramul, metrul şi secunda sunt mărimi fundamentale.

Cele 7 mărimi fizice fundamentale
Mărime fizicăNume unitateSimbol unitate
Lungimemetrum
Masăkilogramkg
Timpsecundăs
Intensitatea curentului electricamperA
Temperatura termodinamicăkelvinK
Cantitate de substanţămolmol
Intensitate luminoasăcandelăcd

Lungimea se măsoară în metri (m).
1 metru reprezintă lungimea drumului parcurs de lumină în vid în intervalul de 1 / 299 792 458 secunde. (1983)

Masa se măsoară în kilograme (kg).
1 kilogram reprezintă masa prototipului internațional al kilogramului confecționat dintr-un aliaj de platină și iridiu și care se păstreaza la Biroul Internațional de Măsuri si Greutăți de la Sèvres, Franța. (1889)

Timpul se măsoară în secunde (s).
1 secundă reprezintă durata a 9 192 631 770 perioade ale radiației corespunzătoare tranziției dintre două nivele de energie hiperfine ale stării fundamentale a atomului de cesiu 133. (1967)

Restul definiţiilor (pentru cei curioşi) se află aici.

Pentru exprimarea cantităţilor foarte mici şi foarte mari se foloseşte notaţia ştiinţifică.

7 710 000 000 m = 7.71 x 109 m

0.000 000 492 s = 4.92 x 10-7 s

Când lucrăm cu astfel de numere este convenabil să folosim prefixele SI ca factori de multiplicare.
Aceste prefixe reprezintă puteri de-ale lui 10.
De exemplu, 1.14 x 109 jouli = 1.14 gigajouli = 1.14 GJ [energia se măsoară în jouli (J)].

Prefixe SI des folosite
PrefixFactorSimbol
giga109G
mega106M
kilo103k
centi10-2c
mili10-3m
micro10-6`mu`
nano10-9n
pico10-12p

Tabelul complet se află aici.

De multe ori vom fi nevoiţi să exprimăm anumite mărimi fizice în alte unităţi.
De exemplu, timpul se măsoară în secunde, dar poate fi măsurat şi în minute, şi în ore, şi în zile, etc.
Ca să putem converti dintr-o unitate de măsură într-alta, folosim un factor de conversie (un raport de unităţi echivalent cu 1, unitatea).
De exemplu, 1 minut este egal cu 60 de secunde (1 min = 60 s), deci:

`{1 min} / {60 s} = {60 s} / {1 min} = 1`

Putem transforma minutele în secunde şi viceversa. Ca să transformăm 5 minute în secunde, înmulţim cele 5 minute cu factorul de conversie:

`5 min = (5 min)(1) = (5 min)({60 s} / {1 min}) = 300 s`

Minutele de la numitor şi numărător se anulează, şi rămâne 5 x 60 s = 300 s.

Trataţi valoarea şi unitatea împreună, ca o singură entitate!

`1 / 60` NU este echivalent cu `{1 min} / {60 s}` şi în NICIUN caz `1 / 60 = 1`!

vineri, 14 iunie 2013

Tutorial C++: Pointeri, Memorie Dinamică, Referinţe

Ce este un pointer?

Un pointer este o variabilă care memorează o adresă de memorie.
Un pointer se declară în felul următor:

tip * nume;
Aici tip* reprezintă tipul de dată al variabilei (obiectului) pe care pointerul îl referă.
Un pointer este un pointer deoarece el memorează o adresă şi ocupă acelaşi spaţiu de memorie cu un pointer de alt tip*.
Totuşi, asta nu înseamnă că puteţi memora adresa unui double într-un pointer int* sau să interschimbaţi adrese între pointeri de tipuri diferite.
Pe lângă memorarea adreselor, pointerii mai pot accesa şi conţinutul de la respectivele adrese. Decodificarea conţinutului se face pe baza tipului pointerului tip*.
Spaţiul de memorie pe care îl ocupă un pointer depinde de compilator şi sistemul de operare.
Exemplu de pointeri:
int * p1; // Refera un int
char * p2; // Refera un char
Aveţi grijă să iniţializaţi un pointer înainte de a-l folosi, altfel puteţi accesa zone de memorie pentru care programul vostru nu are acces, iar acest lucru poate cauza erori în program.
Puteţi iniţializa un pointer fie cu o adresă de memorie, fie cu pointerul NULL nullptr.
Acest keyword a fost introdus în noul standard C++11 şi diferă de NULL prin faptul că nu este un întreg, pur şi simplu reprezintă un pointer NULL.
NULL în C/C++ este definit ca #define NULL 0 - practic NULL este constanta zero.
Dacă compilatorul vă permite, folosiţi nullptr în loc de NULL.
Adresa unei variabile se obţine cu operatorul de referenţiere (&) (sau operatorul adresă).
ptr = &var;
Conţinutul unei adrese de memorie (stocată într-un pointer) se obţine cu operatorul de dereferenţiere (*) (sau operatorul de indirectare).
*ptr
Nu confundaţi acest operator cu steluţa (*) din declararea unui pointer! Ea face parte din tipul pointerului.
Iată un exemplu mai concret:
#include <iostream>
using namespace std;

int main()
{
    int * p1; double * p2;
    int d1 = 45; double d2 = 3.14;
    p1 = &d1;  // p1 refera variabila d1
    p2 = &d2;  // p2 refera variabila d2
    // Afisez adresele de memorie stocate in cei doi pointeri
    cout << p1 << ' ' << p2 << '\n';
    // Afisez continutul stocat la adresele de memorie
    // din cei doi pointeri
    cout << *p1 << ' ' << *p2 << '\n';
    cout << sizeof(p1) << ' ' << sizeof(p2);
    return 0;
}
Output:
0x22fef4 0x22fee8
45 3.14
4 4
Prin convenţie numerele hexazecimale sunt reprezentate cu prefixul 0x.
Operatorul sizeof(ob), unde ob reprezintă un tip (int, char, float, etc.) sau un obiect (variabilă), returnează mărimea, în bytes, a argumentului.
După cum vedeţi, pointerii ocupă 4 bytes de memorie (în sistemele de 32-biţi).

Puteţi scădea doi pointeri ca să obţineţi numărul de elemente de tipul respectiv ce încap între ei.
#include <iostream>
using namespace std;

int main()
{
    double * p1, * p2;
    double d1 = 1.1, d2 = 2.2;
    p1 = &d1; p2 = &d2;
    cout << p1 << ' ' << p2 << '\n';
    cout << p1 - p2;
    return 0;
}
Output:
0x22fef0 0x22fee8
1
Diferenţa dintre 0x22fef0 şi 0x22fee8 este 0x8, adică 8, iar 8 bytes este spaţiul de memorie pe care îl ocupă 1 double, deci rezultatul afişat este 1.
Puteţi scădea numai pointeri ce referă acelaşi tip de dată.
Dacă p1 ar fi fost int*, codul de mai sus nu ar fi compilat.

Alocarea dinamică

Când declaraţi un vector trebuie să precizaţi numărul de elemente ce-l vor alcătui.
Asta înseamnă că stabiliţi spaţiu de memorie ocupat de vector înainte de a ştii exact numărul de elemente ce va fi memorat în vectorul respectiv.
Vectorii statici pot fi ineficienţi din punct de vedere al memoriei ocupate atunci când nu se ştie exact câte elemente va memora vectorul.
De aceea C++ ne pune la dispoziţie operatorii new şi delete cu care putem aloca / dealoca memorie dinamic în timpul execuţiei programului.
Cu new se alocă (rezervă) memorie. Operatorul returnează adresa de memorie a spaţiului alocat.

tip* var = new tip;
Sau putem aloca un bloc de memorie (vector dinamic):
tip* var = new tip[nr_elem];
Nu există succes garantat când alocaţi memorie dinamic.
Operatorul new alocă memorie din zona liberă (heap).
Este posibil ca această zonă din memorie să fie ocupată de alte aplicaţii sau starea sistemului să nu permită o astfel de alocare.
În această situaţie new lansează o excepţie. Puteţi folosi (nothrow) astfel încât new să returneze pointerul NULL în caz de eşec.
tip* var = new (nothrow) tip;
Memoria alocată cu new - numai aceasta - poate fi dealocată (eliberată) cu delete.
delete var;
delete[] var; // pentru vectori
După ce aţi folosit delete pe un pointer, acesta nu mai indică către o adresă validă (respectiva adresă nu mai este rezervată pentru programul vostru), de aceea este bine să-i atribuiţi valoarea nullptr (sau NULL).
ATENŢIE: delete nu distruge variabila pointer! El eliberează spaţiul de memorie indicat de pointer prin adresa pe care o memorează.

Nu uitaţi să dealocaţi spaţiul de memorie atunci când nu mai aveţi nevoie de el!
Dacă nu dealocaţi memoria, aceasta rămâne rezervată, degeaba, pentru aplicaţia voastră, când alte aplicaţii ar putea avea nevoie de ea sau chiar programul vostru, care alocă fără să elibereze, poate rămâne fără spaţiu de lucru. Fenomenul se numeşte memory leak.
Un exemplu de alocare dinamică:
#include <iostream>
using namespace std;

int main()
{
    int* x = new int;
    *x = 98;
    cout << *x << ' ' << x;
    delete x;
    return 0;
}
Output:
98 0x7c0f70
Un vector static este practic un pointer constant. El referă numai blocul de memorie care îi este atribuit de compilator.
Numele vectorului este un pointer către primul element din vector. Exemplu:
#include <iostream>
using namespace std;

int main()
{
    int v[] = {1, 2, 3, 4, 5};
    cout << *v; // Se va afisa 1
    return 0;
}
Puteţi accesa elementele unui vector, static sau dinamic, în două moduri:
v[i]; // SAU
*(v+i);
// unde i este un intreg
Atunci când adunaţi sau scădeţi un intreg dintr-un pointer, deplasaţi pointerul peste i blocuri de memorie de tipul referit de pointer.
De exemplu, dacă un pointer (int* ptr;) referă / indică adresa 0x22feec, atunci ptr+1 va referi adresa 0x22fef0.
0x22feec + 4 = 0x22fef0 (4 este mărimea în bytes a tipului int). Vezi imaginea de mai sus.
Aşadar, putem folosi operatorii ++ şi -- pentru a parcurge un vector:
#include <iostream>
using namespace std;

int main()
{
    int v[] = {1, 2, 3, 4, 5};
    for (int i = 0; i < 5; i++)
        cout << *(v+i) << ' '; // v[i]
    return 0;
}
Din acest motiv vectorii se transmit prin referinţă.
Deoarece ei sunt nişte pointeri constanţi a căror adresă (stocată în ei) este transmisă (prin valoare) parametrului funcţiei, care este un vector.
Acel vector accesează, şi eventual modifică, conţinutul de la adresa de memorie primită.
Un exemplu cu vectori dinamici. Programul cere utilizatorului să introducă o listă de numere, iar acesta calculează şi afişează suma lor:
#include <iostream>
using namespace std;

int main()
{
    int n, suma = 0;
    cout << "Cate numere vrei sa adun? "; cin >> n;
    int * nr = new int[n];
    cout << "Scrie numerele despartite prin spatiu: ";
    for (int i = 0; i < n; i++)
        cin >> nr[i];
    // Suma
    for (int i = 0; i < n; i++)
        suma += nr[i];
    cout << "Suma lor este: " << suma;
    delete[] nr;
    return 0;
}

Pointerii constanţi sunt de două feluri:
- Pointeri care nu pot modifica datele referite (date constante), dar care pot fi modificaţi (le puteţi atribui o altă adresă);

const tip* ptr;
- Pointeri care pot modifica datele referite (date neconstante), dar care nu pot fi modificaţi (nu le puteţi atribui o altă adresă);
tip* const ptr;
- Pointeri care nu pot modifica datele referite şi nu pot fi modificaţi.
const tip* const ptr;
Exemplu:
#include <iostream>
using namespace std;

int main()
{
    const int sz = 10;
    int money = 3000;

    const int* pSz = &money;
    int* const pMoney = &money;
    const int* const ptr = &sz;

    pSz = &sz; // OK!
// *pSz = 11;  // Eroare! Nu pot modifica variabila referita
    *pMoney = 4000;   // OK!
//  pMoney = &sz;  // Eroare! Pointer constant
//  ptr = &money;  // Eroare!
//  *ptr = 13;     // Eroare
    pSz = &money;

    // Eroare! Chiar daca money nu este const
    // Pointerul n-o poate modifica
//  *pSz = 5000;

    // Iar daca se incearca modificarea unei variabile const
    // Cu un pointer neconstant, din nou eroare
    // const int* este DIFERIT de int*
    // &sz returneaza o adresa (pointer) de tip const int*
//  int* pt = &sz;
//  *pt = 17;

    return 0;
}

Pointerii pot fi parametri ai unei funcţii.
Studiaţi cu atenţie exemplele următoare.

#include <iostream>
using namespace std;

void vreauPointer(int* p)
{
    cout << "In functie *p = " << *p << '\n';
    *p = 90;  // Modific continutul de la adresa memorata in p
    cout << "Dupa modificare *p = " << *p << '\n';
    int x = 12;
    // Modificarea pointerului este vizibila doar aici
    // Deoarece variabila pointer nu este
    // transmisa prin referinta
    p = &x;
    cout << "In functie p refera " << p << '\n';
}

int main()
{
    int q = 70;
    int* ptr = &q;
    cout << "In main q = " << q << '\n';
    cout << "In main *ptr = " << *ptr << '\n';
    cout << "In main ptr refera " << ptr << '\n';
    vreauPointer(ptr);
    cout << "Dupa apel q = " << q << '\n';
    cout << "Dupa apel *ptr = " << *ptr << '\n';
    cout << "Dupa apel ptr refera " << ptr << '\n';
    return 0;
}
Output:
In main q = 70
In main *ptr = 70
In main ptr refera 0x22fef8
In funtie *p = 70
In functie p refera 0x22fef8
Dupa modificare *p = 90
Dupa modificare p refera 0x22fe6c
Dupa apel q = 90
Dupa apel *ptr = 90
Dupa apel ptr refera 0x22fef8
Pointerii pot fi transmişi prin referinţă.
#include <iostream>
using namespace std;

void vreauPointer(int* &p)  // Referinta!
{
    cout << "In functie *p = " << *p << '\n';
    *p = 90;  // Modific continutul de la adresa memorata in p
    cout << "In functie p refera " << p << '\n';
    cout << "Dupa modificare *p = " << *p << '\n';
    int x = 12;  // Aceasta variabila va fi distrusa la sfarsitul functiei
    // Modificarea pointerului este vizibila in afara
    // Deoarece variabila pointer este
    // transmisa prin referinta
    p = &x;
    cout << "Dupa modificare p refera " << p << '\n';
}

int main()
{
    int q = 70;
    int* ptr = &q;
    cout << "In main q = " << q << '\n';
    cout << "In main *ptr = " << *ptr << '\n';
    cout << "In main ptr refera " << ptr << '\n';
    vreauPointer(ptr);
    cout << "Dupa apel q = " << q << '\n';
    cout << "Dupa apel *ptr = " << *ptr << '\n';
    cout << "Dupa apel ptr refera " << ptr << '\n';
    return 0;
}
Output:
In main q = 70
In main *ptr = 70
In main ptr refera 0x22fefc
In funtie *p = 70
In functie p refera 0x22fefc
Dupa modificare *p = 90
Dupa modificare p refera 0x22fe6c
Dupa apel q = 90
Dupa apel *ptr = 4683872
Dupa apel ptr refera 0x22fe6c
Observaţi că *ptr referă o valoare ciudată. Acest lucru se întâmplă deoarece ptr memorează adresa variabilei x din funcţia vreauPointer().
Când o funcţie îşi termină execuţia toate variabilele declarate în acea funcţie (+parametrii) sunt distruse (memoria ocupată de ele este eliberată).
La respectiva adresă rămâne numai gunoi (junk), un şir de 1 şi 0 care nu are niciun sens pentru programul vostru, dar acesta îşi face datoria şi afişează interpretarea int, în acest caz, a biţilor.
Acelaşi lucru se întâmplă şi când folosiţi un pointer neiniţializat sau o variabilă neiniţializată.

Referinţe

O referinţă este un alias pentru o variabilă.
Dacă variabila reprezintă o anumită zonă de memorie, şi referinţa va reprezenta aceeaşi zonă de memorie.
Referinţele se declară cu (&).

#include <iostream>
using namespace std;

int main()
{
    int x = 6;
    int& q = x;  // q refera acelasi spatiu de memorie ca si x
    q += 5;      // Referintele se folosesc normal ca si variabilele
    cout << x;   // se va afisa 11
    return 0;
}
Referinţele trebuie iniţializate!
Nu puteţi iniţializa o referinţă cu o constantă! Trebuie să fie un obiect / variabilă.
Puteţi avea referinţe constante care referă variabile constante.
#include <iostream>
using namespace std;

int main()
{
    const int p = 5;
    const int& x = p;
    cout << x; // 5
    return 0;
}
De îndată ce a fost iniţializată, referinţa nu mai poate fi schimbată. Nu mai aveţi cum.
Referinţa se comportă ca o altă variabilă.

miercuri, 12 iunie 2013

Tutorial C++: Funcţii (Subprograme)

Ce este o funcţie?

O funcţie (subprogram) este un grup de instrucţiuni apelabil (invocabil) din alte părţi ale programului. De exemplu, main este o funcţie.
A apela o funcţie înseamnă a o executa.
Funcţiile ajută la modularizarea programului, la structurarea acestuia în unităţi logice.
O funcţie se declară în felul următor:

tip nume(lista_param); // Prototipul functiei
Exemplu:
int suma(int a, int b);
lista_param - lista de parametri, despărţiţi prin virgulă, este opţională.
Parantezele sunt obligatorii. Ele deosebesc funcţiile de alte entităţi C++ (de exemplu, variabile).
void nimic(); // functie fara parametri
Tipul void reprezintă absenţa tipului. Cu alte cuvinte, funcţiile care nu returnează nicio valoare, au tipul void.
Funcţiile pot returna valori codului apelant (locul de unde a fost apelată funcţia) prin intermediul instrucţiunii return.
După declarare, o funcţie trebuie definită undeva în program.
#include <iostream>
using namespace std;

int suma(int, int); // Declarare

int main()
{
    int x, y;
    cout << "Da-mi doua numere intregi: ";
    cin >> x >> y;
    cout << "Suma lor este " << suma(x, y);
    return 0;
}

int suma(int a, int b) // Definire
{
    int rezultat = a + b;
    // Puteam scrie direct
    // return a + b;
    return rezultat;
}
Când declaraţi o funcţie, puteţi omite numele parametrilor (aşa cum am făcut în exemplu), dar trebuie să precizaţi tipul lor.
Parametrii sunt variabile locale funcţiei (vizibile numai în blocul funcţiei).
Instrucţiunea return întrerupe execuţia funcţiei şi returnează valoarea expresiei, din dreapta, codului apelant.
Într-o funcţie puteţi avea mai multe instrucţiuni return.
Observaţi cum se apelează o funcţie: suma(x, y);. Dacă funcţia nu are parametri, se apelează doar cu nume + paranteze: funcFaraParam();.
Când execuţia unei funcţii se termină, controlul programului revine în punctul apelării şi programul îşi continuă execuţia normal.
Funcţiile void nu returnează valori, ele doar îndeplinesc o sarcină, deci nu pot avea instrucţiunea return expr;.
Puteţi însă folosi return; ca să întrerupeţi execuţia unei funcţii void.
#include <iostream>
using namespace std;

void afiseazaText() // Declarare + Definire
{
    for (int i = 0; i < 5; i++)
        cout << "Hello functions!";
}

int main()
{
    // Se va afisa de 5 ori textul:
    // Hello functions!
    afiseazaText();
    return 0;
}
Puteţi declara şi defini o funcţie în acelaşi loc, dar numai înainte de main, altfel compilatorul nu va recunoaşte funcţia.
Funcţiile void nu pot fi folosite în expresii, deoarece expresiile au în componenţa lor operatori care aşteaptă valori, ori o funcţie void nu returnează nicio valoare.
O funcţie poate avea orice număr de parametri.
Variabilele declarate într-o funcţie sunt locale, adică sunt vizibile numai în respectiva funcţie. Ele nu pot fi folosite în afara ei (valabil şi pentru parametri).
Argumentele funcţiei sunt datele (variabile, expresii, constante, etc.) transmise funcţiei şi primite de parametrii acesteia.
De exemplu, în apelul suma(x, y); argumentele sunt variabilele x şi y.

Parametrii unei funcţii pot avea valori implicite (default). Parametrii default trebuie poziţionaţi la sfârşitul listei de parametri.
După ce aţi declarat un parametru implicit, nu mai aveţi voie să declaraţi parametri normali în continuare acestuia, ci doar parametri impliciţi.

#include <iostream>
using namespace std;

double arieCerc(double raza, double PI = 3.14)
{
    return 2 * PI * raza * raza;
}

int main()
{
    cout << "Aria cercului de raza 2 este " << arieCerc(2.0) << '\n'; // PI == 3.14
    cout << "Aria cercului de raza 2 este " << arieCerc(2.0, 3.141592);
    return 0;
}
Se va afişa
Aria cercului de raza 2 este 25.12
Aria cercului de raza 2 este 25.1327
Când nu transmiteţi o valoare parametrului implicit, compilatorul va folosi valoarea implicită, dată de voi.


Supraîncărcarea (Overloading) funcţiilor

Supraîncărcaţi o funcţie atunci când definiţi mai multe versiuni ale aceleiaşi funcţii.
Funcţia trebuie să aibă acelaşi nume, dar lista de parametri trebuie să difere prin numărul parametrilor sau prin tipul parametrilor (sau ambele).
Tipul funcţiei poate fi diferit, dar nu este necesar să fie aşa.
Versiunile diferă unele de altele numai prin lista de parametri!

#include <iostream>
using namespace std;

// Aria patratului
double arie(double lungime)
{
    return lungime * lungime;
}

// Aria dreptunghiului
double arie(double lungime, double latime)
{
    return lungime * latime;
}

int main()
{
    cout << "Aria patratului de L = 2 este " << arie(2.0) << '\n';
    cout << "Aria dreptunghiului de L = 3 si l = 4 este " << arie(3.0, 4.0);
    return 0;
}
Output:
Aria patratului de L = 2 este 4
Aria dreptunghiului de L = 3 si l = 4 este 12
Funcţia arie este supraîncărcată (overloaded). Am două versiuni care diferă prin numărul de parametri.
Compilatorul se foloseşte de tipul şi numărul de argumente ca să invoce funcţia corectă.


Transmiterea prin valoare şi prin referinţă

Atunci când transmiteţi argumente (şi sunt variabile) unei funcţii, transmiteţi de fapt o copie a acelor variabile.
Aceasta este transmiterea prin valoare (pass by value).
Orice modificare a parametrilor unei funcţii este vizibilă numai în acea funcţie.
Variabilele - folosite ca argumente - rămân nemodificate.

#include <iostream>
using namespace std;

void modifica(int a)
{
    a = a + 5;
    cout << "a are valoarea: " << a << '\n';
}

int main()
{
    int x = 1;
    cout << "x inainte de apel: " << x << '\n';
    modifica(x);
    cout << "x dupa apel: " << x;
    return 0;
}
Output:
x inainte de apel: 1
a are valoarea: 6
x dupa apel: 1
O funcţie poate returna decât o singură valoare printr-un return, ca la matematică.
Uneori vrem ca o funcţie să poată returna mai multe valori. Un mod prin care putem obţine acest lucru este transmiterea prin referinţă (pass by reference).
Atunci când transmiteţi prin referinţă, parametrii funcţiei alterează direct conţinutul variabilelor argumente (nu mai există nicio copie).
Ca să transmiteţi prin referinţă folosiţi ampersand (&) între tipul parametrului şi numele acestuia.
Parametrii referinţă acceptă numai variabile. NU acceptă constante (deoarece nu pot fi alterate)!
#include <iostream>
using namespace std;

void modifica(int& a) // Nu uitati de ampersand !
{
    a = a + 5;
    cout << "a are valoarea: " << a << '\n';
}

int main()
{
    int x = 1;
    cout << "x inainte de apel: " << x << '\n';
    modifica(x);
    cout << "x dupa apel: " << x;
    return 0;
}
Output:
x inainte de apel: 1
a are valoarea: 6
x dupa apel: 6

Vectorii se transmit prin referinţă!

#include <iostream>
using namespace std;

void modifica(int v[], int l)
{
    for (int i = 0; i < l; i++)
        v[i] += 5; // v[i] = v[i] + 5;
}

int main()
{
    int w[] = {1, 2, 3, 4}, k = 4;
    modifica(w, k);

    for (int i = 0; i < k; i++)
        cout << w[i] << ' ';

    return 0;
}
Output:
6 7 8 9
Observaţi cum se transmite un vector ca parametru.
Numărul de elemente poate lipsi (valabil doar pentru prima dimensiune).
Dacă aş fi avut o matrice de 2 x 3, atunci aş fi scris:
void modifica(int v[][3], ...) ...
Veţi înţelege mai bine aceste lucruri după ce veţi învăţa ceva despre pointeri.


Funcţii recursive

O funcţie care se autoapelează se numeşte recursivă.
Aveţi grijă ca funcţia să aibă o condiţie de terminare, altfel puteţi crea o repetiţie infinită.
Parametrii funcţiilor şi variabilele locale sunt încărcate, stocate, pe Stivă (o regiune din memorie structurată pe principiul stivei, LIFO), iar în cazul unei funcţii recursive infinite, Stiva se poate umple repede (stack overflow) cauzând un crash al programului.
Factorialul unui număr poate fi calculat cu o funcţie recursivă (deşi se poate face acelaşi lucru şi cu un loop).

#include <iostream>
using namespace std;

int fact(int n)
{
    if (n == 0) // Conditia de terminare
        return 1;
    else
        return n * fact(n - 1);
}

int main()
{
    cout << fact(0) << '\n';
    cout << fact(3) << '\n';
    cout << fact(8) << '\n';
    return 0;
}
Output:
1
6
40320
Ca să înţelegeţi mai bine mecanismul recursivităţii vizionaţi acest clip de pe Youtube:


Funcţii inline

Atunci când o funcţie este apelată, parametrii şi variabilele locale sunt încărcate pe Stivă.
Acest proces consumă resurse şi timp de execuţie.
Pentru funcţiile ce efectuează puţine operaţii, apelarea poate costa mai mult timp şi spaţiu de execuţie decât dacă aceste operaţii ar fi executate direct în cod.
Din acest motiv există keyword-ul inline.
Cu inline programatorii pot cere compilatorului să insereze blocul funcţiei în punctul apelării, în loc să creeze instrucţiunile de apel.
O funcţie se declară inline în felul următor:

#include <iostream>
using namespace std;

inline double myPI()
{
    return 3.14159265359;
}

int main()
{
    cout << myPI();
    return 0;
}
Nu declaraţi inline funcţii recursive sau funcţii complexe!
Mărimea programului poate creşte considerabil.
Compilatoarele moderne de astăzi optimizează automat codul sursă.
Unele dintre ele vor refuza inline, chiar dacă programatorul le cere explicit (de exemplu, compilatoarele care optimizează mărimea programului), altele vor face automat inline anumite funcţii (compilatoarele optimizate pentru viteză), chiar dacă programatorul nu cere acest lucru.

marți, 11 iunie 2013

Tutorial C++: Vectori - Tablouri de memorie

Vectori

Un vector (array), sau tablou de memorie este o colecţie de date stocate în locaţii succesive din memorie.
Toate elementele vectorului au acelaşi tip de dată. Un vector se declară în felul următor:

tip nume[numar_elemente];
De exemplu, un vector cu 100 de întregi.
int yoyo[100];
Numărătoarea elementelor începe de la zero.
Putem accesa şi modifica un element de-al vectorului cu următoarea sintaxă:
nume[index];
De exemplu, pot atribui elementului al treilea valoarea 10.
yoyo[2] = 10;
ATENŢIE: Primul element este yoyo[0], iar ultimul este yoyo[99].
Aceşti vectori se zic statici (static arrays) deoarece numărul de elemente şi spaţiul de memorie ocupat este constant în timpul compilării. (numar_elemente trebuie să fie o constantă).
Vectorii declaraţi global, în afara oricărei funcţii, sunt iniţializaţi cu zero.
La declarare vectorii pot fi iniţializaţi, aşa cum sunt iniţializate variabilele, dar cu o sintaxă diferită.
int vec[3] = {1, 2, 3};
Sau, doar în acest caz, puteţi omite numărul de elemente:
int vec[] = {1, 2, 3};
Deoarece lungimea va fi dedusă de compilator pe baza numărului de date dintre acolade.
Parcurgerea unui vector se face cel mai simplu cu un loop, în general un for.
#include <iostream>
using namespace std;

int main()
{
    int cifrePare[] = { 0, 2, 4, 6, 8 };
    for (int i = 0; i < 5; i++)
        cout << cifrePare[i];
    return 0;
}
ATENŢIE: Accesarea elementelor din afara vectorului - cum ar fi cifrePare[5] - poate produce rezultate neaşteptate, ca instabilitatea programului sau, în cel mai rău caz, un crash al programului.

Vectori multidimensionali

Vectorii multidimensionali sunt vectori ai vectorilor. Sintaxa generală de declarare este:

tip nume[nr_elem1][nr_elem2]...[nr_elemN]
Aşa cum vedeţi, un vector poate avea oricâte dimensiuni vreţi. Totuşi, spaţiu de memorie fiind limitat, nu veţi întâlni vectori cu mai mult de 3 dimensiuni.
Un vector bidimensional poate fi imaginat ca o matrice (un tabel cu linii şi coloane).
De exemplu, matricea int mat[5][3]; are 5 linii şi 3 coloane.
În realitate, în memorie, elementele tot în ordine secvenţială sunt stocate. Vectorul (matricea) de mai sus este echivalent cu int mat[15];.
Iniţializarea este similară cu cea a vectorilor unidimensionali:
int mat[2][3] = { {1, 2, 3}, {4, 5, 6} };
Parcurgerea se face tot cu un for:
#include <iostream>
using namespace std;

int main()
{
    int cifre[2][3] = { {1, 2, 3}, {4, 5, 6} };
    for (int i = 0; i < 2; i++) {
        for (int j = 0; j < 3; j++)
            cout << cifre[i][j] << ' ';
        cout << '\n';
    }
    return 0;
}
Observaţi cum se accesează elementul de pe linia i şi coloana j.
cifre[i][j];
Programul va afişa:
1 2 3
4 5 6
Primul element al matricei este cifre[0][0], adică 1.

Dacă nu vă place numărătoarea de la zero, puteţi declara vectorul cu 1 + nr_elemente, astfel încât să începeţi numărătoarea de la 1, dar să aveţi spaţiu pentru nr_elemente. Exemplu:

#include <iostream>
using namespace std;

int main()
{
    int yoyo[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    for (int i = 1; i < 10; i++)
        cout << yoyo[i] << i << ' ';
    return 0;
}
Acum indicele i coincide cu yoyo[i].
Primul element al vectorului, yoyo[0], este ignorat, şi al doilea, yoyo[1], este considerat noul prim.

vineri, 7 iunie 2013

Tutorial C++: Booleeni şi Structuri de Control

Tipul boolean

Tipul boolean sau tipul de dată logic este tipul ce are două valori: true şi false.
Acest tip este folosit în contextele booleene, acolo unde expresia este evaluată* la true sau false. De exemplu, în condiţia de la if sau while.
O variabilă de tip bool se declară normal, ca orice variabilă:

bool x = true;
În contextele booleene din C/C++, valorile pozitive sunt evaluate la true, iar ZERO este evaluat la false.
De exemplu:
if (1) cout << "TRUE";
Se va afişa mesajul TRUE deoarece 1 este interpretat ca fiind true.

Operatorii care returnează un rezultat booleean sunt operatorii relaţionali şi operatorii logici.
Operatorii relaţionali sunt:

< // Mai mic
<= // Mai mic sau egal
== // Egal
!= // Diferit
> // Mai mare
>= // Mai mare sau eagl
În general sunt folosiţi cu numere (întregi, reali), dar veţi vedea că pot fi definiţi şi pentru alte tipuri.
Opeatorii logici sunt:
&& // AND - SI LOGIC
|| // OR - SAU LOGIC
! // NOT - NEGATIE LOGICA
Operatorul AND returnează true dacă ambii operanzi sunt true.
Operatorul OR returnează true dacă cel puţin unul dintre operanzi este true.
Operatorul NOT inversează valoarea booleană a expresiei, variabilei, etc.
C++ foloseşte evaluarea scurtă.
Pentru AND: Dacă primul operand este false atunci întreaga expresie este false; nu se mai evaluează operandul al doilea.
Pentru OR: Dacă primul operand este true atunci întreaga expresie este true; nu se mai evaluează operandul al doilea.
Dintre aceşti operatori, NOT are prioritatea cea mai mare, urmat de operatorii relaţionali, apoi (în această ordine) AND şi OR.
Tabelul cu precedenţa operatorilor.

Structuri decizionale

Instrucţiunea if permite execuţia unei porţiuni de cod* doar dacă o anumită condiţie este îndeplinită. Condiţia este dată de programator.

#include <iostream>
using namespace std;

int main()
{
    int a = 4;
    if (a >= 4) {
        cout << "Hi!";
    } else {
        cout << "Bye!";
    }
    return 0;
}
Se va afişa if deoarece variabila a memorează 4, iar 4 este mai mare sau egal cu 4.
Ramura else este executată atunci când condiţia de la if nu este îndeplinită. Dacă if se execută atunci programul ignoră ramura else.
În cazul unei singure instrucţiuni (valabil pentru toate structurile de control) acoladele pot lipsi.
După else poate urma un alt if, şi un altul, până când cascada de if else se termină în else sau nu se termină în nimic, deoarece else este opţional.
#include <iostream>
using namespace std;

int main()
{
    double nr;
    cin >> nr;
    if (nr >= 2) {
        cout << "Ai introdus un numar mai mare sau egal cu 2.";
    } else if (nr < 0) {
        cout << "Ai introdus un numar negativ";
    } else {
        cout << "Ai introdus un numar din intervalul [0, 2)";
    }
    return 0;
}
Aici, ultimul else este executat numai dacă ramurile superioare nu sunt executate.
Condiţia de la if (ca şi la celelalte structuri) poate fi orice: constantă, variabile, expresie, orice se poate evalua la true sau false.
Mod de funcţionare if:
  1. Se evaluează condiţia. Dacă este true se trece la pasul următor; dacă este false se trece la pasul 3.
  2. Se execută codul din blocul if (dintre acolade) şi se trece la pasul 4.
  3. Se execută ramura else şi se trece la pasul 4.
  4. Se iese din if şi se continuă execuţia programului.

Instrucţiunea switch este, într-un fel, similară cu if. Permite execuţia anumitor instrucţiuni în funcţie de rezultatul unei expresii, variabile, etc.

#include <iostream>
using namespace std;

int main()
{
    int nr;
    cin >> nr;
    switch(nr)
    {
        case 1: cout << "Ai introdus 1"; break;
        case 2: cout << "Ai introdus 2"; break;
        default: cout << "Ai introdus un numar diferit de 1 si 2"; break;
    }
    return 0;
}
Instrucţiunea switch compară rezultatul expresiei dintre paranteze (aici este variabila nr) cu fiecare caz şi-l execută atunci când găseşte o potrivire.
Expresiile de la case trebuie să fie constante.
Fără break, switch ar evalua în continuare celelalte cazuri chiar dacă l-ar găsi pe cel potrivit.
Omiterea instrucţiunii break nu generează o eroare, deoarece este opţional.
Ultimul caz, default (implicit), se execută atunci când nu există potriviri cu celelalte cazuri.
Un exemplu care arată cum puteţi valorifica lipsa break-ului:
#include <iostream>
using namespace std;

int main()
{
    int nr;
    cin >> nr;
    switch(nr)
    {
        case 1: case 2: case 3: case 4: case 5: case 6: case 7:
            cout << "Ai introdus ziua a " << nr << "-a"; break;
        default:
            cout << "Te rog sa introduci numarul valid al unei zile";
    }
    return 0;
}
Să zicem că nr este 2. Se execută cazul 2, apoi, deoarece nu există break, se trece la 3, apoi la 4, ... până la 7 unde există break şi se iese din switch.

Structuri iterative - Bucle - Loop-uri

Instrucţiunea while repetă o porţiune de cod până când condiţia devine falsă. Dacă condiţia nu devine falsă la un moment dat, while va continua să ruleze (până când programul este oprit forţat de programator / user).

#include <iostream>
using namespace std;

int main()
{
    int nr;
    cout << "Ca sa inchizi programul scrie 0.\n";
    cin >> nr;
    while (nr != 0) { // Cat timp nr nu este 0
        cout << nr * 2 << '\n';
        cin >> nr;
    }
    return 0;
}
Condiţia de la while, în acest exemplu, este nr != 0.
Atunci când folosim loopuri trebuie să ne asigurăm că aceste condiţii vor deveni false în cele din urmă, ca să evităm repetiţiile infinite.
În exemplul meu, decizia de a opri programul (care în fond înseamnă oprirea iteraţiei) îi aparţine utilizatorului, prin acel cin >> nr; din while.
Mod de funcţionare while:
  1. Se evaluează condiţia. Dacă este true se trece la pasul următor. Dacă este false se trece la pasul 4.
  2. Se execută codul din blocul while (dintre acolade).
  3. Se trece la pasul 1.
  4. Se iese din while şi se continuă execuţia programului.

O variaţie a structurii while este do { } while(conditie); Spre deosebire de while, codul în do while este executat cel puţin o dată indiferent de condiţie.

#include <iostream>
using namespace std;

int main()
{
    bool conditie = false;
    do {
        cout << "In while :)";
    } while (conditie);
    return 0;
}
Se va afişa In while :) deoarece evaluarea se face la final.
Observaţi ; după while. Este obligatoriu.

Instrucţiunea for repetă de un anumit număr de ori o porţiune de cod.

#include <iostream>
using namespace std;

int main()
{
    for (int i = 1; i <= 10; i++) {
        cout << i << ' ';
    }
    return 0;
}
Se va afişa: 1 2 3 4 5 6 7 8 9 10.
for (initializare; conditie; incrementare)
Instrucţiunea for are trei părţi:
  • La iniţializare se declară (şi se iniţializează) variabila (sau variabilele) contor (folosită la numărat).
  • La conditie avem o expresie de terminare a iteraţiei.
  • La incrementare (sau decrementare) variabila contor este modificată astfel încât condiţia să poată deveni la un moment dat falsă.
Toate cele trei părţi sunt opţionale. Nu este obligatoriu să declaraţi variabila contor în for, deşi dacă o declaraţi acolo, ea va fi vizibilă numai în blocul for.
#include <iostream>
using namespace std;

int main()
{
    for (int i = 10; i > 0; i-=2) {
        cout << i << ' ';
    }
    return 0;
}
Se va afişa: 10 8 6 4 2. Mod de funcţionare:
  1. Se iniţializează variabilele (Această parte se execută o singură dată).
  2. Se evaluează condiţia. Dacă este true se trece pasul următor. Dacă este false se trece la pasul 5.
  3. Se execută codul din blocul for.
  4. Se execută instrucţiunea de la incrementare.
  5. Se iese din for şi se continuă execuţia programului.

Prin cod se înţelege o instrucţiune sau grup de instrucţiuni.
A evalua este echivalent cu a executa.