Звертання до елементів структур 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Звертання до елементів структур



В мові С++ існує дві операції для звертання до елементів (полів) структур: операція «крапка», яка застосовується при звертанні до полів структур, заданих через зміну; операція «стрілка», яку позначають парою знаків -> (мінус і більше), і використовують для звертання до поля структури через вказівник на цю структуру. Обидві операції мають найвищий пріоритет і асоціативність зліва направо.

Виділення поля структури, заданої через змінну, виконує така синтаксична конструкція:

 

ім’я_структурної_змінної.ім’я_поля

 

Цей вираз називають уточненим іменем елемента структури. Його значенням є значення змінної, яка формує відповідне поле структури. Поля структур повністю зберігають властивості даних свого типу, над ними можна виконувати всі операції, що допускаються для цього типу.

Знайдемо відстань між двома точками, заданими за допомогою структур.

 

#include <iostream>

using namespace std;

int main() {

struct point {

     double x;

     double y;

};

struct point p1, p2 = { 1.0, 2.0 };

p1.x = 2.0;

p1.y = 3.0;

double d = sqrt(pow(p1.x - p2.x, 2.) + pow(p1.y - p2.y, 2.));

cout << d << endl;

}

 

Якщо полем структури є масив, або виконується звертання до масиву структур, то операції крапка і квадратні дужки, які мають однаковий пріоритет, виконуються зліва направо.

 

#include <iostream>

using namespace std;

int main() {

struct student {

     char name[ 40 ];

     int number;

     int rating;

};

struct student s = { "James Bond", 12, 75 };

cout << s.name << endl;

cout << s.name[ 6 ] << endl;

cout << * (s.name + 6) << endl;

}

 

Тут полем структури є масив символів name. Вираз s.name[ 6 ] обчислюється зліва направо, тобто спочатку виконується операція крапка, її результатом буде значення поля char name[ 40 ], тобто адреса першого елементу масиву. Після цього над отриманою адресою виконується операція «квадратні дужки», тобто індексне звертання до елементу масиву.

 

#include <iostream>

using namespace std;

int main() {

struct student {

     char name[ 40 ];

     int number;

     int rating;

};

struct student as[ 3 ] = {

     { "James Bond", 12, 75 },

     { "Bill Gates", 13, 98 },

     { "Steve Jobs", 12, 100 }

};

cout << as[ 2 ].rating << endl;

}

 

В цьому прикладі є масив структур as з трьох елементів. Вираз as[ 2 ].rating обчислюється зліва направо, тобто спочатку виконується операція «квадратні дужки», результатом якої є адреса третьої структури, після цього виконується операція крапка – звертання до відповідного поля цієї структури (поля rating).

 

#include <iostream>

using namespace std;

int main() {

struct student {

     char name[ 40 ];

     int number;

     int rating;

};

struct student s = { "James Bond", 12, 75 };

cout << s.name << endl;

cout << s.name[ 6 ] << endl;

cout << * (s.name + 6) << endl;

struct student as[ 3 ] = {

     { "James Bond", 12, 75 },

     { "Bill Gates", 13, 98 },

     { "Steve Jobs", 12, 100 }

};

cout << as[ 2 ].name[ 6 ] << endl;

}

 

Тут при обчисленні виразу as[ 2 ].name[ 6 ] операції теж виконуються зліва направо – знаходимо адресу третьої структури, звертаємося до поля name – отримуємо адресу початку масиву, звертаємося до сьомого елементу масиву name.

Операція «стрілка» спрощує звертання до полів структур, які виконуються через адреси цих структур або вказівники на структури. Синтаксис цих операцій такий:

 

вказівник_на_структуру -> ім’я_поля

 

Наприклад, звернення до поля rating третього елемента масиву as з попереднього прикладу можна зробити таким чином:

 

cout << (as + 2)->rating << endl;

 

А звертання до сьомого елементу масиву name третього елемента масиву as таким:

 

cout << *((as + 2)->name + 6) << endl;

 

Вказівникову форму звертання до елементів структур зручно використовувати, коли для роботи з масивами структур використовують зовнішні вказівники, базовий тип яких збігається з типом структур – елементів масиву. Знайдемо середній бал студентів, інформація про яких зберігається в масиві структур.

 

#include <iostream>

using namespace std;

int main() {

struct student {

     char name[ 40 ];

     int number;

     int rating;

};

const int NS = 5;

struct student as[ NS ] = {

     { "James Bond",      12, 80 },

     { "Bill Gates",      13, 92 },

     { "Paul Allen",      14, 93 },

     { "Steve Jobs",      15, 95 },

     { "Stephen Wozniak", 16, 96 }

};

struct student * ps, *plast;

int avg;

for (ps=as, plast = &as[ NS-1 ], avg=0; ps <= plast; ps++) {

     cout << ps->name << "\t";

     cout << ps->number << "\t";

     cout << ps->rating << "\n";

     avg += ps->rating;

}

avg /= NS;

cout << avg << endl;

}

 

Мова С++ не допускає, щоб вкладена структура мала той самий тип, що й структура, елементом якої вона є. Але поле структури може бути вказівником з довільним базовим типом, в тому числі і вказівником на структуру. Тому дозволено, щоб поле структури було вказівником на структуру свого типу. Це використовують при створенні динамічних структур даних, наприклад, динамічних списків:

 

#include <iostream>

using namespace std;

int main() {

struct student {

     int id;

     int rating;

     student * next;

} * first = NULL, * p;

// Створюємо першого студента у списку

first = new student;

first->id = 1;

first->rating = 90;

first->next = NULL;

// Створюємо наступного студента у списку

p = new student;

p->id = 2;

p->rating = 92;

p->next = first;

first = p;

// Створюємо наступного студента у списку

p = new student;

p->id = 3;

p->rating = 95;

p->next = first;

first = p;

// виводимо список студентів на екран

p = first;

while (p) {

     cout << p->id << "\t";

     cout << p->rating << "\n";

     p = p->next;

}

// Звільняємо пам’ять

do {

     p = first;

     first = first->next;

     cout << "deleting " << p->id << endl;

     delete p;

} while (first);

}

Перейменування типів

До складу мови С++ входить спеціальна декларація typedef, яка дає програмісту змогу надавати власні імена типам даних програми. Синтаксис перейменування типів такий:

 

typedef тип користувацьке_ім’я_типу;

 

Декларація typedef не створює нового типу, а тільки надає можливість звертатися до існуючого типу за допомогою нового імені. За допомогою цієї декларації можна змінювати довгі імена типів даних на більш короткі і зручні:

 

typedef unsigned int UINT;

UINT a = 23, b;

 

В цьому прикладі ми перейменували тип даних unsigned int в UINT. Після цього нове ім’я (UINT) можна використовувати в програмі так само, як і старе (unsigned int).

Всередині typedef користувацьке ім’я типу записується в тому місці, де для звичайного оголошення вказується ім’я змінної. Наступне оголошення можна використовувати для оголошення символьних рядків:

 

typedef char STRING[ 80 ];

STRING s = "a string";

cout << s << endl;

 

Використання декларації typedef сприяє підвищенню наочності імен типів даних та скорочує їх запис. Це передусім стосується таких типів даних, як структури, об’єднання і переліки, для яких typedef можна використовувати одночасно з оголошенням шаблону. Наприклад:

 

typedef struct student {

char name[ 40 ];

int number;

int rating;

} STUDENT;

STUDENT s = { "James Bond", 12, 80 };

 

Крім наочності та простоти іменування типів декларація typedef дає програмістові змогу створювати машино незалежні типи даних. Насамперед це стосується тих типів, параметри яких залежать від апаратних особливостей комп’ютера. Якщо такому типові надати певне typedef – ім’я і використовувати це ім’я всюди в тексті програми, то в новому середовищі достатньо внести зміни в рядок декларації typedef, щоб встановити нові параметри для перейменованого типу.

Об’єднання

Об’єднання – це особливий тип даних, який дає змогу записувати в одну і ту ж встановлену ділянку оперативної пам'яті дані різних типів і розмірів. Таким чином створюється ділянка пам'яті спільного користування, до якої можна звертатись рідними способами через одну змінну, що має тип об’єднання.

Оголошення об’єднання подібне до оголошення структури: задається шаблон об’єднання і перелічуються відповідні змінні. Так само, як і для структур, шаблон об’єднання можна оголошувати окремо або одночасно з оголошенням змінних. У разі автономного оголошення шаблону об’єднання застосовують синтаксичну конструкцію, аналогічну до шаблону структури, але починають її ключовим словом union:

 

union тег_ об’єднання {

тип_поля_1 ім’я_поля_1;

тип_поля_2 ім’я_поля_2;

тип_поля_3 ім’я_поля_3;

};

 

Тег об’єднання ідентифікує дане об’єднання, а список полів задає перелік даних, які можна заносити в це об’єднання.

Як і у випадку структур, змінні з типом об’єднання можна оголошувати спільно з оголошенням шаблону або пізніше, використовуючи тип union тег об’єднання. Обсяг ділянки пам'яті, яка виділяється для кожної змінної, що має тип об’єднання, визначається розміром найдовшого поля даного об’єднання.

 

#include <iostream>

using namespace std;

int main() {

union demo {

     double d;

     int i;

     char c;

};

union demo d;

d.d = 1.2;

cout << d.d << endl; // 1.2

cout << d.i << endl; // 858993459

cout << d.c << endl; // 3

}

 

Всі три ділянки змінної d займають спільну ділянку оперативної пам'яті, обсяг якої буде дорівнювати розміру найдовшого поля. Якщо розмір змінних типів double, int i char складає 8, 4 і 1 байт відповідно, то розмір змінних типу union demo буде 8 байт. Всі три ділянки мають однакове нульове зміщення відносно адреси початку ділянки, яку займає змінна.

При оголошенні змінних з типом об’єднання можна виконувати ініціалізацію. Але ініціалізувати об’єднання можна значенням, сумісним з типом першого поля цього об’єднання. Наприклад:

 

#include <iostream>

using namespace std;

int main() {

typedef union demo {

     double d;

     int i;

     char c;

} TEST;

TEST d = { 2.3 };

cout << d.d << endl;

}

 

До змінних з типом об’єднання можна застосовувати всі операції, що й до структурних змінних: присвоювання (тобто копіювання цілого об’єднання), визначення адреси змінної, виділення окремих елементів (полів) об’єднання. Для звертання до полів об’єднання використовують такі ж синтаксичні конструкції на основі операцій «крапка» і «стрілка», як і в разі звертання до елементів структур:

 

ім’я_змінної_об’єднання.ім’я_поля

вказівник_на_об’єднання->ім’я_поля

 

Поля об’єднань можуть бути довільного типу – простого чи складеного. Зокрема, поле об’єднання може бути структурою, вкладеним об’єднанням або вказівником на структуру чи об’єднання. Водночас об’єднання можуть бути елементами масиву або складовими частинами структур інших типів.

В якості приклада розглянемо таку програму. Необхідно зберігати інформацію про платіж, який може здійснюватися кредитною карткою або чеком. Для збереження інформації про платіж кредитною карткою необхідний масив символів з 25 елементів, для збереження інформації про платіж чеком – довге ціле. Двома методами одночасно платіж здійснений не може бути, тому можна використовувати об’єднання:

 

union payment {

char card[ 25 ];

long check;

};

 

В кожен момент часу в змінній типу об’єднання зберігається тільки одне значення, відповідальність за його правильне використання лежить на програмісті. Тому зручно разом з об’єднаннями зберігати інформацію про те, який саме елемент використовується в даний момент.

 

#include <iostream>

using namespace std;

int main() {

enum paytype { CARD, CHECK };

struct payment {

     paytype ptype;

     union {

           char card[ 25 ];

           long check;

     };

};

payment p;

p.ptype = CHECK;

p.check = 123456;

switch (p.ptype) {

     case CARD: cout << p.card << endl; break;

     case CHECK: cout << p.check << endl; break;

}

}

 

В цьому прикладі використовується об’єднання без імені, тому для звертання до його полів необхідно просто вказувати імена цих полів.

Поля бітів

Поля бітів – це особливий вид елементів структур та об’єднань. Такі поля складаються з послідовних бітів, записаних в дане цілого типу. Поля бітів застосовують, коли необхідно мати доступ до окремих бітів всередині байта або слова даних. Оголошення поля бітів виконується через таку синтаксичну конструкцію:

 

тип_поля ім’я_бітового_поля: розмір_поля;

 

де тип може бути тільки типом int з допоміжними специфікаторами unsigned i signed; ім’я бітового поля – звичайний ідентифікатор; розмір поля – ціле число, яке задає ширину поля в бітах. Доступ до поля здійснюється по імені. Адресу поля отримати не можна, в іншому поля бітів використовуються так само, як поля структур.

Виведемо на екран двійкове представлення числа, використовуючи поля бітів і маску.

 

#include <iostream>

using namespace std;

int main() {

union binary {

     char value;

     struct {

           unsigned a0: 1;

           unsigned a1: 1;

           unsigned a2: 1;

           unsigned a3: 1;

           unsigned a4: 1;

           unsigned a5: 1;

           unsigned a6: 1;

           unsigned a7: 1;

     } byte;

};

binary t;

t.value = 15;

cout << t.byte.a7 << t.byte.a6 << t.byte.a5 << t.byte.a4;

cout << t.byte.a3 << t.byte.a2 << t.byte.a1 << t.byte.a0;

int a = 15, m = 1;

for (m = 128; m; m >>= 1)

     if (a & m) cout << "1";

     else cout << "0";

}

 

Використаємо поля бітів для простого шифрування – будемо міняти місцями три перші і три останні біта символу.

 

#include <iostream>

using namespace std;

int main() {

union code {

     char value;

     struct {

           unsigned int lbt: 3;

           unsigned int mbt: 2;

           unsigned int hbt: 3;

     };

};

code t1, t2;

const int N = 10;

char c1[ N ] = "Hello", c2[ N ] = "", c3[ N ] = "";

c1[ N - 1 ] = c2[ N - 1 ] = c3[ N - 1 ] = '\0';

// Шифруємо рядок символів

for (int i = 0; i < N - 1; i++) {

     t1.value = c1[ i ];

     t2.lbt = t1.hbt;

     t2.hbt = t1.lbt;

     t2.mbt = t1.mbt;

     c2[ i ] = t2.value;

}

// Дешифруємо рядок символів

for (int i = 0; i < N - 1; i++) {

     t1.value = c2[ i ];

     t2.lbt = t1.hbt;

     t2.hbt = t1.lbt;

     t2.mbt = t1.mbt;

     c3[ i ] = t2.value;

}

// Виводимо результат у вигляді рядків символів і

// в двійковому вигляді

cout << c1 << endl;

cout << c2 << endl;

cout << c3 << endl;

for (int i = 0; i < N - 1; i++) {

     for (int m = 128; m; m >>= 1)

           if (c1[ i ] & m) cout << "1";

           else cout << "0";

     cout << " ";

}

cout << endl;

for (int i = 0; i < N - 1; i++) {

     for (int m = 128; m; m >>= 1)

           if (c2[ i ] & m) cout << "1";

           else cout << "0";

     cout << " ";

}

cout << endl;

for (int i = 0; i < N - 1; i++) {

     for (int m = 128; m; m >>= 1)

           if (c3[ i ] & m) cout << "1";

           else cout << "0";

     cout << " ";

}

}

 

Директиви препроцесора



Поделиться:


Последнее изменение этой страницы: 2021-12-15; просмотров: 153; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.19.211.134 (0.105 с.)