Жасмин Бланшет - QT 4: программирование GUI на С++
Массивы
Массивы в С++ объявляются с указанием количества элементов массива в квадратных скобках после имени переменной массива. Допускаются двумерные массивы, т.е. массив массивов. Ниже приводится определение одномерного массива, содержащего 10 элементов типа int:
int fibonacci[10];
Доступ к элементам осуществляется с помощью следующей записи: fibonacci[0], fibonacci[1], … fibonacci[9]. Часто требуется инициализировать массив при его определении:
int fibonacci[10] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
В таких случаях можно не указывать размер массива, поскольку компилятор может его рассчитать по количеству элементов в списке инициализации:
int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
Статическая инициализация также работает для сложных типов, например для Point2D:
Point2D triangle[] = {
Point2D(0.0, 0.0), Point2D(1.0, 0.0), Point2D(0.5, 0.866)
};
Если не предполагается в дальнейшем изменять массив, его можно сделать константным:
const int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
Для нахождения количества элементов в массиве можно использовать оператор sizeof():
int n = sizeof(fibonacci) / sizeof(fibonacci[0]);
Оператор sizeof() возвращает размер аргумента в байтах. Количество элементов массива равно его размеру в байтах, поделенному на размер одного его элемента. Поскольку это долго вводить, распространенной альтернативой является объявление константы и ее использование при определении массива:
enum { NFibonacci = 10 };
const int fibonacci[NFibonacci] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
Есть соблазн объявить константу как переменную типа const int. К сожалению, некоторые компиляторы имеют проблемы при использовании константных переменных для представления размера массива. Ключевое слово enum будет объяснено далее в этом приложении.
Проход в цикле по массиву обычно выполняется с использованием переменной целого типа. Например:
for (int i = 0; i < NFibonacci; ++i)
cout << fibonacci[i] << endl;
Массив можно также проходить с помощью указателя:
const int *ptr = &fibonacci[0];
while (ptr != &fibonacci[10]) {
cout << *ptr << endl;
++ptr;
}
Мы инициализируем указатель адресом первого элемента и проходим его в цикле, пока не достигнем элемента «после последнего элемента» («одиннадцатого» элемента, fibonacci[10]). На каждом шаге цикла оператор ++ продвигает указатель к следующему элементу.
Вместо &fibonacci[0] можно было бы также написать fibonacci. Это объясняется тем, что указанное без элементов имя массива автоматически преобразуется в указатель на первый элемент массива. Аналогично можно было бы подставить fibonacci + 10 вместо &fibonacci[10]. Эти приемы работают и в других местах: мы можем получить содержимое текущего элемента, используя запись *ptr или ptr[0], а получить доступ к следующему элементу могли бы, используя *(ptr + 1) или ptr[1]. Это свойство иногда называют «эквивалентностью указателей и массивов».
Чтобы не допустить того, что считается необоснованной неэффективностью, С++ не позволяет передавать массивы функциям по значению. Вместо этого передается адрес массива. Например:
01 #include <iostream>
02 using namespace std;
03 void printIntegerTable(const int *table, int size)
04 {
05 for (int i = 0; i < size; ++i)
06 cout << table[i] << endl;
07 }
08 int main()
09 {
10 const int fibonacci[10] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
11 printIntegerTable(fibonacci, 10);
12 return 0;
13 }
Ирония в том, что, хотя С++ не позволяет выбирать между передачей массива по ссылке и передачей по значению, он предоставляет некоторую свободу синтаксиса при объявлении типа параметра. Вместо const int *table можно было бы также написать const int table[] для объявления в качестве параметра указателя на константный тип int. Аналогично параметр argv функции main() можно объявлять как char *argv[] или как char **argv.
Для копирования одного массива в другой можно пройти в цикле по элементам массива:
const int fibonacci[NFibonacci] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 };
int temp[NFibonacci];
for (int i = 0; i < NFibonacci; ++i)
temp[i] = fibonacci[i];
Для базовых типов, таких как int, можно также использовать функцию std::memcpy(), которая копирует блок памяти. Например:
memcpy(temp, fibonacci, sizeof(fibonacci));
При объявлении массива С++ его размер должен быть константой[10]. Если необходимо создать массив переменного размера, это можно сделать несколькими способами:
• Выделять память под массив можно динамически:
int *fibonacci = new int[n];
Оператор new [] выделяет последовательные участки памяти под определенное количество элементов и возвращает указатель на первый элемент. Благодаря принципу «эквивалентности указателей и массивов» обращаться к элементам можно с помощью указателей: fibonacci[0], fibonacci[1], … fibonacci[n — 1]. После завершения работы с массивом необходимо освободить занимаемую им память, используя оператор delete []:
delete [] fibonacci;
• Можно использовать стандартный класс std::vector<T>:
#include <vector>
using namespace std;
vector<int> fibonacci(n);
Обращаться к элементам можно с помощью оператора [], как это делается для обычного массива С++. При использовании вектора std::vector<T> (где T — тип элемента, хранимого в векторе) можно изменить его размер в любой момент с помощью функции resize(), и его можно копировать, применяя оператор присваивания. Классы, содержащие угловые скобки в имени, называются шаблонными классами.
• Можно использовать класс Qt QVector<T>:
#include <QVector>
QVector<int> fibonacci(n);
Программный интерфейс вектора QVector<T> очень похож на интерфейс вектора std::vector<T>, кроме того, он поддерживает возможность прохода по его элементам с помощью ключевого слова Qt foreach и использует неявное совмещение данных («копирование при записи») как метод оптимизации расхода памяти и повышения быстродействия. В главе 11 представлены классы—контейнеры Qt и объясняется их связь со стандартными контейнерами С++.
Может возникнуть соблазн применения везде векторов std::vector<T> или QVector<T> вместо встроенных массивов. Тем не менее полезно иметь представление о работе встроенных массивов, потому что рано или поздно вам может потребоваться очень быстрый программный код или придется использовать существующие библиотеки С.
Символьные строки
Основной способ представления символьных строк в С++ заключается в применении массива символов char, завершаемого нулевым байтом (' '). Следующие четыре функции демонстрируют работу таких строк:
01 void hello1()
02 {
03 const char str[] = {
04 'H', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r' 'l', 'd', ' '
05 };
06 cout << str << endl;
07 }
08 void hello2()
09 {
10 const char str[] = "Hello world!";
11 cout << str << endl;
12 }
13 void hello3()
14 {
15 cout << "Hello world!" << endl;
16 }
17 void hello4()
18 {
19 const char *str = "Hello world!";
20 cout << str << endl;
21 }
В первой функции строка объявляется как массив и инициализируется посимвольно. Обратите внимание на символ в конце ' ', обозначающий конец строки. Вторая функция имеет аналогичное определение массива, но на этот раз для инициализации массива используется строковый литерал. В С++ строковые литералы — это просто массивы символов const char, завершающиеся символом ' ', который не указывается в литерале. В третьей функции строковый литерал используется непосредственно без придания ему имени. После перевода на инструкции машинного языка она будет идентична первым двум функциям.
Четвертая функция немного отличается, поскольку создает не только массив (без имени), но и переменную—указатель с именем str, в которой хранится адрес первого элемента массива. Несмотря на это, семантика данной функции идентична семантике предыдущих трех функций, и оптимизирующий компилятор удалит лишнюю переменную str.
Функции, принимающие в качестве аргументов строки С++, обычно объявляют их как char * или const char *. Ниже приводится короткая программа, иллюстрирующая оба подхода:
01 #include <cctype>
02 #include <iostream>
03 using namespace std;