QT 4: программирование GUI на С++
Шрифт:
В отличие от Java и C#, в С++ определяемые пользователем типы используются так же, как встроенные типы:
Если требуется сделать класс С++ копируемым, необходимо предусмотреть в этом классе конструктор копирования и оператор присваивания. Конструктор копирования вызывается при инициализации объекта другим объектом того же типа. Синтаксически в С++ это обеспечивается двумя способами:
Оператор
При определении класса компилятор С++ автоматически обеспечивает конструктор копирования и оператор присваивания, выполняющие копирование члена в член. Для класса Point2D это равносильно тому, как если бы мы написали следующий программный код в определении класса:
Для некоторых классов создаваемые по умолчанию конструктор копирования и оператор присваивания оказываются неподходящими. Обычно это происходит в тех случаях, когда класс использует динамическую память. Чтобы сделать класс копируемым, мы должны сами реализовать конструктор копирования и оператор присваивания.
Для классов, которые не должны быть копируемыми, можно отключить конструктор копирования и оператор присваивания, если сделать их закрытыми. Если мы случайно попытаемся копировать экземпляры такого класса, компилятор выдаст сообщение об ошибке. Например:
В Qt многие классы проектировались как используемые по значению. Они имеют конструктор копирования и оператор присваивания и обычно инстанцируются в стеке без использования оператора new. Это относится к классам QDateTime, QImage, QString и к классам—контейнерам, например QList<T>, QVector<T> и QMap<K, T>.
Другие классы попадают в категорию «типа ссылок», в частности QObject и его подклассы (QWidget, QTimer, QTcpSocket и т.д.). Они имеют виртуальные функции и не могут копироваться. Например, QWidget представляет конкретное окно или элемент управления на экране дисплея. Если в памяти находится 75 экземпляров QWidget, на экране также будет находиться 75 окон или элементов управления. Обычно эти классы инстанцируются при помощи оператора new.
Глобальные переменные и функции
С++ позволяет объявлять функции и переменные, которые не принадлежат никакому классу и к которым можно обращаться из любой другой функции. Мы видели несколько примеров глобальных функций, в частности main — точка входа
в программу. Глобальные переменные встречаются реже, потому что они плохо влияют на модульность и реентерабельность. Все же важно иметь представление о них, поскольку вам, возможно, придется с ними столкнуться в программном коде, написанном программистом, который раньше писал на С, и другими пользователями С++.Для иллюстрации работы глобальных функций и переменных рассмотрим небольшую программу, которая печатает список из 128 псевдослучайных чисел, используя придуманный на скорую руку алгоритм. Исходный код программы находится в двух файлах .cpp.
Первый исходный файл — random.cpp:
В этом файле объявляются две глобальные переменные (randomNumbers и seed) и две глобальные функции (nextRandomNumber и populateRandomArray). В двух объявлениях используется ключевое слово static; эти объявления видимы только внутри текущей единицы компиляции (random.cpp), и говорят, что они статически связаны (static linkage). Два других объявления доступны из любой единицы компиляции программы, они обеспечивают внешнюю связь (external linkage).
Статическая компоновка идеально подходит для вспомогательных функций и внутренних переменных, которые не должны использоваться в других единицах компиляции. Она снижает риск «столкновения» идентификаторов (наличия глобальных переменных с одинаковым именем или глобальных функций с одинаковой сигнатурой в разных единицах компиляции) и не позволяет злонамеренным или другим опрометчивым пользователям получать доступ к внутренним объектам единицы компиляции.
Теперь рассмотрим второй файл main.cpp, в котором используется две глобальные переменные, объявленные в random.cpp с обеспечением внешней связи:
Мы объявляем внешние переменные и функции до их вызова. Объявление randomNumbers внешней переменной (что делает ее видимой в текущей единице компиляции) начинается с ключевого слова extern. Если бы не было этого ключевого слова, компилятор «посчитал» бы, что он имеет дело с определением переменной, и компоновщик «пожаловался» бы на определение одной и той же переменной в двух единицах компиляции (random.cpp и main.cpp). Переменные могут объявляться любое количество раз, однако они могут иметь только одно определение. Именно благодаря определению компилятор резервирует пространство для переменной.