Conversia la standardul ANSI/ISO C++
Este posibil sa aveti programe (sau obisnuinte de programare) create în C sau în versiuni mai vechi de C++ pe care doriti sa le convertiti în C++ Standard. Aceasta anexa contine câteva îndrumari. Unele se refera la trecerea de la C la C++, altele de la C++ mai vechi la C++ Standard.
Directive pentru preprocesor
Preprocesorul C/C++ furnizeaza mai multe directive. În general, în practica C++, se folosesc directivele create pentru gestionarea procesului de compilare si se evita utilizarea directivelor ca substitut pentru cod. De exemplu, directiva #include este o componenta esentiala pentru gestionarea fisierelor program. Alte directive, cum ar fi #ifndef si #endif, va permit sa specificati compilarea sau excluderea anumitor blocuri de cod. Directiva #pragma va permite sa controlati anumite optiuni de compilare specifice compilatorului. Toate acestea sunt instrumente utile si uneori strict nec 10210b16k esare. În schimb, folositi cu precautie directiva #define
Utilizati const în locul #define pentru definirea constantelor
Codul devine mai usor de citit si de întretinut utilizând constantele simbolice. Numele constantei ar trebui sa indice sensul acesteia, iar daca trebuie sa schimbati valoarea, o schimbati o singura data, în definitie, iar apoi compilati programul din nou. În C se folosea pentru acest scop preprocesorul:
#define LUNG_MAX 100
Preprocesorul efectueaza apoi o substitutie de text în codul sursa, înlocuind aparitiile lui LUNG_MAX cu înainte de compilare.
Abordarea C++ este aplicarea modificatorului const unei declarari de variabila:
const int LUNG_MAX = 100;
Astfel LUNG_MAX este considerat un int care poate fi numai citit.
Exista mai multe avantaje în folosirea const. În primul rând, declaratia specifica explicit tipul. În cazul #define, trebuie sa folositi mai multe sufixe dupa un numar pentru a indica alte tipuri în afara de char int si double; de exemplu, 100L indica tipul long sau 3.14F indica tipul float. Dar mai important este ca abordarea care foloseste const poate fi folosita si pentru tipuri derivate:
const int val_baza[5] = ;
const string rasp[3] = ;
În sfârsit, identificatorii const se supun regulilor de vizibilitate ca si variabilele. Prin urmare, puteti crea constante cu vizibilitate globala, cu vizibilitate de zona de nume sau cu vizibilitate de bloc. Daca, sa zicem, definiti o constanta într-o functie, nu trebuie sa va gânditi la conflictele de nume cu o constanta globala folosita undeva în program. De exemplu, în urmatorul cod:
#define n 5
const int cl = 12;
...
void clocot()
Preprocesorul va înlocui
int n;
cu
int 5;
rezultând o eroare de compilare. În schimb cl din clocot() este o variabila locala. Daca este necesar, clocot() va utiliza operatorul de vizibilitate pentru a accesa constanta ca ::cl
C a împrumutat cuvântul cheie const din C++, dar varianta C++ este mult mai utila. De exemplu, versiunea C++ foloseste legaturile interne pentru valorile const externe, în locul legaturilor externe prestabilite folosite de variabile si de valorile const C. Deci fiecare fisier al unui program, care foloseste un const are nevoie de definitia acelui const în interiorul sau. Pare sa duca la munca în plus, dar de fapt simplifica lucrurile. Având legare interna puteti plasa definitiile const într-un fisier antet care va fi folosit de toate fisierele proiectului care au nevoie. Aceasta duce la o eroare de compilare pentru legarea externa, dar nu si pentru legarea interna. De asemenea, deoarece o valoare const trebuie sa fie definita în fisierul care o foloseste (daca este definita în fisierul antet folosit de acesta este la fel de corect), puteti folosi valori const ca argumente pentru dimensiunile tablourilor:
const int LUNG_MAX = 100;
...
double incarca[LUNG_MAX];
for (int i = 0; i < LUNG_MAX; i++)
incarca[i] = 50;
Acest cod nu functioneaza în C deoarece definitia LUNG_MAX poate fi într-un fisier separat si astfel nu este disponibila când se compileaza acest fisier. Ca sa fim drepti, si în C puteti crea constante cu legare interna folosind modificatorul static. Dar în C++, static fiind prestabilit, nu mai trebuie sa va amintiti de el.
Directiva #define ramâne însa o parte utila a jargonului standard pentru controlul compilarii unui fisier antet:
// gafa.h
#ifndef _GAFA_H_
#define _GAFA_H_
// aici e codul
#endif
Pentru constantele simbolice tipice obisnuiti-va sa folositi const în locul #define. O alternativa potrivita, mai ales în cazul unor constante întregi înrudite, este utilizarea enum
enum ;
Utilizati inline în locul #define pentru definirea functiilor scurte
În C, modul traditional de definire a functiilor in-line (de fapt un fel de echivalent al acestora) era utilizarea unei definitii de macrocomanda cu #define
#define Cub(X) X*X*X
Preprocesorul executa substitutia de text, X fiind înlocuit de argumentul corespunzator al Cub()
y = Cub(x); // se inlocuieste cu y = x*x*x;
y = Cub(x + z++); // se inlocuieste cu
// y = x + z++*x + z++*x + z++;
Deoarece preprocesorul foloseste substitutia si nu transferul argumentului, utilizarea unei astfel de macrocomenzi poate avea rezultate neasteptate si incorecte. Numarul erorilor poate fi redus folosind din plin parantezele pentru a asigura ordinea corecta a operatiilor:
#define Cub(X) ((X)*(X)*(X))
Chiar si asa, cazurile cum este cel cu z++ nu sunt rezolvate.
Abordarea C++ care foloseste cuvântul cheie inline pentru a identifica functiile in-line este mult mai de încredere deoarece foloseste într-adevar transferul argumentelor. În plus, functiile in-line C++ pot fi functii obisnuite sau metode ale claselor.
O caracteristica pozitiva a abordarii cu #define este ca nu depinde de tip si se poate aplica cu orice tip pentru care operatia are sens. În C++ puteti scrie sabloane in-line pentru a avea functii in-line independente de tip si în acelasi timp un transfer real al argumentelor.
Pe scurt, în C++ folositi inline în loc de macrocomenzile C cu #define
Utilizati prototipuri de functii
De fapt, nu aveti de ales. Prototipurile sunt optionale în C, dar în C++ au devenit obligatorii. Retineti ca o definitie de functie care apare înainte de prima utilizare a functiei, cum ar fi cazul functiilor in-line serveste si ca prototip.
Folositi atunci când se poate const în prototipurile si anteturile functiilor. În particular, utilizati const cu parametri pointeri sau referinte care reprezinta date ce nu trebuie modificate. Astfel compilatorul va putea sa detecteze erorile care modifica datele, iar functia devine mai generala. Adica o functie cu un pointer sau o referinta const poate fi utilizata atât cu date const cât si cu cele care nu sunt const, pe când o functie care nu foloseste const poate prelucra numai datele care nu sunt const
Conversii de tip
Principala nemultumire legata de C a lui Stroustrup este indisciplina operatorului de conversie a tipului. Conversiile de tip sunt într-adevar necesare, dar conversia standard este mult prea liberala. De exemplu, sa vedem urmatorul cod:
struct Duba
;
Duba plici;
short * ps = (short *) & plici; // sintaxa veche
int * pi = int * (&plici); // sintaxa noua
Limbajul nu va împiedica sa convertiti un pointer la un pointer de un tip fara nici o legatura cu primul.
Într-un fel, situatia este similara cu cea a instructiunii goto. Problema acesteia era ca avea o flexibilitate prea mare, rezultând un cod încurcat. Solutia era sa se introduca versiuni mai limitate si mai structurate ale goto, pentru a realiza actiunile tipice pentru care era necesara goto. Astfel au aparut elemente de limbaj ca buclele for si while sau instructiunea if else. În C++ Standard exista o solutie similara pentru conversiile de tip indisciplinate, si anume, conversiile restrictive, care se folosesc în situatiile obisnuite în care sunt necesare conversii de tip. Acestea se realizeaza prin operatorii de conversie de tip discutati în Capitolul 14:
dynamic_cast
static_cast
const_cast
reinterpret_cast
Deci, daca realizati conversii de tip ce implica pointeri, utilizati daca este posibil, unul dintre operatorii de mai sus. Astfel, va documentati intentiile, iar compilatorul va verifica daca într-adevar se întâmpla ceea ce doriti.
Familiarizati-va cu caracteristicile C++
Daca foloseati malloc() si free(), treceti la new si delete. Daca foloseati setjmp() si longjmp() pentru tratarea erorilor, folositi de acum try throw si catch. Utilizati tipul bool pentru valori care reprezinta adevarat sau fals.
Utilizati noua organizare a fisierelor antet
Standardul specifica nume noi pentru fisierele antet, asa cum ati vazut în Capitolul 2. Daca ati folosit fisierele antet în stil vechi, ar trebui sa treceti la numele în stil nou. Nu este doar o schimbare cosmetica, deoarece versiunile noi pot adauga noi caracteristici. De exemplu, fisierul antet ostream asigura folosirea caracterelor extinse pentru intrari si iesiri. De asemenea, furnizeaza manipulatori noi cum ar fi boolalpha si fixed (descrisi în Capitolul 16). Acestia ofera o interfata mai comoda decât setf() sau functiile iomanip pentru setarea optiunilor de formatare. Daca folositi setf(), utilizati ios_base în loc de ios când specificati constante; adica folositi ios_base::fixed în loc de ios::fixed. De asemenea, noile fisiere antet încorporeaza zone de nume.
Utilizati zonele de nume
Zonele de nume va permit sa organizati identificatorii pe care îi folositi într-un program pentru a evita conflictele de nume. Deoarece biblioteca standard, asa cum este implementata în noile fisiere antet, plaseaza numele în zona de nume std, utilizarea acestor fisiere necesita folosirea zonelor de nume.
În exemplele din aceasta carte, pentru simplitate, se foloseste o directiva de utilizare pentru ca toate numele din zona de nume std sa devina disponibile:
#include <iostream>
#include <string>
#include <vector>
using namespace std; // directiva de utilizare
Totusi, exportarea tuturor numelor dintr-o zona de nume, ca este nevoie de ele sau nu, este în opozitie cu scopul introducerii zonelor de nume.
Abordarea recomandata este sa utilizati directive de utilizare sau operatorul de vizibilitate ( ) pentru a face disponibile numai denumirile utile în program. De exemplu, folosind
#include <iostream>
using std::cin; // declaratie de utilizare
using std::cout;
using std::endl;
cin cout si endl devin disponibile în restul fisierului. Utilizarea operatorului de vizibilitate face ca numele sa fie disponibil doar în expresia în care este folosit operatorul:
cout << std::fixed << x << endl; // utilizarea operatorului de
// vizibilitate
Poate deveni obositor, dar puteti aduna toate declaratiile de utilizare obisnuite într-un fisier antet:
// numelemele - - un fisier antet
#include <iostream>
using std::cin; // declaratie de utilizare
using std::cout;
using std::endl;
Puteti chiar mai mult, puteti aduna declaratiile de utilizare în zone de nume:
// numelemele - - un fisier antet
#include <iostream>
namespace io
namespace formate
Atunci, un program poate include acest fisier si poate folosi numai zona de nume care este utila:
#include "numelemele"
using namespace io;
Utilizati sablonul autoptr
Fiecarei utilizari a lui new trebuie sa îi corespunda o utilizare a lui delete. Pot sa apara probleme daca functia în care se foloseste new se termina prematur prin aruncarea unei exceptii. Cum am vazut în Capitolul 15, daca utilizati un obiect auto_ptr pentru un obiect creat prin new va asigurati ca delete va fi folosit în mod automat si în orice conditie.
Utilizati clasa string
sirurile traditionale în stil C nu sunt efectiv un tip. Puteti stoca un sir într-un tablou de caractere si puteti initializa un tablou de caractere cu un sir. În schimb nu puteti atribui un sir la un tablou de caractere folosind un operator de atribuire; trebuie sa va amintiti sa folositi strcpy() sau strncpy(). Nu puteti folosi operatorii relationali pentru a compara doua siruri în stil C, trebuie sa folositi strcmp(). (Daca uitati, si folositi sa zicem operatorul >, nu va rezulta o eroare de sintaxa; de fapt, programul va compara adresele celor doua siruri, nu continuturile lor.)
Pe de alta parte, clasa string (Capitolul 15 si Anexa F) va permite sa folositi obiecte pentru a reprezenta siruri. Sunt definite atribuirea, operatorii relationali si operatorul de adunare (pentru concatenare). În plus, clasa string asigura gestiunea automata a memoriei deci nu mai trebuie sa va faceti griji ca un utilizator va introduce un sir care sau depaseste spatiul alocat sau va fi scurtat înainte de stocare.
Clasa string asigura numeroase metode comode si oportune. De exemplu, puteti adauga un obiect string la altul, dar puteti adauga de asemenea si un sir în stil C sau o valoare char la un obiect string. Pentru functiile care cer un argument de tip sir în stil C, puteti folosi metoda c_str() care returneaza un pointer la char potrivit.
Clasa string furnizeaza un set de metode legate de manipularea sirurilor, cum ar fi gasirea de subsiruri, foarte bine proiectat si care în plus este compatibil cu design-ul STL, deci puteti folosi algoritmii STL si cu obiecte string
Utilizati STL
Biblioteca de sabloane standard (STL) (Capitolul 15 si Anexa G) asigura solutii gata facute pentru multe cerinte de programare, deci încercati sa o folositi. De exemplu, în loc sa declarati un tablou de double sau de obiecte string, puteti crea un obiect vector<double> sau un obiect vector<string>. Avantajul este similar cu cel pe care l-am subliniat în cazul utilizarii obiectelor string în locul sirurilor în stil C. Este definita atribuirea, deci puteti folosi operatorul de atribuire pentru a atribui un obiect vector la altul. Puteti transfera un obiect vector prin referinta, iar o functie care primeste un astfel de obiect poate folosi metoda size() pentru a determina numarul de elemente din obiectul vector. Gestiunea memoriei este încorporata în design si permite dimensionarea automata a obiectului vector când îi adaugati elemente prin metoda push_back(). Bineînteles, o multime de metode ale clasei si de algoritmi generali va stau la îndemâna.
Daca aveti nevoie de o lista, de o coada cu doua capete (deque), de o stiva, de o coada normala, de o multime, de un obiect map, în STL veti gasi sabloane utile pentru toate aceste containere. Biblioteca de algoritmi este proiectata astfel încât sa puteti copia usor continutul unui vector într-o lista sau sa puteti compara continutul unei multimi cu un vector. Design-ul va permite sa folositi componentele STL ca niste piese pe care puteti sa le asamblati dupa necesitati.
Unul dintre principalele scopuri ale bibliotecii de algoritmi a fost eficienta acestora, deci puteti obtine programe performante cu un efort de programare minim. Conceptul de iterator folosit pentru implementarea algoritmilor îi face utili si în cazul unor obiecte care nu sunt componente STL. În particular, acestia se pot folosi si pentru tablourile normale.
|