Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Программирование на C / C++ / Ален И. Голуб. Правила программирования на Си и Си++ [pdf]

.pdf
Скачиваний:
237
Добавлен:
02.05.2014
Размер:
5.67 Mб
Скачать

С++ для начинающих

112

extern int get_size();

// buf_size и max_files константы

const int buf_size = 512, max_files = 20; int staff_size = 27;

// правильно: константа

char input_buffer[ buf_size ];

// правильно: константное выражение: 20 - 3 char *fileTable[ max_files-3 ];

// ошибка: не константа double salaries[ staff_size ];

// ошибка: не константное выражение

int test_scores[ get_size() ];

Объекты buf_size и max_files являются константами, поэтому определения массивов input_buffer и fileTable правильны. А вот staff_size переменная (хотя и инициализированная константой 27), значит, salaries[staff_size] недопустимо. (Компилятор не в состоянии найти значение переменной staff_size в момент определения массива salaries.)

Выражение max_files-3 может быть вычислено на этапе компиляции, следовательно, определение массива fileTable[max_files-3] синтаксически правильно.

Нумерация элементов начинается с 0, поэтому для массива из 10 элементов правильным диапазоном индексов является не 1 – 10, а 0 – 9. Вот пример перебора всех элементов

int main()

{

const int array_size = 10; int ia[ array_size ];

for ( int ix = 0; ix < array_size; ++ ix ) ia[ ix ] = ix;

массива:

}

При определении массив можно явно инициализировать, перечислив значения его

const int array_size = 3;

элементов в фигурных скобках, через запятую: int ia[ array_size ] = { 0, 1, 2 };

Если мы явно указываем список значений, то можем не указывать размер массива:

// массив размера 3

компилятор сам подсчитает количество элементов: int ia[] = { 0, 1, 2 };

С++ для начинающих

113

Когда явно указаны и размер, и список значений, возможны три варианта. При совпадении размера и количества значений все очевидно. Если список значений короче, чем заданный размер, оставшиеся элементы массива инициализируются нулями. Если же

// ia ==> { 0, 1, 2, 0, 0 } const int array_size = 5;

в списке больше значений, компилятор выводит сообщение об ошибке: int ia[ array_size ] = { 0, 1, 2 };

Символьный массив может быть инициализирован не только списком символьных значений в фигурных скобках, но и строковым литералом. Однако между этими

const char cal[] = {'C', '+', '+' };

способами есть некоторая разница. Допустим, const char cal2[] = "C++";

Размерность массива ca1 равна 3, массива ca2 – 4 (в строковых литералах учитывается

// ошибка: строка "Daniel" состоит из 7 элементов

завершающий нулевой символ). Следующее определение вызовет ошибку компиляции: const char ch3[ 6 ] = "Daniel";

Массиву не может быть присвоено значение другого массива, недопустима и инициализация одного массива другим. Кроме того, не разрешается использовать массив

const int array_size = 3; int ix, jx, kx;

//правильно: массив указателей типа int* int *iar [] = { &ix, &jx, &kx };

//error: массивы ссылок недопустимы

int &iar[] = { ix, jx, kx };

int main()

{

int ia3{ array_size ]; // правильно

// ошибка: встроенные массивы нельзя копировать

ia3 = ia; return 0;

ссылок. Вот примеры правильного и неправильного употребления массивов:

}

Чтобы скопировать один массив в другой, придется проделать это для каждого элемента по отдельности:

С++ для начинающих

114

const int array_size = 7;

int ia1[] = { 0, 1, 2, 3, 4, 5, 6 };

int main()

{

int ia3[ array_size ];

for ( int ix = 0; ix < array_size; ++ix ) ia2[ ix ] = ia1[ ix ];

return 0;

}

В качестве индекса массива может выступать любое выражение, дающее результат целого

int someVal, get_index();

типа. Например:

ia2[ get_index() ] = someVal;

Подчеркнем, что язык С++ не обеспечивает контроля индексов массива ни на этапе компиляции, ни на этапе выполнения. Программист сам должен следить за тем, чтобы индекс не вышел за границы массива. Ошибки при работе с индексом достаточно распространены. К сожалению, не так уж трудно встретить примеры программ, которые компилируются и даже работают, но тем не менее содержат фатальные ошибки, рано или поздно приводящие к краху.

Упражнение 3.22

(a)

int

ia[

buf_size ];

(d)

int ia[ 2 *

7

-

14 ]

(b)

int

ia[

get_size() ];

(e)

char st[ 11

]

=

"fundamental";

Какие из приведенных определений массивов содержат ошибки? Поясните.

(c) int ia[ 4 * 7 - 14 ];

Упражнение 3.23

Следующий фрагмент кода должен инициализировать каждый элемент массива

int main() {

const int array_size = 10; int ia[ array_size ];

for ( int ix = 1; ix <= array_size; ++ix ) ia[ ia ] = ix;

// ...

значением индекса. Найдите допущенные ошибки:

}

С++ для начинающих

115

3.9.1. Многомерные массивы

В С++ есть возможность использовать многомерные массивы, при объявлении которых

необходимо указать правую границу каждого измерения в отдельных квадратных скобках. Вот определение двумерного массива:

int ia[ 4 ][ 3 ];

Первая величина (4) задает количество строк, вторая (3) – количество столбцов. Объект ia определен как массив из четырех строк по три элемента в каждой. Многомерные

int ia[ 4 ][ 3 ] = {

{0, 1, 2 },

{3, 4, 5 },

{6, 7, 8 },

{9, 10, 11 }

массивы тоже могут быть инициализированы:

};

Внутренние фигурные скобки, разбивающие список значений на строки, необязательны и используются, как правило, для удобства чтения кода. Приведенная ниже инициализация в точности соответствует предыдущему примеру, хотя менее понятна:

int ia[4][3] = { 0,1,2,3,4,5,6,7,8,9,10,11 };

Следующее определение инициализирует только первые элементы каждой строки. Оставшиеся элементы будут равны нулю:

int ia[ 4 ][ 3 ] = { {0}, {3}, {6}, {9} };

Если же опустить внутренние фигурные скобки, результат окажется совершенно иным. Все три элемента первой строки и первый элемент второй получат указанное значение, а остальные будут неявно инициализированы 0.

int ia[ 4 ][ 3 ] = { 0, 3, 6, 9 };

При обращении к элементам многомерного массива необходимо использовать индексы для каждого измерения (они заключаются в квадратные скобки). Так выглядит

int main()

{

const int rowSize = 4; const int colSize = 3;

int ia[ rowSize ][ colSize ];

for ( int = 0; i < rowSize; ++i ) for ( int j = 0; j < colSize; ++j )

ia[ i ][ j ] = i + j j;

инициализация двумерного массива с помощью вложенных циклов:

}

С++ для начинающих

116

Конструкция

ia[ 1, 2 ]

является допустимой с точки зрения синтаксиса С++, однако означает совсем не то, чего ждет неопытный программист. Это отнюдь не объявление двумерного массива 1 на 2. Агрегат в квадратных скобках это список выражений через запятую, результатом которого будет последнее значение 2 (см. оператор запятаяв разделе 4.2). Поэтому объявление ia[1,2] эквивалентно ia[2]. Это еще одна возможность допустить ошибку.

3.9.2. Взаимосвязь массивов и указателей

Если мы имеем определение массива:

int ia[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21 };

то что означает простое указание его имени в программе?

ia;

Использование идентификатора массива в программе эквивалентно указанию адреса его

ia;

первого элемента:

&ia[0]

// оба выражения возвращают первый элемент

*ia;

Аналогично обратиться к значению первого элемента массива можно двумя способами: ia[0];

Чтобы взять адрес второго элемента массива, мы должны написать:

&ia[1];

Как мы уже упоминали раньше, выражение

ia+1;

также дает адрес второго элемента массива. Соответственно, его значение дают нам

*(ia+1);

следующие два способа: ia[1];

С++ для начинающих

117

Отметим разницу в выражениях:

*ia+1

и

*(ia+1);

Операция разыменования имеет более высокий приоритет, чем операция сложения (о приоритетах операций говорится в разделе 4.13). Поэтому первое выражение сначала разыменовывает переменную ia и получает первый элемент массива, а затем прибавляет к нему 1. Второе же выражение доставляет значение второго элемента.

Проход по массиву можно осуществлять с помощью индекса, как мы делали это в

#include <iostream> int main()

{

int ia[9] = { 0, 1, 1, 2, 3, 5, 8, 13, 21 }; int *pbegin = ia;

предыдущем разделе, или с помощью указателей. Например:

while ( pbegin != pend ) { cout << *pbegin <<; ++pbegin;

int *pend = ia + 9;

}

Указатель pbegin инициализируется адресом первого элемента массива. Каждый проход по циклу увеличивает этот указатель на 1, что означает смещение его на следующий элемент. Как понять, где остановиться? В нашем примере мы определили второй указатель pend и инициализировали его адресом, следующим за последним элементом массива ia. Как только значение pbegin станет равным pend, мы узнаем, что массив кончился.

Перепишем эту программу так, чтобы начало и конец массива передавались параметрами

#inc1ude <iostream>

void ia_print( int *pbegin, int *pend )

{

while ( pbegin != pend ) { cout << *pbegin << ' '; ++pbegin;

}

}

int main()

{

int ia[9] = { 0, 1, 1, 2, 3, 5, 8, 13, 21 }; ia_print( ia, ia + 9 );

в некую обобщенную функцию, которая умеет печатать массив любого размера:

}

С++ для начинающих

118

Наша функция стала более универсальной, однако, она умеет работать только с массивами типа int. Есть способ снять и это ограничение: преобразовать данную

#inc1ude <iostream>

template <c1ass e1emType>

void print( elemType *pbegin, elemType *pend )

{

while ( pbegin != pend ) { cout << *pbegin << ' '; ++pbegin;

}

функцию в шаблон (шаблоны были вкратце представлены в разделе 2.5):

}

 

 

int main()

 

 

{

{ 0, 1, 1,

2, 3, 5, 8, 13, 21 };

int ia[9] =

double da[4]

= { 3.14,

6.28, 12.56, 25.12 };

string sa[3]

= { "piglet", "eeyore", "pooh" };

print( ia, ia+9 ); print( da, da+4 ); print( sa, sa+3 );

Теперь мы можем вызывать нашу функцию print() для печати массивов любого типа:

}

Мы написали обобщенную функцию. Стандартная библиотека предоставляет набор обобщенных алгоритмов (мы уже упоминали об этом в разделе 3.4), реализованных подобным образом. Параметрами таких функций являются указатели на начало и конец массива, с которым они производят определенные действия. Вот, например, как выглядят

#include <a1gorithm> int main()

{

int ia[6] = { 107, 28, 3, 47, 104, 76 }; string sa[3] = { "piglet", "eeyore", "pooh" };

sort( ia, ia+6 ); sort( sa, sa+3 );

вызовы обобщенного алгоритма сортировки:

};

(Мы подробно остановимся на обобщенных алгоритмах в главе 12; в Приложении будут приведены примеры их использования.)

В стандартной библиотеке С++ содержится набор классов, которые инкапсулируют использование контейнеров и указателей. (Об этом говорилось в разделе 2.8.) В следующем разделе мы займемся стандартным контейнерным типом vector, являющимся объектно-ориентированной реализацией массива.

С++ для начинающих

119

3.10. Класс vector

Использование класса vector (см. раздел 2.8) является альтернативой применению встроенных массивов. Этот класс предоставляет гораздо больше возможностей, поэтому его использование предпочтительней. Однако встречаются ситуации, когда не обойтись без массивов встроенного типа. Одна из таких ситуаций обработка передаваемых программе параметров командной строки, о чем мы будем говорить в разделе 7.8. Класс vector, как и класс string, является частью стандартной библиотеки С++.

Для использования вектора необходимо включить заголовочный файл:

#include <vector>

Существуют два абсолютно разных подхода к использованию вектора, назовем их идиомой массива и идиомой STL. В первом случае объект класса vector используется точно так же, как массив встроенного типа. Определяется вектор заданной размерности:

vector< int > ivec( 10 );

что аналогично определению массива встроенного типа:

int ia[ 10 ];

void simp1e_examp1e()

{

const int e1em_size = 10; vector< int > ivec( e1em_size ); int ia[ e1em_size ];

for ( int ix = 0; ix < e1em_size; ++ix ) ia[ ix ] = ivec[ ix ];

// ...

Для доступа к отдельным элементам вектора применяется операция взятия индекса:

}

Мы можем узнать размерность вектора, используя функцию size(), и проверить, пуст ли

void print_vector( vector<int> ivec )

{

if ( ivec.empty() ) return;

for ( int ix=0; ix< ivec.size(); ++ix ) cout << ivec[ ix ] << ' ';

вектор, с помощью функции empty(). Например:

}

Элементы вектора инициализируются значениями по умолчанию. Для числовых типов и указателей таким значением является 0. Если в качестве элементов выступают объекты

С++ для начинающих

120

класса, то инициатор для них задается конструктором по умолчанию (см. раздел 2.3). Однако инициатор можно задать и явно, используя форму:

vector< int > ivec( 10, -1 );

Все десять элементов вектора будут равны -1.

Массив встроенного типа можно явно инициализировать списком:

int ia[ 6 ] = { -2, -1, О, 1, 2, 1024 };

Для объекта класса vector аналогичное действие невозможно. Однако такой объект

// 6 элементов ia копируются в ivec

может быть инициализирован с помощью массива встроенного типа: vector< int > ivec( ia, ia+6 );

Конструктору вектора ivec передаются два указателя указатель на начало массива ia и на элемент, следующий за последним. В качестве списка начальных значений допустимо

// копируются 3 элемента: ia[2], ia[3], ia[4]

указать не весь массив, а некоторый его диапазон: vector< int > ivec( &ia[ 2 ], &ia[ 5 ] );

Еще одним отличием вектора от массива встроенного типа является возможность инициализации одного объекта типа vector другим и использования операции

vector< string > svec;

void init_and_assign()

{

//один вектор инициализируется другим vector< string > user_names( svec );

//...

//один вектор копируется в другой svec = user_names;

присваивания для копирования объектов. Например:

}

Говоря об идиоме STL6, мы подразумеваем совсем другой подход к использованию вектора. Вместо того чтобы сразу задать нужный размер, мы определяем пустой вектор:

vector< string > text;

6 STL расшифровывается как Standard Template Library. До появления стандартной библиотеки С++ классы vector, string и другие, а также обобщенные алгоритмы входили в отдельную библиотеку с названием STL.

С++ для начинающих

121

Затем добавляем к нему элементы при помощи различных функций. Например, функция push_back()вставляет элемент в конец вектора. Вот фрагмент кода, считывающего

string word;

while ( cin >> word ) { text.push_back( word ); // ...

последовательность строк из стандартного ввода и добавляющего их в вектор:

}

cout << "считаны слова: \n";

for ( int ix =0; ix < text.size(); ++ix ) cout << text[ ix ] << ' ';

Хотя мы можем использовать операцию взятия индекса для перебора элементов вектора: cout << endl;

cout << "считаны слова: \n";

for ( vector<string>::iterator it = text.begin(); it != text.end(); ++it )

cout << *it << ' ';

более типичным в рамках данной идиомы будет использование итераторов: cout << endl;

Итератор это класс стандартной библиотеки, фактически являющийся указателем на элемент массива.

Выражение

*it;

разыменовывает итератор и дает сам элемент вектора. Инструкция

++it;

сдвигает указатель на следующий элемент. Не нужно смешивать эти два подхода. Если следовать идиоме STL при определении пустого вектора:

vector<int> ivec;

будет ошибкой написать:

ivec[0] = 1024;

У нас еще нет ни одного элемента вектора ivec; количество элементов выясняется с помощью функции size().