Namespace CounterNameSpace 1 страница

{

int upperbound;

int lowerbound;

 

class counter {

int count;

public:

counter(int n) {

if(n <= upperbound) count = n;

else count = upperbound;

}

 

void reset(int n) {

if(n <= upperbound) count = n;

}

 

int run() {

if(count > lowerbound) return count--;

else return lowerbound;

}

};

}

 

int main()

{

// Використовується тільки член upperbound з

// простору імен CounterNameSpace.

using CounterNameSpace::upperbound;

 

// Тепер для встановлення значення змінній upperbound

// не потрібно вказувати простір імен.

upperbound = 100;

 

// Але під час звернення до змінної lowerbound і до інших

// об'єктів, як і раніше, необхідно вказувати простір імен.

CounterNameSpace::lowerbound = 0;

CounterNameSpace::counter A_ob (10);

int izm;

 

cout << "Розрахунок у зворотному порядку для об'єкта A_ob" << "\n";

do {

izm = A_ob.run();

cout << izm << " ";

} while(izm > CounterNameSpace::lowerbound);

cout << endl;

 

// Тепер використовуємо весь простір імен CounterNameSpace.

using namespace CounterNameSpace;

 

counter B_ob(20);

 

cout << "Розрахунок у зворотному порядку для об'єкта B_ob" << "\n";

do {

izm = B_ob.run();

cout << izm << " ";

} while(izm > lowerbound);

cout << endl;

 

B_ob.reset(100);

lowerbound = 80;

cout << "Розрахунок у зворотному порядку для об'єкта B_ob" << "\n";

do {

izm = B_ob.run();

cout << izm << " ";

} while(izm > lowerbound);

cout << endl;

 

getch(); return 0;

}

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

21.1.3. Неіменовані простори імен

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

namespace {

// Оголошення

}

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

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

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

Файл One Файл Two
static int kzm; void fun_1() { kzm = 99; //OK } extern int kzm; void fun_2() { kzm = 10; // помилка }

Оскільки змінну kzm визначено у файлі One, то її і можна використовувати у файлі One. У файлі Two змінну kzm визначено як зовнішню (extern-змінна), а це означає, що її ім'я і тип відомі, але саму змінну kzm насправді не визначено. Коли ці два файли будуть скомпоновані, спроба використовувати змінну kzm у файлі Two призведе до виникнення помилки, оскільки у ньому немає визначення для змінної kzm. Той факт, що kzm оголошена як static-змінна у файлі One, означає, що її область видимості обмежується цим файлом, і тому вона недоступна для файлу Two.

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

Файл One Файл Two
namespace { int kzm; } static int kzm; void fun_1() { kzm = 99; // OK } extern int kzm; void fun_2() { kzm = 10; // помилка }

Тут змінну kzm також обмежено рамками файлу One. Для нових програм рекомендується використовувати замість модифікатора static неіменований простір імен.

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

21.1.4. Застосування простіру імен std

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

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

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

Простір імен std використовується власною бібліотекою мови програмування C++.

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

Код програми 21.3. Демонстрація механізму використання безпосередньо заданої кваліфікації бібліотечних імен префіксом std::

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

int main()

{

double num;

 

std::cout << "Введіть число: ";

std::cin >> num;

 

std::cout << "Ви ввели число ";

std::cout << num;

 

getch(); return 0;

}

У цій програмі імена cout і cin безпосередньо доповнені іменами своїх просторів імен. Отже, щоб записати дані у стандартний вихідний потік, необхідно використовувати не просто ім'я потоку cout, а ім'я з префіксом std::cout, а щоб зчитати дані із стандартного вхідного потоку, потрібно застосувати "префіксне" ім'я std::cin.

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

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

Код програми 21.4. Демонстрація внесення в глобальний простір імен декількох імен

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

// Отримуємо доступ до імен потоків cout і cin

using std::cout;

using std::cin;

 

int main()

{

double num;

 

cout << "Введіть число: ";

cin >> num;

 

cout << "Ви ввели число ";

cout << num;

 

getch(); return 0;

}

У цій програмі імена потоків cin і cout можна використовувати безпосередньо, але інша частина простору імен std не внесена в область видимості.

Як ми вже зазначали вище, початкова бібліотека мови програмування C++ була визначена в глобальному просторі імен. Якщо Вам доведеться модернізувати старі С++-програми, то програміст повинен або включити в них настанову using namespace std, або доповнити кожне звернення до члена бібліотеки префіксом std::. Це особливо важливо, якщо Вам доведеться замінювати старі заголовні *.h-файли сучасними заголовками.

Необхідно пам'ятати! Старі заголовні *.h-файли поміщають свій вміст у глобальний простір| імен, а сучасні заголовки – у простір імен std.

21.2. Застосування покажчиків на функції

Покажчик на функцію – це достатньо складний, але дуже могутній засіб C++-програ­му­вання. Незважаючи на те, що функція не є змінною, проте вона займає фізи|чну| область пам'яті, певну адресу якої можна присвоїти покажчику. Адреса, що присвоюється покажчику, є вхідною точкою функції[82]. Якщо деякий покажчик посилається на функцію, то її (функцію) можна викликати за допомогою цього покажчика.

Покажчик на функцію посилається на вхідну точку цієї функції.

Покажчики на функції також дають змогу передавати функції як аргументи іншим функціям. Адресу функції можна отримати, використовуючи ім'я функції без круглих дужок і аргументів[83]. Якщо присвоїти адресу функції покажчику, то цю функцію можна викликати через покажчик. Щоб зрозуміти сказане, розглянемо наведену нижче програму. Вона містить дві функції – vLine() і hLine(), які малюють на екрані вертикальні та горизонтальні лінії заданої довжини.

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

#include <vcl>

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

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

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

 

void vLine(int i), hLine(int i);

 

int main()

{

void (*p)(int i);

 

p = vLine; // Покажчик на функцію vLine()

 

(*p)(4); // Виклик функції vLine()

 

p = hLine; // Покажчик на функцію hLine()

 

(*p)(5); // Виклик функції hLine()

 

getch(); return 0;

}

 

void hLine(int i)

{

for(; i; i--) cout << "-";

cout << "\n";

}

 

void vLine(int i)

{

for(; i; i--) cout << "|\n";

}

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

|

|

|

|

–––––

Розглянемо цю програму у деталях. У першому рядку тіла функції main() визначається змінна р як покажчик на функцію, яка приймає один цілочисельний аргумент і не повертає ніякого значення. Це оголошення не визначає, про яку саме функцію йдеться. Воно тільки створює покажчик, який можна використовувати для адресації будь-якої функції цього типу. Необхідність круглих дужок, у які поміщено покажчик *р, випливає з С++-правил передування.

У наступному рядку покажчику р присвоюється адреса функції vLine(). Потім виконується виклик функції vLine() з аргументом 4. Після цього покажчику р присвоюється адреса функції hLine(), і за допомогою цього покажчика реалізується її виклик. У цій програмі під час виклику функцій за допомогою покажчика використовується такий формат:

(*р)(4);

Проте функцію, яка адресується покажчиком р, можна викликати з використанням дещо простішого синтаксису:

р(4);

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

21.2.1. Передача покажчиком на функцію її адреси іншій функції

Незважаючи на те, що у попередньому прикладі покажчик на функцію використано тільки заради ілюстрації, часто таке його застосування має дуже важливе значення. Покажчик на функцію дає змогу передавати її адресу іншій функції. Як показовий приклад можна навести функцію qsort() із стандартної С++-бібліотеки. Функція qsort() – це функція швидкого сортування, що базується на алгоритмі Quicksort, який упорядковує вміст масиву. Ось як виглядає її прототип:

void qsort(void *start, size_t length size_t size,

int (*compare) (const void *, const void *));

Функція qsort() – це функція сортування із стандартної С++-бібліотеки.

Прототип функції qsort() "прописаний" у заголовку <cstdlib>, у якому також визначено тип size_t (як тип unsigned int). Щоб використовувати функцію qsort(), необхідно передати їй покажчик на початок масиву об'єктів, який Ви хочете відсортувати (параметр start), довжину цього масиву (параметр length), розмір у байтах кожного елемента (параметр size) і покажчик на функцію порівняння елементів масиву (параметр * compare).

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

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

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

#include <vcl>

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

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

#include <cstdlib> // Для використання бібліотечних функцій

#include <cstring> // Для роботи з рядковими типами даних

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

 

int Сomp(const void *a, const void *b);

 

int main()

{

char str[] = "Покажчики на функції дають гнучкість.";

 

qsort(str, strlen(str), 1, Сomp);

cout << "Відсортований рядок: " << str;

cout << endl;

 

getch(); return 0;

}

 

int Сomp(const void *a, const void *b)

{

return * (char *) a – * (char *) b;

}

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

Відсортований рядок: .Пааагджииііїккккннносттууфцччьью

Ця програма сортує рядок str у зростаючому порядку. Оскільки функції qsort() передається вся необхідна їй інформація, в т.ч. покажчик на функцію порівняння, то її можна використовувати для сортування даних будь-якого типу. Наприклад, наведений нижче код програми сортує масив цілих чисел. Для гарантії переносності під час визначення розміру цілочисельного значення в ній використовують оператор sizeof.

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

#include <vcl>

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

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

#include <cstdlib> // Для використання бібліотечних функцій

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

 

int Сomp(const void *a, const void *b);

 

int main()

{

int num[] = {10, 4, 3, 6, 5, 7, 8};

int i;

 

qsort(num, 7, sizeof(int), Сomp);

 

for(i=0; i<7; i++)

cout << num[i]<< " ";

cout << endl;

 

getch(); return 0;

}

 

int Сomp(const void *a, const void *b)

{

return * (char *) a – * (char *) b;

}

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

21.2.2. Пошук адреси перевантаженої функції

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

У наведеному нижче прикладі коду програми міститься дві версії функції space(). Перша версія виводить на екран count пропусків, а друга – count символів, переданих як аргумент ch. У функції main() оголошуються два покажчики на функції. Перший заданий як покажчик на функцію з одним цілочисельним параметром, а другий – як покажчик на функцію з двома параметрами.

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

#include <vcl>

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

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

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

 

// Введення на екран count пропусків.

void space(int count)

{

for(; count; count–-) cout << " ";

}

 

// Введення на екран count символів, переданих в ch.

void space(int count, char ch)

{

for(; count; count-–) cout << ch;

}

 

int main()

{

// Створення покажчика на void-функцію з одним int-параметром.

void (*fp1)(int);

 

/* Створення покажчика на void-функцію з одним int-параметром

і одним параметром типу char. */

void (*fp2)(int, char);

 

fp1 = space; // Отримуємо адресу функції space(int)

 

fp2 = space; // Отримуємо адресу функції space(int, char)

 

fp1(22); // Виводимо 22 пропуски (цей виклик є аналогічним

// виклику (*fp1)(22)).

cout << "|\n";

 

fp2(30, 'x'); // Виводимо 30 символів "x" (цей виклик є

// аналогічним до виклику (*fp2)(30, 'x').

cout << "|\n";

 

getch(); return 0;

}

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

|

хххххххххххххххххххххххххххххх|

Як зазначено у коментарях до цієї програми, компілятор здатний визначити, адресу якої перевантаженої функції він отримує, на основі того, як оголошено покажчики fp1 і fр2.

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

21.3. Поняття про статичні члени-даних класу

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

Один статичний член-даних класу розділяється між всіма об'єктами класу.

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

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

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

#include <vcl>

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

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

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

 

class ShareVar {

static int num;

public:

void setNum(int izm) { num = izm;}

void showNum() { cout << num << " ";}

};

 

int ShareVar::num; // Визначаємо static-член num

 

int main()

{

ShareVar A_ob, B_ob;

 

A_ob.showNum(); // Виводиться 0

B_ob.showNum(); // Виводиться 0

 

A_ob.setNum(10); // Встановлюємо static-члена num дорівнює 10

 

A_ob.showNum(); // Виводиться 10

B_ob.showNum(); // Також виводиться 10

 

getch(); return 0;

}

Звернемо Вашу увагу на те, що статичний цілочисельний член num оголошений і у класі ShareVar, і його визначено як глобальна змінна. Як було сказано вище, необхідність такого подвійного оголошення зумовлена тим, що під час оголошення члена num у класі ShareVar пам'ять для нього не виділяється. Компілятор C++ ініціалізував змінну num значенням 0, оскільки ніякої іншої ініціалізації у програмі немає. Тому внаслідок двох перших викликів функції showNum() для об'єктів A_ob і B_ob відображається значення 0. Потім об'єкт A_ob встановлює члену num значення, що дорівнює 10, після чого об'єкти A_ob і B_ob знову виводять на екран його значення за допомогою функції showNum(). Але оскільки існує тільки одна копія змінної num, що розділяється об'єктами A_ob і B_ob, то значення 10 буде виведено під час виклику функції showNum() для обох об'єктів.

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

Якщо static-змінна є відкритою (тобто public-змінною), до неї можна звертатися безпосередньо через ім'я її класу, без посилання на будь-який конкретний об'єкт[84]. Розглянемо, наприклад, таку версію класу ShareVar:

class ShareVar {

public:

static int num;

void setNum(int izm) { num = izm;};

void showNum() { cout << num << " "; }

};

У даній версії змінна num є public-членом даних класу. Це дає змогу нам звертатися до неї безпосередньо, як це показано в такій настанові:

ShareVar::num = 100;

У цьому записі значення змінної num встановлюється незалежно від об'єкта, а для звернення до неї достатньо використовувати ім'я класу і оператора дозволу області видимості. Більше того, ця настанова є правомірною навіть для створення яких-небудь об'єктів типу ShareVar. Таким чином, отримати або встановити значення static-члена класу можна до того, як будуть створені будь-які об'єкти.

І хоча Ви, можливо, поки що не відчули потреби в static-членах класу, проте, у міру набуття досвіду програмування мовою C++, Вам доведеться наштовхнутися на ситуацію, коли вони виявляться дуже корисними, тобто дадуть змогу уникнути застосування глобальних змінних.

Можна також оголосити статичною і функцію-члена класу, але це непоширена практика. До статичної функції-члена класу можуть отримати доступ тільки інші static-члени цього класу[85]. Статична функція-член не має покажчика this. Створення віртуальних статичних функцій-членів класу не дозволяється. Окрім того, їх не можна оголошувати з модифікаторами const або volatile. Статичну функцію-члена можна викликати для об'єкта її класу або незалежно від будь-якого об'єкта, а для звернення до неї достатньо використовувати ім'я класу і оператор дозволу області видимості.

21.5. Застосування до функцій-членів класу модифікаторів const і mutable

Функції-члени класу можуть бути оголошені з використанням модифікатора const. Це означає, що з покажчиком this у цьому випадку необхідно звертатися як з const-покажчиком. Іншими словами, const-функція не може модифікувати об'єкт, для якого вона викликана. Окрім того, const-об'єкт не може викликати не const-функцію|-члена класу. Але const-функцію|-члена можуть викликати як const-, так і не const-об'єкти.