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

Масиви та вказівники

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

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

Операція sizeof, застосована до імені масиву, дає розмір масиву в байтах. Наприклад, якщо означено масив int a[5], то значенням sizeof a є 20, оскільки sizeof int дорівнює 4.

Значення, що ініціалізують послідовні елементи масиву, задаються списком констант у дужках {}, наприклад, int a[4]={2,10,3,15};. Якщо констант менше ніж елементів у масиві, вони присвоюються першим елементам масиву, а решта елементів отримують нульові значення відповідного типу. Наприклад, оголошення int a[4]={}; ініціалізує всі елементи значенням 0. Якщо ж констант більше ніж елементів масиву, то це є помилкою.

В ініціалізації можна не вказувати розмір масиву — він стає рівним кількості констант. Наприклад, оголошення int a[]={9,8,7}; задає масив з трьох елементів.

Якщо масив оголошено без ініціалізації за межами функцій, то значеннями його елементів є нулі відповідного типу, а якщо всередині функції — випадкове «сміття».

Типу можна дати ім’я за допомогою інструкції оголошення імені типу typedef такого загального вигляду: typedef вираз_що_задає_тип ім’я;

Оголошення імені типу масивів має такий вигляд.

typedef тип-елементів ім’я-типу-масивів[розмір];

Далі ім’я-типу-масивів використовується для оголошення масивів, наприклад, так.

const int N=10; // розмір

typedef int AInt[N]; // AInt - ім’я типу масивів

AInt a, b; // два масиви типу AInt

Адреси та вказівники

Пам’ять комп’ютера являє собою послідовність байтів з номерами 0, 1, 2, …, які називаються адресами. Кожна змінна займає залежно від її типу певну кількість послідовних байтів пам’яті. Адреса змінної — це адреса її першого байта. Вказівник — це змінна, значеннями якої є адреси.

Будемо розглядати лише типізовані вказівники — їхніми значеннями можуть бути адреси даних тільки певного типу. Тип адрес даних типу T позначається виразом T*. При цьому тип T називається базовим, а вказівник типу T* — вказівником на дані типу T (вказівником типу T).

У 32-розрядних середовищах програмування вказівник будь-якого типу займає чотири байти, у 64-розрядних – вісім байтів.

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

Вираз *p з операцією розіменування вказівника * позначає дані, на які встановлено p, тобто ідентифікує їх.

Тип вказівників можна іменувати за допомогою інструкції typedef. Наприклад, після інструкції

typedef int * PInt;

ім’я PInt позначає тип вказівників на цілі типу int. Це дозволяє оголошувати вказівники без знаків * перед їхніми іменами.

PInt pi1, pi2;

Мова С++ дозволяє додати до адреси ціле число. Якщо доданок-адреса має тип T*, то ціле значення розглядається як кількість одиниць даних типу T. &x+1 більше адреси змінної x на sizeof(int), тобто на 4.

До вказівників застосовні складені присвоювання +=, -= (з цілим виразом праворуч), а також ++ і --. Адреси однотипних даних можна порівнювати за допомогою операторів ==, !=, <, <=, >, >=.

Вказівники та масиви

Вирази *(p+i) та p[i] еквівалентні. У мові С++ ім’я масиву позначає незмінюваний вказівник на перший елемент масиву. Виразу вигляду a[], який у заголовку функції оголошує параметр, еквівалентний вираз T* a.

За дії цих оголошень обидва вирази p=&a[0] та p=a встановлюють p на елемент a[0]. Після цього вирази p[0], p[1], …, p[9] ідентифікують елементи масиву a так само, як і вирази a[0], a[1], …, a[9]. Можна встановити p на який-небудь елемент масиву, присвоївши адресу елемента, наприклад, p=&a[5] або p=a+5.

Багатовимірні масиви

Масив int a2[2][3]. Елементи цього масиву розташовано в пам’яті «рядками».

Багатовимірний масив: перший вимір називається зовнішнім, останній — внутрішнім.

Розмір зовнішнього виміру не обов’язковий. Наприклад, параметр, що позначає матрицю цілих, можна оголосити як int a[][50] або як int (*a)[50]. Обидва оголошення свідчать, що a є вказівником на масиви з 50 цілих, тому вираз вигляду a[i][j] або (*(a+i))[j] ідентифікує елемент у i-му рядку та j-му стовпчику.

В оголошенні параметра int(*a)[50] дужки обов’язкові, оскільки пріоритет постфіксної операції [] вище ніж префіксної *. Вираз без дужок int*a[50] та еквівалентний йому int*(a[50]) оголошують масив з 50 вказівників на цілі.

Нехай означено масив int a2[2][3]. Значення виразів a2 й a2+1 – це адреси початків першого й другого масивів («рядків таблиці»), кожен з трьох цілих змінних. Вирази a2[i] та *(a2+i) позначають i-й масив так само, як ім’я a2 – весь двовимірний масив.

Аналогічно, значеннями цих виразів є адреси перших елементів цих масивів. Звідси, вирази *(a2+i)+j та a2[i]+j є адресними й значенням кожного з них є адреса цілої змінної, що знаходиться в i-му «рядку» на j-му місці, тобто адреса цілого елемента a2[i][j]. Цей елемент позначають також вирази *(*(a2+i)+j), *(a2[i]+j) та (*(a2+i))[j].

Перетворення типів адрес

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

char*p = (char*)&a[0]; або

char*p = (char*)a;

Вказівники на функції

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

Ім’я функції – це адресний вираз, значенням якого є адреса функції.

На функцію можна встановити вказівник, тип якого відповідає прототипу функції. Наприклад, якщо функція f має прототип int f(int), то вказівник pf на функції з цим прототипом оголошується так.

int (*pf)(int);

Дужки навколо *pf обов’язкові. Пріоритет префіксного оператора * нижче ніж пріоритет дужок (), тому оголошення без дужок int*pf(int); є прототипом функції, яка має ім’я pf і повертає вказівник на int.

Рядки мови С

Рядок – це послідовність символів. Найпростіша структура даних для зберігання послідовності символів – символьний масив. Масив символів має особливість: послідовність символів від початку до першого символу '\0' («нуль-символу») називається рядковим значенням масиву.

У мові С++ рядки з символом-обмежувачем '\0' називаються С-рядками.

Рядкова константа, наприклад, "abc", зображується в пам’яті не трьома, а чотирма байтами. До байтів з символами 'a', 'b', 'c' додається ще один – з нуль-символом '\0'. Вираз "abc" насправді має тип char*, а його значенням є адреса першого байта вказаної послідовності байтів. Звідси, рядкову константу можна присвоїти вказівнику типу char* або ініціалізувати нею вказівник або масив символів.

Взагалі, адресний вираз типу char* (рядкова константа, ім’я вказівника або масиву тощо) задає адресу деякого байта. Послідовність символів, яка починається з цього байта й яку обмежує найближчий '\0', будемо називати рядковим значенням, що відповідає адресному виразу типу char*.

char a[]="abc";

маємо sizeof(a)=4. Водночас, ініціалізація

char a[]={'a','b','c'};

означає масив з трьох елементів, в якому немає '\0'.

Операція cout<<s, де s – ім’я масиву, тип якого не є символьним, виводить значення вказівника s, тобто деяку адресу. Проте, якщо s – масив символів, то операція виводить рядкове значення масиву. Взагалі, якщо AE – адресний вираз типу char*, то операція вигляду cout<<AE виводить рядкове значення – від байта, на який вказує адресний вираз, до байта перед найближчим символом '\0'. Наприклад, якщо вказівник p встановлено на рядок "12345", то вираз

cout << p << '::' << p+2

виводить символи 12345::345.