C ++ Succinct Tipuri

Tipuri fundamentale

C ++ conține aceleași cuvinte cheie (de exemplu, int) pe care le recunoașteți din C #. Acest lucru nu este surprinzător dat fiind că ambele sunt limbi asemănătoare C. Există totuși o potențială mină de teren care vă poate face probleme. În timp ce C # definește în mod explicit dimensiunile tipurilor fundamentale (un scurt este un număr întreg de 16 biți, un int este un număr întreg pe 32 de biți, un lung este un număr întreg pe 64 de biți, un dublu este o floare IEEE 754 de dublă precizie pe 64 de biți numărul de puncte etc.), C ++ nu oferă astfel de garanții.

Cea mai mică unitate fundamentală din C ++ este char, care trebuie să fie suficient de mare pentru a păstra cele 96 de caractere de bază specificate de standardul C ++, plus orice alte caractere din setul de caractere de bază al implementării. În teorie, o anumită implementare a C ++ ar putea defini un caracter ca 7 biți sau 16 biți ... aproape orice este posibil. Dar, în practică, nu trebuie să vă faceți griji prea mult despre un caracter care este altceva decât 8 biți (echivalentul tipului octet sau sbyte din C #), care este dimensiunea sa în Visual C++.

În C ++, caracterele, caracterele semnate și caracterele nesemnate sunt trei tipuri distincte. Toate cele trei sunt necesare pentru a prelua aceeași cantitate de memorie în memorie. Deci un caracter în practică este fie semnat, fie nesemnificat. Indiferent dacă este semnată sau nesemnată, implementarea este definită (vezi bara laterală). În Visual C ++, tipul de caractere este, în mod implicit, semnat. Dar puteți utiliza un comutator pentru a fi tratat ca nesemnat. În GCC, dacă este semnată sau nesemnată depinde de arhitectura procesorului pe care îl vizați.

Tipurile întregi semnate, în ordinea dimensiunii de la cea mai mică la cea mai mare, sunt:

  1. semnat char
  2. scurt int
  3. int
  4. lung int
  5. lung lung int

Singura garanție a mărimii fiecăruia dintre aceste tipuri întregi este aceea că fiecare dintre ele este cel puțin la fel de mare ca și cel mai mic întreg cel mai mic tip. În Visual C ++, un int și un int int sunt ambii numere pe 32 de biți. Numai lungul lung int este un număr întreg pe 64 de biți.

Notă: Puteți scrie pur și simplu lungi sau lungi; nu aveți nevoie să scrieți int int sau long long int, respectiv. Același lucru este valabil și pentru int intreg (adică puteți scrie scurt). Tipul scurt este un număr întreg pe 16 biți în Visual C++.

Fiecare dintre tipurile întregi are un tip întreg întreg nesemnat. Tocmai ați pus cuvântul cheie nesemnat în față pentru a obține versiunea nesemnată (cu excepția caracterelor semnate, pe care le schimbați la caractere nesemnate).

Dacă trebuie să vă asigurați că utilizați mărimi specifice, puteți include fișierul antet al Bibliotecii Standard C ++ cstdint (de exemplu., #include ), care definește, printre altele, tipurile:

  1. int8_t
  2. int16_t
  3. int32_t
  4. int64_t
  5. uint8_t
  6. uint16_t
  7. uint32_t
  8. uint64_t

Aceste tipuri au utilizarea lor, dar veți observa că majoritatea API-urilor nu le utilizează; în schimb, folosesc direct tipurile fundamentale. Acest lucru poate face confuzia dvs. de programare, pe măsură ce aveți nevoie în mod constant pentru a verifica tipul fundamental de bază pentru a vă asigura că nu ajungeți cu trunchiere sau expansiune neintenționată.

Aceste tipuri ar putea intra în uz mai mult, așadar vă recomand să verificați din când în când utilizarea acestora în biblioteci importante și API și să vă adaptați codul în consecință, dacă acestea vor fi adoptate pe scară largă. Desigur, dacă aveți absolut nevoie de o variabilă pentru a fi, de exemplu, un număr întreg nesemnat pe 32 de biți, ar trebui să utilizați cu siguranță uint32_t și să efectuați orice ajustări pentru apelurile API și portabilitatea după cum este necesar.

Numerele cu puncte în virgulă sunt aceleași ca și regulile pentru ordinea dimensiunii. Ele merg de la plutitor la dublu, pentru a lungi dublu. În Visual C ++, float este un număr de punct floating pe 32 de biți și dublu și lung dublu sunt ambele numere de puncte plutitoare pe 64 de biți (dublul lung nu este mai mare decât dublu, cu alte cuvinte).

C ++ nu are niciun tip nativ care este comparabil cu tipul zecimal al C #. Cu toate acestea, unul dintre lucrurile frumoase despre C ++ este că există de obicei un număr mare de biblioteci libere sau ieftine pe care le puteți licenția. De exemplu, există biblioteca decNumber, Biblioteca de date matematice cu numere plutitoare Intel și Biblioteca aritmetică de precizie multiplă GNU. Niciunul nu este exact compatibil cu tipul zecimal al lui C #, dar dacă scrieți numai pentru sistemele Windows, atunci puteți utiliza tipul de date DECIMAL pentru a obține compatibilitatea dacă este necesar, împreună cu funcțiile aritmetice zecimale și funcțiile de conversie de tip de date.

Există, de asemenea, un tip boolean, bool, care poate fi adevărat sau fals. În Visual C ++, un bool ocupă un octet. Spre deosebire de C #, un bool poate fi transformat într-un tip întreg. Când este falsă, are o valoare echivalentă întregului de 0, iar atunci când este adevărată, are valoarea de 1. Deci, declarația bool result = true == 1; va compila și rezultatul va fi evaluat la adevărat atunci când declarația a fost executată.

Apoi, există tipul wchar_t, care deține un caracter larg. Dimensiunea unui personaj larg variază în funcție de platformă. Pe platforme Windows, este un caracter pe 16 biți. Este echivalentul tipului de caractere C #. Este frecvent folosit pentru a construi șiruri de caractere. Vom discuta șiruri într-un alt capitol, deoarece multe variante pot fi folosite pentru corzi.

În cele din urmă, există tipul vid, care este folosit la fel ca în C #. Și există un tip std :: nullptr_t, care este dezordonat pentru a explica corect, dar în esență există tipul de literal nullptr, care ar trebui să utilizați în loc de NULL sau literal 0 (zero) pentru a verifica null valorile.

enumerările

Enumerările sunt destul de asemănătoare între ele în C ++ și C #. C ++ are două tipuri de enumuri: cu scop și fără scop.

O enumerare cu scop este definită fie ca o clasă enum, fie ca o structură enum. Nu există nicio diferență între cele două. O enumerare fără scop este definită ca un enum simplu. Să aruncăm o mostră:

Mostră: EnumSample \ EnumSample.cpp

#include  #include  #include  #include "... /pchar.h" clasa enum Culoare Roșu, Orange, Galben, Albastru, Indigo, Violet; // Puteți specifica orice tip integral de bază dorit, cu condiția să se potrivească. enum Aromă: nesemnată scurtă int Vanilie, Ciocolată, Căpșună, Menta,; int _pmain (int / * argc * /, _pchar * / * argv * / []) Aromă f = Vanilie; f = Mentă; // Aceasta este legală, deoarece enum Flavour este un enum fără scop. Culoare c = Culoare: Portocaliu; // c = Orange; // Aceasta este ilegală deoarece enum Culoarea este un enum scoped. std :: aromă wstring; std :: culoarea wstring; comutator (c) caz culoare :: roșu: culoare = L "roșu"; pauză; carcasă Culoare :: Portocaliu: culoare = L "Portocaliu"; pauză; carcasă Culoare :: galben: culoare = L "galben"; pauză; carcasă Culoare :: Albastru: culoare = L "Albastru"; pauză; carcasă Culoare :: Indigo: color = L "Indigo"; pauză; carcasă Culoare :: Violet: culoare = L "Violet"; pauză; implicit: color = L "Necunoscut"; pauză;  comutator (f) caz Vanilie: aromă = L "Vanilie"; pauză; caz Chocolate: aromă = L "Ciocolată"; pauză; caz Strawberry: aromă = L "Strawberry"; pauză; caz Monetarie: aromă = L "Monetărie"; pauză; prestabilit: pauză;  std :: wcout << L"Flavor is " << flavor.c_str() << L" (" << f << L"). Color is " << color.c_str() << L" (" << static_cast(C) << L")." << std::endl << L"The size of Flavor is " << sizeof(Flavor) << L"." << std::endl << L"The size of Color is " << sizeof(Color) << L"." << std::endl; return 0; 

Acest cod va da următoarea ieșire:

Aroma este mentă (3). Culoarea este portocalie (1). Dimensiunea Aroma este 2. Dimensiunea Culorii este de 4.

După cum puteți vedea în eșantion, enumerarea de culori scoped vă cere să accesați membrii săi în același mod ca și C #, prefacând membrul enumerării cu numele enumerării și operatorul de rezoluție a domeniului. Prin contrast, enumerarea fără limite de aromă vă permite să specificați membrii fără prefix. Din acest motiv, cred că este o practică mai bună de a prefera enumerările scopate: Reduceți la minimum riscurile de numire a coliziunilor și de reducere a poluării din spațiul de nume.

Observați că există o altă diferență cu enumerările scop: Când vroiam să ieșim valoarea numerică a enumului de culori scoped, a trebuit să folosim operatorul static_cast pentru a-l converti la un int, în timp ce nu era nevoie să facem nicio casting pentru - enumerarea aromei sculptate.

Pentru enumerarea Aromei, am specificat tipul de bază ca fiind un scurt int nesemnat. De asemenea, puteți specifica tipul de bază pentru enumerările cu scop. Specificarea tipului de bază este opțională, dar este obligatorie dacă doriți să utilizați o declarație în avans cu o enumerare fără scop. Declarația înainte este o modalitate de a accelera timpul de compilare a programului, spunând doar compilatorului ce trebuie să știe despre un tip, mai degrabă decât forțând-o să compileze întregul fișier antet pe care este definit tipul.

Vom analiza mai târziu acest lucru. Pentru moment, trebuie doar să vă amintiți că o enumerare nespecificată trebuie să aibă în mod explicit specificul său de bază pentru a utiliza o declarație înaintea ei; o enumerare a domeniului nu necesită specificarea tipului său de bază pentru a utiliza o declarație în avans a acestuia (tipul de bază va fi int dacă niciuna nu este specificată).

Puteți face același lucru cu enumerările în C ++ după cum puteți în C # în ceea ce privește atribuirea explicită a valorilor membrilor și în ceea ce privește crearea de enumerări de pavilion. Faceți totul în același mod, cu excepția faptului că nu trebuie să aplicați nimic ca FlagAttribute în C ++ pentru a crea enumerări de pavilion; trebuie doar să atribuiți valorile corecte și să continuați de acolo.

std :: wcout, std :: wcerr, std :: wcin

Std :: wcout << L”Flavor… code outputs wide character data to the standard output stream. In the case of a console program such as this, the standard output is the console window. There is also a std::wcerr output stream, which will output wide character data to the standard error output stream. This is also the console window, but you can redirect std::wcout output to one file and std::wcerr output to another file. There is also a std::wcin for inputting data from the console. We won't explore this, nor will we explore their byte counterparts: std::cout, std::cerr, and std::cin.

Doar pentru a vă permite să vedeți cum arată intrarea, iată un exemplu.

Mostră: ConsoleSample \ ConsoleSample.cpp

#include  #include  #include  #include "... /pchar.h" struct Color float ARGB [4]; void A (valoare float) ARGB [0] = valoare;  float A (void) const întoarcere ARGB [0];  void R (valoare float) ARGB [1] = valoare;  float R (void) const retur ARGB [1];  void G (valoare float) ARGB [2] = valoare;  float G (void) const retur ARGB [2];  void B (valoare float) ARGB [3] = valoare;  float B (void) const retur ARGB [3]; ; // Aceasta este o funcție autonomă, care se întâmplă a fi un operator binar // pentru << operator when used with a wostream on // the left and a Color instance on the right. std::wostream& operator<<(std::wostream& stream, const Color& c)  stream << L"ARGB: " << c.A() << L"f, " << c.R() << L"f, " << c.G() << L"f, " << c.B() << L"f "; return stream;  int _pmain(int /*argc*/, _pchar* /*argv*/[])  std::wcout << L"Please input an integer and then press Enter: "; int a; std::wcin >> a; std :: wcout << L"You entered '" << a << L"'." << std::endl; std::wcout << std::endl << L"Please enter a noun (one word, no spaces) " << L"and then press Enter: "; std::wstring noun; // wcin breaks up input using white space, so if you include a space or // a tab, then it would just put the first word into noun and there // would still be a second word waiting in the input buffer. std::wcin >> substantiv; std :: wcerr << L"The " << noun << L" is on fire! Oh no!" << std::endl; Color c =   100.0f/255.0f, 149.0f/255.0f, 237.0f/255.0f, 1.0f  ; // This uses our custom operator from above. Come back to this sample // later when we've covered operator overloading and this should make // much more sense. std::wcout << std::endl << L"Cornflower Blue is " << c << L"." << std::endl; return 0; 

Codul anterior este o demonstrație destul de simplă. Nu are nici o verificare a erorilor, de exemplu. Deci, dacă introduceți o valoare incorectă pentru întreg, va trece până la capăt cu std :: wcin întorcându-se instantaneu fără nici un fel de date (asta este ceea ce face dacă și până când rezolvați eroarea).

Dacă sunteți interesat de programarea iostream, inclusiv folosind std :: wofstream la datele de ieșire într-un fișier și std :: wifstream pentru a citi date dintr-un fișier (funcționează la fel ca std :: wcout și std :: wcin, doar cu funcționalitate adăugată pentru a face față faptului că lucrează cu fișiere), consultați paginile de programare MSDN iostream. Învățând toate intrările și ieșirile fluxurilor ar putea umple cu ușurință o carte doar pe cont propriu.

Un ultim lucru, totuși. Ați observat, fără îndoială, că funcționalitatea fluxului pare puțin ciudat cu operatorii de schimbare de biți << and >>. Asta pentru că acești operatori au fost supraîncărcați. În timp ce vă așteptați ca operatorii de biți să acționeze într-un anumit mod asupra numerelor întregi, nu există nicio așteptare specifică pe care ar trebui să o aibă despre modul în care acestea ar trebui să funcționeze atunci când sunt aplicate unui flux de ieșire sau respectiv unui flux de intrare. Deci, fluxurile Bibliotecii Standard C ++ au cooptat pe acești operatori pentru a le folosi pentru introducerea și transmiterea datelor către fluxuri. Când vrem să citim sau să scriem un tip personalizat pe care l-am creat (cum ar fi structura Color anterioară), trebuie doar să creăm o supraîncărcare corespunzătoare a operatorului. Vom afla mai multe despre supraîncărcarea operatorului mai târziu în carte, așa că nu vă faceți griji dacă este puțin confuză chiar acum


Clase și structuri

Diferența dintre o clasă și o structură în C ++ este pur și simplu aceea că membrii unei structuri sunt implicați la public, în timp ce membrii unei clase sunt impliciți la privat. Asta e. Ele sunt altfel aceleași. Nu există nicio distincție de tip de tip față de tipul de referință, așa cum există în C #.

Acestea fiind spuse, de obicei veți vedea că programatorii folosesc clase pentru tipuri complexe (combinații de date și funcții) și structuri pentru tipuri simple de date. În mod normal, aceasta este o alegere stilistică care reprezintă originea orientată non-obiect a structurii în C, făcând-o ușor să se diferențieze rapid între un container simplu de date și un obiect plin de suflare, căutând să vadă dacă este o structură sau o clasă. Vă recomandăm să urmați acest stil.

Notă: O excepție de la acest stil este cazul în care un programator scrie un cod care este destinat să fie utilizat atât în ​​C, cât și în C ++. Deoarece C nu are un tip de clasă, tipul de structură ar putea fi folosit în mod similar cu modul în care ați folosi o clasă în C ++. Nu mă voi referi la scrisul C-compatibil C ++ în această carte. Pentru a face acest lucru, trebuie să fiți familiarizați cu limbajul C și cu diferențele dintre acesta și C ++. În schimb, ne concentrăm pe scrierea unui cod curat și modern C ++.

În programarea Windows Runtime ("WinRT"), o structură publică poate avea doar membri de date (fără proprietăți sau funcții). Acești membri de date pot fi compuși numai din tipuri de date fundamentale și din alte structuri publice - care, desigur, au aceleași restricții numai pentru date, fundamentale și structuri publice. Țineți cont de acest lucru dacă lucrați la orice aplicații în stil Metro pentru Windows 8 utilizând C++.

Veți vedea, uneori, cuvântul cheie de prietenie utilizat în cadrul unei definiții de clasă. Acesta este urmat fie de un nume de clasă, fie de o declarație a funcției. Ceea ce face acest construct de cod este să dea acelei clase sau funcții accesul la datele și funcțiile membrilor non-public din clasă. De obicei, veți dori să evitați acest lucru, deoarece clasa ar trebui să expună în mod normal tot ceea ce doriți să expuneți prin interfața sa publică. Dar în acele cazuri rare în care nu doriți să expuneți public anumiți membri de date sau funcții ale membrilor, dar doriți ca una sau mai multe clase sau funcții să aibă acces la el, puteți utiliza cuvântul cheie prieten pentru a realiza acest lucru.

Pe măsură ce clasele reprezintă o parte foarte importantă a programării C ++, le vom explora mai detaliat mai târziu în carte.

Sindicatele

Tipul de uniune este un pic ciudat, dar are utilizările sale. O veți întâlni din când în când. O uniune este o structură de date care apare pentru a deține mulți membri de date, dar vă permite doar să utilizați unul dintre membrii săi de date la un moment dat. Rezultatul final este o structură de date care vă oferă multe utilizări posibile fără a pierde memoria. Mărimea uniunii trebuie să fie suficient de mare pentru a conține cel mai mare membru al uniunii. În practică, aceasta înseamnă că membrii de date se suprapun reciproc în memorie (prin urmare, puteți utiliza unul singur la un moment dat). Acest lucru înseamnă, de asemenea, că nu aveți nici o modalitate de a ști ce este membru activ al unei uniuni, dacă nu îl veți urmări cumva. Există multe moduri în care puteți face acest lucru, dar punerea unei unități și a unui enum într-o structură este o modalitate bună, simplă și ordonată de ao face. Iată un exemplu.

Mostră: UnionSample \ UnionSample.cpp

#include  #include  #include "... /pchar.h" clasa enum SomeValueDataType Int = 0, Float = 1, Double = 2; struct SomeData SomeValueDataType Type; unitate int iData; float fData; date duble dData;  Valoare; Câteva date (void) SomeData (0);  SomeData (int i) Tip = SomeValueDataType :: Int; Value.iData = i;  SomeData (float f) Tip = SomeValueDataType :: Float; Value.fData = f;  SomeData (dublu d) Type = SomeValueDataType :: Double; Value.dData = d; ; int _pmain (int / * argc * /, _pchar * / * argv * / []) Unele date = SomeData (2.3F); std :: wcout << L"Size of SomeData::Value is " << sizeof(data.Value) << L" bytes." << std::endl; switch (data.Type)  case SomeValueDataType::Int: std::wcout << L"Int data is " << data.Value.iData << L"." << std::endl; break; case SomeValueDataType::Float: std::wcout << L"Float data is " << data.Value.fData << L"F." << std::endl; break; case SomeValueDataType::Double: std::wcout << L"Double data is " << data.Value.dData << L"." << std::endl; break; default: std::wcout << L"Data type is unknown." << std::endl; break;  return 0; 

După cum puteți vedea, definim un enum care are membri reprezentând fiecare dintre tipurile de membri ai uniunii. Apoi definim o structură care include atât o variabilă a tipului acelui enum, cât și o uniune anonimă. Acest lucru ne oferă toate informațiile de care avem nevoie pentru a determina ce tip de uniune deține în prezent într-un pachet încapsulat.

Dacă v-ați dorit ca uniunea să fie utilizabilă în mai multe structuri, ați putea să o declarați în afara structurii și să îi dați un nume (de exemplu, union SomeValue ...;). Apoi le puteți folosi în structură, de exemplu, SomeValue Value ;. Este de obicei mai bine să o păstrați ca o uniune anonimă, deși nu aveți nevoie să vă faceți griji cu privire la efectele secundare ale unei modificări, cu excepția structurilor în care este definită.

Sindicatele pot avea constructori, distrugatori și funcții ale membrilor. Dar, deoarece pot avea vreodată doar un membru activ de date, rareori are sens să se scrie funcțiile membre pentru o uniune. Le vei vedea rareori, poate niciodată.

typedef

Primul lucru pe care trebuie să-l înțelegeți este că, în ciuda implicațiilor numelui său, typedef nu creează noi tipuri. Este un mecanism de aliasing care poate fi folosit pentru multe lucruri.

Este folosită foarte mult în implementarea Bibliotecii Standard C ++ și a altui cod bazat pe șabloane. Aceasta este, fără îndoială, cea mai importantă utilizare. O vom explora mai mult în capitolul despre șabloane.

Acesta vă poate salva de la o mulțime de tastare (deși acest argument a pierdut o parte din forța sa prin repopularea automată a cuvintelor cheie pentru deducerea de tip în C ++ 11). Dacă aveți un tip de date deosebit de complicat, crearea unui tippedef pentru aceasta înseamnă că trebuie doar să o tastați o singură dată. Dacă scopul complex al tipului de date este neclar, oferindu-i un nume mai semnificativ din punct de vedere semantic, cu un tip de tastă, vă poate ajuta să înțelegeți programul mai ușor.

Este uneori folosit ca o abstracție de către dezvoltatori pentru a schimba cu ușurință un tip de suport (de exemplu, de la o std :: vector la o listă std ::) sau de tipul unui parametru (de exemplu, de la un int la un lung). Pentru propriul cod de utilizare internă, acest lucru ar trebui să fie frământat. Dacă dezvoltați codul pe care alții îl vor folosi, cum ar fi o bibliotecă, nu ar trebui să încercați niciodată să utilizați un tippedef în acest fel. Tot ceea ce faceți este să scadă capacitatea de descoperire a ruperii modificărilor la API dacă schimbați un tip. Utilizați-le pentru a adăuga context semantic, sigur, dar nu le folosiți pentru a schimba un tip de bază în codul pe care se bazează ceilalți.

Dacă trebuie să schimbați tipul de ceva, amintiți-vă că orice modificare a parametrilor unei funcții este o schimbare de rupere, ca de exemplu schimbarea tipului de returnare sau adăugarea unui argument implicit. Modul corect de a face față unei posibile schimbări de tip viitoare este cu clase abstracte sau cu șabloane (oricare dintre acestea este mai potrivit sau oricare preferați, dacă ambele vor servi). În acest fel, interfața publică pentru codul dvs. nu se va schimba, numai implementarea va. Idiomul Pimpl este o altă modalitate bună de a păstra un API stabil, păstrând în același timp libertatea de a schimba detaliile implementării. Vom explora idiomul Pimpl, scurt pentru "pointer la implementare", într-un capitol ulterior.

Aici este un bloc de cod mic care ilustrează sintaxa pentru typedef.

clasa ExistingType; typedef ExistingType AliasForExistingType;

Iar următorul exemplu este un exemplu succint care arată modul în care ar putea fi folosit tipul. Scopul acestei eșantioane este de a ilustra o utilizare simplificată, dar realistă a unui tippedef. În practică, un tipar ca acesta ar intra într-un spațiu de nume și va fi apoi inclus într-un fișier antet. Deoarece nu am acoperit niciunul din aceste lucruri, acest exemplu a fost păstrat simplu în mod intenționat.

Exemplu: TypedefSample \ TypedefSample.cpp

#include  #include  #include  #include  #include "... /pchar.h" // Aceasta face WidgetIdVector un alias pentru std :: vector, care are // mai mult sens decât std :: vector ar fi, din moment ce știm că // ceva ce folosește acest alias așteaptă un vector de ID widget / / și nu un vector de întregi. typedef std :: vector WidgetIdVector; bool conține WidgetId (WidgetIdVector idVector, int id) retur (std :: end (idVector)! = std :: găsi (std :: begin (idVector), std :: end (idVector), id));  int _pmain (int / * argc * /, _pchar * / * argv * / []) WidgetIdVector idVector; // Adăugați niște numere de identificare la vector. idVector.push_back (5); idVector.push_back (8); // Efectuați un rezultat informându-ne dacă id-ul este în // WidgetIdVector. std :: wcout << L"Contains 8: " << (ContainsWidgetId(idVector, 8) ? L"true." : L"false.") << std::endl; return 0; 

Concluzie

Ar trebui să aveți acum o înțelegere clară a tipurilor disponibile în C ++. În următorul articol, vom examina mai atent spațiile de nume din C++.

Această lecție reprezintă un capitol din C ++ Succinctly, o carte electronică gratuită de la echipa de la Syncfusion.
Cod