Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
консплекС++1к 2013.doc
Скачиваний:
0
Добавлен:
01.04.2025
Размер:
667.14 Кб
Скачать

Лекція 5. Вказівний тип та посилання

План лекції(2 години)

  1. Оголошення вказівників та ініціювання вказівників.

  2. Арифметичні операції над вказівниками

  3. Посилання у мові С++

Суть концепції вказівників полягає у тому, що програміст працює з адресою комірки пам’яті, отримуючи лише непрямий доступ до її вмісту, завдяки цьому виникає можливість динамічно створювати та знищувати змінні. За допомогою вказівників можна створювати надзвичайно ефективні програми, проте складність програми, де використовуються вказівники значно зростає. Для простих змінних пам’ять відводиться автоматично при завантаженні програми чи функції, у якій вони оголошені, та звільнюється також автоматично при завершенні програми чи функції. Приклад доступу до простої змінної за її іменем: imem+=10;

Інший спосіб доступу до значення змінної полягає у використанні іншої змінної, яка містить її адресу Нехай, є змінна типу int з іменем imem та змінна pimem, що є вказівником на неї. У С/С++ є оператор отримання адреси &, який повертає адресу його операнду. Тому не складно буде зрозуміти синтаксис присвоєння одній змінній адреси іншої змінної: pimem=&imem;

Змінні, які зберігають адреси інших змінних, називаються вказівниками. звернення до змінної, адреса якої зберігається в іншій змінній, виконується через запис перед вказівником оператора *, тобто * pimem. Такий запис означає, що буде виконуватись непрямий доступ до комірки пам’яті через ім’я вказівника, що містить адресу комірки. Наприклад, якщо виконати два записані нижче рядки, тоді змінна imem набуде значення 20: pimem=&imem; *pimemorycell_adress=20;

З урахуванням того, що вказівник pimem зберігає адресу змінної imem, обидва наступні рядки призведуть до однакового результату: присвоєння змінній imem значення 20. imem=20; *pimem=20;

Оголошення вказівників

Оголошення вказівника pimem має такий вигляд: int *pimem;

Символ * повідомляє про створення вказівника, який адресуватиме змінну типу int. У С/С++ вказівники можуть зберігати адреси тільки змінних визначеного типу. Якщо спробувати надати вказівнику одного типу адресу змінної іншого типу, тоді виникатиме помилка при компіляції чи при виконанні програми.

Ініціювання вказівників.

Вказівники можна ініціювати при їх оголошенні. Наприклад, створюються дві іменовані комірки пам’яті: int iresult; int *piresult=&iresult;

Ідентифікатор iresult є звичайною цілочисловою змінною, а piresult – вказівник на змінну типу int. Одночасно з оголошенням вказівника piresult йому надається адреса змінної iresult. Тут ініціюється вміст вказівника, тобто адреса, яка у ньому зберігається, але не значення змінної iresult. Змінна iresult залишається неініційованою.

Обмеження на використання оператора &

Отримання адреси (&) можна застосовувати не до кожного виразу. Наприклад:

pivariable=&48; // з константами – недопустимо, з константою не поєднана комірка пам’яті

int iresult=5;

pivariable=&(iresult+15);

// у виразах з арифметичними операторами - результат у стеку

register int regіster1; pivariable=&register1; //зі змінними класу пам’яті regіster

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

Вказівники на вказівники

У .С/С++ можна створювати вказівники на інші вказівники, які, у свою чергу містять адреси реальних змінних.

Для оголошення у програмі вказівника, який буде зберігати адресу іншого вказівника, потрібно записати: int **ppi;

Кожен символ * читається як вказівник на. Таким чином, кількість вказівників у ланцюжку, що задає рівень непрямої адресації, відповідає кількості зірочок перед іменем ідентифікатора. Рівень непрямої адресації визначає, скільки разів потрібно виконати операцію розкриття вказівника, щоб отримати значення кінцевої змінної.

У фрагменті створюється кілька вказівників з різними рівнями непрямої адресації: int ivalue=10; int *pi; int **ppi; int ***pppi; pi=&ivalue; ppi=π pppi=&ppi;

У перший чотирьох рядках оголошуються чотири змінні: ivalue типу int, вказівник pi на змінну типу int (перший рівень непрямої адресації), вказівник ppi (другий рівень непрямої адресації) та вказівник pppi (третій рівень непрямої адресації). Цей приклад показує, що при бажанні можна створити вказівник довільного рівня.

У п’ятому рядку вказівнику першого рівня надається адреса змінної ivalue. Тепер значення змінної ivalue (10) можна отримати за допомогою виразу * pi. У шостому рядку у вказівник другого рівня ppi записується адреса (але не вміст) вказівника pi, який , у свою чергу, вказує на змінну ivalue. Звернутися до значення змінної можна буде через вираз **ppi. У останньому рядку подібним чином заповнюється вказівник третього рівня.

У С/С++ вказівники можна ініціювати одразу при оголошенні, як і довільні інші змінні. Наприклад, вказівник pppi можна було б ініціювати так : int ***pppi=&ppi;

Вказівники на рядки

Рядкові константи є символьними масивами з кінцевим нульовим символом. Вказівник на рядок оголошується та ініціюється: char *psz=”Файл не знайдений»;

Даний вираз створює вказівник psz типу char та надає йому адресу першого символу рядка «Ф».Рядок записується компілятором у спеціальну службову таблицю.

Наведений рядок можна записати по-іншому: char *psz; psz=” Файл не знайдений”;

Наступний приклад ілюструє одну помилку, яка часто зустрічається у програмуванні, яка відноситься до використання рядкових вказівників та символьних масивів: char *psz=”Файл не знайдений”; char pszarray[]=”Файл не знайдений”;

Основна різниця між вказаними двома записами полягає у тому, що значення вказівника psz можна змінювати (оскільки вказівник є різновидом змінної), а значення імені масиву pszarray не можна змінити, оскільки це константа. З цієї причини записані нижче вирази будуть помилковими: char pszarray[15]; pszarray=”Файл не знайдений”;

Не дивлячись на те, що на перший погляд все видається вірним, насправді у даному виразі оператор присвоювання пробує скопіювати адресу першої комірки рядка ”Файл не знайдений” у об’єкт pszarray. Але, оскільки ім’я масиву pszarray є константою, а не змінною-вказівником, тому виведеться повідомлення про помилку.

Арифметичні операції над вказівниками

Мови С/С++ дають можливість виконувати різні операції над вказівниками. У попередніх прикладах видно було, як адреса, що знаходиться у вказівнику, чи вміст змінної, що адресується вказівником, присвоювались іншим вказівникам подібного типу. Окрім того, в С/С++ над вказівниками можна виконувати дві математичні дії: додавання та віднімання, наприклад,. pi++;// збільшує адресу на одиницю даного типу

pf--;// зменшує адресу на одиницю даного типу

Оператори інкременту(++) та декременту(--) є перевантаженими операторами. Вони модифікують значення операнду-вказівника на число, яке відповідає розміру комірки пам’яті, яка відводиться операнду.

Застосування кваліфікатора const сумісно з вказівником

Розглянемо наступні два оголошення вказівників та спробуємо зрозуміти різницю між ними: const MYTYPE *pmytype_1; MYTYPE * const pmytype_2=&mytype;

У першому випадку змінна pmytype_1 оголошена як вказівник на константу типу MYTYPE. У другому випадку pmytype_2 – це константний вказівник на змінну MYTYPE.

Ідентифікатор pmytype_1 є вказівником. Вказівнику, як і довільній змінній, можна надати значення відповідного типу даних.У даному випадку це повинна бути адреса, за якою локалізований об’єкт типу MYTYPE. А ключове слово const у оголошенні означає наступне: не дивлячись на те, що вказівнику pmytype_1 можна надати адресу довільної комірки пам’яті, що містить дані типу MYTYPE, у подальшому вміст цієї комірки не може бути змінений шляхом звернення до вказівника.

Тепер розглянемо змінну pmytype_2, яка оголошена як константний вказівник. Вона може містити адресу комірки пам’яті з даними типу MYTYPE, але ця адреса недоступна для зміни. З цієї причини ініціювання константного вказівника обов’язково повинне відбуватися одночасно з його оголошенням ( у наведеному випадку вказівник ініціюється адресою змінної MYTYPE). З іншого боку, вміст комірки, на яку посилається такий вказівник, можна вільно змінювати:

pmytype_2=&mytype1;//недопустимо змінити захищену адресу

*рmytype_2=( MYTYPE) float_value;//допустима зміна вмісту комірки пам’яті

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

тип_даних * const ім’я_масиву=адреса_першого_елементу.

Інші операції над вказівниками.

Нижче названі інші операції, які можна застосовувати до вказівників:

  • віднімання цілого значення від вказівника;

  • віднімання одного вказівника від іншого (обидва повинні посилатися на один тип);

  • порівняння вказівників за допомогою операторів <=,= та >=.

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

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

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

Вказівники одного типу можна порівнювати один з одним. Повернені значення true (!0) чи false (0) можна використовувати в умовних виразах та присвоювати змінним типу int так же, як результати довільних інших логічних операторів. Один вказівник буде меншим від іншого, якщо він вказує на комірку пам’яті з меншим індексом. При цьому передбачається, що обидва вказівники поєднані одним і тим же масивом.

Вказівники можна порівнювати з нулем. У даному випадку можна вияснити тільки рівність/нерівність нулю, оскільки вказівники не можуть містити від’ємні значення. Нульове значення вказівника означає, що він не поєднаний ні з яким об’єктом. Нуль є єдиним числовим значенням, яке можна безпосередньо присвоювати вказівнику незалежно від його типу. Окрім того, вказівник можна порівнювати з константним виразом, результатом якого є нуль, а також з іншим вказівником типу void (а в останньому випадку вказівник потрібно спочатку привести до типу void).

Всі інші операції з вказівниками заборонені, не можна додавати, множити, ділити.

Вказівники типу void

Якби всі вказівники мали стандартний розмір, тоді не виникало б потреби на етапі компіляції визначати тип адресованої змінної. Окрім того, за допомогою «стандартизованого» вказівника можна було б передавати у функції адрес змінних довільного типу, а потім, у тілі функції, привести вказівник до потрібного типу відповідно до отриманої додаткової інформації. Використовуючи такий підхід, можна створювати універсальні функції, які підходять для обробки даних різного типу.

Це те, для чого у мову С++ доданий вказівник типу void. Використання ключового слова void при оголошенні вказівника має іншу суть , ніж у списку аргументів чи в якості поверненого значення ( у таких випадках void означає «нічого»). Вказівник на void – це універсальний вказівник на дані довільного типу.;

Посилання у мові С++

Мова С++ надає можливість прямого звернення до змінних за адресами, що навіть простіше, ніж використовувати вказівники. Подібно до мови С, у С++ допускається створення як звичайних змінних, так і вказівників. У першому випадку резервується область пам’яті для безпосереднього збереження даних, тоді як у другому випадку в пам’яті комп’ютера відводиться місце для зберігання адрес деякого об’єкту, який можна створювати у довільну мить. На додаток до цього у мові С++ пропонується третій спосіб оголошення змінних – посилання. Як і вказівники, посилання містять дані про розташування інших змінних, але для отримання таких даних не потрібно використовувати спеціальний оператор непрямого звернення. Синтаксис використання посилань у програмі простий: int iresult_a=5;

int &riresult_a=iresult_a;// допустиме створення посилання

int& riresult_b; // недопустиме, оскільки немає ініціювання

У данному прикладі створюється посилання (на це вказує символ & після імені типу даних) riresult_a, якому надається адреса існуючої змінної iresult_a. Тепер на одну комірку пам’яті посилаються дві змінні: iresult_a та riresult_a. Це два імені однієї змінної. Довільне присвоювання значення змінній riresult_a відображається на вмісті змінної iresult і навпаки: довільна зміна змінної iresult викличе зміну значення змінної riresult. Посилання у С++ дають змогу реалізувати систему псевдонімів змінних.

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

РОЗДІЛ 3. ПОХІДНІ ТИПИ ДАНИХ У МОВІ С++