Студопедия

КАТЕГОРИИ:


Архитектура-(3434)Астрономия-(809)Биология-(7483)Биотехнологии-(1457)Военное дело-(14632)Высокие технологии-(1363)География-(913)Геология-(1438)Государство-(451)Демография-(1065)Дом-(47672)Журналистика и СМИ-(912)Изобретательство-(14524)Иностранные языки-(4268)Информатика-(17799)Искусство-(1338)История-(13644)Компьютеры-(11121)Косметика-(55)Кулинария-(373)Культура-(8427)Лингвистика-(374)Литература-(1642)Маркетинг-(23702)Математика-(16968)Машиностроение-(1700)Медицина-(12668)Менеджмент-(24684)Механика-(15423)Науковедение-(506)Образование-(11852)Охрана труда-(3308)Педагогика-(5571)Полиграфия-(1312)Политика-(7869)Право-(5454)Приборостроение-(1369)Программирование-(2801)Производство-(97182)Промышленность-(8706)Психология-(18388)Религия-(3217)Связь-(10668)Сельское хозяйство-(299)Социология-(6455)Спорт-(42831)Строительство-(4793)Торговля-(5050)Транспорт-(2929)Туризм-(1568)Физика-(3942)Философия-(17015)Финансы-(26596)Химия-(22929)Экология-(12095)Экономика-(9961)Электроника-(8441)Электротехника-(4623)Энергетика-(12629)Юриспруденция-(1492)Ядерная техника-(1748)

Вказівники

Переліки

Похідні типи даних

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

Значенням кожного імені списку є деяке ціле число. Змінна типу переліку може приймати значення однієї з іменованих констант списку. Змінні типу enum можуть використовуватись і як операнды в арифметичних операціях та в операціях відношення, і як індекси в індексних виразах.

Список переліку може містити одну або декілька конструкцій виду:

 

ідентифікатор [= константний вираз]

 

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

У випадку відсутності константного виразу перший ідентифікатор набуває значення 0, наступний ідентифікатор - значення 1, наступний - 2 і т.д. Отже, пам'ять, що відводиться під змінну типу перелік - це пам'ять, необхідна для розміщення значення типу іnt.

Ідентифікатор, зв'язаний з константним виразом, приймає значення, що задається цим константним виразом. Константний вираз повинен мати тип іnt і може бути як додатнім, так і від’ємним. Константні вирази можуть містити однакові значення. Наступний ідентифікатор в списку отримує значення, рівне константному виразу плюс 1, якщо цей ідентифікатор не має свого константного виразу.

 

Приклад 7

enum week {

SUBOTA,

NEDILJA = 0,

PONEDILOK,

VIVTOROK,

SEREDA = VIVTOROK * 2,

CHETVER,

PJATNYTCJA

};

week den = CHETVER;

У цьому прикладі визначається тип перелік з іменем week і оголошується змінна den цього типу. З ідентифікатором SUBOTA за замовчуванням асоціюється значення 0. Ідентифікатор NEDILJA явно встановлюється в 0. Ідентифікатори PONEDILOK та VIVTOROK за замовчуванням набувають значень 1 та 2 відповідно. Константний вираз VIVTOROK * 2 дорівнює значенню 4 і це значення надається ідентифікаторові SEREDA. Ідентифікатори, що залишилися, за замовчуванням приймають значення 5 та 6 відповідно.

Отже, в пам’яті комп’ютера під зміну den буде виділено 4 байта пам’яті в яких буде записана наступна послідовність: 0000 0101 0000 0000 0000 0000 0000 0000.

 

Коли компілятор обробляє оператор визначення змінної, наприклад, іnt і=10, він виділяє пам'ять відповідно до типу (в прикладі іnt) та ініціалізує її заданим значенням (в прикладі 10). Всі звертання в програмі до змінного по її імені (в прикладі і) заміняються компілятором на адресу області пам'яті, в якій зберігається значення змінної. Можна також визначити власні змінні для збереження адрес областей пам'яті. Такі змінні називаються вказівниками.

У мові C++ розрізняють три види вказівників: вказівники на функцію, на об'єкт і на voіd, що відрізняються властивостями і набором допустимих операцій. Вказівник не є самостійним типом, він завжди зв'язаний з яким-небудь іншим конкретним типом.

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

Вказівник на функцю має тип "вказівник функції, що повертає значення заданого типу і має аргументи заданого типу":

тип (*ім'я) (список_типів_аргументів);

 

Наприклад, оголошення іnt (*fun) (double, double); задає вказівник на функцію з ім'ям fun, що повертає значення типу іnt і має два аргументи типу double.

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

тип *ім'я;

 

де тип може бути довільним, крім посилання і бітового поля, причому тип може бути до цього моменту тільки оголошений, але ще не визначений (наприклад, в структурі може бути присутнім вказівник на структуру того ж типу). Можна визначити вказівник на вказівник.

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

іnt *a, b, *c;

описуються два вказівники на ціле з іменами а і с, а також ціла змінна b.

Розмір вказівника залежить від моделі пам'яті. Значенням вказівника є адреса оперативної пам’яті, яка складається з адреси сегмента (номера сегмента оперативної пам’яті) і зміщення (адреси стосовно початку сегмента). Формат адреси:

<сегмент>: <зміщення>

Адреса сегмента зберігається в старшому слові, а зміщення – у молодшому слові повної адреси.

Вказівник на voіd застосовується в тих випадках, коли конкретний тип об'єкта, адресу якого потрібно зберігати, не визначений (наприклад, якщо в одній і тій самій змінній в різні моменти часу потрібно зберігати адреси об'єктів різних типів).

Вказівникові на voіd можна присвоїти значення вказівника будь-якого типу, а також порівнювати його з будь-якими вказівниками, але перед виконанням будь-яких дій з областю пам'яті, на яку він посилається, потрібно явно перетворити його до конкретного типу. Вказівник може бути константою або змінною, а також вказувати на константу або змінну.

Наприклад:

1) unsіgned іnt *a; // змінна а – це вказівник на тип unsіgned іnt

2) double *x; // вказівник х вказує на тип double

3) char *fuffer; // змінна fuffer – це вказівник на тип char

4) double nomer;

voіd *addres;

addres = & nomer;

х = (double *) addres;

Змінна addres оголошується як вказівник на об'єкт будь-якого типу. Тому їй можна присвоїти адресу будь-якого об'єкта (& - операція взяття адреси). Однак, як зазначено вище, жодна арифметична операція не може бути виконана над вказівником, поки не буде явно визначено тип даних, на які він вказує. Це можна зробити, використовуючи операцію приведення типу (double *) для перетворення змінної addres до вказівника на тип double, а потім постфіксне збільшення значення (++) цієї адреси.

5) const *d; // змінна d оголошується як вказівник на константний вираз, тобто

// значення вказівника може змінюватися в процесі виконання

// програми, але величина, на яку він вказує, не може змінюватись

 

6) char *const v = &obj; // змінна v оголошується як константний вказівник на дані типу

// char, тобто на протязі всієї програми v буде вказувати на ту

// саму область пам'яті, але зміст цієї області може бути змінений

Як видно з прикладів, модифікатор const, що знаходиться між ім'ям вказівника і зірочкою, відноситься до самого вказівника і забороняє його зміну, а const ліворуч від зірочки задає сталість значення, на яке він вказує.

Величини типу вказівника підкоряються загальним правилам визначення області дії, видимості і часу життя.

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

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

Існують такі способи ініціалізації вказівника:

1. Присвоювання вказівникові адреси існуючого об'єкта:

a. за допомогою операції взяття адреси:

іnt а = 5; // ціла змінна

іnt *р = &а; // у вказівник р записується адреса змінної а

іnt *р (&а): // те саме іншим способом

· за допомогою значення іншого ініціалізованого вказівника:

іnt *r = р;

· за допомогою імені масиву, що трактується як адреса:

іnt b[10]; // масив

іnt *t = b; // у вказівник t записується адреса початку масиву

· за допомогою імені функції, що трактується як адреса:

voіd f (іnt а){ /*... * / } // визначення функції

voіd (*pf) (іnt); // вказівник на функцію

pf = f; // присвоювання адреси функції

2. Присвоювання вказівникові адреси області пам'яті в явному вигляді:

char *vp = (char *) 0хB8000000; // 0хB8000000- шестнадцатеричная константа,

// (char *) - операція приведення типу: константа

// приводиться до типу "вказівник на char"

3. Присвоювання порожнього значення:

іnt *s = NULL;

іnt *rl = 0:

В першому рядку використовується константа NULL. Рекомендується використовувати просто число 0, тому що це значення типу іnt правильно перетворюється стандартними способами відповідно до контексту. Оскільки гарантується, що об'єктів з нульовою адресою немає, порожній вказівник можна використовувати для перевірки, чи посилається вказівник на деякий конкретний об'єкт чи ні.

4. Виділення ділянки динамічної пам'яті і присвоєння її адреси вказівникові:

· за допомогою операції new:

іnt *n = new іnt; // 1

іnt *k = new іnt (10); // 2

іnt *q = new іnt [10]; // 3

· за допомогою функції malloc:

іnt *u = (іnt *) malloc(sizeof(іnt)); // 4

В операторі 1 операція new виконує виділення ділянки динамічної пам'яті необхідної для розміщення величини типу іnt і записує адресу початку цієї ділянки в змінну n. Пам'ять під саму змінну n (тобто пам'ять для розміщення вказівника) виділяється на етапі компіляції.

В операторі 2, крім описаних вище дій, виконується ініціалізація виділеної динамічної пам'яті значенням 10.

В операторі 3 операція new виконує виділення динамічної пам'яті під 10 величин типу іnt (масиву з 10 елементів) і записує адресу початку цієї ділянки в змінну q, яка може трактуватись як ім'я масиву. Через ім'я можна звертатись до будь-якого елемента масиву.

Якщо пам'ять виділити не вдалося, то по стандарту повинне породжуватись виключення bad_alloc. Старі версії компіляторів можуть повертати значення 0.

В операторі 4 робиться те саме, що й в операторі 1, але за допомогою функції виділення пам'яті malloc. Для того щоб використовувати malloc, потрібно підключити до програми заголовний файл <malloc.h>. В функцію передається один параметр - кількість виділюваної пам'яті в байтах. Конструкція (іnt*) використовується для приведення типу вказівника, що повертається функцією, до необхідного типу. Якщо пам'ять виділити не вдалося, функція повертає значення 0.

Бажано переважно використовувати операцію new, ніж функцію malloc, особливо при роботі з об'єктами.

Звільнення пам'яті, виділеної за допомогою операції new, виконується за допомогою операції delete, а пам'яті, виділеної функцією malloc - за допомогою функції free. При цьому змінна-вказівник зберігається і може бути ініціалізована повторно. Наведені вище динамічні змінні знищуються в такий спосіб:

delete n; delete k; delete [ ] q; free (u);

Якщо пам'ять виділялася за допомогою new[], для звільнення пам'яті необхідно застосовувати delete[]. Розмірність масиву при цьому не вказується. Якщо квадратних дужок немає, то ніякого повідомлення про помилку не видається, але помічений як вільний буде лише перший елемент масиву, а інші виявляться недоступними для подальших операцій. Такі комірки пам'яті називаються сміттям.

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

За допомогою комбінацій зірочок, круглих і квадратних дужок можна описувати складені типи і вказівники на складені типи, наприклад, в операторі

іnt *(*р[10])();

оголошується масив з 10 вказівників на функції без параметрів, що повертають вказівники на іnt.

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

При інтерпретації складних описів необхідно дотримуватись правила "зсередини назовні", суть якого полягає в наступному:

1) якщо справа від імені стоять квадратні дужки, то це масив, якщо дужки круглі, то це функція;

2) якщо зліва є зірочка, то це вказівник на проінтерпретовану раніше конструкцію;

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

4) в останню чергу інтерпретується специфікатор типу.

Для приведеного вище опису порядок інтерпретації зазначений цифрами:

іnt * (* р [10]) ();

// 5 4 2 1 3 - порядок інтерпретації опису

 

З вказівниками можна виконувати такі операції:

· розадресація, або непряме звертання до об'єкта (*),

· присвоювання,

· додавання з константою,

· віднімання,

· інкремент (+ +),

· декремент (– –),

· порівняння,

· приведення типів.

При роботі з вказівниками часто використовується операція взяття адреси (&).

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

char а = 'd'; // змінна типу char

char *р = new char; // виділення пам'яті під вказівник і під динамічну змінну типу char

*р = 'д'; а = *р; // присвоювання значень обом змінним

 

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

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

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

short *р = new short [5];

р++; // значення р збільшується на 2 байта

long *q = new long [5];

q++; // значення q збільшується на 4 байта

 

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

*р++ = 10;

 

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

*р = 10: р++;

 

Вираз (*р)++, навпаки, інкрементує значення, на яке посилається вказівник.

<== предыдущая лекция | следующая лекция ==>
Дійсні типи даних | Структури
Поделиться с друзьями:


Дата добавления: 2014-01-05; Просмотров: 2378; Нарушение авторских прав?; Мы поможем в написании вашей работы!


Нам важно ваше мнение! Был ли полезен опубликованный материал? Да | Нет



studopedia.su - Студопедия (2013 - 2024) год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! Последнее добавление




Генерация страницы за: 0.011 сек.