Жасмин Бланшет - QT 4: программирование GUI на С++
• char — 8-битовый целый тип,
• short — 16-битовый целый тип,
• int — 32-битовый целый тип,
• long — 32- или 64-битовый целый тип,
• long long[9] — 64-битовый целый тип,
• float — 32-битовое значение числа с плавающей точкой (IEEE 754),
• double — 64 битовое значение числа с плавающей точкой (IEEE 754).
По умолчанию short, int, long и long long — типы данных со знаком, т.е. они могут содержать как отрицательные, так и положительные значения. Если необходимо хранить только неотрицательные целые числа, мы можем поставить ключевое слово unsigned (без знака) перед типом. Если тип short может хранить любое значение в промежутке между —32,768 и +32,767, то unsigned short — от 0 до 65 535. Оператор сдвига вправо >> имеет семантику чисел без знака («заполнить нулями»), если один из операндов является типом без знака.
Тип bool может принимать значения true и false. Кроме того, числовые типы могут использоваться вместо типа bool; в этом случае 0 соответствует значению false, а любое ненулевое значение означает true.
Тип char используется для хранения как символов ASCII, так и 8-битовых целых чисел (байтов). Целое число, представленное этим типом, в зависимости от платформы может иметь или не иметь знак. Типы signed char и unsigned char могут использоваться для однозначной интерпретации типа char. Qt предоставляет тип QChar, который хранит 16-битовые символы в кодировке Unicode.
По умолчанию экземпляры встроенных типов не инициализируются. Когда создается переменная типа int, ее значение вполне могло бы быть нулевым, однако с той же вероятностью оно может равняться —209 486 515. К счастью, большинство компиляторов предупреждает нас о попытках чтения неинициализированной переменной, и мы можем использовать такие инструментальные средства, как Rational PurifyPlus и Valgrind, для обнаружения обращений к неинициализированной памяти и других связанных с памятью проблем на этапе выполнения.
В памяти числовые типы (кроме long) имеют идентичные размеры на различных платформах, поддерживаемых Qt, но их представление меняется в зависимости от принятого в системе порядка байтов. В архитектурах с прямым порядком байтов (например, PowerPC и SPARC) 32-битовое значение 0x12345678 последовательно занимают четыре байта 0х12 0x34 0x56 0х78, в то время как в архитектурах с обратным порядком байтов (например, Intel x86) последовательность байтов будет обратной. Это следует учитывать в программах, копирующих области памяти на диск или посылающих двоичные данные по сети. Класс Qt QDataStream, представленный в главе 12 («Ввод—вывод»), можно использовать для хранения двоичных данных независимым от платформы способом.
Определения класса
Классы определяются в С++ аналогично тому, как это делается в Java и C#, однако надо иметь в виду, что существует несколько отличий. Мы рассмотрим эти отличия на нескольких примерах. Начнем с класса, представляющего пару координат (x, у):
01 #ifndef POINT2D_H
02 #define POINT2D_H
03 class Point2D
04 {
05 public:
06 Point2D() {
07 xVal = 0;
08 yVal = 0;
09 }
10 Point2D(double x, double у) {
11 xVal = x;
12 yVal = у;
13 }
14 void setX(double x) { xVal = x; }
15 void setY(double у) { yVal = у; }
16 double x() const { return xVal; }
17 double y() const { return yVal; }
18 private:
19 double xVal;
20 double yVal;
21 };
22 #endif
Представленное выше определение класса обычно оформляется в виде заголовочного файла, типичным названием которого может быть point2d.h. В этом примере проявляются следующие характерные особенности С++:
• Определение класса разделяется на секции (открытую, защищенную и закрытую) и заканчивается точкой с запятой. Если не указано ни одной секции, по умолчанию используется закрытая секция. (Для совместимости с языком С в С++ предусмотрено ключевое слово struct, идентичное классу с тем исключением, что по умолчанию используется открытая секция).
• Данный класс имеет два конструктора (один без параметров и другой с двумя параметрами). Если в классе вообще не объявляется конструктор, С++ автоматически добавляет конструктор без параметров и с пустым телом.
• Функции, получающие данные, x() и y(), объявляются как константные. Это значит, что они не будут (и не смогут) модифицировать переменные—члены или вызывать неконстантные функции—члены (например, setX() и setY().)
Указанные выше функции реализовывались бы как встроенные функции, являющиеся частью определения класса. Альтернативный подход заключается в предоставлении в заголовочном файле только прототипов функций и реализации функций в файле .cpp. В этом случае заголовочный файл имел бы следующий вид:
01 #ifndef POINT2D_H
02 #define POINT2D_H
03 class Point2D
04 {
05 public:
06 Point2D();
07 Point2D(double x, double у);
08 void setX(double x);
09 void setY(double у);
10 double x() const;
11 double y() const;
12 private:
13 double xVal;
14 double yVal;
15 };
16 #endif
Реализация функций выполнялась бы в файле point2d.cpp:
01 #include "point2d.h"
02 Point2D::Point2D()
03 {
04 xVal = 0.0;
05 yVal = 0.0;
06 }
07 Point2D::Point2D(double x, double у)
08 {
09 xVal = x;
10 yVal = у;
11 }
12 void Point2D::setX(double x)
13 {
14 xVal = x;
15 }
16 void Point2D::setY(double у)
17 {
18 yVal = у;
19 }
20 double Point2D::x() const
21 {
22 return xVal;
23 }
24 double Point2D::y() const
25 {
26 return yVal;
27 }
Этот файл начинается с включения заголовочного файла point2d.h, потому что прежде чем компилятор будет выполнять синтаксический анализ реализаций функций—членов, он должен иметь определение класса. Затем идут реализации функций, перед именем которых через оператор :: указывается имя класса.
Мы узнали, как можно реализовать встроенную функцию и как можно реализовать ее в файле .cpp. Семантически эти два подхода эквивалентны, однако при вызове встроенной функции большинство компиляторов просто разворачивают тело функции вместо формирования реального вызова функции. Обычно это ведет к получению более быстрого кода, но может увеличить размер приложения. По этой причине только очень короткие функции следует делать встроенными; длинные функции всегда следует реализовывать в файле .cpp. Кроме того, если мы забудем реализовать какую-нибудь функцию и попытаемся ее вызвать, компоновщик «пожалуется» на существование неразрешенного символа.
Теперь попытаемся использовать этот класс.
01 #include "point2d.h"
02 int main()
03 {
04 Point2D alpha;
05 Point2D beta(0.666, 0.875);
06 alpha.setX(beta.y());
07 beta.setY(alpha.x());
08 return 0;
09 }
В С++ переменные любого типа можно объявлять без непосредственного использования оператора new. Первая переменная инициализируется с помощью стандартного конструктора Point2D (т.е. конструктора без параметров). Вторая переменная инициализируется с использованием второго конструктора. Обращение к члену объекта осуществляется с использованием оператора . (точка).
Объявленные таким образом переменные ведут себя как элементарные типы Java и C# (такие, как int и double). Например, при использовании оператора присваивания копируется содержимое переменной, а не ссылка на объект. И если позже переменная будет модифицирована, значение всех других переменных, к которым присваивалась первая переменная, не изменится.
С++, как объектно—ориентированный язык, поддерживает наследование и полиморфизм. Для иллюстрации этих свойств мы рассмотрим пример абстрактного класса Shape (фигура) и подкласса Circle (окружность). Начнем с базового класса:
01 #ifndef SHAPE_H
02 #define SHAPE_H
03 #include "point2d.h"
04 class Shape
05 {
06 public:
07 Shape(Point2D center) { myCenter = center; }
08 virtual void draw() = 0;
09 protected:
10 Point2D myCenter;