Жасмин Бланшет - QT 4: программирование GUI на С++
10 Point2D myCenter;
11 };
12 #endif
Определение класса создается в заголовочном файле с именем shape.h. Поскольку в этом определении делается ссылка на класс Point2D, мы включаем заголовочный файл point2d.h.
Класс Shape не имеет базового класса. В отличие от Java и C#, в С++ не предусмотрен обобщенный класс Object, который наследуется всеми другими классами. Qt предоставляет QObject в качестве естественного базового класса для объектов всех типов.
Объявление функции draw() имеет две интересные особенности. Она содержит ключевое слово virtual и завершается равенством = 0. Ключевое слово virtual означает, что данная функция может быть переопределена в подклассах. Подобно C# функции—члены в С++ по умолчанию не могут переопределяться. Странное приравнивание = 0 указывает на то, что данная функция — чисто виртуальная функция, которая не имеет реализации по умолчанию, и она должна быть реализована в подклассах. Концепция «интерфейса» в Java и C# соответствует в С++ классу, содержащему только чисто виртуальные функции.
Ниже приводится определение подкласса Circle:
01 #ifndef CIRCLE_H
02 #define CIRCLE_H
03 #include "shape.h"
04 class Circle : public Shape
05 {
06 public:
07 Circle(Point2D center, double radius = 0.5)
08 : Shape(center) {
09 myRadius = radius;
10 }
11 void draw() {
12 // здесь выполняются какие-то действия
13 }
14 private:
15 double myRadius;
16 };
17 #endif
Класс Circle наследует класс Shape в открытой форме, т.е. все открытые члены класса Shape остаются открытыми в Circle. С++ поддерживает также защищенное и закрытое наследование, которое ограничивает доступ к открытым и защищенным членам базового класса.
Конструктор принимает два параметра. Второй параметр необязателен, по умолчанию он принимает значение 0.5. Конструктор передает параметр center конструктору базового класса, для чего используется специальный синтаксис списка инициализации между сигнатурой функции и телом функции. В теле функции мы инициализируем переменную—член myRadius. Инициализацию этой переменной можно было сделать в той же строке, где инициализируется конструктор базового класса:
Circle(Point2D center, double radius = 0.5)
: Shape(center), myRadius(radius) { }
С другой стороны, С++ не позволяет инициализировать переменную—член в определении класса, поэтому следующий программный код неверен:
// НЕ БУДЕТ КОМПИЛИРОВАТЬСЯ
private:
double myRadius = 0.5;
};
Сигнатура функции draw() совпадает с сигнатурой виртуальной функции draw(), определенной в классе Shape. Она здесь переопределяется и будет вызываться полиморфно, когда draw() вызывается экземпляром Circle через ссылку или указатель на Shape. С++ не имеет ключевого слова override, доступного в C#. С++ также не имеет ключевых слов super и base, ссылающихся на базовый класс. Если требуется вызвать базовую реализацию функции, можно перед именем функции указать имя базового класса и оператор ::. Например:
01 class LabeledCircle : public Circle
02 {
03 public:
04 void draw() {
05 Circle::draw();
06 drawLabel();
07 }
08 };
С++ поддерживает множественное наследование, т.е. возможность создавать класс, производный сразу от нескольких других классов. При этом используется следующий синтаксис:
class DerivedClass : public BaseClass1, public BaseClass2, …,
public BaseClassN
{
…
};
По умолчанию функции и переменные, объявленные в классе, связываются с экземплярами этого класса. Мы можем объявлять статические функции—члены и статические переменные—члены, которые могут использоваться без экземпляра. Например:
01 #ifndef TRUCK_H
02 #define TRUCK_H
03 class Truck
04 {
05 public:
06 Truck() { ++counter; }
07 ~Truck() { --counter; }
08 static int instanceCount() { return counter; }
09 private:
10 static int counter;
11 };
12 #endif
Статическая переменная—член счетчика counter отслеживает количество экземпляров truck, которые существуют в любой момент времени. Конструктор truck его увеличивает на единицу. Деструктор, опознаваемый по префиксу ~, уменьшает счетчик на единицу. В С++ деструктор автоматически вызывается, когда статически распределенная переменная выходит из области видимости или когда удаляется переменная, память для которой выделяется при помощи оператора new. Это аналогично тому, что делается в методе finalize() в Java, за исключением того, что мы можем рассчитывать на его вызов в определенный момент времени.
Статическая переменная—член существует в единственном экземпляре для класса — такие переменные являются «переменными класса», а не «переменными экземпляра». Каждая статическая переменная—член должна определяться в файле .cpp (но без повторения ключевого слова static). Например:
#include "truck.h"
int Truck::counter = 0;
Если этого не сделать, компоновщик выдаст сообщение об ошибке из-за наличия «неразрешенного символа». Обращаться к статической функции instanceCount() можно за пределами класса, указывая имя класса перед ее именем. Например:
01 #include <iostream>
02 #include "truck.h"
03 using namespace std;
04 int main()
05 {
06 Truck truck1;
07 Truck truck2;
08 cout << Truck::instanceCount() << " equals 2" << endl;
09 return 0;
10 }
Указатели
Указатель в С++ — это переменная, содержащая не сам объект, а адрес памяти, где располагается объект. Java и C# имеют аналогичную концепцию «ссылки» при другом синтаксисе. Мы начнем с рассмотрения придуманного нами примера, иллюстрирующего применение указателей:
01 #include "point2d.h"
02 int main()
03 {
04 Point2D alpha;
05 Point2D beta;
06 Point2D *ptr;
07 ptr = α
08 ptr->setX(1.0);
09 ptr->setY(2.5);
10 ptr = β
11 ptr->setX(4.0);
12 ptr->setY(4.5);
13 ptr = 0;
14 return 0;
15 }
В этом примере используется класс Point2D из предыдущего подраздела. В строках 4 и 5 определяется два объекта типа Point2D. Эти объекты инициализируются в значение (0, 0) стандартным конструктором Point2D.
В строке 6 определяется указатель на объект Point2D. Для обозначения указателя здесь используется звездочка перед именем переменной. Поскольку мы не инициализируем указатель, он будет содержать произвольный адрес памяти. Эта ситуация изменяется в строке 7, в которой адрес alpha присваивается этому указателю. Унарный оператор & возвращает адрес памяти, где располагается объект. Адрес обычно представляет собой 32-битовое или 64-битовое целое число, задающее смещение объекта в памяти.
В строках 8 и 9 мы обращаемся к объекту alpha с помощью указателя ptr. Поскольку ptr является указателем, а не объектом, необходимо использовать оператор -> (стрелка) вместо оператора . (точка).
В строке 10 указателю присваивается адрес beta. С этого момента любая выполняемая нами операция с этим указателем будет воздействовать на объект beta.
В строке 13 указатель устанавливается в нулевое значение. С++ не имеет ключевого слова для представления указателя, который не ссылается ни на один объект; вместо этого мы используем значение 0 (или символическую константу NULL, которая разворачивается в 0). Попытка применения нулевого указателя приведет к краху приложения с выводом такого сообщения об ошибке, как «Segmentation fault» (ошибка сегментации), «General protection fault» (общая ошибка защиты) или «Bus error» (ошибка шины). Применяя отладчик, можно найти строку программного кода, которая приводит к краху.
В конце функции объект alpha содержит пару координат (1.0, 2.5), а объект beta — (4.0,4.5).
Указатели часто используются для хранения объектов, память для которых выделяется динамически с помощью оператора new. Используя жаргон С++ можно сказать, что эти объекты распределяются в «куче», в то время как локальные переменные (т.е. переменные, определенные внутри функции) хранятся в «стеке».