Apel la structura c. Structuri în C și transferul lor

Acum doar imaginați-vă - dumneavoastră înșivă puteți crea, într-un fel, tipurile de date de care aveți nevoie și cu care vă va fi convenabil să lucrați! Și nu e greu!

Structura este un fel de unificare a diferitelor variabile (chiar și cu tipuri diferite date) căruia îi puteți atribui un nume. De exemplu, puteți combina date despre obiectul Casa: oraș (în care se află casa), stradă, număr de apartamente, Internet (conectat sau nu) etc. într-o singură structură. În general, puteți colecta într-un singur set de date despre orice, sau mai degrabă despre tot ceea ce are nevoie un anumit programator. A devenit clar pentru toată lumea imediat :)

Dacă abia începeți să vă familiarizați cu structurile în C++, mai întâi trebuie să vă familiarizați cu sintaxa structurilor în limbajul C++. Să ne uităm la un exemplu simplu care vă va ajuta să vă familiarizați cu structurile și să vă arătați cum să lucrați cu ele. În acest program, vom crea o structură, vom crea un obiect de structură, vom completa elementele de structură (date obiect) cu valori și vom afișa aceste valori pe ecran. Ei bine, să începem!

#include folosind namespace std; struct building //Creează o structură! ( char *proprietar; //numele proprietarului va fi stocat aici char *oraș; //numele orașului int sumaCamere; //numărul de camere float price; //preț ); int main() ( setlocale (LC_ALL, "rus"); building apartment1; //acesta este un obiect de structură cu un tip de date, numele structurii, clădirea apartament1.owner = "Denis"; //completați informații despre proprietar, etc apartament1 .oraș = „Simferopol”;<< "Владелец квартиры: " << apartment1.owner << endl; cout << "Квартира находится в городе: " << apartment1.city << endl; cout << "Количество комнат: " << apartment1.amountRooms << endl; cout << "Стоимость: " << apartment1.price << " $" << endl; return 0; }

ÎN liniile 4 - 10 creăm o structură. Pentru a-l declara, folosim cuvântul rezervat struct și îi dăm orice nume, de preferință logic. În cazul nostru - clădire. Vă puteți familiariza cu regulile de denumire a variabilelor din acest articol. Apoi, deschidem acolada ( , enumerăm cele 4 elemente ale structurii separate prin punct și virgulă; , închidem acolada ) și la final punem punct și virgulă; . Acesta va fi șablonul nostru (forma) structurii.

ÎN linia 16 declara un obiect de structură. Ca și în cazul variabilelor obișnuite, trebuie să declarați tipul de date. Numele structurii noastre create va acționa în această capacitate - construirea.

Cum să umpleți (inițializați) elementele structurii cu date? Sintaxa este următoarea: Nume obiect urmat de operator punct. și numele elementului de structură. De exemplu: apartament1.proprietar . Astfel, în rândurile 18-21 atribuie date elementelor de structură.

Și așa, am introdus datele. Următoarea întrebare este: „Cum să le accesezi, cum să lucrezi și să le folosești în program?” Răspunsul este „Foarte simplu - la fel ca în cazul inițializării, folosind un punct. și numele elementului de structură.” ÎN liniile 23 - 26 Afișăm pe ecran elementele de structură finalizate.

Și asta este ceea ce vom vedea ca rezultat când vom compila programul nostru:

Proprietar apartament: Denis Apartamentul este situat în orașul: Simferopol Număr camere: 5 Cost: 150.000 USD

Ce altceva este important de știut:

  • Obiectul de structură poate fi declarat înaintea funcției main(). Ar arata asa:
struct building ( char *proprietar char *oras; int sumaCamere; float price; )apartament1; //declararea unui obiect de tip clădire
  • Puteți inițializa structura în acest fel:
bloc apartament1 = ("Denis", "Simferopol", 5, 150000);

dar acest lucru se face extrem de rar;

  • O structură poate fi imbricată în alte structuri (ne vom uita la asta în exemplul următor).

Să extindem exemplul anterior pentru a vedea posibilități suplimentare de lucru cu structuri.

Exemplu:

#include folosind namespace std; data struct //creează o altă structură pentru a o cuibă în structura clădirii // data construcției ( char *lună; // Luna în care a fost construită casa în anul; // An ); struct building (char *proprietar; char *oraș; int amountRooms; float price; data construită; //pune o structură în definiția celei de-a doua); void show(building object) //creează o funcție care ia o structură ca parametru (cout<< "Владелец квартиры: " << object.owner << endl; cout << "Квартира находится в городе: " << object.city << endl; cout << "Количество комнат: " << object.amountRooms << endl; cout << "Стоимость: " << object.price << " $" << endl; cout << "Дата постройки: " << object.built.month << " " << object.built.year << endl; } int main() { setlocale (LC_ALL, "rus"); building apartment1; apartment1.owner = "Денис"; apartment1.city = "Симферополь"; apartment1.amountRooms = 5; apartment1.price = 150000; apartment1.built.month = "январь"; apartment1.built.year = 2013; struct building *pApartment; //это указатель на структуру pApartment = &apartment1; //Обратите внимание, как нужно обращаться к элементу структуры через указатель //используем оператор ->cout<< "Владелец квартиры: " << pApartment->proprietar<< endl; cout << "Квартира находится в городе: " << pApartment->oraș<< endl; cout << "Количество комнат: " << pApartment->sumaCamere<< endl; cout << "Стоимость: " << pApartment->Preț<< " $" << endl; cout << "Дата постройки: " << pApartment->construit.lună<< " " << pApartment->construit.an<< "\n\n\n"; building apartment2; //создаем и заполняем второй объект структуры apartment2.owner = "Игорь"; apartment2.city = "Киев"; apartment2.amountRooms = 4; apartment2.price = 300000; apartment2.built.month = "январь"; apartment2.built.year = 2012; building apartment3 = apartment2; //создаем третий объект структуры и присваиваем ему данные объекта apartment2 show(apartment3); cout << endl << endl; return 0; }

Comentarii despre codul programului:

Linia 17— crearea unui obiect de tip construitdata în definiția structurii clădirii. Rândurile 42 - 43: creați un pointer către structura struct building *pApartment; și apoi atribuiți-i adresa pApartment = obiect care a fost deja creat și umplut cu date. Când accesăm elementele de structură printr-un pointer, folosim operatorul -> (liniuță și semnul >). Acest lucru poate fi văzut în liniile 47 - 51.

ÎN linia 62 arată cum poate fi inițializată o structură. Și anume, puteți crea un nou obiect de structură și îl puteți atribui într-o singură bucată, un obiect care a fost deja creat și umplut cu date. Trecem obiectul de structură la funcția show() ca parametru - linia 64. Rezultat:

Proprietar apartament: Denis
Apartamentul este situat in orasul: Simferopol
Numar camere: 5
Cost: 150.000 USD
Data constructiei: ianuarie 2013
Proprietar apartament: Igor
Apartamentul este situat în orașul: Kiev
Numar camere: 4
Cost: 300.000 USD
Data constructiei: ianuarie 2012
Pentru a continua, apăsați orice tastă. . .

După ce am analizat acest exemplu, am văzut următoarele în practică:

  • o structură poate fi imbricată în cadrul unei alte structuri;
  • am văzut cum este creat un pointer către o structură;
  • cum se accesează un element de structură printr-un pointer. Și anume, folosind operatorul -> ; În exemplu a fost așa: apartament0->proprietar , dar poți să o faci așa (*apartament0).proprietar . În al doilea caz, sunt necesare paranteze.
  • datele dintr-o structură pot fi atribuite unei alte structuri;
  • Puteți trece o structură unei funcții ca parametru (apropo, elementele unei structuri pot fi trecute și unei funcții ca parametri).

În plus, trebuie remarcat faptul că funcțiile pot returna și structuri ca urmare a activității lor. De exemplu:

Building Set() ( obiect de construcție; // formarea obiectului //... obiect de returnare a codului funcției; )

Deci, pe scurt, ne-am familiarizat cu structurile în limbajul C++, am exersat cu exemple și am învățat elementele de bază. Acesta este doar începutul!

Înainte de a începe să studiem clasele în C++, ne vom uita la un tip de date similar cu o clasă - structs. Structurile sunt utile atunci când trebuie să combinăm mai multe variabile de diferite tipuri sub un singur nume. Acest lucru face ca programul să fie mai compact și mai flexibil pentru a face modificări. Structurile sunt de asemenea indispensabile atunci când este necesară gruparea unor date, de exemplu, o înregistrare dintr-o bază de date sau un contact dintr-o agendă. În acest din urmă caz, structura va conține date de contact precum numele, adresa, numărul de telefon etc.

Sintaxă

Pe măsură ce scrieți un program, poate fi necesar să grupați date diferite. De exemplu, este posibil să doriți să stocați coordonatele unor obiecte și numele acestora. Puteți face acest lucru cu:

Int x_coor; int y_coor; nume de șiruri;

Dar, deoarece fiecare element al unei matrice este conectat la altul, atunci când schimbați unul, va trebui să schimbați și restul. Și cu cât trebuie să combinați mai multe date, cu atât un astfel de program va fi mai complex. Prin urmare, pentru a combina diferite date, folosim structurilor .

Format de anunț structura arata asa:

Struct Car (int x_coor; int y_coor; nume șir; );

Prin declararea unei structuri, introducem în program propriul tip de date, pe care îl putem folosi la fel ca tipurile standard, adică. Declarația unei variabile de tipul nostru va fi astfel:

StructName variableName;

structName este numele structurii, variableName este numele variabilei.

x_coor, y_coor și nume - câmpuri structura noastră. Când declarăm o structură, creăm un tip de date compus care poate fi folosit pentru a crea variabile care combină mai multe valori (de exemplu, coordonate și un nume). În interiorul structurii, dăm fiecărui câmp un nume, astfel încât apoi să ne putem referi la această valoare prin numele ei.

Pentru a accesa câmpurile structurii, utilizați un punct:

// declar variabila Car myCar; // și folosește-l myCar.x_coor = 40; myCar.y_coor = 40; myCar.name = "Porche";

După cum puteți vedea, puteți stoca câte câmpuri doriți în structură și pot avea diferite tipuri.

Să ne uităm la un exemplu care demonstrează combinația de matrice și structuri.

#include folosind namespace std; struct PlayerInfo (int skill_level; nume șir; ); folosind namespace std; int main() ( // ca și în cazul tipurilor obișnuite, puteți declara o matrice de structuri PlayerInfo players; for (int i = 0; i< 5; i++) { cout << "Please enter the name for player: " << i << "\n"; // сперва получим доступ к элементу массива, используя // обычный синтаксис для массивов, затем обратимся к полю структуры // с помощью точки cin >> jucători[ i ].nume; cout<< "Please enter the skill level for " << players[ i ].name << "\n"; cin >> jucători[ i ].skill_level; ) pentru (int i = 0; i< 5; ++i) { cout << players[ i ].name << " is at skill level " << players[i].skill_level << "\n"; } }

La fel ca în cazul tipurilor simple (int, de exemplu), puteți crea matrice de structuri. Și lucrați cu fiecare element al acestei matrice în același mod ca și cu o variabilă separată. Pentru a accesa câmpul de nume al primului element al unei matrice de structuri, scrieți pur și simplu:

Jucători[ 0 ].nume

Structuri și funcții

Foarte des trebuie să scrieți funcții care iau structuri ca argumente sau returnează o structură. De exemplu, dacă trebuie să scrieți un joc arcade în spațiu mic, este posibil să aveți nevoie de o funcție pentru a inițializa un nou inamic:

Struct EnemySpaceShip ( int x_coordinate; int y_coordinate; int weapon_power; ); EnemySpaceShip getNewEnemy();

Funcția getNewEnemy ar trebui să returneze o structură cu câmpuri inițializate:

EnemySpaceShip getNewEnemy() ( nava EnemySpaceShip; ship.x_coordinate = 0; ship.y_coordinate = 0; ship.weapon_power = 20; retur nava; )

Această funcție va returna de fapt o copie a navei variabilei locale create. Aceasta înseamnă că fiecare câmp al structurii va fi copiat într-o nouă variabilă. În cazul nostru, copierea unui număr mic de câmpuri nu este vizibilă, dar atunci când lucrați cu cantități mari de date, trebuie să evitați acțiunile inutile, vom vorbi mai multe despre acest lucru în articolul despre indicatori;

Astfel, pentru a obține o nouă variabilă vom folosi următorul cod:

nava EnemySpaceShip = getNewEnemy();

Acum această variabilă poate fi folosită ca o structură obișnuită.

Puteți trece structuri la o funcție ca aceasta:

Upgrade EnemySpaceShipWeapons (nava EnemySpaceShip) ( ship.weapon_power += 10; return ship; )

Când trecem o structură unei funcții, aceasta este copiată, la fel ca atunci când returnăm o structură. Prin urmare, orice modificări făcute în interiorul funcției se vor pierde, așa că returnăm structura după modificare.

Folosind funcția:

Navă = upgradeArme(navă);

Când o funcție este apelată, variabila ship este copiată și modificată în funcție, iar când variabila este returnată, este copiată din nou și suprascrie câmpurile variabilei originale.

Și, în sfârșit, un program pentru crearea și îmbunătățirea unei nave:

Struct EnemySpaceShip ( int x_coordinate; int y_coordinate; int weapon_power; ); ENEMYSPACSESHIP GetNeWeNemy () (EnemySspaceship Ship; Ship.x_Coordinat = 0; Ship.Y_Coordinat = 0; Ship.Weapon_Power = 20; Return Ship;) EnemySspaceship UpgradeWeapons (EnemySspaceship Ship) (Ship.weapon_Power += 10; () ( EnemySpaceShip enemy = getNewEnemy(); enemy = upgradeWeapons(inamic); )

Indicatoare

Dacă lucrați cu o structură, atunci pentru a accesa variabile trebuie să utilizați operatorul „->” în loc de un punct. Toate proprietățile pointerului sunt neschimbate. Exemplu:

#include folosind namespace std; struct xampl(int x;); int main() ( structura xampl; xampl *ptr; structura.x = 12; ptr = cout<< ptr->X; cin.get(); )

Recent m-am familiarizat cu structurile C/C++ - struct. Doamne, „de ce să-i cunoști”, spui? Astfel, veți face două greșeli deodată: în primul rând, nu sunt Dumnezeu și, în al doilea rând, am crezut și că structurile sunt structuri și în Africa. Dar după cum sa dovedit, nu. Vă voi spune despre câteva detalii vitale care îi vor salva pe unii dintre cititori de o depanare de o oră...

Alinierea câmpurilor în memorie

Acordați atenție structurii:

Struct Foo ( char ch; int value; );
Ei bine, în primul rând, care este dimensiunea acestei structuri în memorie? sizeof(Foo) ?
Mărimea acestei structuri în memorie depinde de setările compilatorului și de directivele din codul dvs....

În general, câmpurile sunt aliniate în memorie de-a lungul unei granițe care este un multiplu al propriei dimensiuni. Adică, câmpurile de 1 octet nu sunt aliniate, câmpurile de 2 octeți sunt aliniate la poziții pare, câmpurile de 4 octeți sunt aliniate la poziții care sunt multipli de patru etc. În majoritatea cazurilor (sau să presupunem că acesta este cazul astăzi) dimensiunea de aliniere a structurii în memorie este de 4 octeți. Deci sizeof(Foo) == 8 . Unde și cum vor fi adăugați cei 3 octeți suplimentari? Dacă nu știi, nu vei ghici niciodată...

  • 1 octet: ch
  • 2 octeți: gol
  • 3 octeți: gol
  • 4 octeți: gol
  • 5 octeți: valoare
  • 6 octeți: valoare
  • 7 octeți: valoare
  • 8 octeți: valoare
Să ne uităm acum la plasarea în memorie a următoarei structuri:

Struct Foo ( char ch; short id; int value; );
Arata cam asa:

  • 1 octet: ch
  • 2 octeți: gol
  • 3 octeți: id
  • 4 octeți: id
  • 5 octeți: valoare
  • 6 octeți: valoare
  • 7 octeți: valoare
  • 8 octeți: valoare
Adică, ceea ce poate fi înghesuit într-o aliniere de 4 octeți este înghesuit cu un bang (fără a crește dimensiunea structurii din memorie), să mai adăugăm un câmp:

Struct Foo ( char ch; short id; short opt; int value; );
Să ne uităm la plasarea câmpurilor în memorie:

  • 1 octet: ch
  • 2 octeți: gol
  • 3 octeți: id
  • 4 octeți: id
  • 5 octeți: opt
  • 6 octeți: opt
  • 7 octeți: gol
  • 8 octeți: gol
  • 9 octeți: valoare
  • 10 octeți: valoare
  • 11 octeți: valoare
  • 12 octeți: valoare
Toate acestea sunt atât de trist, dar există o modalitate de a rezolva asta direct din cod:

#pragma pack(push, 1) struct Foo ( // ... ); #pragma pack(pop)
Am setat dimensiunea de aliniere la 1 octet, am descris structura și am returnat setarea anterioară. Recomand cu caldura revenirea setarii anterioare. Altfel, totul s-ar putea termina foarte prost. Mi s-a întâmplat asta odată - Qt s-a prăbușit. Undeva am inclus porecla lor.h-sub numele meu.h-porecla...

Câmpuri de biți

În comentarii mi s-a subliniat că câmpurile de biți din structurile conform standardului sunt „implementare definită”- prin urmare este mai bine să evit să le folosesc, dar pentru mine tentația este prea mare...

Nu numai că mă simt neliniștit în suflet, dar în general mă simt rău când văd în cod umplerea câmpurilor de biți folosind măști și schimburi, de exemplu, astfel:

Câmp nesemnat = 0x00530000; // ... câmp &= 0xFFFF00FF; câmp |= (id)<< 8; // ... field &= 0xFFFFFF83; field |= (proto) << 2;
Toate astea miroase a atâta tristețe și așa erori și a depanării lor încât îmi iese imediat o migrenă! Și apoi ies din culise - Câmpurile Bit. Cel mai surprinzător este că erau încă în limbajul C, dar nu întreb pe nimeni - toată lumea aude despre ele pentru prima dată. Această mizerie trebuie corectată. Acum le voi da tuturor un link, sau cel puțin un link către acest articol.

Cum vă place această bucată de cod:

#pragma pack(push,1) struct IpHeader ( uint8_t header_length:4; uint8_t version:4; uint8_t type_of_service; uint16_t total_length; uint16_t identifier; // Semnalează uint8_t _reserved:1; uintg8_t_t;_tframent8_1;_t fragment_off set_part1 :5 uint8_t fragment_offset_part2 uint8_t time_to_live; #pragma pack(pop)
Și apoi, în cod, putem lucra cu câmpuri, așa cum lucrăm întotdeauna cu câmpuri în C/C++. Toate lucrează în ture etc. Compilatorul preia controlul. Desigur, există unele restricții... Când enumerați mai multe câmpuri de biți la rând legate de același câmp fizic (mă refer la tipul care apare în stânga numelui câmpului de biți) - specificați nume pentru toți biții până la sfârșitul câmpului, altfel nu veți avea acces la acești biți va fi, cu alte cuvinte, codul:

#pragma pack(push,1) stuct MyBitStruct ( uint16_t a:4; uint16_t b:4; uint16_t c; ); #pragma pack(pop)
Rezultatul este o structură de 4 octeți! Cele două jumătăți ale primului octet sunt câmpurile a și b. Al doilea octet nu este accesibil prin nume, iar ultimii 2 octeți sunt accesibili după numele c. Acesta este un moment foarte periculos. După ce ați descris structura cu câmpuri de biți, asigurați-vă că îi verificați dimensiunea !

De asemenea, ordinea în care biții sunt plasați într-un octet depinde de ordinea octeților. Cu ordinea LITTLE_ENDIAN, câmpurile de biți sunt distribuite începând de la primii octeți, cu BIG_ENDIAN - invers...

Ordinea octetilor

De asemenea, sunt întristat de apelurile la funcțiile htons() , ntohs() , htonl() , nthol() în codul C++. În C acest lucru este încă posibil, dar nu în C++. Nu mă voi împăca niciodată cu asta! Vă rugăm să rețineți că toate următoarele se aplică pentru C++!

Ei bine, voi fi scurt aici. Într-unul dintre articolele mele anterioare am scris deja ce trebuie făcut cu ordinele de octeți. Este posibil să descriem structuri care funcționează extern ca numere, dar în interior ele însele determină ordinea de stocare în octeți. Deci, structura antetului nostru IP va arăta astfel:

#PRAGMA PACK (Push, 1) Struct IPHEADER (Uint8_T Header_LENGTH: 4; UINT8_T VERSIUNE: 4; UINT8_T TYPE_OF_SERVICE; U16BE TOTAL_LENGTH; U16BE IDENTIC T _Reserved: 1; :5; uint6_t_t_t_t check_to_t; ; #pragma pack(pop)
Acordați atenție tipurilor de câmpuri de 2 octeți - u16be. Acum, câmpurile de structură nu au nevoie de conversii în ordinea octeților. Există încă probleme cu fragment_offset , dar cei care nu le au au probleme. Cu toate acestea, puteți, de asemenea, să veniți cu un șablon care ascunde această rușine, să-l testați o dată și să vă simțiți liber să îl utilizați pe tot parcursul codului.

„Limbajul C++ este suficient de complex pentru a ne permite să scriem în el simplu.” Destul de ciudat - eu

ZYÎntr-unul dintre următoarele articole plănuiesc să prezint structurile ideale, din punctul meu de vedere, pentru lucrul cu anteturile de protocol ale stivei TCP/IP. Vorbește înapoi - înainte să fie prea târziu!

O structură este o colecție de variabile unite printr-un singur nume, oferind o modalitate comună de a stoca informații împreună. Declararea unei structuri are ca rezultat un șablon care este utilizat pentru a crea obiecte ale structurii. Variabilele care alcătuiesc o structură sunt numite membri ai structurii. (Membrii unei structuri sunt adesea numiți și elemente sau câmpuri.)

De obicei, toți membrii unei structuri sunt conectați unul cu celălalt. De exemplu, informațiile despre nume și adresă găsite pe o listă de corespondență sunt de obicei reprezentate ca o structură. Următorul fragment de cod declară un șablon de structură care definește un nume și o adresă. Cuvântul cheie struct îi spune compilatorului să declare o structură.

Adresă structura (
nume char;
strada char ; char city;
stare char;
unsigned long int zip;
};

Declarația se termină cu punct și virgulă deoarece o declarație struct este o instrucțiune. Numele structurii addr identifică structura de date și este un specificator de tip. Numele unei structuri este adesea folosit ca scurtătură.

În acest moment, nu este creată nicio variabilă. Doar formularul de date este definit. Pentru a declara o variabilă reală corespunzătoare unei structuri date, ar trebui să scrieți:

Struct addr addr_info;

Această linie declară variabila addr_info de tip addr. La declararea unei structuri, este definită o variabilă de tip mixt. Până când o variabilă de un anumit tip nu este declarată, aceasta nu va exista.

Când o variabilă de structură este declarată, compilatorul alocă automat cantitatea necesară de memorie pentru a găzdui toți membrii săi. Orez. arată locația addr_info în memorie.

Figura: Plasarea structurii addr_info în memorie

Când declarați o structură, puteți declara una sau mai multe variabile în același timp.

De exemplu:

Adresă structura (
nume char;
strada char;
char city;
stare char;
unsigned long int zip;
) addr_info; binfo, cinfo;

declară structura addr și declară variabilele addr_info, binfo, cinfo de acest tip.

Este important să înțelegeți că fiecare variabilă de structură nou creată conține propriile copii ale variabilelor care alcătuiesc structura. De exemplu, câmpul zip al variabilei binfo este separat de câmpul zip al variabilei cinfo. De fapt, singura conexiune dintre binfo și cinfo este că ambele sunt instanțe ale aceluiași tip de structură. Nu mai există nicio legătură între ei.

Dacă este necesară o singură variabilă de structură, atunci nu este nevoie de o etichetă de structură. Înseamnă că

Struct(
nume char;
strada char;
char city;
stare char;
unsigned long int zip;
) addr_info;

Declara o singura variabila addr_info cu tipul definit de structura care o precede. Forma standard a unei declarații de structură este următoarea:

eticheta structurii (
tip nume variabilă;
tip nume variabilă;
tip nume variabilă;
) variabile structurale;

Eticheta este numele tipului de structură, nu numele variabilei. Variabilele structurale sunt o listă de nume de variabile separate prin virgulă. Rețineți că poate lipsi fie eticheta, fie variabilele de structură, dar nu ambele.

O structură este o stocare convenabilă pentru date eterogene pe care doriți să le combinați. De exemplu, puteți crea o structură care descrie parametrii dispozitivului dvs. - setările de rețea, timeout-ul de repaus, identificatorul acestuia etc., cum ar fi un fel de linie de salut și starea LED-ului. Deoarece toți parametrii vor fi stocați într-un singur loc, ei vor fi întotdeauna vizibili, iar IDE-urile normale vă vor solicita câmpurile structurii atunci când le accesați. Vom analiza, de asemenea, stocarea și restaurarea structurilor dintr-o arhivă, precum și transmiterea acestora prin rețea.

Declararea unei structuri ca aceasta:

Struct (uint32_t ID; char IP; uint16_t timeout; bool led; char text; ) parametri;

Cum functioneaza?

În C, sintaxa este destul de convenabilă, în sensul că multe lucruri sunt scrise ca „variabilă de tip_date”, începând cu „int i” care se termină cu „void main() ()”. Deci, aici, cuvântul de cod struct începe declararea structurii, iar întreaga bucată de cod „struct ( ... )” definește pur și simplu un nou tip. În consecință, params este o variabilă gata făcută (o instanță a unui tip) care poate fi utilizată. În interiorul acoladelor sunt listate toate câmpurile structurii, care vor fi apoi accesibile astfel: params.ID sau params.IP. Lungimea câmpurilor trebuie să fie fixă, astfel încât șirurile din formularul *text nu pot fi folosite, ci doar matrice din textul formularului.

Ar fi putut fi făcut puțin diferit: declarați doar tipul și creați o variabilă mai târziu. Pentru a face acest lucru, vom folosi cuvântul cheie typedef și îl vom scrie astfel:

Typedef struct ( uint32_t ID; char IP; uint16_t timeout; bool led; char text; ) params_struct; params_struct params;

Acest lucru face posibilă lăsarea tuturor declarațiilor de tipuri structurale într-un fișier separat (antet), iar în fișierul principal pur și simplu utilizați tipuri structurale gata făcute pentru a declara structurile direct la locul lor.

Desigur, în ambele opțiuni puteți declara oricâte instanțe de structuri doriți sau puteți crea o matrice a acestora:

Struct (uint32_t ID; char IP; uint16_t timeout; bool led; char text; ) params1, params2, params;

Opțiunea de matrice este deosebit de convenabilă pentru un server într-o topologie de rețea client-server - fiecare client stochează propriii parametri într-o structură, iar dispozitivul principal conține un tabel cu parametrii tuturor clienților sub forma unei matrice de structuri.

În principiu, nu este nimic complicat în structuri, iar cu tema serverelor și clienților am abordat treptat un subiect mai interesant:

Stocarea, transferul și sincronizarea structurilor

Va fi o surpriză pentru mulți că aceste structuri sunt stocate în memorie ca o listă plată, toate câmpurile structurii pur și simplu se succed în memorie. Prin urmare, devine posibil să se trateze această structură ca o simplă matrice de octeți! Să verificăm și să creăm o matrice „pe partea de sus” a acestei structuri.

Obținem compensarea inițială astfel:

Char *Bytes =

am declarat un pointer char și am pus adresa params în el. Acum Bytes indică primul octet al structurii, iar cu citirea secvențială vom citi întreg octetul de structură. Dar câți octeți trebuie citiți? Pentru a face acest lucru, luați în considerare două funcții interesante.

sizeof și offsetof

Acestea nu sunt nici măcar funcții, ci macrocomenzi încorporate ale limbajului C. Să începem cu unul mai simplu, dimensiunea.

Compilatorul înlocuiește toate intrările de forma sizeof X cu valoarea lungimii X. X poate fi fie un tip, fie o instanță a unui tip, de exemplu. în cazul nostru, putem înlocui în dimensiune atât tipul de structură (dacă îl setăm folosind typedef) cât și variabila de structură în sine astfel: sizeof params_struct sau sizeof params. Va parcurge toate câmpurile structurii, va adăuga lungimile lor și va returna suma, care va fi lungimea structurii.

offsetof- o macrocomandă reală care preia doi parametri (structura _s_ și câmpul _m_ în ea) și returnează poziția acestui câmp în structură, offset-ul acestuia față de începutul structurii. Această macrocomandă pare foarte simplă:

Offsetof(s, m) (size_t)&(((s *)0)-›m).

Cum lucrează?

  1. Luați numărul 0
  2. Să-l convertim la tipul „pointer la structura s”: (s*)0
  3. Accesăm câmpul m din această structură: ((s*)0)->m
  4. Ii calculăm adresa: &(((s*)0)->m)
  5. Convertiți adresa într-un număr întreg: (size_t)&(((s*)0)->m)

Magia este tocmai în primul pas, în care luăm 0. Datorită acestuia, în al patrulea pas, adresa absolută a câmpului, calculată de compilator, este numărată relativ la începutul structurii - punem structura la adresa 0. Astfel, după executarea acestei macrocomenzi, avem de fapt un offset câmpuri relativ la începutul structurii. Este clar că această macrocomandă va determina corect offset-urile chiar și în structuri complexe și imbricate.

Aici trebuie să facem o mică digresiune. Cert este că am considerat cel mai simplu caz, când câmpurile sunt împachetate exact unul după altul. Există și alte metode de ambalare numite „aplatizare”. De exemplu, puteți da fiecărui câmp un „slot” care este un multiplu de 4 octeți sau 8 octeți. Apoi, chiar și un caracter va ocupa 8 octeți, iar dimensiunea totală a structurii va crește, iar toate offset-urile se vor deplasa și deveni un multiplu al alinierii. Acest lucru este util atunci când programați pentru un computer, deoarece datorită granulării RAM, procesorul poate prelua datele aliniate din memorie mult mai rapid, necesită mai puține operațiuni.

Lucrul cu o matrice dintr-o structură

Bine, acum putem reprezenta orice structură ca o matrice de octeți și invers. Ai înțeles ideea? Acum avem aceeași zonă de memorie cu rolurile „structură” și „matrice”. Schimbăm ceva în structură - matricea se schimbă, schimbăm matricea - structura se schimbă.

Aceasta este esența procesului! Nu avem o matrice separată, deoarece structura în sine este deja o matrice și pur și simplu accesăm memorie folosind metode diferite. Și nu avem bucle de copiere pe câmpuri sau octeți, această buclă va fi imediat în funcția de transfer.

Acum tot ce rămâne este să înveți cum să lucrezi confortabil cu toate acestea.

Structura de stocare și transmisie

Pentru a crea o copie de arhivă a structurii, pentru transmiterea prin rețea sau pentru stocarea într-un loc sigur, transmiteți adresa acestei matrice funcției dumneavoastră de transfer de date. De exemplu, funcția mea pentru scrierea unei matrice de date pe EEPROM arată astfel: I2C_burst_write (I2Cx, HW_address, addr, n_data, *data). Trebuie doar să treceți sizeof params în loc de n_data și ¶ms în loc de *data:

I2C_burst_write (I2Cx, HW_address, adresa, dimensiunea parametrilor, ¶ms)

Funcțiile de transfer de date din rețea arată de obicei cam așa. Transmiteți ¶ms ca date și sizeof params ca lungime a datelor.

Recepția și restaurarea structurii

Totul este exact la fel. Funcția mea pentru citirea unei matrice din EEPROM: I2C_burst_read (I2Cx, HW_address, addr, n_data, *data). n_data = dimensiunea parametrilor, *data = ¶ms:

I2C_burst_read (I2Cx, HW_address, adresa, dimensiunea parametrilor, ¶ms)

Nu uitați că scrieți imediat octeții primiți direct în structură. Dacă transferul este lent sau nu este de încredere, este logic să scrieți datele într-un buffer temporar și, după ce le verificați, să le transferați în structură prin

Memcpy(¶ms, &temp_buffer, sizeof params).

Prin implementarea acestor metode, vom implementa sincronizarea convenabilă a două structuri situate pe computere diferite: client-microcontroller poate fi chiar și de cealaltă parte a globului de server, dar transferul structurilor va fi la fel de ușor.

Stocarea/recuperarea câmpurilor individuale

Și de ce am petrecut atât de mult timp privind compensarea macro? Este foarte convenabil de utilizat pentru citirea și scrierea câmpurilor individuale ale unei structuri, de exemplu, astfel:

I2C_burst_write (I2Cx, HW_address, addr + offsetof(params, IP), sizeof params.IP, ¶ms.IP) I2C_burst_read (I2Cx, HW_address, addr + offsetof(params, IP), dimensiunea params.IP, ¶ms.IP)

Ei bine, în general, ar fi bine să faceți macrocomenzi de wrapper convenabile în acest scop.

#define store(structure, field) I2C_burst_write (I2Cx, HW_address, addr + offsetof(structure, field), sizeof(structure.field), &(structure.field)) #define load(structure, field) I2C_burst_read (I2Cx, HW_address) , addr + offsetof(structură, câmp), dimensiunea(structură.câmp), &(structură.câmp))