Sizeof value 3 страница

#include <vcl>

#include <iostream>// Для потокового введення-виведення

#include <conio>// Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

struct demoStruct { // Оголошення типу структури

int a;

char ch;

};

 

void fun_a(demoStruct pm); // Оголошення прототипу функції

 

int main()

{

demoStruct arg; // Визначення змінної arg типу demoStruct.

 

arg.a = 1000;

arg.ch = 'x';

 

fun_a(arg); // Передача структури функції як аргументу

 

getch(); return 0;

}

 

void fun_a(demoStruct pm) // Визначення функції

{

cout << "Передана функції структура: "

<< "a= " << pm.a << "; ch= " << pm.ch << "\n";

}

Внаслідок виконання цієї програми на моніторі буде відображено такий результат:

Передана функції структура: a= 1000; ch= x

У цій програмі як аргумент arg у функції main(), так і параметр pm у функції fun_a() мають однаковий тип. Тому аргумент arg можна передати функції fun_a(). Якби типи цих структур були різні, у процесі компілювання коду програми було б видано повідомлення про помилку.

10.1.7. Повернення функцією структури як значення

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

Використовуючи структуру як параметр, необхідно також пам'ятати, що тип структури повинен відповідати типу повернутого значення. Наприклад, у наведеному нижче коді програми спочатку оголошується структура demoStruct, а потім функція fun_a()приймає параметр типу demoStruct.

Код програми 10.4. Демонстрація механізму повернення функцією структури як значення

#include <vcl>

#include <iostream>// Для потокового введення-виведення

#include <conio>// Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

struct demoStruct { // Оголошення типу структури

int a;

char ch;

};

 

void fun_a(demoStruct pm, char zm); // Оголошення прототипу функції

demoStruct fun_b(demoStruct pm); // Оголошення прототипу функції

 

int main()

{

demoStruct arg1={1000,'x'}, arg2; // Визначення структурних змінних

 

fun_a(arg1,'1'); // Передача структури функції як аргументу

arg2 = fun_b(arg1); // Повернення функцією структури як значення

fun_a(arg2,'2'); // Передача структури функції як аргументу

 

getch(); return 0;

}

 

void fun_a(demoStruct pm, char zm) // Визначення функції

{

cout << "Передана функції структура arg" << zm

<< ": a= " << pm.a << "; ch= " << pm.ch << "\n";

}

 

demoStruct fun_b(demoStruct pm) // Визначення функції

{

demoStruct pn;

 

pn.a = 3*pm.a;

pn.ch = 'y';

return pn;

}

Внаслідок виконання цієї програми на моніторі буде відображено такий результат:

Передана функції структура arg1: a= 1000; ch= x

Передана функції структура arg2: a= 3000; ch= y

У цій програмі як аргументи arg1 і arg2 у функції main(), так і параметр pm у функції fun_a() мають однаковий тип. Аналогічно у функції fun_b() визначена структурна змінна pn має цей самий тип. Тому аргументи arg1 і arg2 можна передати функції fun_a(), а значення структурної змінної pn можна повернути з функції fun_b() у функції main(). Якби типи цих структур були різні, у процесі компілювання коду програми було б видано повідомлення про помилку. Окрім цього, у функції fun_a() як аргумент передається проста змінна типу char для ідентифікування назви структурної змінної.

10.2. Використання покажчиків на структури і оператора "стрілка"

У мові програмування C++ покажчики на структури можна використовувати так само, як і покажчики на змінні будь-якого іншого типу. Проте використання покажчиків на структури має ряд особливостей, які необхідно враховувати.

10.2.1. Особливості використання покажчиків на структури

Покажчик на структуру оголошується так само, як покажчик на будь-яку іншу змінну, тобто за допомогою символу "*", поставленого перед іменем структурної змінної. Наприклад, використовуючи визначену вище структуру invStruct, можна записати таку настанову, яка оголошує змінну inv_pointer покажчиком на дані типу invStruct:

invStruct *inv_pointer;

Щоб знайти адресу структурної змінної, необхідно перед її іменем розмістити оператор "&". Наприклад, припустимо, що за допомогою наведеного нижче коду програми ми оголошуємо структуру, визначаємо структурну змінну і покажчик на структуру оголошеного нами типу:

struct balStruct { // Оголошення типу структури

float balance;

char nazva[80];

} person; // Визначення структурної змінної

 

balStruct *p; // Визначаємо покажчик на структуру.

Тоді у процесі виконання настанови

p = &person;

у покажчик р буде поміщено адресу структурної змінної person.

До членів структури можна отримати доступ за допомогою покажчика на цю структуру. Але|та| у цьому випадку використовується не оператор "крапка", а оператор "->". Наприклад, у процесі виконання такої настанови ми отримуємо доступ до поля balance через покажчик р:

p->balance

Оператор "->" називається оператором "стрілка". Він утворюється з використанням знаків "мінус" і "більше".

Оператор "стрілка" (->) дає змогу отримати доступ до членів структури за допомогою покажчика.

Покажчик на структуру можна використовувати як параметр функції. Важливо пам'ятати про такий спосіб передачі параметрів, оскільки він працює набагато швидше, ніж у випадку, коли функції "власною персоною" передається об'ємна структура[38].

Необхідно пам'ятати!Щоб отримати доступ до членів структури, необхідно використовувати оператор "крапка". Щоб отримати доступ до членів структури за допомогою покажчика, потрібно використовувати оператор "стрілка".

10.2.2. Приклад використання покажчиків на структури

Використання покажчиків на структури можна розглянути на прикладі функцій часу і дати, які часто використовується у мові С++, призначена для зчитування значення поточного системного часу і дати. Для цього у програму необхідно включити заголовок <ctime>, який підтримує два типи дати, що вимагаються згаданими функціями. Один з цих типів, time_t призначений для представлення системного часу і дати у вигляді довгого цілочисельного значення, яке використовується як календарний час. Другий тип є структурою tm, яка містить окремі елементи дати і часу. Таке представлення часу називають поелементним. Структура tm має такий формат:

struct tm { // Оголошення типу структури часу і дати

int tm_sec; /* секунди, 0-61 */

int tm_min; /* хвилини, 0-59 */

int tm_hour; /* годинник, 0-23 */

int tm_mday; /* день місяця, 1-31 */

int tm_mon; /* місяць, починаючи з січня, 0-11 */

int tm_year; /* рік після 1900 */

int tm_wday; /* день, починаючи з неділі, 0-6 */

int tm_yday; /* день, починаючи з 1-го січня, 0-365 */

int tm_isdst /* індикатор літнього часу */

};

Значення індикатора tm_isdst є позитивним, якщо діє режим літнього часу (Daylight Saving Time), дорівнює нулю, якщо не діє, і є негативним, якщо інформація про це недоступна.

Основним засобом визначення часу і дати у мові C++ є функція time(), яка має такий прототип:

time_t time(time_t *curtime);

Функція time() повертає поточний календарний час системи. Якщо в системі відлік часу не виконується, повертається значення -1. Функцію time() можна викликати або з нульовим покажчиком, або з покажчиком на змінну curtime типу time_t. У останньому випадку цій змінній буде присвоєне значення поточного календарного часу.

Щоб перетворити календарний час в по-елементний, необхідно використати функцію localtime(), яка має такий прототип:

struct tm *localtime(const time_t *curtime);

Функція localtime() повертає покажчик на поелементну форму параметра curtime, представленого у вигляді структури tm. Значення curtime вказує на локальний час. Його зазвичай отримують за допомогою функції time().

Структура, що використовується функцією localtime() для зберігання часу в поелементній формі, розміщується в пам'яті статично і перезаписується під час кожного виклику цієї функції. Якщо потрібно зберегти вміст цієї структури, потрібно скопіювати його в яку-небудь іншу область пам'яті.

Наведений нижче код програми демонструє використання функцій time() і localtime() для відображення на екрані поточного системного часу.

Код програми 10.5. Демонстрація механізму використання функцій time() і localtime()для відображення поточного системного часу

#include <vcl>

#include <iostream>// Для потокового введення-виведення

#include <conio>// Для консольного режиму роботи

#include <ctime> // Для використання системного часу і дати

using namespace std; // Використання стандартного простору імен

 

int main()

{

struct tm *ptr;

time_t lt;

 

lt = time('\0');

 

ptr = localtime(&lt);

cout<<ptr->tm_hour << ":" << ptr->tm_min;

cout<<":" << ptr->tm_sec <<"\n";

 

getch(); return 0;

}

Ось|от| один з можливих результатів виконання цієї програми:

20:32:44

Незважаючи на те, що у наведеному вище коді програми може використовуватися поелементна форма представлення часу і дати, найпростіше згенерувати рядок часу і дати за допомогою функції asctime(), прототип якої має такий вигляд:

char *asctime(const struct tm *ptr);

Функція asctime() повертає покажчик на рядок, який містить результат перетворення інформації, яка зберігається в ptr структурі, що адресується покажчиком, і має такий формат:

день місяць число час:хвилини:секунди рік\n\0

Покажчик на структуру, що передається функції asctime(), часто отримують за допомогою функції localtime().

Область пам'яті, яка використовується функцією asctime() для зберігання форматованого рядка результату, є символьним масивом (що статично виділяється в пам'яті), який перезаписується під час кожного виклику цієї функції. Якщо потрібно зберегти вміст цього рядка, то необхідно скопіювати його в яку-небудь іншу область пам'яті.

У наведеному нижче коді програми продемонстровано механізм використання функції asctime() для відображення системного часу і дати.

Код програми 10.6. Демонстрація механізму використання функції asctime() для відображення системного часу і дати

#include <vcl>

#include <iostream>// Для потокового введення-виведення

#include <conio>// Для консольного режиму роботи

#include <ctime> // Для використання системного часу і дати

using namespace std; // Використання стандартного простору імен

 

int main()

{

struct tm *ptr;

time_t lt;

 

lt = time('\0');

 

ptr = localtime(&lt);

cout<<asctime(ptr);

 

getch(); return 0;

}

Ось один з можливих результатів виконання цієї програми.

Sun Sep 28 17:20:35 2006

У мові C++ передбачені і інші функції дати і часу, з якими можна познайомитися, звернувшись до документації, що додається до Вашого компілятора.

10.3. Посилання на структури

Для доступу до структури можна використовувати посилання. Посилання на структуру часто використовують як параметр функції або значення, що повертається функцією. Під час отримання доступу до членів структури за допомогою посилання використовують оператор "крапка"[39].

10.3.1. Використання структур під час передачі функції параметрів за посиланням

У наведеному нижче коді програми показано, як можна використовувати структуру під час передачі функції параметрів за посиланням.

Код програми 10.7. Демонстрація механізму використання посилання на структуру

#include <vcl>

#include <iostream>// Для потокового введення-виведення

#include <conio>// Для консольного режиму роботи

using namespace std; // Використання стандартного простору імен

struct myStruct { // Оголошення типу структури

int a;

int b;

};

 

// Оголошення прототипу функції,

// яка отримує і повертає посилання на структуру

myStruct &fun_2(myStruct &var);

 

int main()

{

myStruct X_st, Y_st;

X_st.a = 10; X_st.b = 20;

cout<<"Початкові значення полів : X_st.a= "

<< X_st.a << "; X_st.b= " << X_st.b << "\n";

 

Y_st = fun_2(X_st);

cout<< "Модифіковані значення полів: X_st.a= "

<< X_st.a << "; X_st.b= " << X_st.b << "\n";

cout<< "Модифіковані значення полів: Y_st.a= "

<< Y_st.a << "; Y_st.b= " << Y_st.b << "\n";

 

getch(); return 0;

}

 

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

myStruct &fun_2(myStruct &var)

{

var.a = var.a * var.a;

var.b = var.b / var.b;

 

return var;

}

Ось результати виконання цієї програми.

Початкові значення полів: X_st.а= 10; X_st.b= 20

Модифіковані значення полів: X_st.а= 100; X_st.b= 1

Модифіковані значення полів: Y_st.а= 100; Y_st.b= 1

Зважаючи на істотні витрати системних ресурсів, що використовуються на передачу структури функції (або під час повернення її функцією) багато С++-програмістів для виконання таких завдань використовують посилання на структури.

10.3.2. Використання як членів структур масивів і структур

Кожен член структури може мати будь-який допустимий тип даних, y тому числі і такі складені типи, як масиви й інші структури. Оскільки ця тема пов'язана з певними труднощами для програмістів, то зупинимося на ній детальніше.

Масив, що використовується як член структури, обробляється цілком звичайним способом. Розглянемо таку структуру:

struct demoStruct { // Оголошення типу структури

int tMas[10][10]; // Цілочисельний масив розміром 10х10.

float b;

} var; // Визначення структурної змінної

Щоб присвоїти певне значення елементу масиву tMas з "координатами" 3´7 в структурі var типу demoStruct, необхідно записати таку настанову:

var.tMas[3][7] = 2.37;

Як показано у цьому прикладі, якщо масив є членом структури, то для доступу до елементів цього масиву індексується ім'я масиву, а не ім'я структури.

Якщо певна структура є членом іншої структури, то вона називається вкладеною структурою. У наведеному нижче прикладі структура addrStruct вкладена у структуру empStruct:

struct addrStruct { // Оголошення адреси структури

char nazva[40]; // Прізвище службовця

char street[40]; // Вулиця

char city[40]; // Місто

char zip[10]; // Поштовий індекс

};

struct empStruct { // Оголошення реквізитів структури

addrStruct address; // Адреса службовця

float wage; // Оклад службовця

} worker;

Тут структура empStruct має два члени. Першим членом є структура типу addrStruct, яка міститиме адресу службовця. Другим членом є змінна wage, яка зберігає його оклад. У процесі виконання наведеної нижче настанови полю zip структури address, яка є членом структури worker, буде присвоєно поштовий індекс 76285:

worker.address.zip = 76285;

Як бачимо, члени структур вказуються зліва направо, – від крайньої зовнішньої до найдальшої внутрішньої.

Структура також може містити як свій член покажчик на цю ж структуру. Тобто для структури цілком допустимо містити член, який є покажчиком на неї саму. Наприклад, в такій структурі змінна sptr оголошується як покажчик на структуру типу myStruct, тобто на оголошувану тут структуру:

struct myStruct { // Оголошення типу структури

int a;

char str[80];

myStruct *sptr; // Покажчик на структуру типу myStruct

};

Структури, що містять покажчики на самих себе, часто використовують під час створення таких структур даних, як зв'язні списки. У міру вивчення мови C++ Вам обов'язково потраплять програмні коди, у яких застосовуються подібні речі.

10.3.3. Порівняння С- і С++-структур

С++-структури – нащадки С-структур. Отже, будь-яка С-структура також є дійсною С++-структурою. Між ними, проте, існують важливі відмінності:

по-перше, як буде показано в наступному розділі, С++-структури мають деякі унікальні атрибути, які дають змогу їм підтримувати об'єктно-орієнтоване програмування;

по-друге, у мові С структура не оголошує насправді новий тип даних. Цим може "похвалитися" тільки С++-структура.

Як було сказано вище, оголошуючи структуру у мові програмування C++, оголошуємо новий тип, який називається за іменем цієї структури. Цей новий тип можна використовувати для визначення змінних, визначення значень, що повертаються функціями, і т.ін. Проте у мові С ім'я структури називається її тегом (або дескриптором). А тег сам по собі не є іменем типу.

Щоб зрозуміти цю відмінність, розглянемо такий фрагмент С-коду:

struct cStruct {

int a;

int b;

};

// Оголошення змінної cStruct

struct cStruct strVar;

Звернемо Вашу увагу на те, що наведене вище оголошення структури точно таке ж саме, як у мові C++. Тепер уважно розглянемо визначення структурної змінної strVar. Воно також починається з ключового слова struct. У мові С після оголошення структури для повного задавання типу даних все одно потрібно використовувати ключове слово struct спільно з тегом| цієї структури (у цьому випадку з ідентифікатором cStruct).

Якщо Вам доведеться перетворювати старі С-програми у код програми мовою C++, не варто турбуватися про відмінності між С- і С++-структурами, оскільки мова C++, як і раніше, приймає С-орієнтовані оголошення. Наприклад, попередній фрагмент С-коду програми коректно скомпілюється як частина будь-якої С++-програми. З погляду компілятора мови C++ у визначенні змінної strVar зайвим використано тільки ключове слово struct, без якого мова C++ може обійтися.

10.4. Бітові поля структур

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

по-перше, якщо ми маємо справу з обмеженим об'ємом пам'яті, можна зберігати декілька булевих (логічних) значень в одному байті;

по-друге, деякі інтерфейси пристроїв передають інформацію, закодовану саме в бітах;

по-третє, існують підпрограми кодування, яким потрібен доступ до окремих бітів у рамках байта.

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

Бітове поле – це біт-орієнтований член структури.

Метод, який використовується у мові C++ для доступу до бітів, базується на застосуванні структур. Бітове поле – це спеціальний тип члена структури, який визначає свій розмір у бітах. Загальний формат визначення бітових полів є таким:

struct ім'я_типу_структури {

тип ім'я_1: довжина;

тип ім'я_2: довжина;

. . . . . . . .

тип ім'я_n|: довжина;

};

У цьому записі елемент тип означає тип бітового поля, а елемент довжина – кількість бітів у цьому полі. Бітове поле повинно бути оголошено як значення цілочисельного типу або перерахунку. Бітові поля завдовжки 1 біт оголошуються як значення типу без знаку (unsigned), оскільки єдиний біт не може мати знакового розряду.

Бітові поля зазвичай використовують для аналізу вхідних даних, які приймаються від пристроїв, що входять до складу устаткування системи. Наприклад, порт станів послідовного адаптера зв'язку може повертати байт стану, організований так:

Біт Значення у встановленому стані
Зміна в лінії установки в початковий стан
Зміна в лінії готовності даних
Виявлений задній фронт
Зміна в лінії прийому даних
Встановлення в початковий стан
Дані готові
Телефонний сигнал виклику
Сигнал прийнято

Для представлення інформації, яка міститься в байті станів, можна використовувати такі бітові поля.

struct status_type {

unsigned delta_cts: 1; // Зміна в лінії установки в початковий стан

unsigned delta_dsr: 1; // Зміна в лінії готовності даних

unsigned tr_edge: 1; // Виявлений задній фронт

unsigned delta_rec: 1; // Зміна в лінії прийому даних

unsigned cts: 1; // Встановлення в початковий стан

unsigned dsr: 1; // Дані готові

unsigned ring: 1; // Телефонний сигнал виклику

unsigned rec_line: 1; // Сигнал прийнято

} status;

Щоб визначити, коли можна відправити або отримати дані, використовується такий програмний код:

status = get_port_status();

if(status.cts) cout << "Встановлення в початковий стан";

if(status.dsr) cout << "Дані готові";

Щоб присвоїти бітовому полю значення, достатньо використовувати таку ж форму, яка зазвичай застосовується для елемента структури будь-якого іншого типу. Наприклад, настанова

status.ring = 0;

очищає бітове поле ring. Як видно з цих прикладів, доступ до кожного бітового поля можна отримати за допомогою оператора "крапка". Але, якщо загальний доступ до структури здійснюється через покажчик, необхідно використовувати оператор "->".

Потрібно мати на увазі, що зовсім необов'язково присвоювати ім'я кожному бітовому полю. Це дає змогу звертатися тільки до потрібних бітів, "обходячи" інші. Наприклад, якщо нас цікавлять тільки біти cts і dsr, то ми могли б оголосити структуру status_type так:

struct status_type {

unsigned: 4;

unsigned cts: 1;

unsigned dsr: 1;

} status;

Необхідно звернути тут увагу на те, що біти після останнього іменованого dsr немає потреби взагалі згадувати.

У структурі можна змішувати "звичайні" члени з бітовими полями. Наприклад,:

struct empStruct {

struct addrStruct address;

float pay;

unsigned lay_off: 1; // працює чи ні

unsigned hourly: 1; // погодинна оплата або оклад

unsigned deductions: 3; // утримання податку

};

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

Використання бітових полів має певні обмеження. Програміст не може отримати адресу бітового поля або посилання на нього. Бітові поля не можна зберігати в масивах. Їх не можна оголошувати статичними. При переході від одного комп'ютера до іншого неможливо знати впевнено порядок проходження бітових полів: справа наліво або зліва направо. Це означає, що будь-яка програма, у якій використовуються бітові поля, може страждати певною залежністю від марки комп'ютера. Можливі і інші обмеження, пов'язані з особливостями реалізації компілятора мови C++. Тому, перш ніж використовувати бітові поля структур, доцільно насамперед з'ясувати ці питання у відповідній документації до Вашого компілятора.

У наступному підрозділі представлено код програми (прог. 10.8), у якому для відображення символьних ASCII-кодів у двійковій системі числення використовуються бітові поля.

10.5. Особливості використання об'єднань

10.5.1. Оголошення об'єднання

Об'єднання складається з декількох змінних, які розділяють між собою одну і ту саму область пам'яті. Це означає, що об'єднання забезпечує можливість інтерпретації однієї і тієї ж самої конфігурації бітів двома (або більше) різними способами. Оголошення об'єднання, як неважко переконатися на наведеному нижче прикладі, є подібним до оголошення структури:

union demoUnion { // Оголошення типу об'єднання

short int izm;

char ch;

};

У наведеному прикладі оголошується об'єднання, у якому значення типу short int і значення типу char розділяють одну і ту саму область пам'яті. Необхідно відразу ж з'ясувати один момент: неможливо зробити так, щоб це об'єднання зберігало і цілочисельне значення, і символ одночасно, оскільки змінні izm та ch накладаються (у пам'яті) одне на друге. Але програма у будь-який момент може обробляти інформацію, що міститься у цьому об'єднанні, як цілочисельне значення або як символ. Отже, об'єднання забезпечує два (або більше) способи представлення однієї і тієї ж самої порції даних. Як видно з цього прикладу, об'єднання оголошується за допомогою ключового слова union.