- •12. Класи пам'яті даних 100
- •13. Робота з файлами 106
- •14. Посилання 111
- •Склад мови
- •Алфавіт мови
- •Лексеми
- •Ключові слова
- •Ідентифікатори
- •Константи
- •Символьні рядки
- •Знаки операцій, роздільники, коментарі
- •Типи даних
- •Класифікація типів даних
- •Цілочислові типи
- •Дійсні типи
- •Оголошення змінних і констант
- •Переліки
- •Структура програми
- •Функція main
- •Область дії змінної
- •Введення і виведення даних
- •Функції стандартної математичної бібліотеки
- •Вирази і операції
- •Поняття виразу, операнда, операції
- •Порядок виконання операцій
- •Арифметичні операції
- •Порозрядні операції
- •Операції порівняння
- •Логічні операції
- •Операції присвоєння
- •Умовна операція
- •Операція визначення розміру sizeof
- •Узгодження типів у виразах
- •Умовні оператори
- •Оператори
- •Умовний оператор if
- •Оператор вибору switch
- •Оператори циклу
- •Цикл з параметром. Оператор for
- •Цикл з передумовою. Оператор while
- •Цикл з постумовою. Оператор do while
- •Оператори переходу
- •Використання псевдовипадкових чисел
- •Одновимірні масиви
- •Багатовимірні масиви
- •Символьні рядки
- •Вказівники
- •Оголошення вказівника, операції, пов’язані з вказівниками
- •Адресна арифметика
- •Void – вказівники, типізація вказівників
- •Звертання до елементів масивів через вказівники
- •Масиви символьних рядків і масиви вказівників
- •Динамічне виділення пам’яті
- •Структури і об’єднання
- •Оголошення і ініціалізація структур
- •Звертання до елементів структур
- •Перейменування типів
- •Об’єднання
- •Поля бітів
- •Директиви препроцесора
- •Призначення директив препроцесора
- •Директива включення #include
- •Директиви макропідстановок #define I #undef
- •Директиви умовної компіляції
- •Директиви #ifdef, #ifndef
- •Функції
- •Структура функції
- •Виклик функцій. Прототипи функцій
- •Взаємодія фактичних і формальних параметрів функцій
- •Inline – функції
- •Масиви і символьні рядки як параметри функцій
- •Використання кваліфікатора const в оголошеннях параметрів
- •Багатовимірні масиви як параметри функцій
- •Опрацювання структур у функціях
- •Вказівники на функції
- •Рекурсивні функції
- •Класи пам'яті даних
- •Клас пам'яті, час існування і видимість об’єкта
- •Область дії глобальних і локальних змінних
- •Специфікатори класів пам'яті
- •Специфікатори глобальних змінних
- •Багатофайлові програми
- •Робота з файлами
- •Звертання до файлів
- •Послідовний запис до файлу і послідовне читання з файлу
- •Файли з довільним доступом
- •Посилання
- •Призначення посилань
- •Передача аргументів функцій як посилань
Масиви символьних рядків і масиви вказівників
Масиви символів можна створювати двома способами: з використанням масивів і з використанням вказівників:
char с1[ ] = "now is the time";
char * с2 = "now is the time";
В цьому визначенні с1 – це масив з кількістю елементів достатньою для розміщення символів рядка ініціалізації і нульового символу. Символи масиву можуть змінюватися, с1 є константним вказівником. с2 – це вказівник, який ініціалізований таким чином, щоб посилатися на рядкову константу. Значення вказівника можна змінювати, елементи символьного рядка – ні. Розглянемо програму, яка буде копіювати один рядок в інший.
#include <iostream>
using namespace std;
int main() {
char c1[ 80 ] = "Good old string";
char * c2 = "Nice new string (pointer version)";
char c3[ 80 ] = "Nice new string (array version)";
char * t1, *t2;
int i;
for ( t1 = c1, t2 = c2, i = 0; t2[ i ] != '\0'; i++ )
t1[ i ] = t2[ i ];
cout << c1 << endl;
for ( t1=c1, t2=c3, i=0; ( t1[ i ] = t2[ i ] ) != 0; i++ );
cout << c1 << endl;
t1 = c1;
t2 = c2;
while ( ( * t1++ = * t2++ ) != '\0' );
cout << c1 << endl;
t1 = c1;
t2 = c3;
while ( * t1++ = * t2++ );
cout << c1 << endl;
}
Операцію звертання за адресою можна використовувати разом з префіксним інкрементом (декрементом). Ці операції мають однаковий пріоритет і асоціативність справа наліво, тобто спочатку буде збільшене (зменшене) значення вказівника, після цього – виконане звертання за новою адресою. Знайдемо кількість елементів рядка за допомогою вказівників.
#include <iostream>
using namespace std;
int main() {
char c[ 80 ] = "A string";
char * t;
t = c;
while (* ++t);
cout << t - c << endl;
}
Поширеним видом багатовимірних масивів є масиви символьних рядків, тобто масиви, кожен елемент яких – окремий символьний рядок. Наприклад, масив повідомлень про помилки можна оголосити таким чином:
char Error[ 3 ][ 20 ] = {
"File Write Error",
"File Read Error",
"File Open Error"
};
Масив Error в пам'яті буде розташований таким чином (рис. 8.1):
|
0 |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
0 |
F |
i |
l |
e |
|
W |
r |
i |
t |
e |
|
E |
r |
r |
o |
r |
\0 |
|
|
|
1 |
F |
i |
l |
e |
|
R |
e |
a |
d |
|
E |
r |
r |
o |
r |
\0 |
|
|
|
|
2 |
F |
i |
l |
e |
|
O |
p |
e |
n |
|
E |
r |
r |
o |
r |
\0 |
|
|
|
|
Рис. 8.1. Розташування елементів масиву рядків в пам'яті
Правила звертання до елементів символьних рядків такі ж, як і до звичайних багатовимірних масивів. Наприклад, до першого рядка символів і до першого символу другого рядка можна звернутися таким чином:
#include <iostream>
using namespace std;
int main() {
char Error[ 3 ][ 20 ] = {
"File Write Error",
"File Read Error",
"File Open Error"
};
cout << Error[ 0 ]; // Перший рядок символів
cout << Error[ 1 ][ 0 ]; // Перший символ другого рядка
}
Як і для інших масивів, старшу розмірність можна не указувати, вона буде визначена по кількості ініціалізаторів. В наступній програмі виконується перетворення чисел у двійково-десятковий вид.
#include <iostream>
#include <ctime>
using namespace std;
int main() {
srand( unsigned( time ( NULL ) ) );
char code[ ][ 5 ] = {
"0000", "0001", "0010", "0011", "0100",
"0101", "0110", "0111", "1000", "1001"
};
for ( int i = 0, t; i < 20; i++ ) {
t = rand( ) % 100;
cout << t << "\t" << code[ t / 10 ] << " "
<< code[ t % 10 ] << endl;
}
}
В цьому прикладі всі рядки в масиві мають однакову довжину – 5 символів (4 символи для цифр і один символ для нуль – символу), тому всі елементи цього двовимірного масиву будуть зайняті корисною інформацією. В попередньому прикладі для збереження кожного повідомлення про помилку мі виділяли 20 символів, по розміру найбільш довгого повідомлення. Через це частина елементів декількох з рядків не використовувалась.
Для уникнення цього недоліку можна використовувати масиви вказівників на перші символи набору рядків, кожен з яких зберігається в оперативній пам'яті окремо. Наприклад:
#include <iostream>
using namespace std;
int main() {
char * month[ ] = {
"Wrong month number",
"January", "February", "March", "April", "May",
"June", "July", "August", "September", "October",
"November", "December"
};
int t;
cin >> t;
cout << ( 1 <= t && t <= 12 ? month[ t ] : month[ 0 ] );
}
тут month – це масив вказівників на char, кількість елементів масиву визначається кількістю ініціалізаторів, їх 13, кожен елемент масиву – вказівник на перший елемент відповідного рядка.
Напишемо програму, яка буде визначати день року для зазначеної дати і навпаки. В цьому прикладі змінна leap має значення 0, якщо рік не високосний і 1 – якщо високосний.
#include <iostream>
using namespace std;
int main() {
char * month_name[ ] = {
"Wrong month number",
"January", "February", "March" , "April", "May",
"June", "July", "August", "September", "October",
"November", "Deсember"
};
char ndays[ 2 ][ 13 ] = {
{ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
int year = 2011, month = 3, day = 16, i, j, n;
// Номер дня для заданої дати
int leap= year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
for ( i = 1, n = day; i < month; i++ )
n += ndays[ leap ][ i ];
cout << n << endl;
// Дата для номера дня і року
n = 75;
for ( i = 1; ndays[ leap ][ i ] <= n; i++ )
n -= ndays[ leap ][ i ];
cout << month_name[ i ] << " " << n << endl;
// Всі номера днів
for ( i = 1, n = 1; i <= 12; i++ )
for ( j = 1; j <= ndays[ leap ][ i ]; j++, n++ ) {
cout << n << "\t";
cout << month_name[ i ] << "\t";
cout << j << endl;
}
}
