Conversia la standardul ANSI ISO C++



Conversia la standardul ANSI/ISO C++

Este posibil sa aveti programe (sau obisnuinte de programare) create in C sau in versiuni mai vechi de C++ pe care doriti sa le convertiti in C++ Standard. Aceasta anexa contine cateva indrumari. 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. In general, in 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 necesare. In schimb, folositi cu precautie directiva #define.



Utilizati const in locul #define pentru definirea constantelor

Codul devine mai usor de citit si de intretinut utilizand constantele simbolice. Numele constantei ar trebui sa indice sensul acesteia, iar daca trebuie sa schimbati valoarea, o schimbati o singura data, in definitie, iar apoi compilati programul din nou. In C se folosea pentru acest scop preprocesorul:

#define LUNG_MAX 100

Preprocesorul efectueaza apoi o substitutie de text in codul sursa, inlocuind aparitiile lui LUNG_MAX cu 100 inainte de compilare.

Abordarea C++ este aplicarea modificatorului const unei declarari de variabila:

const int LUNG_MAX = 100; 37957vmg14idq3q

Astfel LUNG_MAX este considerat un int care poate fi numai citit.

Exista mai multe avantaje in folosirea const. In primul rand, declaratia specifica explicit tipul. In cazul #define, trebuie sa folositi mai multe sufixe dupa un numar pentru a indica alte tipuri in 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] = {1000, 2000, 3500, 6000, 10000};

const string rasp[3] = {”da”, ”nu”, ”poate”};

In sfarsit, 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 intr-o functie, nu trebuie sa va ganditi la conflictele de nume cu o constanta globala folosita undeva in program. De exemplu, in urmatorul cod:

#define n 5

const int cl = 12; md957v7314iddq

...

void clocot()

{

int n;

int cl;

...

}

Preprocesorul va inlocui

int n;

cu

int 5;

rezultand o eroare de compilare. In 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 imprumutat cuvantul cheie const din C++, dar varianta C++ este mult mai utila. De exemplu, versiunea C++ foloseste legaturile interne pentru valorile const externe, in 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 in interiorul sau. Pare sa duca la munca in plus, dar de fapt simplifica lucrurile. Avand legare interna puteti plasa definitiile const intr-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 in fisierul care o foloseste (daca este definita in fisierul antet folosit de acesta este la fel de corect), puteti folosi valori const ca argumente pentru dimensiunile tablourilor:

const int LUNG_MAX = 100; 37957vmg14idq3q

...

double incarca[LUNG_MAX];

for (int i = 0; i < LUNG_MAX; i++)

incarca[i] = 50;

Acest cod nu functioneaza in C deoarece definitia LUNG_MAX poate fi intr-un fisier separat si astfel nu este disponibila cand se compileaza acest fisier. Ca sa fim drepti, si in C puteti crea constante cu legare interna folosind modificatorul static. Dar in C++, static fiind prestabilit, nu mai trebuie sa va amintiti de el.

Directiva #define ramane insa 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 in locul #define. O alternativa potrivita, mai ales in cazul unor constante intregi inrudite, este utilizarea enum:

enum {NIVEL1 = 1, NIVEL2 = 2, NIVEL3 = 4, NIVEL4 = 8};

Utilizati inline in locul #define pentru definirea functiilor scurte

In 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 inlocuit 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 cuvantul cheie inline pentru a identifica functiile in-line este mult mai de incredere deoarece foloseste intr-adevar transferul argumentelor. In 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. In C++ puteti scrie sabloane in-line pentru a avea functii in-line independente de tip si in acelasi timp un transfer real al argumentelor.

Pe scurt, in C++ folositi inline in loc de macrocomenzile C cu #define.

Utilizati prototipuri de functii

De fapt, nu aveti de ales. Prototipurile sunt optionale in C, dar in C++ au devenit obligatorii. Retineti ca o definitie de functie care apare inainte de prima utilizare a functiei, cum ar fi cazul functiilor in-line serveste si ca prototip.

Folositi atunci cand se poate const in prototipurile si anteturile functiilor. In 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 atat cu date const cat si cu cele care nu sunt const, pe cand 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 intr-adevar necesare, dar conversia standard este mult prea liberala. De exemplu, sa vedem urmatorul cod:

struct Duba

{

double buf;

double bif;

char spif[10];

};

Duba plici;

short * ps = (short *) & plici; // sintaxa veche

int * pi = int * (&plici); // sintaxa noua

Limbajul nu va impiedica sa convertiti un pointer la un pointer de un tip fara nici o legatura cu primul.

Intr-un fel, situatia este similara cu cea a instructiunii goto. Problema acesteia era ca avea o flexibilitate prea mare, rezultand un cod incurcat. 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. In C++ Standard exista o solutie similara pentru conversiile de tip indisciplinate, si anume, conversiile restrictive, care se folosesc in situatiile obisnuite in care sunt necesare conversii de tip. Acestea se realizeaza prin operatorii de conversie de tip discutati in 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 intr-adevar se intampla 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 in Capitolul 2. Daca ati folosit fisierele antet in stil vechi, ar trebui sa treceti la numele in 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 in Capitolul 16). Acestia ofera o interfata mai comoda decat setf() sau functiile iomanip pentru setarea optiunilor de formatare. Daca folositi setf(), utilizati ios_base in loc de ios cand specificati constante; adica folositi ios_base::fixed in loc de ios::fixed. De asemenea, noile fisiere antet incorporeaza zone de nume.

Utilizati zonele de nume

Zonele de nume va permit sa organizati identificatorii pe care ii folositi intr-un program pentru a evita conflictele de nume. Deoarece biblioteca standard, asa cum este implementata in noile fisiere antet, plaseaza numele in zona de nume std, utilizarea acestor fisiere necesita folosirea zonelor de nume.

In 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 in 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 in program. De exemplu, folosind

#include <iostream>

using std::cin; // declaratie de utilizare

using std::cout;

using std::endl;

cin, cout si endl devin disponibile in restul fisierului. Utilizarea operatorului de vizibilitate face ca numele sa fie disponibil doar in expresia in care este folosit operatorul:

cout << std::fixed << x << endl; // utilizarea operatorului de

// vizibilitate

Poate deveni obositor, dar puteti aduna toate declaratiile de utilizare obisnuite intr-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 in zone de nume:

// numelemele - - un fisier antet

#include <iostream>

namespace io

{

using std::cin;

using std::cout;

using std::endl;

}

namespace formate

{

using std::fixed;

using std::scientific;

using std::boolalpha;

}

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 ii corespunda o utilizare a lui delete. Pot sa apara probleme daca functia in care se foloseste new se termina prematur prin aruncarea unei exceptii. Cum am vazut in Capitolul 15, daca utilizati un obiect auto_ptr pentru un obiect creat prin new va asigurati ca delete va fi folosit in mod automat si in orice conditie.

Utilizati clasa string

Sirurile traditionale in stil C nu sunt efectiv un tip. Puteti stoca un sir intr-un tablou de caractere si puteti initializa un tablou de caractere cu un sir. In 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 in 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). In 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 inainte 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 in stil C sau o valoare char la un obiect string. Pentru functiile care cer un argument de tip sir in 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 in 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 incercati sa o folositi. De exemplu, in 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 in cazul utilizarii obiectelor string in locul sirurilor in 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 incorporata in design si permite dimensionarea automata a obiectului vector cand ii adaugati elemente prin metoda push_back(). Bineinteles, o multime de metode ale clasei si de algoritmi generali va stau la indemana.

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, in STL veti gasi sabloane utile pentru toate aceste containere. Biblioteca de algoritmi este proiectata astfel incat sa puteti copia usor continutul unui vector intr-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 ii face utili si in cazul unor obiecte care nu sunt componente STL. In particular, acestia se pot folosi si pentru tablourile normale.